camertron/arel-helpers

Breaks subscript notation for array like behavior of ActiveRecord::Relation

Closed this issue · 4 comments

Possibly a regression in 2.1.0 (#10), or maybe a slightly different bug

I'm seeing this with fairly normal AR class

class Company < ActiveRecord::Base
  include ArelHelpers::ArelTable
Company.all.class                                                                                                                                     
=> Company::ActiveRecord_Relation
[149] pry(main)> Company.all[0]                                                                                                                                        
=> #<struct Arel::Attributes::Attribute
 relation=
  #<Arel::Table:0x007faa3c4d9f20
   @aliases=[],
   @columns=nil,
   @engine=
.....
[150] pry(main)>                   

Thanks for the bug report @svoynow. What version of Rails/ActiveRecord/Arel are you using? Once I know that I can try reproducing.

rails 4.2.2
active-record 4.2.2
arel 6.0.3
arel-helpers 2.1.0

Awesome, thanks. I'll take a look.

Ok, I've tracked down the cause of the problem. It seems that ActiveRecord::Relation instances delegate certain array methods like #[] to their #to_a methods. This makes sense when you realize that relations behave like arrays without actually being arrays. The following code lives in activerecord's delegation.rb:

def method_missing(method, *args, &block)
  if @klass.respond_to?(method)
    self.class.delegate_to_scoped_klass(method)
    scoping { @klass.send(method, *args, &block) }
  elsif Array.method_defined?(method)
    self.class.delegate method, :to => :to_a
    to_a.send(method, *args, &block)
  elsif arel.respond_to?(method)
    self.class.delegate method, :to => :arel
    arel.send(method, *args, &block)
  else
    super
  end
end

Notice that activerecord sets up the delegation as soon as any array method is detected by method_missing, effectively memoizing the lookup. Any subsequent calls to the same array method will be handled by the delegation and bypass method_missing entirely.

Unfortunately for arel-helpers, this is a bit of bad news. Notice that before array methods are examined, the method_missing above checks to see if the model class (i.e. @klass) responds to the method. ArelHelpers::ArelTable adds #[] to the model class, which gets invoked instead of the correct array method.

I think the fix here is to force delegation to #[] with a call to ActiveRecord::Delegation#delegate (which by the way is mixed into ActiveRecord::Relation). I've created #19 to address the issue.