Pull to refresh

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

Reading time12 min
Views1.1K
За временем не успеть. Все вокруг развивается очень стремительно. В какой-то момент я заметил, что хоть и работаю с последней версией 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-а, которая у вас в проекте по максимуму, не изобретайте велосипедов :) Удачи и доброго дня. Замечания и исправления приветствуются.
Tags:
Hubs:
+51
Comments22

Articles

Change theme settings