anil.n
anil.n

Reputation: 519

Rails, use different join table for `has_many :through` association based on condition

I'm working on a application in which there is table named companies and a company can have multiple users and users can have multiple books and magazines belonging to company. Currently the associations is as follows:

Company.rb

has_many :users
has_many :books
has_many :magazines

User.rb

belongs_to :company
has_many :user_books, dependent: :destroy
has_many :books, through: :user_books
has_many :user_magazines, dependent: :destroy
has_many :magazines, through: :user_magazines

Book.rb

belongs_to :company
has_many :user_books, dependent: :destroy
has_many :users, through: :user_books

Same association exists for magazines as well. Not using has_and_belongs_to_many association as we are capturing additional data in association table. Now we need to introduce a new entity called audio_books similar to books and magazines but instead of creating one more association table called user_audio_books we are planning to create a polymorphic association table called user_items which would record the association between user and the company items(books, magazines & audio_books).

New association: User.rb

has_many :user_items, dependent: destroy
has_many :books, through: :user_items, as: :source, source: source, source_type: 'Book'
has_many :magazines, through: :user_items, as: :source, source: source, source_type: 'Magazine'

However this new association will be enabled only for few companies for time being, until then we need to support all the 16 methods added by has_many association on both the models on conditional basis.

I'm looking for something like in user.rb:

has_many :user_books, dependent: :destroy
has_many :user_items, dependent: :destroy
<if company.audio_books_enabled? >
has_many :books, through: :user_items, as: :source, source: source, source_type: 'Book'
<else>
has_many :books, through: :user_books
<end>

audio_books_enabled? is not an sql query but a redis based check.

Is it possible to achieve this?

Upvotes: 3

Views: 667

Answers (2)

Alex
Alex

Reputation: 29751

You can't have two associations with the same name. But a method that would return different associations could work:

class User < ApplicationRecord
  belongs_to :company
  has_many :user_books, dependent: :destroy
  has_many :user_items, dependent: :destroy

  # NOTE: rename the old `books` association
  has_many :legacy_books, through: :user_books, source: :book, class_name: "Book"

  # NOTE: add another `books` association
  has_many :new_books, through: :user_items, as: :source, source: :source, source_type: "Book"

  def books
    if company.audio_books_enabled?
      new_books
    else
      legacy_books
    end
  end

  # maybe something like this for associating books with user
  def books= params
    if company&.audio_books_enabled?
      self.new_books = params
    else
      self.legacy_books = params
    end
  end
end

You could have a books association and books method if you have to:

class User < ApplicationRecord
  belongs_to :company
  has_many :user_books, dependent: :destroy
  has_many :user_items, dependent: :destroy

  # NOTE: rename the old `books` association
  has_many :legacy_books, through: :user_books, source: :book, class_name: "Book"

  # NOTE: add another `books` association
  has_many :books, through: :user_items, as: :source, source: :source, source_type: "Book"

  def books
    if company.audio_books_enabled?
      super
    else
      legacy_books
    end
  end
end

Upvotes: 1

Ben Sheldon
Ben Sheldon

Reputation: 1104

You write that "this new association will be enabled only for few companies for time being" by which I assume you mean "I would only like to allow some companies to create these objects/associations".

You can do that with a validation:

# user_item.rb

validate do |user_item|
  errors.add(:company, :audio_books_not_enabled, message: "does not have audio books enabled") unless user_item.company.audio_books_enabled?
end

Upvotes: 0

Related Questions