featurebranch.com

GitHub RSS

The two biggest bugs fixed in Rails 4.1

Rails 4.0 has been out now for a while. It’s brought some great new features but as always there have been a few bugs too. And two of them that have been annoying me especially much will be fixed in Rails 4.1. Hooray! \o/

1. has_one associations will stop always using ORDER BY id

Best to illustrate this with an example (taken from the corresponding rails/rails#12623 issue)

class User < ActiveRecord::Base
  has_one :profile
end
class Profile < ActiveRecord::Base
  belongs_to :user
end

In Rails 3.2 some_user.profile executed this database statement

SELECT * FROM profiles WHERE profiles.user_id = ? LIMIT 1

but starting with Rails 4 first always used a default ORDER BY (rails/rails#5153) because good databases (PostgreSQL :) ) will optimize a query without an explicit order, which means calling User.first may return different users every time, while User.order(:id).first may not.

(FYI Rails 4.0 introduced take which doesn’t use a default order. Most of the time you probably want to write User.where(...).take instead of User.where(...).first)

Because of that in Rails 4.0 some_user.profile executed this database statement:

-- Rails 4
SELECT * FROM profiles WHERE profiles.user_id = ? ORDER BY profiles.id ASC LIMIT 1

You would think, why does it matter if I send the ORDER BY or not, the result set has only one row anyhow.

But if the stars are just right PostgreSQL will decide to use a different (slower) index depending on whether the ORDER BY is there or not. And yes I did run into that.

Marko Tiikkaja explained PostgreSQL’s thought process like this:

hey, this user_id is quite common, I bet if I just start scanning in id order, I’ll find one row quickly

Rails 4.1 (and the upcoming Rails 4.0.3) will go back to the old behavior.

-- Rails 4.1
SELECT * FROM profiles WHERE profiles.user_id = ? LIMIT 1

doge on Rails

2. associations can be unscoped

This one is rails/rails#10643. Clark Giorgos posted a nice demo:

class Product
  default_scope deleted_at: nil
end

class OrderItem
  belongs_to  :product
  belongs_to  :unscoped_product, -> { unscoped }, foreign_key: :product_id, class_name: "Product"
end

OrderItem.joins(:unscoped_product).group(:product_id).count

leads to Rails executing this SQL:

SELECT COUNT(*) AS count_all, product_id AS product_id
FROM "order_items"
INNER JOIN "products" ON "products"."id" = "order_items"."product_id"

AND "products"."deleted_at" IS NULL  -- This should NOT be here

GROUP BY product_id

And starting with Rails 4.1 this will be fixed. \o/

Getting rid of a default_scope in an association is going to be very useful. Some people think that default_scope is EVIL and being able to unscope an association is going to go some way towards disproving that statement.

Thoughts? Tweet me @MSch, send an email or leave a comment below.