<% "MODULARNE APLIKACJE WWW ".each_char do |c| %>
"><%= c %>
<% end %>

Jak budujemy modularne aplikacje WWW?

<%= image_tag "/images/rack-logo.png", :class => "right", :alt => "rack logo" %> Zaczynamy od postawienia stałej konstrukcji nośnej: stojaka (ang. rack). Następnie wstawiamy do stojaka wymienialne moduły, które zestawiamy według potrzeb, na przykład:

Czym są moduły, które wstawiamy do stojaka?

Christian Neukirchen, Rack: a Ruby Webserver Interface

Dabbling in my own web framework experiments, I noticed that there is a lot of code duplication among frameworks since they essentially all do the same things. And still, every Ruby web framework developer is writing his own handlers for every webserver he wants to use. Hopefully, the framework users are satisfied with that choice.

Nieformalnie, modułem/aplikacją Rack jest obiekt odpowiadający na wywołanie #call z haszem jako jedynym argumentem i zwracającym tablicę trzech elementów: status, headers i body.

Funkcja modułem Rack

:::ruby
# app.rb
require 'rubygems'
require 'rack'

App = lambda do |env|
  [
   200,                                             # status
   {"Content-Type" => "text/html; charset=utf-8"},  # headers
   [ "hello world" ]                                # body
  ]
end

Rack::Handler::Thin.run App, :Port => 9292

Tę aplikację uruchamiamy po prostu tak (bez wstawiania do stojaka):

ruby app.rb

Czym jest argument *env*?

Argument env jest haszem, wyglądającym mniej więcej tak:

:::ruby
{
  "HTTP_ACCEPT" => "*/*",
  "HTTP_HOST" => "inf.univ.gda.pl",
  "HTTP_PRAGMA" => "no-cache",
  "HTTP_USER_AGENT" => "curl/7.12.2 ..."
  "PATH_INFO" => "/",
  "QUERY_STRING" => "",
  "REMOTE_ADDR" => "127.0.0.1",
  "REMOTE_HOST" => "127.0.0.1",
  "REQUEST_METHOD" => "GET"
  "REQUEST_PATH" => "/",
  "REQUEST_URI" => "http://inf.univ.gda.pl/",
  "SCRIPT_NAME" => "",
  "SERVER_PORT" => "80",
  "SERVER_PROTOCOL" => "HTTP/1.1",
}

Instancja klasy modułem Rack

:::ruby
# helloworld.rb
class HelloWorld
  def call(env)
    [200, {"Content-Type" => "text/html"}, ["hello world"]]
  end
end

Tę klasę wstawiamy do stojaka (jako jedyny moduł):

:::ruby
# app2.ru
require 'helloworld'
run HelloWorld.new

i uruchamiamy wpisując w wierszu poleceń:

rackup -s thin -p 9292 app2.ru 

Filtry, czyli Rack Middleware

Between the server and the framework, Rack can be customized to your applications needs using middleware, for example:

Aplikacja Rack z dołożonym Middleware

<%= image_tag "/images/rack-1.png", :alt => "rack: middleware | apps" %>

Jak składamy aplikacje modularne?

Aplikacje składamy tak, jak składamy funkcje:

:::ruby
app = Rack::CommonLogger.new(                  
        Rack::ShowExceptions.new(              
          Rack::Lint.new(MyRackApp.new)))

Albo tak:

:::ruby
app = MyRackApp.new
app = Rack::Lint.new(app)
app = Rack::ShowExceptions.new(app)
app = Rack::CommonLogger.new(app)

albo w postaci „kaskady”...

Iteracyjne składanie + kaskada aplikacji Rack

<%= image_tag "/images/rack-2.png", :alt => "rack: middleware | apps" %>

Rack::Builder — składamy middleware z aplikacją

:::ruby
# rapp.ru
require 'helloworld'

Rapp = Rack::Builder.new do
  use Rack::Lint
  run HelloWorld.new
end

use Rack::CommonLogger
run Rapp

Aplikację rapp.ru uruchamiamy tak:

rackup rapp.ru

Polecenie rackup doda za nas wiersz z Rack::Handler oraz bibliotekę rubygems i rack.

Aplikacje Rack: Pi, Euler...

:::ruby
require 'rack/request' ; require 'rack/response'
require 'bigdecimal' ; require 'bigdecimal/math'
include BigMath
module Rack
  class Pi
    def call(env)
      req = Request.new(env)
      prec = req.GET["prec"].to_i
      res = Response.new
      res.write "<title>PI</title>"
      res.write "<p>"
      res.write PI(prec + 1).to_s("10F")
      res.write "</p>"
      res.finish
    end
  end
end

Iteracyjnie budujemy aplikację

:::ruby
# app3.rb
require 'rack-math' # gem z aplikacjami Rack:Euler, Rack:Pi...
App3 = Rack::Builder.new do
  map '/pi' do
    run Rack::Pi.new
  end
  map '/euler' do
    run Rack::Euler.new
  end
end

Umieszczamy aplikację w stojaku, który uruchamiamy za pomocą rackup:

:::ruby
# app3.ru
require 'app3'
run App3

...kontynuuuuacja poprzedniego slajdu

Włączamy jeszcze jedną aplikację z gemu Rack:

:::ruby
# app4.rb
require 'rack/lobster' ; require 'app3'
App4 = Rack::Builder.new do
  map '/math' do
    run App3
  end
  map '/lobster' do
    run Rack::Lobster.new
  end
end

Uruchamiamy aplikację app4.ru:

:::ruby
require 'app4'
use Rack::CommonLogger
run App4

Modułowe aplikacje Sinatry: Sinatra:Pi, Sinatra:Euler

Korzystamy z layoutu i szablonów. Kopiujemy zmienne do szablonów.

:::ruby
# sinatra-math/pi.rb
require 'bigdecimal'
require 'bigdecimal/math'
include BigMath

module Sinatra
  class Pi < Sinatra::Base
    get '/?' do
      prec = params[:prec].to_i
      @pi = PI(prec + 1).to_s("10F")
      @cname = 'PI'
      erb :pi
    end    
  end
end

...kontunuuuuacja poprzedniego slajdu

Poniższą aplikację urozmaicimy homarem:

:::ruby
# app5.rb
require 'sinatra/base' ; require 'sinatra-math' ; require 'rack/lobster'
SinatraApp5 = Rack::Builder.new do
  map '/' do 
    run Sinatra::Pi.new
  end
  map '/lobster' do
    run Rack::Lobster.new
  end
end

i uruchomimy ją korzystając via prosty plik:

:::ruby
# app5.ru
require 'app5'
run SinatraApp5

Modułowe aplikacje Rails 3

Yehuda Katz, The Russian Doll Pattern: Mountable apps in Rails 3

One of the hottest new features in Rails 3 is the ability to embed a Rails application in another Rails application. This allows the development of components that range from user authentication to a fully featured forum. These components can then be distributed as gems and fully integrated with another application. In fact, user private messaging could be a stand alone app, which is then mounted into a forum app, and finally mounted into your own custom app.

Prawa Murphy’ego (1918–1990)

E. A. Murphy

Nic nie jest tak łatwe, jak wygląda.

Railsconf 2009 Recap

Interestingly, I really didn’t care for many of the (very rough) ideas expressed in Yehuda’s mountable Rails apps (Rails 3) session — in particular I really had no clue why they kept comparing Rails (a framework) to Drupal (a CMS). But, that said, the talk did do a great job stimulating discussion about alternative approaches in „CabooseConf” — apparently just a small room with, uh, tables and stuff — between myself, Bryan, Josh, Ted and others. For this reason it definitely belongs in my favorite sessions list.

...more Railsconf 2008

Każde rozwiązanie ujawnia nowe problemy.

— Edward A. Murphy

[mountable app slices] are a challenging problem, and there are a lot of issues in terms of sharing application state, resolving cross-app dependencies, and so on. I hope that we’ll have an elegant solution to this soon; but I suspect that the real answer may be in making component-sized micro-apps easier to mount and integrate rather than taking an „app slices” or engines approach (if the latter case prevails, the Radiant extensions system has some stuff we can learn from).