Skip to content

Loading…

inverse_of doesn't work with "includes" selections #7440

Closed
pluff opened this Issue · 6 comments

4 participants

@pluff

inverse_of does not applies if I use "includes" in relation. In example below accessing without includes will NOT cause additional queries, but version with includes will do so.

class Plan < ActiveRecord::Base
  has_many :programs, inverse_of: :plan
end

class Program < ActiveRecord::Base
  belongs_to :plan, inverse_of: :programs
  has_many :user_programs, inverse_of: :program
end

class UserProgram < ActiveRecord::Base
  belongs_to :user
  belongs_to :program, inverse_of: :user_programs
end

1.9.3p125 :087 > pl = Plan.first
  Plan Load (0.1ms)  SELECT "plans".* FROM "plans" WHERE (plans.created_at > 1) LIMIT 1
 => #<Plan id: 1, created_at: "2012-07-19 10:27:57", updated_at: "2012-07-19 10:27:57"> 
1.9.3p125 :088 > pl.programs
  Program Load (0.3ms)  SELECT "programs".* FROM "programs" WHERE "programs"."plan_id" = 1
 => [#<Program id: 1, plan_id: 1, created_at: "2012-07-19 10:28:14", updated_at: "2012-07-19 10:28:14">, #<Program id: 2, plan_id: 1, created_at: "2012-07-19 10:28:41", updated_at: "2012-07-19 10:28:41">] 
1.9.3p125 :089 > pl.programs.map(&:plan)
 => [#<Plan id: 1, created_at: "2012-07-19 10:27:57", updated_at: "2012-07-19 10:27:57">, #<Plan id: 1, created_at: "2012-07-19 10:27:57", updated_at: "2012-07-19 10:27:57">] 
1.9.3p125 :090 > pl.programs.includes(:user_programs)
  Program Load (0.4ms)  SELECT "programs".* FROM "programs" WHERE "programs"."plan_id" = 1
  UserProgram Load (0.1ms)  SELECT "user_programs".* FROM "user_programs" WHERE "user_programs"."program_id" IN (1, 2)
 => [#<Program id: 1, plan_id: 1, created_at: "2012-07-19 10:28:14", updated_at: "2012-07-19 10:28:14">, #<Program id: 2, plan_id: 1, created_at: "2012-07-19 10:28:41", updated_at: "2012-07-19 10:28:41">] 
1.9.3p125 :091 > pl.programs.includes(:user_programs).map(&:plan)
  Program Load (0.4ms)  SELECT "programs".* FROM "programs" WHERE "programs"."plan_id" = 1
  UserProgram Load (0.4ms)  SELECT "user_programs".* FROM "user_programs" WHERE "user_programs"."program_id" IN (1, 2)
  Plan Load (0.3ms)  SELECT "plans".* FROM "plans" WHERE "plans"."id" = 1 AND (plans.created_at > 1) LIMIT 1
  Plan Load (0.3ms)  SELECT "plans".* FROM "plans" WHERE "plans"."id" = 1 AND (plans.created_at > 1) LIMIT 1
 => [#<Plan id: 1, created_at: "2012-07-19 10:27:57", updated_at: "2012-07-19 10:27:57">, #<Plan id: 1, created_at: "2012-07-19 10:27:57", updated_at: "2012-07-19 10:27:57">]

P.S. Rails 3.2.6, Ruby 1.9.3

@al2o3cr

Can you show / describe what's producing the additional SQL AND (plans.created_at > 1) that appears in the logs? Not sure if it's relevant, but it's important to have all the information when thinking about these loading behaviors.

@pixeltrix
Ruby on Rails member

This is a duplicate of a ticket (#5717) I opened - what happens is that when includes or any other scope method is called a new ActiveRecord::Relation is created and that doesn't know anything about the association. I've a fix (9f607fb) that appears to work but it will only applied to master as it changes current behavior.

@pixeltrix pixeltrix closed this
@pixeltrix
Ruby on Rails member

If anybody wants this fixed in 3.2.x then put this code in an initializer:

ActiveSupport.on_load(:active_record) do
  ActiveRecord::Relation.class_eval do
    attr_accessor :association

    def merge_with_association(other)
      merge_without_association(other).tap do |relation|
        relation.association = other.association if other.association
      end
    end

    alias_method_chain :merge, :association

    private

    def exec_queries_with_association
      exec_queries_without_association.tap do |records|
        if association
          records.each{ |record| association.set_inverse_instance(record) }
        end
      end
    end

    alias_method_chain :exec_queries, :association
  end

  ActiveRecord::Associations::AssociationScope.class_eval do
    def scope_with_association
      scope_without_association.tap do |relation|
        relation.association = association
      end
    end

    alias_method_chain :scope, :association
  end
end
@grzuy

@pixeltrix thanks for the 3.2.x fix. Just one comment.
In the #merge_with_association method, shouldn't you check "other" being not nil also?

@pixeltrix
Ruby on Rails member

Actually merge can take an array as well so yes the 3.2 patch could do with some refactoring - you'd also want to add a patched merge! method as well.

@grzuy

@pixeltrix good point about Array. Probably need to check other.respond_to? :association.
On the other hand, no merge! in 3.2.x :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.