Mimicking Rails 3’s ActiveRecord query interface in Rails 2.x
NOTICE: This is only for educational and fun purposes, nothing else. I'm not trying to say that you'll get all the pros of the new query interface in Rails 2.x, it isn't even close.
The first time I used the new ActiveRecord query interface (Rails 3) I started to get boring with the one in Rails 2.x, I can't do anything, I have to use the old query interface since converting my projects from Rails 2 to Rails 3 is not an option, so I thought to make a sort of placebo, just for fun, just to be used in my local console, and here is.
module FakeActiveRecord3Methods
def self.included(base)
base.class_eval do
%w/select group order limit offset joins having/.each do |method|
named_scope method.to_sym, lambda { |arg| { method.to_sym => arg } }
end
named_scope :where, lambda { |arg| { :conditions => arg } }
named_scope :includes, lambda { |arg| { :include => arg } }
end
end
end
Click here to see a Gist in GitHub
I used named scopes because they are chainable, if I would have added instance methods to ActiveRecord::Base it would haven't worked as I want to, maybe there's a way to make it work that way, but this was a 5-minutes module, I didn't want to spent too much time on it.
well, I have to say I don't use this module in production servers, only locally in development mode. This is how I configure my development environment to use it:
In my environment/development.rb
config.after_initialize do
[User, And, All, The, Models, You, Want].each do |model|
model.class_eval do
include FakeActiveRecord3Methods
end
end
end
Click here to see a Gist in GitHub
I add the models I use the most, if I need to use the FakeActiveRecord3Methods module in other model I include it in the console. To make this even easier I added to my ~/.irbrc the following code:
class Class
public :include
end
It allows us to include a module this way:
Profile.include FakeActiveRecord3Methods
otherwise Ruby will throw an exception saying we're trying to access a private method.
It might seem cumbersome to make all of this but it isn't that hard.
It's a shame I won't be able to prove that FakeActiveRecord3Methods works in most of the cases but you can test with the examples shown in Rails Guides: Active Record Querying.
Here are some queries using this module anyways. I think all of them are self-explanatory.
User.select("id, email").limit(2)
=> [#<User id: 1, email: "e@mail.com">, #<User id: 2, email: "bar@foo.com">]
User.select("id, email").where(["created_at > ?", 1.month.ago]).order("email DESC").limit(1)
=> [#<User id: 5, email: "foo@bar.com">
User.limit(5) User.select(:id) User.select(:id).limit(10).offset(100) User.order :created_at
Joins
users = User.select("users.id, profiles.company").joins(:profile)
users.first.company # => Foo Company
users.first.profile.company # => Foo Company
Includes
users = User.limit(10) users.first.name # => Foo users.first.profile.company # => it will query the database users = User.includes(:profile).limit(10) # it will bring the profile as well users.first.profile.company # it won't query the database
Of course, you should be aware that in named scopes the order really matters, be careful and remember, in Rails 3, the where, select, group, order, limit, offset, joins and includes returns an instance of ActiveRecord::Relation which is not the case in the FakeActiveRecord3Methods module, it returns an instance of ActiveRecord::NamedScope::Scope and that's because named scopes returns an instance of such class.
By the way, if you want to read the Rails 2's ActiveRecord Query Interface, you can do so clicking here
Thanks for reading.
Adding a :noselect option to ActiveRecord (Rails 2.3.x)
Let's say you have an users table with the following columns
firstname, lastname, email, crypted_password and salt
so you get all the records with:
User.find(:all) User.all #it calls find internallywell, you should know that ActiveRecord creates one instance of the User model for every record it founds and that every column of such records is stored in a instance variable of the User model instance.
now, let's say your table stores 2,000 users, ActiveRecord would create 2,000 instances of the User model and let's say your table have 15 columns, so each of this 2,000 instances of the User model will have 15 instances variables and at least 30 dynamic methods (setters and getters), so, it'd be a good idea if we can reduce, at least, the quantity of instance variables and setters and getters, don't you think?
well, for me, the first approach would be to check if I need all the columns, once I realize that I am not, I'd change the call, let's say I don't need the salt column:
User.all(:select => "firstname, lastname, email, crypted_password")let's benchmark! I'll use a table with 130 users.the results are:Only :all (3528.6ms):all with :select (3439.1ms)3439.1ms is less than 3528.6ms!! yes, milliseconds, but what about a table with thousands and thousands of users?ok, now let's say you have a table with 20 or 30 columns, can you imagine?User.all(:select => "col_1, col_2, col_3, col_4, col_5, col_6, col_7, col_n")all of this just because you don't want one or two columns???well, I made a patch to add a :noselect option:User.all(:noselect => "salt")and it'll result in the same SQL query thanUser.find(:all, :select => "firstname, lastname, email, crypted_password")which means:SELECT firstname, lastname, email, crypted_password FROM usersAnd here is the link to the patch:
https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4012-patch-add-noselect-option-to-activerecordfind