CSC/ECE 517 Fall 2011/ch2 2f vh
Rails 2 and Rails 3
Rails 3 has incorporated significant changes over its 2.x version improving on performance, modularity and elegant constructs and thereby reinforcing its principles Convention over configuration and Don't repeat yourself DRY again. The internal architecture of the framework was refactored to make it more modular, flexible and coherent. Routing component has been revamped and it makes lot easier configure routes. Dependency management is also one of the significant improvements observed in 3.0. It is taken care by new Bundler component, which automates the process of installing, updating library management. The database operations are made much more efficient and closer to relational databases.
Introduction
Rails 3 has undergone many changes from architectural to syntax changes and it’s too big to discuss all those in this article. However, this wiki article presents only major changes that are relevant to class and at same time illustrating those concepts with examples discussed in class, especially Cookbook Application in Rails 2. It is assumed that readers are familiar with Rails 2. With this, it serves as a quick tutorial to understand Rails 3 features.
Structural
Ruby Version
Rails 3.0 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.0 is also compatible with Ruby 1.9.2.
Modularization
Versions prior to Rails 3 were strongly coupled to a limited choices of frameworks that formed the Rails stack. For example, a standard Rails stack used Active Record as its ORM layer, Action View for rendering its views, PrototypeJS as its javascript library and so on. However, with Rails 3, it has become more modular, starting with a rails-core, and including the ability to opt in or out of specific frameworks. Its default components, like ActiveRecord and ActionController, behave like regular plugins. This allows other plugins, like DataMapper, to use exactly the same APIs used by ActiveRecord and hence replace it if the end user desires..
Active Record
Change in query interface: In Rails 2, ActiveRecord provides the following finder methods :
- find(id_or_array_of_ids, options)
- find(:first, options)
- find(:all, options)
- first(options)
- all(options)
- update_all(updates, conditions, options)
And the following calculation methods :
- count(column, options)
- average(column, options)
- minimum(column, options)
- maximum(column, options)
- sum(column, options)
- calculate(operation, column, options) Each of these methods take an options hash containing :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from & :lock. Starting with Rails 3, supplying any option has been deprecated (an exception here is that count() will still accept a :distinct option). Example of deprecated usage:
User.find(:all, :limit => 1) User.find(:all) User.find(:first) User.first(:conditions => {:name => 'vaibhav'}) User.all(:joins => :items)
In Rails 3, Active Record, uses a framework – Arel, which has been taken on as the underpinnings of Active Record and is now required for Rails. Arel is a Relational Algebra for Ruby. It simplifies the generation complex of SQL queries and adapts to various RDBMS systems. Active Record, through the use of Arel, now returns relations on its core methods. ActiveRecord in Rails 3 will have the following new finder methods.
- where (:conditions)
- having (:conditions)
- select
- group
- order
- limit
- offset
- joins
- includes (:include)
- lock
- readonly
- from
All of the above methods returns a Relation. All these methods are defined on the Relation object as well, making it possible to chain them.
vaibhav = User.where(:name => 'vaibhav') new_users = User.order('users.id DESC').limit(20).includes(:items)
The relation returned also supports the following methods: You could call any of the following methods on a relation :
- new(attributes)
- create(attributes)
- create!(attributes)
- find(id_or_array)
- destroy(id_or_array)
- destroy_all
- delete(id_or_array)
- delete_all
- update(ids, updates)
- update_all(updates)
- exists?
For example, in cookbook application In rails 2 for fetching all categories which have pizza we execute statement
Category.find(:all, :conditions => {:recipe =>’pizza’})
Whereas in rails 3 we have a very simple query that achieves same result by
Category.where(:recipe => ‘pizza’)
Active Model Abstraction
Active Model was extracted while decoupling Rails’ historic connection between ActiveRecord, its default ORM, and ActionPack, its controller and view layer. All new ORM plugins (e.g. DataMapper or Sequel) now just need to implement Active Model interfaces to work seamlessly with Action Pack. The Active Model API itself is fairly small, with a few methods around validation, the ability to determine whether an object has persisted or not (which can be safely stubbed by objects without persistence), and a number of methods that tell ActionPack how to convert the object into a canonical URL or template name. By doing this, Rails has completely decoupled ActionPack from ActiveRecord directly, and ActiveRecord becomes just one of many ORMs to implement the ActiveModel API. An example is the use of validations (traditionally an Active Record feature) in any Ruby object that adheres to the Active Model API.
class Person < ActiveRecord::Base validates_presence_of :first_name, :last_name end
To do the same thing for a plain old Ruby object, simply do the following:
class Person include ActiveModel::Validations validates_presence_of :first_name, :last_name attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name, @last_name = first_name, last_name end end
For example, In Cookbook application discussed in class we have the recipe model class with validations (Rails 2.x code)
class Recipe < ActiveRecord::Base belongs_to :category validates :title, :presence => true validates :instructions, :presence => true validates :category, :presence => true end
In Rails 3 we achive the same functionality by less code and has validates_presence_of method
class Recipe include ActiveModel::Validations belongs_to :category validates_presence_of :title, :instructions, :category end
Some other modules available in Active Model include:
- AttributeMethods: Makes it easy to add attributes that are set like table_name :foo
- Callbacks: ActiveRecord-style lifecycle callbacks.
- Naming: Default implementations of model.model_name, which are used by ActionPack (for instance, when you do render :partial => model
- Observing: ActiveRecord-style observers
Action Controller
Previously, ActionController had a number of disparate elements all in one place. This monolithic component is now divided into multiple pieces. Two significant componets are:
ActionDispatch
The dispatcher functionality has been moved into ActionDispatch, with the code inside tightened up and really made a conceptual component. This has resulted in a new routing API. While the old map.connect DSL still works just fine, the new standard DSL is less verbose and more readable.
Rails 2
ActionController::Routing::Routes.draw do |map| map.connect "/main/:id", :controller => "main", :action => "home" end
Rails 3
Piazza::Application.routes do match "/main/:id", :to => "main#home" end
First, the routes are attached to the application, which is now its own object. Second, map is no longer required, and the new DSL (match/to) is more expressive. Finally, we have a shortcut for controller/ action pairs
("main#home" is {:controller => "main", :action => "home")
Another useful shortcut allows you to specify the method more simply than before:
Piazza::Application.routes do post "/main/:id", :to => "main#home", :as => :homepage end
The :as in the above example specifies a named route, and creates the homepage_url et al helpers as in Rails 2.
Simple routes
Rails 2
map.resources :posts,:member={:confirm=>:post,:notify=>:post} do |post| post.resources :comments,:member=>{:preview=>:post},:collection=>{:archived=>:get} end
Rails 3
Resources :posts do member do post :confirm get :notify end Resources :comments do post :preview, <img src="http://vaibhav.com/images/smilies/icon_surprised.gif"> n=>:member get :archived, <img src="http://harsha.com/images/smilies/icon_surprised.gif""> n=>:collection end end
Root mapping
Rails 2
map.root :controller=>”user”
Rails 3
root :to=>”user#index”
Legacy Route
Rails 2
Map.connect ':controller/:action/:id' Map.connect ':controller/:action/:id.:format'
Rails 3
Match ':controller(/:action(/:id(/:format)))
AbstractController
Rails 3 has refactored ActionController and ActionMailer to inherit from a common superclass: AbstractController. AbstractController includes the basic concept of controllers, rendering, layouts, helpers, and callbacks, but nothing about HTTP or mail delivery. ActionController now has AbstractController, a base superclass that is separated from the notions of HTTP. This AbstractController handles the basic notion of controllers, actions, and action dispatching, and not much else.
Development
Rails generator system
The new script/rails replaces all the scripts that used to be in the script directory. You do not run script/ rails directly though, the rails command detects it is being invoked in the root of a Rails application and runs the script for you. Intended usage is:
Rails 2 | Rails 3 |
---|---|
rails script/generate | rails g |
rails script/console | rails c |
rails script/server | rails s |
rails script/dbconsole | rails db |
JS library support
Rails 2 shipped with prototypejs as the default javascript library. However, Rails 3 makes it very easy to pick a javascript library of choice, for example you can now use jquery as the base javascript library.
Deployment & Dependency Management
Bundler
Rails 2 required specifying gems in the environment.rb files, in the config block associated with the run method as:
Rails::Initializer.run do |config| config.gem "aws-s3", :lib => "aws/s3" config.gem "fastercsv" config.gem "chargify_api_ares" config.gem "acts-as-taggable-on", :source => "http://gemcutter.org", :version => '2.0.0.rc1' end
In contrast, Rails 3 uses a Gemfile in the application root to determine the gems you require for your application to start. This Gemfile is processed by the Bundler, which then installs all your dependencies. It takes a gem manifest file and is able to fetch, download, and install the gems and all child dependencies specified in this manifest. It can manage any update to the gem manifest file and update the bundle's gems accordingly. It also lets you run any ruby code in context of the bundle's gem environment. To use bundler, declare these dependencies in a file at the root of your application, called Gemfile. It looks something like this:
source "http://rubygems.org" gem "aws-s3", "1.2.0" gem "fastercsv" gem "chargify_api_ares" gem "acts-as-taggable-on", "~> 1.4.2"
Using this Gemfile, the bundler looks for gems declared in the Gemfile at http://rubygems.org. You can declare multiple Rubygems sources, and bundler will look for gems in the order you declared the sources. Next, the bundler goes through the list of dependencies declared:
- version 1.2.0 of aws-s3
- any version of fastercsv and chargify_api_ares<\li>
- version of acts-as-taggable-on that is >= 1.4.2 but < 1.5.0<\li>
After declaring the dependencies, you tell bundler to go get them:
$ bundle install
Bundler will connect to rubygems.org (and any other sources that you declared), and find a list of all of the required gems that meet the requirements you specified. Because all of the gems in your Gemfile have dependencies of their own (and some of those have their own dependencies), running bundle install on the Gemfile above will install quite a few gems. If any of the needed gems are already installed, Bundler will use them. After installing any needed gems to your system, bundler writes a snapshot of all of the gems and versions that it installed to Gemfile.lock. On production servers, you can enable deployment mode: $ bundle install --deployment The --deployment flag turns on defaults that are appropriate for a deployment environment. Gems are installed to vendor/bundle and the Gemfile.lock must be checked in and up to date before bundler is run.
Performance
Rails Application object: As part of the groundwork for supporting running multiple Rails applications in the same process, Rails 3 introduces the concept of an Application object. An application object holds all the application specific configurations and is very similar in nature to config/environment.rb from the previous versions of Rails. Each Rails application now must have a corresponding application object. The application object is defined in config/application.rb. If you’re upgrading an existing application to Rails 3, you must add this file and move the appropriate configurations from config/environment.rb to config/application.rb. Rails 3 has improved performance by reducing overhead in following areas
- General Controller Overhead
- Render Collections of Partials
Here is a comparison graph of performance between Rail 2.3 vs Rails 3.0 in the above mentioned areas.
References
- Rails 3 release notes: http://edgeguides.rubyonrails.org/3_0_release_notes.html
- Yehuda Katz's posts on the merge of Rails and Merb: http://www.engineyard.com/blog/2009/rails-and-merb-merge-the-anniversary-part-1-of-6/.
- Nick Kallen's posts on AREL: http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/
- Bundler: http://gembundler.com/
- Pratik Naik's post on the changes in Active Record: http://m.onkey.org/active-record-query-interface
- Cookbook Application on Rails 2 discussed in class.