Composite foreign keys in Rails ActiveRecord - ruby-on-rails

I currently have a model named Image. This model is tied to an Article.
class Article < ActiveRecord::Base
has_many: images
end
The Image table contains an article_id, so an image is associated with an article.
However, I would like to use my images together with other models as well. Therefore I imagine changing article_id into something like owner_id and add a new attribute called owner_model to the Image model.
That way other models can also have many images.
Any idea if this is possible to achieve gracefully using ActiveRecord and how to go about it?
On a sidenote, I am using CarrierWave for images.

You can use polymorphic associations.
The rails guide has a neat example that goes with your question.
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end

Have you looked into polymorphic associations? Ryan has a video up on it: http://railscasts.com/episodes/154-polymorphic-association
It will allow you to associate different classes with your images.

What you are looking for is called polymorphic associations ( http://guides.rubyonrails.org/association_basics.html#polymorphic-associations ).

Related

Rails polymorphic association that multiple models access the same thing

How do you set up a polymorphic association where two different models have access to the same item
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class ModelA < ActiveRecord::Base
has_many :images, :as => :imageable
end
class ModelB < ActiveRecord::Base
has_many :images, :as => :imageable
end
I would like ModelA and ModelB to access the same Image. So if ModelA updates an image ModelB image will be updated also.
UPDATE
I am attempting to create something like the following
Event has many images
Person has many images
Person and Event reference the same image
When a image is added to a person from an Event the record has extra attributes.
Can this be done though a polymorphic association?
Thank you
I think you should use the relation between ModelA and ModelB to do this. Because images are not shared within ModelA. eg:
ModelA.find(1).images
is different from
ModelA.find(2).images
EDIT
If i understand correctly, you dont need a polymorphic relation here.
You can just create this relation
In Person Model
has_many :event_images, :through => :person_event_images

rails - why polymorphic associations

An example from this blog
class Tag < ActiveRecord::Base
attr_accessible :name, :taggable_id, :taggable_type
belongs_to :taggable, :polymorphic => true
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
It looks to me we could do thing like this without polymorphic associations
class Tag < ActiveRecord::Base
attr_accessible :name,
belongs_to :cars,
belongs_to :bikes,
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
What is the difference of with polymorphic associations and without?
THanks
In your last example, Car has many tags, so conventionally the "tags" table will have a field car_id. Now Bike has many tags, so one more field bike_id.
Without Polymorphic, how many fields are you going to create for the tags table? :)
Even you guarantee that there will only be two models have tag ultimately, there will also lots of null data in the table, say a bike doesn't have car_id, and that is not nice.
Polymorphism solved this problem by defining a common interface so that Car and Bike can share same operations with different sub type. http://www.princeton.edu/~achaney/tmve/wiki100k/docs/Polymorphism_in_object-oriented_programming.html
A polymorphic association allows you to create a table with relationships between multiple models.
Consider your example. Both Car and Bike can have many tags, so instead of creating two different tables, say car_tags and bike_tags, you can use a single polymorphic table named Tag which not only stores the foreign key (in a column named resource_id), but also the type of resource it's associated with (in a column named resource_type), which, in this case would be Car or Bike.
In summary, polymorphic relationships are between many different models whereas normal relationships are, generally speaking, only between two.
You can find more information in the RoR Guides; http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Hope that helps clarify things a bit.
Polymorphic associations allow for a single model to belong to multiple models on a single association[1]. Taking your example above, simply adding has_many :tags to the Car and Bike models and belongs_to :car and belongs_to :bike to the Tag model has two major shortcomings:
It introduces a glut of foreign keys of which a large quantity will have no value
It still doesn't allow for a tag to belong to more than one model
Some great resources for learning more about polymorphic associations are listed below.
RailsGuides on Polymorphic
Associations
RailsCasts #154 Polymorphic Associations
(revised)
What's the Deal with Rails' Polymorphic
Associations?

Rails featured image association?

I've been trying to figure out how to associate my models on a project I've been working on for a while, and I've come here for help a couple times before, but I never got a satisfactory answer. I have two models: Post and Image. Every post has several images attached to it and posts can share images, so a HABTM relationship made sense for that, like this:
class Post < ActiveRecord::Base
has_and_belongs_to_many :images
end
class Image < ActiveRecord::Base
has_and_belongs_to_many :posts
end
The problem now is that I want each post to have a single 'featured image.' How do I do this? The first thought that comes to mind is a simple has_one :featured_image on the post and belongs_to :post_featured_on on the image, but the problem with that is that the same image can be featured on multiple posts.
So the next idea I came up with is to reverse the relationship: belongs_to :featured_image on the post and has_many :posts_featured_on on the image. The problem with that is that it isn't very semantic and rails doesn't seem to want to let me set a post's featured image from its form, like this in the controller: Post.new(:featured_image => Image.find(params[:image_id]))
So the next idea suggested to me was a second HABTM relationship, like so: has_and_belongs_to_many :featured_images. There's an obvious problem with this, it's plural. I tried putting unique: true on the post_id column in the migration, but that didn't help the fact that I kept having to do this in my code: post.featured_images.first which can be very frustrating.
The last idea I tried was a has_many :posts, through: :attachment and has_one :featured_posts, through: :attachment in place of the original HABTM, but these just seems unnecessarily cumbersome, and rails doesn't seem to want to let me assign the images on the fly this way like Post.new(:featured_image => Image.find(params[:image_id])).
Is there any good way to do this? Have I done something wrong in my previous attempts? Shouldn't this just be a simple foreign key on the post table? Why does it have to be so difficult?
I like your second idea just fine. The full blown approach is to use a transaction model, such as #depa suggested. The transaction model is great when you want to store additional attributes such as when the Image was made featured for a given post (and perhaps when it was not, as well). But, whether you build that transaction object as well or not, you can just cache the featured image on the post object for quick access. Try just doing this:
class Post < ActiveRecord::Base
has_and_belongs_to_many :images
belongs_to :featured_image, class_name: 'Image'
end
class Image < ActiveRecord::Base
has_and_belongs_to_many :posts
# Purposefully not defining an inverse relationship back to Post.
# You can if you need or want it but you may not.
end
Then, in the controller I'd recommend:
#post = Post.find_by_id(params[:id])
#post.featured_image = Image.find(params[:image_id])
#post.save
You probably didn't have success with this before because of not having attr_accessible :featured_image_id on the Post model. And/or because you were using the wrong attribute name. (It should have been Post.new(featured_image_id: Image.find(params[:image_id])).) Either way, it's good to keep the code a little more object-oriented than all that. The way I laid it out above, you don't have to think about the column name in the database, and can just think about the objects you're dealing with. I.e., just assign the Image to the Post's feature_image reference. Keeping this in mind, I prefer to not set foreign keys as attr_accessible when possible.
You can do what you want using a has_one :through association.
class Post < ActiveRecord::Base
has_and_belongs_to_many :images
has_one :featured_image,
:through => :feature,
:class_name => 'Image'
has_one :feature
end
class Image < ActiveRecord::Base
has_and_belongs_to_many :images
has_many :featured_images,
:through => :features,
:class_name => 'Image',
:foreign_key => :featured_image_id
has_many :features
end
class Feature < ActiveRecord::Base
belongs_to :featured_image,
:class_name => 'Image'
belongs_to :post
end

Rails - Polymorphic association join table

I am currently trying to set up a model structure that seems quite simple, but I haven't quite got it down.
I have a model payment that can belong to either a customer or a supplier (which can both have many payments).
My question is simply whether I need to manually create an interface table to allow this, or if declaring the polymorphic associations will do this for me?
e.g. I have:
class Payment < ActiveRecord::Base
belongs_to :payment_originator, :polymorphic => true
end
class Customer < ActiveRecord::Base
has_many :payments, :as => :payment_originator
end
class Supplier < ActiveRecord::Base
has_many :payments, :as => :payment_originator
end
Is this enough, or do I also need to use a generator to manually create the payment_originator model?
Thanks!
As far as the models go, this is good enough. You just need to migrate a :payment_originator_type and :payment_originator_id to the payments table. The associations you defined above will automatically fill these in for you.

What's the difference between polymorphic and write several one-to-many association

Hey guys,
I'm new to rails
here are the 2 ways of making a Drummer model and Cymbal model both have many videos
1st way by using polymorphic:
class Drummer < ActiveRecord::Base
has_many :videos, :as => :videoable
end
class Cymbal < ActiveRecord::Base
has_many :videos, :as => :videoable
end
class Video < ActiveRecord::Base
belongs_to :videoable, :polymorphic => true
end
2nd way by using two 1:m association:
class Drummer < ActiveRecord::Base
has_many :videos
end
class Cymbal < ActiveRecord::Base
has_many :videos,
end
class Video < ActiveRecord::Base
belongs_to :drummer
belongs_to :cymbal
end
I haven't try them in console, But I think both will work as they should. But I don't know the difference?
I believe that you must use polymorphic method because a model cannot belongs_to (one to one association) more than one other model. For more info see this rails guide: http://guides.rubyonrails.org/association_basics.html
It depends on the columns you have in your database. If you have videoable_type and videoable_id you're doing polymorphism. In that case, calling videoable on an instance of Video can return anything, it's not related to drummers or cymbals. If it is drummer_id and cymbal_id it's the latter version you described.
Both will work.
Imagine you have 5 models sharing Videos.
You would need 5 modelName_id columns in videos.
To determine which type of parent model you have on Video you would need to check each one for id presence.
Validating that only one of the ids is set would be necessary (or valuable).
Polymorphic relations are easier to maintain and expand in such cases.

Resources