Rails 4.1 compatibility
Why bother?
- No features in 3.x branch since February 2013
- No bugfixes since late 2013 (?)
- Rails 4 is threadsafe by default
- Simple tests show that Rails 4 is notably faster than 3
Cost of the change
Many things has been deprecated or removed (preceded by earlier deprecation notices that we happily ignored). Code working in Rails 3 not necessarily works in Rails 4 without changes.
Deprecated finders
DON'T
User.find(:all,
:conditions => {:name => 'Jane Doe'},
:limit => 3,
:order => :created_at)
DO
User.where(name: 'Jane Doe').limit(3).order(created_at: :desc)
Last resort
Gem: activerecord-deprecated_finders
https://github.com/rails/activerecord-deprecated_finders
But we are trying to avoid it.
Behaviour of "includes"
Rails 3
[3] pry(main)> Order.includes(:owner_subject).limit(3)
Order Load (0.9ms) SELECT "orders".* FROM "orders" LIMIT 3
Subject Load (59.2ms) SELECT "subjects".* FROM "subjects" WHERE "subjects"."id" IN (13)
[5] pry(main)> Order.includes(:owner_subject).limit(3).where('subjects.id > 1')
SQL (2.0ms) SELECT "orders"."id" AS t0_r0, "orders"."created_at" AS t0_r1,
"orders"."type" AS t0_r2, "orders"."name" AS t0_r3, "orders"."number" AS t0_r4,
"orders"."job_process_id" AS t0_r5, "orders"."root_id" AS t0_r6,
"orders"."parent_id" AS t0_r7, "orders"."lft" AS t0_r8, "orders"."rgt" AS t0_r9,
"orders"."owner_subject_id" AS t0_r10, "orders"."status" AS t0_r11,
"orders"."deleted" AS t0_r12, "orders"."mis_ref" AS t0_r13,
"orders"."altered_at" AS t0_r14, "orders"."version_no" AS t0_r15,
"orders"."current_version_no" AS t0_r16, "orders"."seq_no" AS t0_r17,
"subjects"."id" AS t1_r0, "subjects"."kind" AS t1_r1, "subjects"."parent_id" AS t1_r2,
"subjects"."lft" AS t1_r3, "subjects"."rgt" AS t1_r4, "subjects"."domain_id" AS t1_r5,
"subjects"."language_locale" AS t1_r6 FROM "orders" LEFT OUTER JOIN "subjects"
ON "subjects"."id" = "orders"."owner_subject_id" WHERE (subjects.id > 1) LIMIT 3
Rails 4
2.1.2 :007 > Document.includes(:user).where('id > 2')
Document Load (1.0ms) SELECT "documents".* FROM "documents" WHERE (id > 2)
User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (3, 1)
2.1.2 :008 > Document.includes(:user).where('users.id < 2')
Document Load (1.5ms) SELECT "documents".* FROM "documents" WHERE (users.id < 2)
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "users"
LINE 1: SELECT "documents".* FROM "documents" WHERE (users.id < 2)
^
: SELECT "documents".* FROM "documents" WHERE (users.id < 2)
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing
FROM-clause entry for table "users"
LINE 1: SELECT "documents".* FROM "documents" WHERE (users.id < 2)
N+1 queries
It is not sufficient to change all "includes" to "joins"!
Only "joins"
2.1.2 :016 > Document.joins(:files).each {|d| d.files.map(&:id) }
Document Load (1.9ms) SELECT "documents".* FROM "documents" INNER JOIN "document_files" ON "document_files"."document_id" = "documents"."id"
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 1]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 2]]
DocumentFile Load (0.6ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 3]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 4]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 5]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 6]]
DocumentFile Load (0.4ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 7]]
DocumentFile Load (0.4ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 8]]
DocumentFile Load (0.4ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 9]]
DocumentFile Load (0.3ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 10]]
DocumentFile Load (0.3ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 11]]
DocumentFile Load (0.3ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 12]]
DocumentFile Load (0.4ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 11]]
DocumentFile Load (0.4ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 10]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 1]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 13]]
DocumentFile Load (0.5ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 13]]
DocumentFile Load (0.6ms) SELECT "document_files".* FROM "document_files" WHERE "document_files"."document_id" = $1 [["document_id", 14]]
[...]
Combined with "includes"
2.1.2 :017 > Document.joins(:files).includes(:files).each {|d| d.files.map(&:id) }
SQL (2.6ms) SELECT "documents"."id" AS t0_r0, "documents"."name" AS t0_r1,
"documents"."status" AS t0_r2, "documents"."user_id" AS t0_r3, "documents"."created_at" AS t0_r4,
"documents"."updated_at" AS t0_r5, "documents"."file_file_name" AS t0_r6,
"documents"."file_content_type" AS t0_r7, "documents"."file_file_size" AS t0_r8,
"documents"."file_updated_at" AS t0_r9, "documents"."details" AS t0_r10,
"document_files"."id" AS t1_r0, "document_files"."document_id" AS t1_r1,
"document_files"."intent" AS t1_r2, "document_files"."created_at" AS t1_r3,
"document_files"."updated_at" AS t1_r4, "document_files"."file_file_name" AS t1_r5,
"document_files"."file_content_type" AS t1_r6, "document_files"."file_file_size" AS t1_r7,
"document_files"."file_updated_at" AS t1_r8, "document_files"."file_processing" AS t1_r9
FROM "documents" INNER JOIN "document_files" ON "document_files"."document_id" = "documents"."id"
Routing
In Rails 4 routing with "match" without specifying verbs is illegal.
# DON'T
match 'orders/:id/condemn' => 'orders#condemn'
# DO
match 'orders/:id/condemn' => 'orders#condemn', via: [:post]
# DO BETTER
post 'orders/:id/condemn' => 'orders#condemn'
Double route-names are forbidden
# DON'T
post '' => 'orders#create', :as => :orders
match '' => 'orders#index', :as => :orders
Scope must be a callable object (lambda)
Scope
# DON'T
scope :not_deleted, where(deleted: false)
has_many :good_comments, conditions: 'karma > 1'
# DO
scope :not_deleted, -> { where(deleted: false) }
has_many :good_comments, -> { where('karma > 1') }
No need to serialize to JSON
Rails 4 has native support for JSON and hstore types in PotgreSQL. Calling "serialize" causes double serialization, which is never good.
# DON'T
serialize :options, ActiveRecord::Coders::Hstore
# DO
# well, just make sure that column is of hstore type and you're good to go
Caveat
# DON'T
@model.options[:active] = true
@model.save!
# DO
@model.options_will_change!
# or @model.attribute_will_change!(:options)
@model.options[:active] = true
@model.save!
# OR DO
opts = @model.options
opts[:active] = true
@model.options = opts
Strong parameters
Strong parameters
- attr_accessible and attr_protected are removed
- allowed attributes are defined in controller
def safe_params
params.require(:document).permit(files_attributes: [:file])
end
Other changes
PATCH instead of PUT
PATCH is default verb for update action since Rails 4.0, however PUT is still supported.
No "assets" group in Gemfile
vendor directory is no longer supported
deck
By katafrakt
deck
- 955