Query objects and delayed execution 7

Posted by pratik
on Wednesday, December 12

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.rb
1
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

Comments

Leave a response

  1. Ilya SabaninDecember 12, 2007 @ 04:01 PM

    That’s awesome

  2. OscarDecember 12, 2007 @ 05:25 PM

    Lifo you are a creative!

  3. Jacek BecelaDecember 12, 2007 @ 10:04 PM

    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.

  4. Eric AndersonDecember 13, 2007 @ 12:06 AM

    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.

  5. RubyPantherDecember 13, 2007 @ 12:25 AM

    See also Ambition, which has already developed the delayed execution very well, in addition to providing a Rubyish interface.

  6. PratikDecember 13, 2007 @ 03:07 AM

    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 :-)

  7. ChrisDecember 13, 2007 @ 07:22 AM

    Very cool. Jesse Newland threw together something similar: http://jnewland.com/svn/public/ruby/rails/plugins/lazy_record/

Comment