Richie Thomas
Richie Thomas

Reputation: 3265

Why is my call to "owner" in a "has_many" relationship not working?

I'm using Rails 5.0.7.1, and I see in the docs that my CollectionProxy instance should have access to an "@owner" instance variable:

Association proxies in Active Record are middlemen between the object that holds the association, known as the @owner, and the actual associated object, known as the @target. The kind of association any proxy is about is available in @reflection. That's an instance of the class ActiveRecord::Reflection::AssociationReflection.

the association proxy in blog.posts has the object in blog as @owner, the collection of its posts as @target, and the @reflection object represents a :has_many macro.

This class delegates unknown methods to @target via method_missing.

In my Rails app, I've got the following (rather unrealistic) test code:

class Post < ApplicationRecord
  has_many :comments  do
    def number_five
      if owner.is_a? Post
        Comment.where(id: 5, post_id: self.id)
      end
    end
  end
end

class Comment < ApplicationRecord
  belongs_to :post
end

When I call Post.last.commments.number_five, I get the following error:

NameError (undefined local variable or method `owner' for #
<Comment::ActiveRecord_Associations_CollectionProxy:0x00007fcbb9106120>)

When I add byebug to the line in between def number_five and owner.is_a? Post, and I check the value of self, I see it's ActiveRecord::Associations::CollectionProxy, so I think I'm calling owner in a scope where it should be defined.

I've tried Post.last.comments.instance_variables, and I don't see :@owner, only the following:

[:@association, :@klass, :@table, :@values, :@offsets, 
:@loaded, :@predicate_builder, :@scope]

I've also tried the following:

comments = Post.last.comments
def comments.get_owner
  self.owner
end

This returns the same NameError as above.

For what it's worth, when I run Post.last.comments.class, I see it's Comment::ActiveRecord_Associations_CollectionProxy.

Given how the docs read, I'd expect to be able to call either Post.last.comments.owner or @owner from within Post.last.comments (both of which I've tried), and have it return the value of Post.last. Is my expectation incorrect, or is my code wrong, or is it something else entirely?

Upvotes: 2

Views: 435

Answers (1)

mu is too short
mu is too short

Reputation: 434755

The documentation is a little confusing. I remember having to spend a few hours guessing, reading the Rails source, and experimenting to figure this out the first time I need to get outside the association from an extension method.

owner is what you're after but that's a method on the association and you get the association via proxy_association (which is just an accessor method for @association):

has_many :comments  do
  def number_five
    if proxy_association.owner.is_a? Post
      #...
    end
  end
end

I'm not sure if this is the "right" or "official" way to do this but this is what I've been doing since Rails 4.

Upvotes: 3

Related Questions