Finding through a joins table - ruby-on-rails-3

So I have a Product, product_tags joins table, and Tags. I would like to find all products with a specific tag name using Product.find. Is this possible?

You didn't post code, but I presume this is your setup:
class Product < ActiveRecord::Base
has_many :product_tags
has_many :tags, :through => :product_tags
end
class Tag < ActiveRecord::Base
has_many :product_tags
has_many :products, :through => :product_tags
end
class ProductTag < ActiveRecord::Base
belongs_to :product
belongs_to :tag
end
Then you can find all the products with a given tag name, e.g. "cool_product" as follows:
cool_tag = Tag.find_by_name("cool_product")
cool_tag.products # => list of all products
For reference and study, I highly recommend the read through the entire doc page on ActiveRecord::Associations::ClassMethods. The association methods are some of the most underappreciated features of Active Record.

Related

ActiveRecord querying through multiple joins

So below are my models:
post.rb
class Post < ActiveRecord::Base
has_many :taggings
has_many :tags, through: :taggings
has_many :collectables
has_many :collections, through: :collectables
end
tagging.rb
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag, counter_cache: :posts_count
end
tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
end
collectable.rb
class Collectable < ActiveRecord::Base
belongs_to :post
belongs_to :collection, counter_cache: :posts_count
end
collection.rb
class Collection < ActiveRecord::Base
has_many :collectables
has_many :posts
end
A Collection has many Posts, and a Post has many Tags. Now I'm trying to create a search bar that'll search for Collections by Posts that have particular Tags (tag.name is the search term). For instance, Collection 1 has a post that has the tag #cat. Now if the user searches for "cat", Collection 1 will show up in the results. I'm not sure how the query should look like for making this happen.
def self.search(search)
Collection.joins(:posts) ... ?
end
try this:
def self.search(search)
Collection.includes(posts: :tags).where(tags: {name: search})
end
Try This
def self.search(search)
Collection.joins(posts: :tags).where({tags: {name: search}})
end
You can use includes, eager_load and joins, but when you use joins then only it will take those collections who have the posts in all other cases, it will take all collections who have the posts and who don't have the posts.

How can I reference the same model twice and create the association in a multiple select form?

I have two classes (Game and Report) and want to link them with an additional attribute (default = yes or no).
The game should then have default_reports and optional_reports.
The association is then updated by selecting the default and optional reports in a select (multiple) in the games create/edit form.
I have tried using has_many and through as well as polymorphic associations, but nothing seems to fit the use case, where the associated objects are fixed and you only want to manage associations.
class Game < ActiveRecord::Base
has_many :game_reports
has_many :reports, :through => :game_reports
end
class Report < ActiveRecord::Base
has_many :game_reports
has_many :games, :through => :game_reports
end
class GameReport < ActiveRecord::Base
belongs_to :game
belongs_to :report
end
Any help is appreciated!
Thanks
this is just the model. the view and form to create the records is an entirely different matter.
you can always add a conditions option to has_many. I'm assuming you're going to add default to game_reports so change your class to something like.
class Game < ActiveRecord::Base
has_many :game_reports
has_many :reports, :through => :game_reports
has_many :default_reports, through: :game_reports, source: :report, conditions: { game_reports: { default: true } }
end
Rails 4.2+, use a Polymorphic association with scope and specify the foreign_key and foreign_type options.
class GameReport
belongs_to :report, :polymorphic => true
end
class Game
has_many :game_reports, :as => :report, :dependent => :destroy
has_many :reports, -> { where attachable_type: "GameReport"},
class_name: GameReport, foreign_key: :game_report_id,
foreign_type: :game_report_type, dependent: :destroy
end
Other approachs:
Rails Polymorphic Association with multiple associations on the same model

Ruby on Rails: Optimal way to filter all products by list of tags

I have 2 models Product and Tag with many-to-many relationship.
class Product < ActiveRecord::Base
has_many :product_tags
has_many :tags, through: :product_tags
end
class Tag < ActiveRecord::Base
has_many :product_tags
has_many :products, through: :product_tags
end
and the relationship model:
class ProductTag < ActiveRecord::Base
belongs_to :product
belongs_to :tag
end
What would be the most optimal way to search for the products by the list of given tags? The products must have all the tags, not just one of them.
Try
products = Product.joins(:tags)
tags.each do |tag|
products = products.where(tags: { name: tag })
end
tags contains the list of tags that you want to search and I'm assuming that tag has a name attribute
I found the answer here https://stackoverflow.com/a/11887362/808175. So in my case it is:
tags = ['1', '2', '3']
Product.joins(:tags)
.where(:tags => {:id => tags})
.group('products.id')
.having("count(product_tags.tag_id) = #{tags.count}")

Rails: Find with join model - How to?

I have the following models associations which I want to use in order to do some searches of players:
class Player < ActiveRecord::Base
belongs_to :user
has_many :abilities
has_many :sports, :through => :abilities
...
end
class User < ActiveRecord::Base
has_one :player
...
end
class Ability < ActiveRecord::Base
belongs_to :player
belongs_to :sport
has_one :level
...
end
class Sport < ActiveRecord::Base
has_and_belongs_to_many :category_sports
has_many :abilities
has_many :players, :through => :abilities
...
end
class CategorySport < ActiveRecord::Base
has_and_belongs_to_many :sports
end
I have a form where a user can fill two inputs: city and sport
The city field is in the User model as (#user.city) and the sport field can be either in CategorySport as (#category_sport.name) or in the Sport model as (#sport.name).
Right now I have the following to perform the search in the Player model:
def search
self.find(:all,:include => 'user',:conditions => ['users.city LIKE ?', "%#{city}%"])
end
I would like to know how would I add the join model (ability) and related (sport, categorysport) in this query in order to find by sport too. So, not just find for user city but also by sport.
Try this:
def search
self.includes(:user, :abilities => {:sport => :category_sports}).
where(['users.city LIKE ? OR category_sports.name=? OR sports.name=?',
"%#{city}%", sport, sport])
end

Multiple INNER JOINs in a Rails has_many relationship?

I have the following models:
class Image < ActiveRecord::Base
belongs_to :gallery
has_many :bookmarks
has_many :gallery_tags, :foreign_key => :gallery_id
end
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :image
has_many :gallery_tags, :through => :image, :source => :gallery_tags
end
class GalleryTag < ActiveRecord::Base
belongs_to :gallery
belongs_to :tag
end
class Gallery < ActiveRecord::Base
belongs_to :provider
has_many :images
belongs_to :user
has_many :gallery_tags
has_many :tags, :through => :gallery_tags
end
class Tag < ActiveRecord::Base
end
class User < ActiveRecord::Base
has_many :bookmarks
has_many :galleries
end
I'd like to be able to do
User.find(1).bookmarked_tags
and retrieve all tags associated with all of the user's bookmarked images via the galleries associated with the images. Tags are associated with galleries.
The query would end up looking like this:
SELECT
tags.*
FROM
tags
INNER JOIN gallery_tags ON gallery_tags.tag_id = tags.id
INNER JOIN images ON gallery_tags.gallery_id = images.gallery_id
INNER JOIN bookmarks ON images.id = bookmarks.image_id
WHERE
bookmarks.user_id = 1
GROUP BY
tags.id;
I've tried adding
has_many :tags, :through => :gallery_tags, :foreign_key => :gallery_id
to Image, which causes Image.find(34).tags to result in
SELECT `tags`.* FROM `tags` INNER JOIN `gallery_tags` ON `tags`.id = `gallery_tags`.tag_id WHERE ((`gallery_tags`.gallery_id = 34))
(it's not using the image's gallery_id in the query), and then I've tried adding
has_many :bookmarked_tags, :through => :bookmarked_images, :source => :tags
to User, which causes User.find(1).bookmarked_tags to result in
ActiveRecord::HasManyThroughSourceAssociationMacroError: Invalid
source reflection macro :has_many :through for has_many
:bookmarked_tags, :through => :bookmarked_images. Use :source to
specify the source reflection.
So: how can I get Rails to run the query I posted with User.find(1).bookmarked_tags?
there are two solutions
First Solution:
Create a view inside your database that acts like a join table:
CREATE VIEW user_bookmarks_tags (
SELECT
bookmarks.user_id AS user_id
gallery_tags.tag_id AS tag_id
FROM
gallery_tags
INNER JOIN images ON gallery_tags.gallery_id = images.gallery_id
INNER JOIN bookmarks ON images.id = bookmarks.image_id
GROUP BY
user_id, tag_id
)
(you can do this inside a migration)
This view acts like a "table" with the columns user_id | tag_id
Now we can adapt our models using has_and_belongs_to_many:
user.rb
class User < ActiveRecord::Base
has_many :bookmarks
has_many :galleries
# we use our magic (view) join table here!
has_and_belongs_to_many :tags, :join_table => :user_bookmarks_tags
end
tag.rb
class Tag < ActiveRecord::Base
# we use our magic (view) join table here
has_and_belongs_to_many :users, :join_table => :user_bookmarks_tags
end
Now you can do: User.find(1).tags or Tag.find(1).users :)
Second Solution
Do the join manually without a view:
define the missing relations (needed for the automated joins foreign_key lookup):
tag.rb
class Tag < ActiveRecord::Base
has_many :gallery_tags
end
gallery_tag.rb
class GalleryTag < ActiveRecord::Base
belongs_to :gallery
belongs_to :tag
# new:
has_many :images, :through => :gallery
end
Now we can add a bookmarked_tags method to our user.rb
class User < ActiveRecordBase
has_many :bookmarks
has_many :galleries
def bookmarked_tags
Tag.joins(:gallery_tags => {:images => :bookmarks}).where('bookmarks.user_id = ?', self.id).group('tags.id')
end
end