3

Is there a way to use the through option in a belongs_to relationship? The Rails documentation on belongs_to doesn't mention through as an option, why not? I want to do something like the following:

class Lesson < ActiveRecord::Base
  attr_accessible :name, :lesson_group_id
  belongs_to :lesson_group
  belongs_to :level, through: :lesson_group
end

class LessonGroup < ActiveRecord::Base
  attr_accessible :name, :level_id
  belongs_to :level
  has_many :lessons
end

class Level < ActiveRecord::Base
  attr_accessible :number
  has_many :lesson_groups
end

Then I can do something like Lesson.first.level. Using latest stable Rails (3.2.9 as of now).

at.
  • 50,922
  • 104
  • 292
  • 461
  • @MrYoshiji - Under `has_one`, the Rails documentation says, "This method should only be used if the other class contains the foreign key. If the current class contains the foreign key, then you should use belongs_to instead." – at. Dec 06 '12 at 17:59
  • The documentation for `has_one` also says "Options: [...] - :through Specifies a Join Model through which to perform the query. Options for :class_name, :primary_key, and :foreign_key are ignored, as the association uses the source reflection. You can only use a :through query through a has_one or belongs_to association on the join model." – MrYoshiji Dec 06 '12 at 18:01

2 Answers2

5

In the link you gave:

Specifies a one-to-one association with another class. This method should only be used if this class contains the foreign key.

I think you should use has_one :level, through: :lesson_group, like following:

class Lesson < ActiveRecord::Base
  attr_accessible :name, :lesson_group_id
  belongs_to :lesson_group
  has_one :level, through: :lesson_group
end

class LessonGroup < ActiveRecord::Base
  attr_accessible :name, :level_id
  belongs_to :level
  has_many :lessons
end

class Level < ActiveRecord::Base
  attr_accessible :number
  has_many :lesson_groups
end

A part of the documentation about the Options for has_one:

:through

Specifies a Join Model through which to perform the query. Options for :class_name, :primary_key, and :foreign_key are ignored, as the association uses the source reflection. You can only use a :through query through a has_one or belongs_to association on the join model.

They talked about that here: Rails has_one :through association

Community
  • 1
  • 1
MrYoshiji
  • 54,334
  • 13
  • 124
  • 117
  • That seems to not make sense. `Lesson`'s `belongs_to :lesson_group` exists because it does have the foreign key. Similarly `LessonGroup`'s `belongs_to :level` exists because it has the foreign key. I'll try this, but reluctant to rely on this mechanism even if it works as the documentation doesn't make it clear at all to use `has_one` in this situation. – at. Dec 06 '12 at 18:05
  • I agree, it is not clear for this kind of situation. This may interest you a bit: http://stackoverflow.com/questions/2116017/rails-has-one-through-association – MrYoshiji Dec 06 '12 at 18:07
  • `belongs_to :through C` does not exist because the current table does not contain the foreign key that point to `C`. – Ciro Santilli OurBigBook.com Jan 24 '14 at 10:15
0

In Rails 6, Ruby 2.6, I ran into a similar issue where I had a User which could belong to many Organizations, but always had a primary_organization which was the first Organization they were assigned to. Originally I had set this with the method:

def primary_organization
  organizations.first
end

However, that ultimately broke my scope chaining. The above primary_organization method can be refactored into a chainable conditional without having to change the user.primary_organization syntax:

user.rb

# has_many: OrganizationUsers > Organizations
# ==================================================================================
has_many    :organization_users,
              class_name:     'OrganizationUser',
              inverse_of:     :user,
              dependent:      :destroy,
              autosave:       true,

has_many    :organizations,
              class_name:     'Organization',
              through:        :organization_users,
              inverse_of:     :organization_users,
              source:         :organization

# *** ADDED IN REFACTOR ***
# has_one: Organization
# shortcut for:
#   • `user.organization_users.first.organization`
#   • `user.organizations.first`
# ==================================================================================
has_one     :primary_organization_user,
              -> {
                order(
                  created_at: :desc
                ).limit(1)
              },
              class_name:     'OrganizationUser',
              inverse_of:     :user

has_one     :primary_organization,
              class_name:     'Organization',
              through:        :primary_organization_user,
              inverse_of:     :organization_users,
              source:         :organization

organization_user.rb

# belongs_to: Organization
# ==================================================================================
belongs_to  :organization,
              class_name:     'Organization',
              inverse_of:     :organization_users

# belongs_to: User
# ==================================================================================
belongs_to  :user,
              class_name:     'User',
              inverse_of:     :organization_users

organization.rb

# has_many: OrganizationUsers > Users
# ==================================================================================
has_many    :organization_users,
              class_name:     'OrganizationUser',
              inverse_of:     :organization,
              autosave:       true,
              dependent:      :destroy

has_many    :users,
              class_name:     'User',
              through:        :organization_users,
              inverse_of:     :organization_users,
              autosave:       true
Eric Norcross
  • 4,177
  • 4
  • 28
  • 53