CSC/ECE 517 Fall 2011/ch2 2f vh

From Expertiza_Wiki
Jump to navigation Jump to search

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.

    Alt text
    For basic operations
    Alt text
    For complex operations

    References

    1. Rails 3 release notes: http://edgeguides.rubyonrails.org/3_0_release_notes.html
    2. 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/.
    3. Nick Kallen's posts on AREL: http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/
    4. Bundler: http://gembundler.com/
    5. Pratik Naik's post on the changes in Active Record: http://m.onkey.org/active-record-query-interface
    6. Cookbook Application on Rails 2 discussed in class.