Neglected ActiveRecord Features
As Rails developers, we’re constantly using ActiveRecord, but are mostly using the same subset of methods and features it provides. Some of its lesser known and lesser used features could help you write cleaner code in the future and delight your co-workers. So let’s dive right in!
> user_ids = User.limit(5).pluck(:id) (0.5ms) SELECT "users"."id" FROM "users" ORDER BY "users"."name" ASC LIMIT 5 => [856, 857, 858, 850, 852]
Instead of calling
map on ActiveRecord objects, use
pluck if you only need a list of attributes. It avoids building ActiveRecord model objects and is a lot more efficient for larger collections.
> Post.where(:user_id => user_ids).count (5.2ms) SELECT COUNT(* ) FROM "posts" WHERE "posts"."user_id" IN (856, 857, 858, 850, 852) => 286
You can provide an array argument for the
where clause, which is translated to an SQL
Where and Not
> Post.count (0.6ms) SELECT COUNT(* ) FROM "posts" => 1120 > Post.where(:category => 'rails').count (0.8ms) SELECT COUNT(* ) FROM "posts" WHERE "posts"."category" = 'rails' => 295 > Post.where.not(:category => 'rails').count (0.9ms) SELECT COUNT(* ) FROM "posts" WHERE ("posts"."category" != 'rails') => 825
where.not will test for inequality, where you might have used
where('category != ?', 'rails') in the past. This was introduced with Rails 4.0.
> date_start = Date.new(2013, 1, 1) => Tue, 01 Jan 2013 > date_end = Date.new(2013, 2, 1) => Fri, 01 Feb 2013 > puts Post.where(:published_on => date_start..date_end).to_sql SELECT "posts".* FROM "posts" WHERE ("posts"."published_on" BETWEEN '2013-01-01' AND '2013-02-01')
Where clauses also support ranges. In the example above we see a ruby date range that gets elegantly translated
> puts Post.unscoped.order(:published_on => :asc).to_sql SELECT "posts".* FROM "posts" ORDER BY "posts"."published_on" ASC => nil > puts Post.unscoped.order(:published_on => :desc).to_sql SELECT "posts".* FROM "posts" ORDER BY "posts"."published_on" DESC => nil
Instead of the using the
order argument with a string as
order("published_on DESC"), you can simply pass a hash.
Discarding From The Chain
> puts Post.unscoped.order(:published_on => :desc).except(:order).to_sql SELECT "posts".* FROM "posts"
expect syntax you can get rid of clauses to the ActiveRecord relation that were added before. This could
come in handy if you’re given a base scope that is valid for all cases except yours and avoids throwing away any other modifications to the relation.
The Null Relation
> Post.none.where(:category => "rails") => 
This feature might look like non-sense at first. The
none clause makes sure that whatever clause is added to the relation, the result will always be empty. Again, this might make sense if you are provided with a relation and have to return one again. Say now we have a case where the current query is not valid due to permission constraints. Adding the
none clause makes sure that whatever relation modification follows, the final query will not hit the database and will be empty.
post = Post.readonly.first post.title = "Don't change me!" post.save!
readonly clause makes sure that a relation cannot be modified. This might make sense if you pass an object to any code that is not yours (e.g. a library) and want to make sure that the object is not modified.
So that’s it. I hope you can use some of these tricks to not only make your code faster and more elegant, but also more readable.