📜 ⬆️ ⬇️

Overview of the development of the Ruby on Rails framework over the past 14 months

Over time, do not have time. Everything develops very rapidly. At some point, I noticed that although I work with the latest version of Ruby on Rails, I don’t use many of the features that I implement in it, and I don’t even hear about many.
I will try to do a retrospective of what has been entered into Rails over the past 14 months. Each innovation will be accompanied by a small example, which I will copy as is from the source on which the article is based, since such explanations for each are a topic for a heap of individual articles or a link.

The narrative goes in the form of:
Version
What happened after her
The new version includes all the innovations described above.

Version 2.0.2. December 2007


Rails simply did level up compared to the 1.xx series. Who worked with it will understand me. What happened then?
There is an opportunity to specify an engine for caching:
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/>

There is also an opportunity to make your own .
Simplified work with time zones .
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/>


Name_scope appeared:
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/>

Inheritance has_one got the option: 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/>

Introduction of "dirty objects":
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/>

That allowed to generate such sql, queries that send only modified fields to the database:
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/>

Now you can specify from which gems and which version your Rails application depends on:
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/> # Ie 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/>

Almost at the same moment Rails-developer had to forget about hemorrhoids with migrations of the type xxx_name. Naming migrations became UTC-based:
Copy Source | Copy HTML<br/>> script/generate migration one<br/> create db/migrate/20080402122512_one.rb <br/>

Version 2.1. June 2008


The name of the partial was determined automatically:
Copy Source | Copy HTML<br/>render :partial => 'employees' , :collection => @workers, :as => :person <br/>

To validate validates_length_of, an option was added: 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/>

In the find method of the models option: joins, you can specify not only a string, while respecting all the SQL rules, but also simply the names of the models:
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/>

And the option: conditions can be described in more detail:
Copy Source | Copy HTML<br/> # Get all the users that have published articles <br/>User.find(:all, :joins => :article, :conditions => { :articles => { :published => true } }) <br/>

Memoization appeared, which allowed us to forget about || =, but personally I somehow didn’t take it.
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/>

Something called “Nested Model Mass Assignment” appeared (how to translate it?), In general, after the example, everything becomes clear:
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/>

Do not forget about the presentation:
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/>

Thus, all the logic can be concluded in the model, and the controller will look like:
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/>

All objects have a metaclass method. wtf

Around the same time, the “Standard Internationalization Framework” is being born, which, I think, no one has lost sight of and now uses in its projects.
An etag appeared, which allows (based on subsequent changes) very well managed with caching both on the server side and the client side. For more details I send to the most wonderful screencasts and article .
named_scope evolved , mini-example:
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 appeared . Note that in later versions this option no longer affects nested routes.
Meanwhile, thanks to Google Summer of Code, Joshua Peak made Rails thread safe. Since this was done Ruby on Rails has acquired a pool of connections to the database.

Version 2.2.2 October, 2008


There were two routes, IMHO, the options are very good: except and: only. Explanatory example:
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/>

A supplement to named_scope has been published - 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/>

It was decided to rename application.rb to application_controller.rb, which will take effect starting with Rails 2.3.
Partial evolved even more :
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/>

Appeared 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/>

Routes with .format at the end were removed. Hooray! Personally, I very rarely needed them. An example of how to use .format now:
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/>

In December 2008, the ability to generate your application based on a template was added. More details.
A little later, Rails Metal appears, which allows you to give a response to the client, bypassing the MVC model. Screencast in the subject .
named_scope becomes dynamic:
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/>

After the new year, HTTP Digest Authentication appears.
In the continuation of the Nested Model Mass Assignment appears Nested Object Forms . Great innovation.

Version 2.3.0RC1. February, 2009


The latest innovation in RoR is Batched Find. Just an example:
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/>

Version 2.3.0. ?, 2009


We are not waiting for you.
')
I draw the attention of developers. Use the version of RoR, which you have in the project to the maximum, do not reinvent the wheel :) Good luck and have a nice day. Comments and corrections are welcome.

Source: https://habr.com/ru/post/53367/


All Articles