So this morning I got up after sleeping 3-4 hours and all I can somehow think of is having Query objects for ActiveRecord finders and delayed query execution. If done well, this could open pandora’s box of neat ways to extend ActiveRecord. So talk stops here.
Here’s the very basic first draft done under influence ( coffee ;-) ) :
# activerecord/lib/active_record/abstract_query.rb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
module ActiveRecord class AbstractRecords attr_reader :records, :klass, :query delegate :connection, :instantiate, :name, :to => :klass delegate :sql, :options, :to => :query def initialize(query, klass) @query = query @klass = klass @loaded = false end def method_missing(method_id, *args, &block) load_records records.send(method_id, *args, &block) end def loaded? @loaded end private def load_records return if @loaded @records = connection.select_all(sql, "#{name} Load").collect! { |record| instantiate(record) } @records.each { |record| record.readonly! } if options[:readonly] @loaded = true end end class AbstractQuery < DelegateClass(AbstractRecords) attr_reader :sql, :options def initialize(klass, sql, options = {}) @sql = sql @options = options super(AbstractRecords.new(self, klass)) end end end |
So after this, a session from console would look something like :
1 2 3 4 5 |
>> i = Item.find :all, :limit => 10 => #<ActiveRecord::AbstractRecords:0x19520e4 @klass=Item(id: integer, name: string, created_at: datetime, updated_at: datetime), @loaded=false, @query=#<ActiveRecord::AbstractRecords:0x19520e4 ...>> >> i.first Item Load (0.000361) SELECT * FROM `items` LIMIT 10 => #<Item id: 1, name: "wtf", created_at: "2007-12-12 13:28:56", updated_at: "2007-12-12 13:28:56"> |
Notice when the query gets executed. Experimental patch can be found here







That’s awesome
Lifo you are a creative!
Looks useful. I like those moments when later IS better :) Inspired by this I deferred hitting db in my humble plugin: https://svn.trix.pl/public/background_fu/lib/background_fu.rb and it saved my day.
How does your abstract query get mixed into Rail’s find method? Also will this respond to the respond_to? method correctly? I.E.
User.find(:all).respond_to? :size
Other than that it looks awesome. No real downside that I can see. In the common case it would not make a performance difference (either way you do the load) but for cases of conditional logic it could increase performance for the lazy programmer or make the code more DRY for the non-lazy programmer. So instead of the following in your controller:
@items = Item.find :all if admin?
then in the views doing
<% if admin? %> Items: <%= @items * ’, ’ %> <% end %>
Now you can remove the if statement in the controller DRYing it up a bit.
See also Ambition, which has already developed the delayed execution very well, in addition to providing a Rubyish interface.
Thanks your comments guys !
Eric : No respond_to? love yet. May be AbstractResult class can inherit from an Array or may be use delegation properly, method_missing() is just a quick hack. For mixing this with AR, check the monkey patch
RubyPanther : Welcome back ! Yeah, I’ve checked ambition. But my aim here to get these stuff in core rails :-)
Very cool. Jesse Newland threw together something similar: http://jnewland.com/svn/public/ruby/rails/plugins/lazy_record/