How can we do genuine rails 3 polymorphic has_many :through? - ruby-on-rails

I want to have the following:
class Foo < AR
has_many :taggings
has_many :tags, :through => :taggings
end
class Bar < AR
has_many :taggings
has_many :tags, :through => :taggings
end
class Tagging < AR
belongs_to :tag
belongs_to :taggable, :polymorphic => true
belongs_to :user # etc
end
class Tag < AR
has_many :taggings
has_many :taggables, :through => :taggings
end
Tag.first.taggables # => [Bar:43, Foo:52, Foo:59, Bar:59, Foo:123, ...]
In particular, I don't want to have to specify Tag.foo_taggables, Tag.bar_taggables, etc. — that's the :source/:source_type method referred to in related questions, and it sucks.
I want it to just work properly with an array of disparate objects. Yes, I realize that they won't all have the same properties; that should be fine (I should be able to just rely on whatever interface all taggables do share, without caring about which kind I'm dealing with).
In particular e.g. tag.taggable_ids will probably have to be an array of id/type tuples, not just an array of int ids.
I'm using tags here just as an example — my actual problem has a different scenario but the same essential issue of polymorphic has_many :through.
In Rails 2, there was a plugin has_many_polymorphs that accomplished this, but it's defunct. Kronn's fork doesn't seem to work. Is there a functioning method to get this in Rails 3?

If you don't need #build, eager-loading it using .include or anything fancy like that, then you can use a simple method instead of an association:
class Tag < AR
has_many :taggings
def taggables
taggings.includes(:taggables).flat_map(&:taggables).uniq
end
end

Related

acts_as_list on has_many :through and sortablejs

I have a fairly large question that I've been unable to resolve after a full day of reading articles, documentation and other Stack questions. At this point it's just messing with my mind!
I have an app that is Vue/Vuetify on the frontend with a data-table that I want to be able to reorder with sortablejs. On the backend, I have a Rails API that I'd like to use the acts_as_list gem to handle reordering for me.
Ideally I'd be able to call something like Category.first.items.last.move_higher, however because I have a join model I'm having to put acts_as_list and the position column on the join model instead of the Item model. Is there a better way to arrange this?
category.rb
class Category < ApplicationRecord
has_many :categories_items
has_many :items, through: :categories_items, source: :item
accepts_nested_attributes_for :items
end
item.rb
class Item < ApplicationRecord
has_many :categories_items
has_many :categories, through: :categories_items, source: :category
end
categories_item.rb
class CategoriesItem < ApplicationRecord
belongs_to :category
belongs_to :item
acts_as_list scope: :category
end
With that structure you'll definitely need to reorder by calling methods on the join model. You can however use the Rails delegate class method to delegate reordering methods to the join model if you like:
https://api.rubyonrails.org/classes/Module.html#method-i-delegate
All of this assumes that you need items and categories to have a has_and_belongs_to_many type relationship.

Rails 4: Turn complicated filter into a scope

I have a many-to-many / has-many-through relationship in my connecting my recipe model to my tag model such that:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :recipes, through: :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :recipe
end
class Recipe < ActiveRecord::Base
has_many :taggings
has_many :tags, through: :taggings
end
...is there any way to filter for recipes with the same tag through a scope? I'm new to scopes, but I find them much more useful than methods, and I can only achieve searching and filtering by tag name through a method.
For example this will get me all recipes tagged with a given name:
def self.tagged_with(name)
Tag.find_by_name!(name).recipes
end
You can basically convert most association-method-chains (though not all*) into a scope
eg I'd try this (note: not tested for bugs) and see how it turned out
scope :tagged_with, ->(name) { find_by_name!(name).recipes }
If that doesn't work, I'd try something like:
scope :tagged_with, ->(name) { where(:name => name).first.recipes }
[*] The big issue with using scopes over method-chaining is that find/first can sometimes do weird things if it doesn't find one... the code in Rails literally defaults to the all scope in some cases (this is really weird behaviour that I think shouldn't happen) so for scopes that find just a single item, I will often not bother with a scope and use a class-method as you have originally.

Using Rails, not sure if I should use belongs_to or not

Very new to Rails... I'm building out functionality that lets people compare photos, and I can't decide exactly how I should structure it. Ideally what I'd like is to have a "comparisons" table which keeps a record of the IDs of the photos compared as well as the user that compared them, but I'm not quite sure whether this warrants use of the "belongs_to" function or not. If so, how do I specify that each comparison belongs to TWO separate photos?
The following has_many, :through => Model structure will let you have additonal properties on the join table, e.g. 'comparing_user_id'.
class Photo < ActiveRecord::Base
has_many :appearances
has_many :users, :through => :appearances
end
class Appearance < ActiveRecord::Base
belongs_to :photo
belongs_to :user
end
class User < ActiveRecord::Base
has_many :appearances
has_many :photos, :through => :appearances
end

Rails modeling for a user

When building a rails app that allows a User to login and create data, is it best to setup a belongs_to :user association on every single model? For example, let's say a user can create Favorites, Colors and Tags.
And let's say Favorites has_many :tags and Colors also has_many :tags. Is it still important for Tags to belong_to :user assuming the User is the only person who has authority to edit those tags?
And a similar question along the same lines: When updating data in FavoritesController, I've come to the conclusion that you perform CRUD operations by always doing something like current_user.favorites.find(param[:id].update_attributes(param[:favorite]) so that they can definitely only update models that belong to them. Right?
Update Wasn't too happy with any of the answers, as no one really answered my question but instead went after the for-example-only Tags model suggesting better ways to do that. I'm assuming I was right, and models should belong_to :user. I also discovered some great security tips that address my questions here: http://asciicasts.com/episodes/178-seven-security-tips
As you describe the tags it seems that they are more of an aspect, so you can implement them as a polymorphic association. But you should do it many-to-many, as tags can be reused among users and taggable objects. Let's call the join model Tagging, which will be the one that belongs to user if you want to remember who created the tagging.
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :colors, :through => :taggings, :source => :taggable, :source_type => "Color"
has_many :favorites, :through => :taggings, :source => :taggable, :source_type => "Favorite"
end
class Tagging < ActiveRecord::Base
belongs_to :user
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Color < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class Favorite < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class User < ActiveRecord::Base
has_many :favorites
has_many :colors
has_many :taggings
has_many :tags, :through => :taggings
end
As for the Favorite updating, I agree with you: you will mostly work within the scope of a user (most likely the currently logged in user).
It depends on your model. Both cases are valid but I'd discorage making a circular relationships like that. Having a hierarchy is more flexible. For example: User->Favorites->Tags (unless you want to tag users as well)
User.favorites.find(params[:id]).update_attributes(param[:favorite])
is what you mean I guess (syntax). Whoever calls the URL will perform that action. Dont rely on the fact that that URL is visible to one user only (owner of the favorite). You should have checks in place that the currently logged in user is the only one performing actions on the objects that belong to him.
The proposed mechanism sounds a bit too complex for me. I prefer the current_user way. Assume there is a current_user (following the authlogic way) in your authentication system, then simple add a user references (user_id) in every relevant table. Update the current_user for new or update record via a controller filter.
In the models, put relevant belongs_to :users accordingly, put enough has_many in users model if needed.
:has_many and :belongs_to in AR will explains the relationship between models, but not necessarily you have to use them in your models, the associaton between them will be already present in the tables as a foreign key.
But adding :has_many or :belongs_to to your models will give you extra methods to your model
ex:
class User < ActiveRecord::Base
has_many :favorites
#def favorites
# Favorite.find_all_by_user_id(self.id)
# end
end
If you mention has_many it will give a new method in your model called favorites, that method will be invisible (will be present in the AR).
Similarly for any association, if you are planning to use this kind of methods you should use associations in your models.

Rails Polymorphism 'backwards'

Say I'm writing a blog app with models for posts, pages and photos. I've got a category model, that may be linked to any of these models. So, a category may contain various kinds of items. Every item only has ONE category.
I could implement this using a generic tagging pattern with a join table, but I want to make sure every subject can have only category.
What would be the best way to implement this in Rails?
Okay, I think I've got it:
class Post < ActiveRecord::Base
has_one :categorization, :as => :categorizable
has_one :category, :through => :categorization
end
class Category < ActiveRecord::Base
has_many :categorizations, :dependent => :destroy
end
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :categorizable, :polymorphic => true
end
Now various models can have a category, but each instance can have only one category… I guess.

Resources