How do I specify associations in Rails that pass through several models - ruby-on-rails

I'm writing some tricky polymorphic relationships to handle tagging.
I have a Tag model, and a Tagging model which belongs_to a polymorphic taggable.
I have an Item model, which has_many :taggings, :as => :taggable, and has_many :tags, :through => :taggings, so that I can call #item.tags.
This is all working ok.
I want to bring another model into the mix - a Store which has_many :items. I want to be able to find all tags associated with all items in the store using #store.tags.
Here's what I have:
class Store < AR::Base
has_many :items
has_many :tags, :through => :items, :source => :taggings
However, this returns all of the taggings associated with items in the store, not the actual tags.
How do I get specify that the store has_many tags, through items, through taggings?
Can post more info if needed - trying to prevent information overload! Thanks :)

The source for a has_many association must be a belongs_to, has_one, or has_many association without a :through option (thanks to this answer for info).
There is a plugin that some people have had success with, but in my case it didn't seem to work correctly with my taggable polymorphic association.
At the moment, my solution is to pass a finder_sql option to has_many:
class Store < ActiveRecord::Base
has_many :items
has_many :tags, :finder_sql =>
'SELECT tags.* from tags
INNER JOIN taggings on tags.id = taggings.tag_id
INNER JOIN items on items.id = taggings.taggable_id AND taggings.taggable_type = "Item"
WHERE items.store_id = #{id}'
end

You can do in plain Ruby:
site.wares.map(&:tags).flatten.uniq
This will be inefficient though, unless you have a low number of tags / wares / items.

Related

On polymorphic associations, is it possible to pull in multiple source types on one has_many through query?

When dealing with polymorphic associations, is it possible to have a has_many through query that pulls in ALL available source_types?
My understanding so far is that each source type needs its own query method, as I show here in the Image model
image.rb
has_many :image_tags
has_many :tags, through: :image_tags, source: :taggable, source_type: 'Tag'
has_many :people, through: :image_tags, source: :taggable, source_type: 'Person'
has_many :businesses, through: :image_tags, source: :taggable, source_type: 'Business'
...
tag.rb
has_many :image_tags, as: :taggable
has_many :images, through: :image_tags
image_tag.rb
belongs_to :image
belongs_to :taggable, polymorphic: true
def build_taggable(params)
self.taggable = taggable_type.constantize.new(params)
end
What I'd like to be able to do, however, is create one query method that pulls in all associated records regardless of what source_type they might belong to.
Just thinking out loud, would that likely involve creating some sort of raw SQL join that acts directly on the ImageTags table? Or is there a more Railsy/ActiveRecordy way of approaching it?
UPDATE 20200319:
I've since found a way to aggregate the individual methods together, but this still requires the creation of unique methods.
def taggables
tags + people + businesses
end
This allows for something like
i = Image.first
i.crops.map(&:taggables)
Still not quite an answer, but it's a temporary workaround in the meantime.
You can use below query to get any association that is attached to image_tag:
# get all images with all associations
imgs = Image.includes(image_tags: :taggable)
# to get value polymorphic association regardless of it's source
# taggable may be tags, people or businesses
poly_assoc = imgs.first.taggable # associaiton of first record
# to get the association type
pp pol_assoc.to_s.class.to_s

Need simple Rails many to many association logic

I have a list of products and a list of categories. Also I have a mapping table product_categories which tells that products come under different categories and also category has many products. Here the category list is defined by admin. The number of categories is fixed, but can be varied. Now I need to get the list of products which are mapped with categories
product.rb
has_many :product_categories, dependent: :destroy
has_many :categories, through: :product_categories
category.rb
has_many :product_categories
has_many :products, :through => :product_categories
product_category.rb
belongs_to :product
belongs_to :category
I have written the code as:
ProductCategory.joins(:category).map(&:category).uniq
Is there any way to simplify this line?
To get a list of categories that have products you can use SQL inner join, which is the default in rails.
Category.joins(:product_categories).distinct
Try this,
Category.joins(:products).distinct

Rails: has_many :through association only at one side

I am new to ruby and ruby-on-rails. I need to implement complicated tagging engine:
each tag could have 0 or more synonyms (yep, just like on SO)
there should be an hierarchy of tags: i.e. there are sub-tags and super-tags. Say, I have three tags: programming, ruby and c. Then, programming is a super-tag of both ruby and c, and when I enter tags for item about ruby, it's not necessary to enter tag programming, I can only tag it with ruby. When I enter tags, all the super-tags should be added recursively. (actually it's strange I've never seen any tags engine with this feature, I really feel lack of it)
each user should have its own set of tags, i.e. each Tag belongs_to User. Tags aren't public to anyone, they are private to its owner.
It seems I can't use acts_as_taggable_on gem, right? So I have to implement my own one. Actually I have already implemented Tag class that supports hierarchy, synonyms and users stuff, it seems to work. But there are a lot of questions how to make something taggable with it.
I am trying to reverse-engineer acts_as_taggable_on, it is really complicated for a newbie like me..
So, there are a lot of questions, but I'm going to be more specific now:
Assume I have a class Item that I want to be taggable. As far as I understand, I should use has_many :through association, both on Item and Tag side. And therefore create intermediate table with item_id and tag_id fields.
BUT: I want my tagging engine to be universal! So I don't want to add to Tag model anything related to Item.
So, the only real question is: is it generally ok to create has_many :through association only on Item side?
And secondly, if anyone has some suggestions on the stuff I explained, I'd be happy to see them.
I can suggest you few things that might help some/most of your problem statements.
there should be an hierarchy of tags
self referential has_many association can be used
Creating a model that has a tree structure
class Tag < ActiveRecord::Base
has_many :sub_tags, class_name: "Tag", :foreign_key => "super_tag_id"
belongs_to :super_tag, class_name: "Tag"
end
I want my tagging engine to be universal! So I don't want to add to Tag model anything related to Item
Tagging should be polymorphic, as its widely applicable as used.
class TaggedItem < ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, polymorphic: true
end
is it generally ok to create has_many :through association only on Item side?
I think yes, has_may through is the way to go. So your Item model may look like
class Item < ActiveRecord::Base
has_many :tags, as: :taggable, through: :tagged_items
end
Further to swapnilabnave's great answer, I'll explain what you're looking for:
I would recommend breaking up your problem into more modular issues
Your question is whether you can use has_many :through for the relation. The answer, as explained, is "yes", but that won't solve the problem in its entirety
Hierarchy Of Tags
As mentioned, your tags will have "parents" and other items
Although I don't have huge experience with this in Rails, in CakePHP, this will be known as a tree structure. CakePHP handles this by having 3 columns - parent_id, left, right. These basically allow the app to specifically show the various items in your tree based on which numbers are used in those 3 columns
In your case, I'd recommend putting a parent_id column in the tags database, which you'll be able to assign a super tag to if required (it seems you only have one super-tag per tag?). You could handle this in your model using the self-referencing association technique that swapnilabnave posted to give the ability to call the super_tag like this:
#tag.super_tag
And important note would be that this would only work if you could only have one super_tag per tag. If you wanted an unlimited number of super tags per tag, you'd do this:
class Tag < ActiveRecord::Base
has_many :sub_tags, class_name: "Tag", :foreign_key => "super_tag_id"
has_many :tag_super_tags, class_name: "TagSuperTag", :foreign_key => "super_tag_id"
has_many :super_tags, :through => :tag_super_tags
end
Class TagSuperTag
belongs_to :tag
belongs_to :super_tag, :class => "Tag"
end
Although I'm not sure if the join model code will work, it will hopefully show you the idea here. This would create a self-referencing join model called TagSuperTag, and allow you to have a table like this:
tag_super_tags
id | tag_id | super_tag_id | created_at | updated_at
tags (no need for super_tag_id)
id | user_id | name | etc
This will allow you to add as many super tags as you wish to each tag
User Has Many Tags
Because each user will have many tags, you can just use the standard has_many association, like this:
class User < ActiveRecord::Base
has_many :tags
end
class Tag < ActiveRecord::Base
belongs_to :user
end
This means that each tag will have to have a user_id column, to act as a foreign_key
Tags Are Universal
Making tags universal (not just for item) will require a polymorphic association, and what seems to be a join model
As swapnilabnave has described, this join model can be accessed by any model which wants to use it to store tags, thus allowing you to associate any model with it. Here's how you could do this:
class TaggedItem < ActiveRecord::Base
belongs_to :taggable, polymorphic: true
belongs_to :tag
end
tagged_items
id | taggable_type | taggable_id | tag_id | created_at | updated_at
This will allow you to reference this model from any other, like this:
class Post < ActiveRecord::Base
has_many :tagged_items, :class_name => "TaggedItem", :as => :taggable, :dependent => :destroy
has_many :tags, :through => :tagged_items
end
class Email < ActiveRecord::Base
has_many :tagged_items, :class_name => "TaggedItem", :as => :taggable, :dependent => :destroy
has_many :tags, :through => :tagged_items
end
And the Tag model should has_many :tagged_items, too:
class Tag < ActiveRecord::Base
has_many :tagged_items, :class_name => "TaggedItem", :foreign_key => "tag_id", :dependent => :destroy
end
Hope this helps

has_many :through query with both values

Let's say I have:
class Post
has_many :tags, :through => :taggings
has_many :taggings
end
Notice there's no :include. Now say I want to retrieve all taggings and tags in the same query. How could I do that?
I'm looking for something like:
taggings = post.taggings(:include => tags) # doesn't work
I could make a custom query or add a third association to Post with an :include, but neither feels right.
I think you can use includes on the association proxy as you would with the model class:
taggings = post.taggings.includes(:tag)

Problems using Thinking Sphinx in Ruby on Rails with multiple models

I'm developing a website on Ruby on Rails with the search engine Sphinx (I'm using Thinking Sphinx). I have a model in which I want to make the searches and I'm using another models (I made the relationships in the models and in the tables) but I want to make additional INNER JOINS, so, I have something like this:
class Group < ActiveRecord::Base
belongs_to :person
has_many :categories, :dependent => :destroy
define_index do
indexes group_name
indexes person.fullnameindexes categories.category_name
indexes categories.category_name
end
end
It's ok to make something like this?
class Group < ActiveRecord::Base
belongs_to :person
has_many :categories, :dependent => :destroy
define_index do
indexes group_name
indexes person.fullnameindexes categories.category_name
indexes categories.category_name
indexes subcategories.subcategory_name #additional table
end
end
As you can see, I'm adding a new model (Subcategory) that has no relationship with the model Group, but it has a relationship with the model Category, is this ok? or what is the right way to do that?
Those are the links I'm following:
http://freelancing-god.github.com/ts/en/indexing.html
http://freelancing-gods.com/posts/a_concise_guide_to_using_thinking_sphinx
If subcategory is referenced in the Category model, you can do this:
indexes categories.subcategories.subcategory_name, :as => :subcategory_names
Thinking Sphinx will happily go through associations into deeper associations if you want it to.
I think the short answer to this is "no". ThinkingSphinx will try to reference an association on Group named subcategories, which won't exist, and you should get an error when indexing.
If Category has_many :subcategories, you can express this in Group with a :through option:
class Group < ActiveRecord::Base
belongs_to :person
has_many :categories, :dependent => :destroy
has_many :subcategories, :through => :categories
and then the index should recognize the association, though the docs point out that you need an explicit alias when doing this, so:
indexes subcategories.subcategory_name, :as => 'subcategory_name'

Resources