Rails and Merb Merge: Rack (Part 6 of 6) »
Created at: 16.03.2010 19:00, source: Engine Yard Blog, tagged: Technology rails Rails 3
In this final segment, I’ll talk about how Rails 3 takes advantage of the Rack standard, leveraging it in ways that make sharing code between Ruby web applications easier. There are a ton of good blog posts and presentations on Rack itself, so I won’t go into too much detail beyond a brief summary.
What is Rack?
Rack defines a standard for interaction between web servers and Ruby web applications. It’s based on the CGI standard, but stripped of CGI’s global characteristics (such as its use of environment variables and standard output).
Servers send requests to Rack applications by calling their #call method with an environment Hash containing information as a number of standardized keys. For instance, it provides the requested path in the PATH_INFO variable. Applications send responses back to the server as an Array of three elements: the status code, a Hash of the headers, and the body. Applications may return any object that responds to #each as the body, and each iteration must return a String object.
Here are a couple of simple Rack applications:
# A Proc responds to #call proc do |env| [200, {"Content-Type" => "text/html"}, ["Hello world"]] end # Other objects that respond to #call work too class MyApp def self.call(env) [ 200, {"Content-Type" => "text/html"}, ["Hello world"] ] end end
Rails 2.3
In Rails 2.3, Rails became a fully valid Rack application, leveraging Rack in a number of places:
- ActionController::Dispatcher.new is the top-level Rack application
- The parameter parser is implemented as middleware
- The router is a Rack application that dispatches to controllers
- Each controller is a Rack application
This was a modest step toward leveraging Rack, and made Rails applications work anywhere Rack worked. However, the internals of some of these pieces were messy, and an overhaul would be needed to take full advantage of the Rack ecosystem and the power of Rack’s architecture.
Enter Rails 3: The Application
First, rather than having a global Dispatcher object pointing at a global router, Rails 3 introduces the idea of the Rails Application. The Application, an object holding the router and other configuration, is itself a Rack application.
The following is a valid config.ru.
# Set up load paths as desired require "action_controller/railtie" class FooController < ActionController::Base def bar self.response_body = "HELLO" end end class MyApp < Rails::Application config.session_store :disabled routes.draw do match "/foo" => "foo#bar" end end run MyApp
As you can see, a Rails application is a valid Rack application. The above config.ru file will run with rackup, Passenger, Glassfish, or any other Rack server.
ActionDispatch and Rack Middleware
Rails 3 introduces a new framework called ActionDispatch, a library extending the features of the Rack library for Rails’ use. This library can also be used standalone in any Rack application.
The new ActionDispatch::Request and ActionDispatch::Response objects now inherit from Rack::Request and Rack::Response. Integration tests now use Rack::Test and can now be used to test any Rack application.
Rails now includes a number of new general purpose middlewares:
- A middleware to run preparation callbacks, which Rails runs once in production mode, or once per request in development mode
- A middleware to set up and write cookies
- A middleware to clean up flash notices
- A middleware to handle HEAD requests
- A middleware to handle IP spoof checking
- A middleware to serve static assets
- Middlewares to handle rescuing exceptions down the stack
- Middlewares for various session stores
Rails also uses a number of middlewares that ship with the Rack library:
- A middleware to synchronize non-threadsafe requests
- A middleware to measure and set the runtime of requests
- A middleware to implement Send-File semantics in various web servers
- A middleware to handle PUT and DELETE requests coming in via POST requests
The middleware stack object, available as config.middleware, supports reordering these middlewares, or inserting new middleware relative to default ones.
One nice thing about implementing the functionality as middleware is that it forces them to be simple, standalone objects, and understanding, patching or even replacing them becomes much easier than trying to modify a monolithic controller or request object.
The Router
The Rails 3 router was written from the ground up by Josh Peek as the Rack::Mount project, which provides a general-purpose Rack router. Rack::Mount recognizes URLs and dispatches them to any Rack application. As a result, the router now natively supports matching any URL to any arbitrary Rack application–even a Sintra application.
Rails.application.routes.draw do match "/blog" => Rack::Blog # Someone should write it! end
The router is itself a Rack application, so it’s possible to mount a router inside the main router. Carl Lerche’s Astaire project uses this to provide the Sinatra routing DSL inside a Rails controller. The following is a valid config.ru:
$:.unshift File.expand_path('../../lib', __FILE__) require 'rubygems' require 'astaire' class OmgController < ActionController::Base include Astaire::DSL append_view_paths File.expand_path('../views', __FILE__) get "/goodbye" do render :text => "goodbye" end get "/hello" do render "hello" end end run OmgController
Astaire uses ActionDispatch::Routing::RouteSet internally, a Rails wrapper around Rack::Mount.
Actions
Because the router dispatches directly to Rack applications, each controller action is its own mini-application. To get a valid Rack application for a controller’s action, use the ArticlesController.action(:index) syntax.
The Rails router uses the syntax match "/articles" => "articles#index" to match a URL to a controller/action pair. That syntax is identical to match "/articles" => ArticlesController.action(:index).
Because actions are their own Rack applications, you can add middleware between the router and controller actions:
class ArticlesController < ApplicationController use MyMiddleware end
This will insert the middleware between the router and the dispatching of an action. The following is a valid config.ru:
class ArticlesController < ActionController::Base append_view_path "/path/to/views" def index render end # implicit actions, such as "articles/show" would work here end run ArticlesController.action(:index)
This endpoint would behave exactly like a normal action, including callbacks and rendering. In fact, it’s roughly identical to what happens internally inside the router.
You can also call into an action using this syntax:
env = Rack::MockRequest.env_for("/articles") status, headers, body = ArticlesController.action(:index).call(env) # Create a response object, if you want one response = ActionDispatch::Response.new(status, headers, body)
You could also use Rack::Test:
class TestArticles < Test::Unit::TestCase include Rack::Test::Methods def app ArticlesController.action(:index) end def test_get get "/articles" assert_equal "Hello world", last_response.body end end # This would include the application's middleware, # which you probably want as it includes sessions # and cookies class TestApplication < Test::Unit::TestCase include Rack::Test::Methods def app MyApplication end def test_get get "/articles" assert_equal "Hello world", last_response.body end end
You would probably use Rails integration tests, which wrap Rack::Test with some additional conveniences, in practice. It’s nice to know that there’s very little magic going on, and that all of the individual pieces of Rails are just standard Rack.
Conclusion
While Rails 2 had started down the path of supporting Rack, Rails 3 took a few dozen steps forward, deeply integrating the framework with the Rack standard. In addition to improving the general interaction with servers, Rails 3 takes much more advantage of the Rack library, exposes its own functionality as reusable Rack components, and wires up the stack using the Rack architecture. This means that Rails itself integrates well with tools that work with Rack applications, and can also incorporate other applications or middleware following the Rack standard.
Stay tuned for more Rails 3 content to come—the series may be over, but there’s plenty left to talk about.
more »
Capistrano & Sunspot in Rails »
Created at: 04.03.2010 06:59, source: Hackido, tagged: ruby solr rails sunspot
more »
Unobtrusive, yet explicit »
Created at: 02.03.2010 02:15, source: the { buckblogs :here } - Home, tagged: Projects Tips & Tricks javascript rails ujs
A few weeks ago I started a new side project (a string-figure catalog, not yet ready for an audience, sadly), and I figured it would be a good opportunity to dabble in the new goodies in Rails 3. It’s been a fun experience, for the most part, but I’ll save my “wins and fails” for a separate post.
For now, I want to focus on one particular frustration: Unobtrusive Javascript (UJS). In any project of even moderate complexity, I’ve found that Javascript plays a role, and in Rails 2 the primary way to play that game was by inlining your Javascript. (This is where you put Javascript directly into your tags, for instance in “onchange” or “onclick” handlers.)
Apparently this is a Bad Thing, although the only arguments I’ve found against inline Javascript sound suspiciously like “purity for purity’s sake”. At any rate, Rails 3 is embracing UJS, and you’ll find that helper methods like “link_to_function” don’t even exist in Rails 3.
This raises the question: what do you do instead? Well, you have to use UJS. Only, UJS in Rails isn’t super mature yet; there’s a lot of manual labor involved simply trying to work around the absence of “link_to_function”.
So, I set to work. Initially, I tried to copy what rails.js was doing (for Ajax operations, etc.): I installed a handler, and examined the triggering element to see what operations match:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
document.observe("dom:loaded", function() { $(document.body).observe("click", function(event) { var element = event.findElement("a[data-toggle]"); if(element) { var action = element.readAttribute("data-toggle"); element.hide(); element.next().show(); event.stop(); } else { var element = event.element(); if(!element.readAttribute("data-tab")) element = element.up("a[data-tab]") if(element) { selectTab(element.readAttribute("data-tab")); } } }); }); |
I quickly realized that this does not scale, for two reasons. The first is that you quickly wind up with a massive branch statement inside each of your observer functions, with finicky conditions that (hopefully) map to actual elements in your views. The second is that the relationship between your markup and your Javascript is tenuous at best; even coming back to my code just a few days later, I found it was challenging to discover what code was executed when a link was clicked.
This is, I believe, one of the greatest strengths of inline Javascript: the relationship between markup and code is immediately obvious, and it requires very little hunting to follow the path of execution from the inception of an event.
So I went looking at what other options exist. Low Pro, an extension to Prototype for aiding with UJS, looked promising; I liked how each behavior was registered separately, which seemed like it would give a stronger relationship between markup and execution. However, Low Pro uses CSS selectors to identify which markup gets associated with which callbacks, and while this sounds like it ought to be a great idea, it falls down for one really big reason: CSS selectors depend on styling attributes (classes and ids), and trying to tie functionality to those means you are still left staring at markup and wondering where all the events go. Sometimes a class is stylistic, and sometimes it is logical, and there is not generally any clear way to determine which is which.
Now, you could resort to naming conventions: if a class name is prefixed with “behavior-” (or similar), then it refers to a behavior that is defined in Javascript. That’s closer to what I was looking for, so I played with that.
But what I soon discovered was that you wind up with a bunch of CSS classes that are not used for styling at all, because they specifically refer to dynamic behaviors. What I really wanted was an altogether different attribute for specifying behaviors, like what “onchange” and “onclick” gave me before. Only I had to beware upsetting the Manifold Avatars of UJS Purity by embedding actual Javascript.
What I finally ended up doing (I’ll say I “stumbled on it”, rather than “invented it”, since I’m positive it’s been done before) was defining a “data-behaviors” attribute on every element that needed one:
<%= link_to "Add an alias", "#", "data-behaviors" => "add-alias" %> |
Then, in my Javascript driver, I registered callbacks for those named behaviors:
1 2 3 |
Behaviors.add("click", "add-alias", function(element) { // ... }); |
The result is UJS that clearly reveals the relationship between the markup and the code; you can easily search for all elements in the views that behave like “add-alias”, for instance, and given a behavior name (like “add-alias”), you can quickly find the code that gets executed for it. Elements can have multiple behaviors, too: just give a space-delimited list of behavior names in the “data-behaviors” attribute.
It’s not perfect, though: the current implementation doesn’t deal well with elements that want to behave like X on “click”, but Y on “change”. That’s not a scenario I’ve needed to deal with yet, though, so I’m sure when (if?) it comes up, a solution can be found. In the meantime, I’m quite pleased with this. It “clicks”, whereas other UJS solutions just felt obscure and heavy-weight.
Below is the code for behaviors.js. Please feel free to fork the gist on Github and hack away; I’m sure it can be improved upon in lots of ways.
Enjoy!
Update: Josh Peek suggested some tips that resulted in a drastic simplification of behaviors.js. It’s simple enough now that there’s almost no point in providing it as a separate library!
more »
Double Shot #658 »
Created at: 01.03.2010 13:08, source: A Fresh Cup, tagged: Double Shot canable mongo rails ruby sinatra subversion
New months always hold so much promise.
- Warehouse -"A Web-based subversion browser that doesn't suck."
- Garbage Collection Slides from LA Ruby Conference - A good intro to a tough ruby topic, with pointers to some useful utilities.
- Great CS Program for GIrls Needs Funding - Another good cause that could use some of your hard-earned dollars.
- Using Sinatra to test remote services in Rails - Another testing idea from Elad Meidar.
- Canable: The Flesh Eating Permission System - John Nunemaker's take on authorization. Looks fairly similar to the way I've been handling it recently.
- rails_indexes - I think I mentioned this plugin for finding missing indexes before, but it came in handy this weekend so I'm mentioning it again.
- Support Details - A way to quickly query a user's machine for things like OS, Browser, IP address, and color depth.
- Migrations with Mongo (and MongoMapper) - Otherwise known as "mongrations".
- Lesson from Madlibs Signup Fad: Do Your Own Tests - Amen.
more »
Double Shot #653 »
Created at: 22.02.2010 12:36, source: A Fresh Cup, tagged: Double Shot currencies gems jquery rails rghost
No shortage of interesting and shiny distractions today.
- Bling Bling gem management - Why yes, there is already a replacement for Bundler.
- Announcing the Currencies Gem - Multi-currency and exchange rate support for the money gem.
- htmlBurger - Paid service to convert PSD to XHTML/CSS/light JS.
- Inline Styler - Convert CSS rules to inline style attributes for use in HTML email.
- rghost - Another alternative for PDF creation from Ruby. Anyone used this one?
- Running Rails 2.3.x on IronRuby - Rob Bazinet shows how to take the first steps.
- HTML Dog - Well-recommended set of HTML and CSS tutorials.
- What really happens when you navigate to a URL - It's complicated.
- RubyGems.org move complete - GemCutter has finished its bloodless coup over ruby gem hosting. Time to upgrade your rubygems to 1.3.6.
- jQuery Simple Multi-Select, jQuery Multiselect list plugin, and jQuery MultiSelect Plugin w/ ThemeRoller Support - Three options for adding click-to-toggle functionality to HTML select controls. I ended up using the first of these on a project, and it works well.
- Use a 503 for your Rails maintenance page - One more little bit of polish and standards.
- Rails 3 Upgrade Handbook - Now out and ready to download. Looks well-worth the $12.
more »
