Краткий обзор развития фреймворка Ruby on Rails за последние 14 месяцев

    За временем не успеть. Все вокруг развивается очень стремительно. В какой-то момент я заметил, что хоть и работаю с последней версией Ruby on Rails, но многих «фич», которые в ней реализованы я не использую, более того о многих я даже не слышал.
    Я попробую сделать ретроспективу, что было введено в Rails за последние 14 месяцев. Каждое нововведение буду сопрождать небольшим примером, который буду копировать as is из источника, на котором основана статья, так как подобные пояснения для каждой это тема для кучи отдельных статей или ссылкой.

    Повествование идет в виде:
    Версия
    Что было после нее
    Новая версия включающая все нововведения, описанные выше.

    Версия 2.0.2. Декабрь, 2007


    Рельсы попросту сделали level up по сравнению с серией 1.x.x. Кто с ней работал тот меня поймет. Что же было потом?
    Появляется возможность указать движок для кеширования:
    Copy Source | Copy HTML<br/>ActionController::Base.cache_store = :memory_store<br/>ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"<br/>ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"<br/>ActionController::Base.cache_store = :mem_cache_store, "localhost" <br/>

    Так же присутствует возможность сделать свой.
    Упрощена работа с таймзонами.
    Copy Source | Copy HTML<br/># Set the local time zone<br/>Time.zone = "Pacific Time (US & Canada)"<br/> <br/># All times will now reflect the local time<br/>article = Article.find(:first)<br/>article.published_at #=> Wed, 30 Jan 2008 2:21:09 PST -08:00<br/> <br/># Setting new times in UTC will also be reflected in local time<br/>article.published_at = Time.utc(2008, 1, 1, 0)<br/>article.published_at #=> Mon, 31 Dec 2007 16:00:00 PST -08:00 <br/>


    Появился named_scope:
    Copy Source | Copy HTML<br/>class User < ActiveRecord::Base<br/>  named_scope :active, :conditions => {:active => true}<br/>  named_scope :inactive, :conditions => {:active => false}<br/>  named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }<br/>end<br/> <br/># Standard usage<br/>User.active # same as User.find(:all, :conditions => {:active => true})<br/>User.inactive # same as User.find(:all, :conditions => {:active => false})<br/>User.recent # same as User.find(:all, :conditions => ['created_at > ?', 1.week.ago])<br/> <br/># They're nest-able too!<br/>User.active.recent<br/>  # same as:<br/>  # User.with_scope(:conditions => {:active => true}) do<br/>  #   User.find(:all, :conditions => ['created_at > ?', 1.week.ago])<br/>  # end <br/>

    Наследование has_one обзавелось опцией :through:
    Copy Source | Copy HTML<br/>class Magazine < ActiveRecord::Base<br/>  has_many :subscriptions<br/>end<br/> <br/>class Subscription < ActiveRecord::Base<br/>  belongs_to :magazine<br/>  belongs_to :user<br/>end<br/> <br/>class User < ActiveRecord::Base<br/>  has_many :subscriptions<br/>  has_one :magazine, :through => : subscriptions, :conditions => ['subscriptions.active = ?', true]<br/>end <br/>

    Введение «грязных объектов»:
    Copy Source | Copy HTML<br/>article = Article.find(:first)<br/>article.changed? #=> false<br/> <br/># Track changes to individual attributes with<br/># attr_name_changed? accessor<br/>article.title #=> "Title"<br/>article.title = "New Title"<br/>article.title_changed? #=> true<br/> <br/># Access previous value with attr_name_was accessor<br/>article.title_was #=> "Title"<br/> <br/># See both previous and current value with attr_name_change accessor<br/>article.title_change #=> ["Title", "New Title"] <br/>

    Что позволило генерировать такие sql, запросы, которые отправляют в БД только измененные поля:
    Copy Source | Copy HTML<br/>article = Article.find(:first)<br/>article.title #=> "Title"<br/>article.subject #=> "Edge Rails"<br/> <br/># Update one of the attributes<br/>article.title = "New Title"<br/> <br/># And only that updated attribute is persisted to the db<br/>article.save<br/>  #=> "UPDATE articles SET title = 'New Title' WHERE id = 1" <br/>

    Появилась возможность указывать от каких гемов и какой версии зависит ваше Rails приложение:
    Copy Source | Copy HTML<br/>Rails::Initializer.run do |config| <br/> <br/>  # Require the latest version of haml<br/>  config.gem "haml"<br/> <br/>  # Require a specific version of chronic<br/>  config.gem "chronic", :version => '0.2.3'<br/> <br/>  # Require a gem from a non-standard repo<br/>  config.gem "hpricot", :source => "http://code.whytheluckystiff.net"<br/> <br/>  # Require a gem that needs to require a file different than the gem's name<br/>  # I.e. if you normally load the gem with require 'aws/s3' instead of<br/>  # require 'aws-s3' then you would need to specify the :lib option<br/>  config.gem "aws-s3", :lib => "aws/s3" <br/>end <br/>

    Почти в этот же момент Rails-девелопером пришлось забыть про геморрои с миграциями вида xxx_название. Именование миграций стало UTC-based:
    Copy Source | Copy HTML<br/>> script/generate migration one<br/>      create db/migrate/20080402122512_one.rb <br/>

    Версия 2.1. Июнь, 2008


    Имя партиала стало определяться автоматически:
    Copy Source | Copy HTML<br/>render :partial => 'employees', :collection => @workers, :as => :person <br/>

    Для валидации validates_length_of добавилась опция :tokenizer:
    Copy Source | Copy HTML<br/>validates_length_of :article, :minimum => 10,<br/>  :too_short => "Your article must be at least %d words in length.",<br/>  :tokenizer => lambda {|str| str.scan(/\w+/) } <br/>

    В методе find моделей опцию :joins можно указывать не только строку, соблюдая при этом все правила SQL, но и просто названия моделей:
    Copy Source | Copy HTML<br/>class Article < ActiveRecord::Base<br/>  belongs_to :user<br/>end<br/> <br/>class User < ActiveRecord::Base<br/>  has_many :articles<br/>end<br/> <br/># Get all the users that have published articles<br/>User.find(:all, :joins => :article,<br/>  :conditions => ["articles.published = ?", true]) <br/>

    А опцию :conditions можно более детально расписать:
    Copy Source | Copy HTML<br/># Get all the users that have published articles<br/>User.find(:all, :joins => :article, :conditions => { :articles => { :published => true } }) <br/>

    Появилась мемоизация, которая позволила забыть об ||=, но лично у меня она как-то пока не прижилась.
    Copy Source | Copy HTML<br/>class Person < ActiveRecord::Base<br/> <br/>  def social_security<br/>    decrypt_social_security<br/>  end<br/> <br/>  # Memoize the result of the social_security method after<br/>  # its first evaluation (must be placed after the target<br/>  # method definition).<br/>  #<br/>  # Can pass in multiple symbols:<br/>  #  memoize :social_security, :credit_card<br/>  memoize :social_security<br/>  ...<br/>end<br/> <br/>@person = Person.new<br/>@person.social_security # decrypt_social_security is invoked<br/>@person.social_security # decrypt_social_security is NOT invoked <br/>

    Появилось нечто под названием «Nested Model Mass Assignment» (как бы это перевести?), вобщем после примера все становится ясным:
    Copy Source | Copy HTML<br/>class User < ActiveRecord::Base<br/>  validates_presence_of :login<br/>  has_many :phone_numbers<br/>end<br/> <br/>class PhoneNumber < ActiveRecord::Base<br/>  validates_presence_of :area_code, :number<br/>  belongs_to :user<br/>end<br/>class User < ActiveRecord::Base<br/>  validates_presence_of :login<br/>  has_many :phone_numbers, :accessible => true<br/>end<br/> <br/>ryan = User.create( {<br/>  :login => 'ryan',<br/>  :phone_numbers => [<br/>    { :area_code => '919', :number => '123-4567' },<br/>    { :area_code => '920', :number => '123-8901' }<br/>  ]<br/>})<br/> <br/>ryan.phone_numbers.count #=> 2<br/> <br/># one more way<br/>class User < ActiveRecord::Base<br/> <br/>  ...<br/> <br/>  def phone_numbers=(attrs_array)<br/>    attrs_array.each do |attrs|<br/>      phone_numbers.create(attrs)<br/>    end<br/>  end<br/> <br/>end <br/>

    Не забыли и про представления:
    Copy Source | Copy HTML<br/><% form_for @user do |f| %><br/>  <%= f.text_field :login %><br/>  <% fields_for :phone_numbers do |pn_f| %><br/>    <%= pn_f.text_field :area_code %><br/>    <%= pn_f.text_field :number %><br/>  <% end %><br/>  <%= submit_tag %><br/><% end %> <br/>

    Таким образом всю логику можно заключить в модель, а контроллер будет иметь вид:
    Copy Source | Copy HTML<br/>class UserController < ApplicationController<br/> <br/>  # Create a new user and their phone numbers with mass assignment<br/>  def new<br/>    @user = User.create(params[:user])<br/>    respond_to do |wants|<br/>      ...<br/>    end<br/>  end<br/>end <br/>

    У всех объектов появился метод metaclass. wtf

    Примерно в это же время рождается «Standard Internationalization Framework», который, я думаю, уж никто не упустил из виду и сейчас использует в своих проектах.
    Появился etag, который позволяет (на основе последующих изменений) очень неплохо управляться с кешированием как на стороне сервера так и клиента. За большими подробностями отправляю к замечательнейшим скринкастам и статье.
    named_scope эволюционировал, мини-пример:
    Copy Source | Copy HTML<br/>class Article < ActiveRecord::Base<br/> <br/>  # Only get the first X results<br/>  named_scope :limited, lambda { |num| { :limit => num } }<br/> <br/>end<br/> <br/># Get the first 5 articles - instead of Article.find(:all, :limit => 5)<br/>Article.limited(5) #=> [<Article id: ...>, <..>] <br/>

    Появились Shallow Routes. Обратите внимание, что в более поздних версиях эта опция перестала затрагивать вложенные роуты.
    Тем времени благодаря программе Google Summer of Code некто Джошуа Пик сделал Rails потоко-безопасными. Коль скоро это было сделано Ruby on Rails обзавелся пулом соединений к базе данных.

    Версия 2.2.2 Октябрь, 2008


    У роутов появились две, имхо, очень не плохи опции :except и :only. Поясняющий пример:
    Copy Source | Copy HTML<br/># Only generate the :index route of articles<br/>map.resources :articles, :only => :index<br/> <br/># Generate all but the destroy route of articles<br/>map.resources :articles, :except => :destroy<br/> <br/># Only generate the non-modifying routes of articles<br/>map.resources :articles, :only => [:index, :show] <br/>

    Вышло в свет дополнение к named_scope — default_scope:
    Copy Source | Copy HTML<br/>class Article < ActiveRecord::Base<br/>  default_scope :order => 'created_at DESC'<br/>  named_scope :published, :conditions => { :published => true }<br/>end<br/>Article.find(:all) #=> "SELECT * FROM `articles` ORDER BY created_at DESC"<br/>Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY created_at DESC" <br/>

    Было принято решение о переименование application.rb в application_controller.rb, которое вступит в силу начиная с Rails 2.3.
    Партиалы эволюционировали еще сильнее:
    Copy Source | Copy HTML<br/>render :partial => 'articles/article', :locals => { :article => @article }<br/># Render the 'article' partial with an article local variable<br/>render 'articles/article', :article => @article<br/> <br/># Or even better (same as above)<br/>render @article<br/> <br/># And for collections, same as:<br/># render :partial => 'articles/article', :collection => @articles<br/>render @articles <br/>

    Появился Object.try:
    Copy Source | Copy HTML<br/># No exceptions when receiver is nil<br/>nil.try(:destroy) #=> nil<br/> <br/># Useful when chaining potential nil items<br/>User.admins.first.try(:address).try(:reset) <br/>

    Роуты с .format на конце убрали. Ура! Лично мне они очень редко были нужны. Пример как теперь использовать .format:
    Copy Source | Copy HTML<br/>formatted_article_path(article, :xml) => article_path(article, :format => :xml)<br/>formatted_new_article_path(:json) => new_article_path(:format => :json)<br/> <br/>

    В декабре 2008 была добавлена возможность генерировать свое приложение на основе шаблона. Подробнее.
    Чуть позже появляется Rails Metal, который позволяет отдавать ответ клиенту, в обход модели MVC. Скринкаст в тему.
    named_scope становиться динамическим:
    Copy Source | Copy HTML<br/>Article.find_by_published_and_user_id(true, 1)<br/>  #=> "SELECT * FROM articles WHERE published = 1 AND user_id = 1"<br/>Article.scoped_by_published_and_user_id(true, 1).find(:all, :limit => 5)<br/>  #=> "SELECT * FROM articles WHERE published = 1 AND user_id = 1 LIMIT 5"<br/>Article.scoped_by_published(true).scoped_by_user_id(1)<br/>  #=> "SELECT * FROM articles WHERE published = 1 AND user_id = 1 <br/>

    После нового года появляется HTTP Digest Authentication
    В продолжении к Nested Model Mass Assignment появляется Nested Object Forms. Отличнейшее нововведение.

    Версия 2.3.0RC1. Февраль, 2009


    Последнее нововведение в RoR это Batched Find. Сразу пример:
    Copy Source | Copy HTML<br/>Article.each { |a| ... } # => iterate over all articles, in chunks of 1000 (the default)<br/>Article.each(:conditions => { :published => true }, :batch_size => 100 ) { |a| ... }<br/>  # iterate over published articles in chunks of 100<br/>Article.find_in_batches { |articles| articles.each { |a| ... } }<br/>  # => articles is array of size 1000<br/>Article.find_in_batches(:batch_size => 100 ) { |articles| articles.each { |a| ... } }<br/>  # iterate over all articles in chunks of 100<br/>class Article < ActiveRecord::Base<br/>  named_scope :published, :conditions => { :published => true }<br/>end<br/> <br/>Article.published.find_in_batches(:batch_size => 100 ) { |articles| ... }<br/>  # iterate over published articles in chunks of 100 <br/>

    Версия 2.3.0. ?, 2009


    Ждем не дождемся.

    Обращаю внимание девелоперов. Используйте версию RoR-а, которая у вас в проекте по максимуму, не изобретайте велосипедов :) Удачи и доброго дня. Замечания и исправления приветствуются.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 22

      +1
      Не совсем понятно почему Rails Metal и templates, а так же еще куча всего относятся к 2.2.2, если это нововведения 2.3, просто небольшой сумбур это вносит, многие же пойдут проверять и сильно удивятся, когда не заработает =)
        0
        Предвидя такие вопросы написал в начале статьи:
        Повествование идет в виде:
        Версия
        Что было после нее
        Новая версия включающая все нововведения, описанные выше.
          +1
          Ясно, невнимательность подвела
        0
        Спасибо, очень познавательно.
        На Merb не планируете переходить ввиду его будущего слияния с рельсами? Имхо, объединенная версия будет ближе к мербу, чем к рельсам.
          0
          «у меня нет проблем с логикой», ога
            0
            Вообще не известно еще что будет. Мне вот интересно пара фишек в рельсах, которых нету в мербе и наоборот, но делаю проект на мербе с учетом последующего перехода на третьи рельсы. Старательно жду новостей от йехуды.
            За пост автору большущее спасибо. Потому что на рельсы давно не смотрел. Хотелось бы еще сравнения с мербом. Типа Эта фишка есть там то, но нету там то. И если у каждого есть то как использовать. Было бы очень круто. Возможно сам напишу если время будет.
              0
              «Неизвестро что будет»? Будет мердж Merb в Rails, зайдите на irc.freenode.net #rails-contrib, от вас никто не прячется.
                0
                Ясно что мердж, но прям весь мерб запихнут в рельсы? Останутся мербовские конфиги, хелперы, партишлы, все все все останется?
            0
            Спасибо за ваш труд! Очень полезная статья.
              +2
              Было принято решение о переименование application_controller.rb в application.rb, которое вступит в силу начиная с Rails 2.3.

              Наоборот, application.rb начиная с версии 2.3 станет application_controller.rb
                0
                Дададада, вот что значит в 5 утра писать что-то. Сейчас исправлю.
                0
                Статья, в общем-то, неплохая. Но, само собой, не охватвывает всех нововведений и изменений в рельсах. Лучше просто мониторить RoR-блог на предмет «This Week in Edge Rails».
                  0
                  Ну напишите какое изменение я не охватил — добавлю. Все таки цель ставилась все указать в сжатом варианте.
                  +3
                  ИМХО тут Article.find_in_batches(batch_size => 100 )
                  пропушено: перед batch_size
                    –1
                    спасибо, отличная статья
                    жду продолжения
                    0
                    Отличная выжимка. Сейчас тестирую 2.3, в проекте в файле environment.rb была задана глобальная константа. Теперь приложение ее не видит. Никто не сталкивался с проблемой?
                      0
                      Огромное спасибо!
                      Начинали свой проект на 2.1 и настолько были им заняты, что не уследили за некоторыми новвоведениями и изобретали велосипед. Благодаря статье можем теперь часть кода, который нас тянет назад отбросить и использовать готовые решение в рельсах. Слава богу много не нагородили, осталось только мигрировать проект на последнюю версию.
                      Спасибо!
                        0
                        Эх… Где же тот 1.1.6? Рельсы и тогда были вполне юзабельными!
                          0
                          Их мало кто застал из нынешних железнодорожников:)
                            0
                            А у нас проект даже был в production на них )
                          0
                          Хотя и нет проблем с английским, но было бы очень классно This Week in Edge Rails читать на русском и с примерами.

                          Only users with full accounts can post comments. Log in, please.