Reputation: 1172
So I have this sort of system currently:
class Tag < ActiveRecord::Base
# attr :name
end
class Article < ActiveRecord::Base
has_many :tag_associations, dependent: :destroy
has_many :tags, through: :tag_associations
end
class Company < ActiveRecord::Base
has_many :articles
end
class User < ActiveRecord::Base
belongs_to :company
end
I would like to do this:
user.company.articles.includes(:tags).all
I am going to use it like this (I already have a question on the eager-loading aspect of this):
company.articles.all.select do |article|
article.tags.any? do |tag|
tag.name == 'foo'
end
end
How can I make a new association on the company object for articles_by_tag
?
class Company < ActiveRecord::Base
has_many :articles
has_many :articles_by_tag, scope -> { |tag| ... }
end
And how would I end up using it?
company.articles_by_tag(tag: 'foo').all
Upvotes: 0
Views: 30
Reputation: 102213
This is a textbook X & Y question. Eager loading everything and then using .select
with a block to loop through it in Ruby is basically like ordering every pizza at a restaurant just to find the one that has anchovies on it and tossing everything else.
Instead you use a INNER JOIN to filter the rows:
Acticle.joins(:tags)
.where(tags: { name: 'foo' })
This will only return rows with a match in the tags table.
If you want to write this into a scope you would do:
class Article < ApplicationRecord
scope :with_tag, ->(name){ joins(:tags).where(tags: { name: name }) }
end
As a bonus you can use GROUP
and HAVING
to find articles with a list of tags:
class Acticle < ApplicationRecord
def self.with_tags(*tags)
left_joins(:tags)
.group(:id)
.where(tags: { name: tag })
.having(Tag.arel_table[Arel.star].count.eq(tags.length))
end
end
Upvotes: 1