A question regarding Rails associations.
Consider the following models:
People
Events
Images
People and events can have many images.
Images uploaded to an event, need to have the ability to be associated with multiple people.
This means that there are two relationships between people and images.
One where images are uploaded directly on the person. And one where a person is tagged in an event.
Can there also be a relationship between a person and an event based on the fact they were tagged in one (or multiple) images in an event? In this regard, it's a sort of image tagging system where associations are created based on what event people are tagged in.
Wondering what is the best practice in Rails to create this association? Any help or advice is greatly appreciated!
I am not absolutely sure about best practice,
but there is a chance to solve your case in simple way, as far as i understand you:
We have:
Event ... has_many :photos; Photo ... belongs_to :event
and solution is to create some intermediary ActiveRecord class
People ... has_many :photos, :through photo_mappings
PhotoMapping ... belongs_to :user; belongs_to :photo
Have a nice AR associations best practice1
In rails it is definitely possible to define a join-table that has extra fields. So in this case I would define the following table:
class LinkedImage
belongs_to :person
belongs_to :image
OWNER=1
TAGGED=2
validates :relation_type, :inclusion => {:in => [OWNER, TAGGED]}
end
This table would link an image to a person, and has an extra field relation_type (you could think of a more appropriate name maybe), which now can have two values: 1 (for the OWNER, meaning the image was directly uploaded to the person), and 2 (the person is tagged in the image). Aside from the relation, maybe you want to store something extra, like the position in the image, an extra comment, then you could easily add that here as well.
And the person would look like:
class Person
has_many :linked_images
has_many :uploaded_images, :class_name => Image,
:through => :linked_images,
:conditions => "relation_type=#{LinkedImage::OWNER}"
has_many :tagged_in_images, :class_name => Image,
:through => :linked_images,
:conditions => 'relation_type=#{LinkedImage::TAGGED}"
end
and the code for Image could be similar.
I hope this helps.
I don't know if this is the best approach, but I would do something like this:
class User < ActiveRecord::Base
has_many :images, :as => :owner
has_many :tags
has_many :tagged_images, :through => :tags, :source => :image
has_many :tagged_events, :finder_sql => %q( SELECT `events`.* FROM `events`, `images` WHERE `events`.id = `images`.owner_id AND `images.owner_type ="Event" `
WHERE (`images`.id IN (#{tagged_image_ids.join(',')}) ))
end
class Event < ActiveRecord::Base
has_many :images, :as => :owner
has_many :tagged_users, :finder_sql => %q( SELECT `users`.* FROM `users`, `images` WHERE `users`.id = `images`.owner_id AND `images.owner_type ="User" `
WHERE (`images`.id IN (#{image_ids.join(',')}) ))
end
class Tag < ActiveRecord::Base
belongs_to :user
belongs_to :image
end
class Image < ActiveRecord::Base
belongs_to :owner, :polymorphic => true
has_many :tags
has_many :tagged_users, :through => :tags, :source => :user
end
Note: Using finder_sql is not the best in Rails, because for example it doesn't allow you to add new tagged images just using the relationship, but it works fine for reading.
Related
I have two models 'Product' and 'Variant',product has many variants,now I have to set assocaiation of a new Model named as "Ingridient" with variant in such a way that Variant has many ingridients.but in ingridents I want to show that it again has_one product and variant.for example
class Variant
has_many :ingridients
belongs_to :product
end
class Ingridient
belongs_to :variant
belongs_to :product
end
now in my ingrident model I also want has_one variant and has_one product at the same time.how should I set my associations.I know multiple association with same table but dont know how to implement it in my app.Iam using mongoid as my database.
Edit : Actually in my flow User can create a Product with many variants and then each variant can have multiple ingridients.In that each ingrident i have select box for selecting products and variants form database .means ingridients will consist a product a variant ,quantity etc fields .To make it more clear Iam making a Recipe
.
I am not 100% clear on what you want to do.
But what it looks like is that you want to have a association with a different name.
class Variant
has_many :ingridients, :foreign_key => 'FieldName', :class_name => 'CassName'
belongs_to :product, :foreign_key => 'FieldName', :class_name => "CassName"
end
class Ingridient
belongs_to :variant, :foreign_key => 'FieldName', :class_name => "CassName"
belongs_to :product, :foreign_key => 'FieldName', :class_name => "CassName"
end
I hope that this helps
I a while ago i read this article: http://pivotallabs.com/users/nick/blog/articles/275-advanced-proxy-usage-part-i which is talking about AR proxy etc..
The author pointed what is my issue now.
This example will describe it.
class Gallery
has_many :images, :class_name => 'Image'
has_many :my_images, :class_name => 'Image' #, :conditions => "images.user_id == current_user.id" # FIX THIS
end
and here
class Image
belongs_to :user
belongs_to :gallery
end
Notice: I use PostgreSQL
So when you access my all images with a gallery: Gallery.first.includes(:my_images)
If i want to return all images: Gallery.first.includes(:images)
So now how to pass the current user to the conditions in the has_many?
Edit
User
has_many :images
has_many :galleries, through: :images
If i understand well, you want to get something like :
current_user.galleries.first.images
If so, you would just need to create a has_many association between User and Gallery through a third model, possibly named UserGallery.
More information on has many through :
http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
How guys
I'm new to rails, here's my code:
class Video < ActiveRecord::Base
belongs_to :videoable, :polymorphic => true
end
class Drummer < ActiveRecord::Base
has_many :videos,:as => :videoable
end
class Cymbal < ActiveRecord::Base
has_many :videos, :as => :videoable
end
From this point I can use the drummer.videos to get all the video that belong to drummer,
But I can't use video.drummer to get who is the video belongs to.
of course I can use video.where(:videoable_id => '1', :videoable_type => 'drummer') to get the to find the exact drummer record, but I think there must be a elegant to do that in rails, right?
and one more question, I want to improve this association, video and drummer, video and cymbal should be many-to-many, sometime there are more than 1 drummer or 1 cymbal in one video, so it makes sense do it this way. how can I do this?
If it's always a one-to-many relationship that you want (a video can at most have one drummer), you can add these lines to your Video model:
belongs_to :drummer, :class_name => "Drummer", :foreign_key => "videoable_id"
belongs_to :cymbal, :class_name => "Cymbal", :foreign_key => "videoable_id"
Rails will figure out what class the foreign key maps to and fetch the correct entry.
You might be looking for video.videoable and many-to-many join tables.
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods
http://railscasts.com/episodes/47-two-many-to-many
http://railscasts.com/episodes/154-polymorphic-association
If your Drummer and Cymbal models are similar, you might consider using STI instead of polymorphism. Define a new model with at type column to act as a parent, and add the has_many association, then add the child models:
class Subject < ActiveRecord::Base
has_many :subject_videos, :dependent => :destroy
has_many :videos, :through => :subject_videos
end
class Drummer < Subject
end
class Cymbal < Subject
end
Add a SubjectVideo model with foreign keys subject_id and video_id. Then make your Video model associate through it:
class Video < ActiveRecord::Base
has_many :subject_videos, :dependent => :destroy
has_many :subjects, :through => :subject_videos
end
Now you have a many-to-many association.
d = Drummer.create
d.videos # []
d.videos.create(:name=>"Stompin' at the Savoy")
v = Video.find_by_name("Stompin' at the Savoy")
v.subjects # [Drummer]
The primary drawback of this approach is that Drummer and Cymbal are now stored in the same table, which can be undesirable if they share few columns.
If still need a many-to-many relationship using polymorphism, also take a look at has_many_polymorphs.
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.
I'm slightly confused about a polymorphic association I've got. I need an Article model to have a header image, and many images, but I want to have a single Image model. To make matters even more confusing, the Image model is polymorphic (to allow other resources to have many images).
I'm using this association in my Article model:
class Article < ActiveRecord::Base
has_one :header_image, :as => :imageable
has_many :images, :as => :imageable
end
Is this possible? Thanks.
I tried this, but then header_image returns one of the images. Simply because the images table doesn't specify a different image use type (header_image vs. normal image). It simply says: imageable_type = Image for both uses. So if there's no information stored about the use type, ActiveRecord cannot differentiate.
Yep. That's totally possible.
You might need to specify the class name for header_image, as it can't be inferred. Include :dependent => :destroy too, to ensure that the images are destroyed if the article is removed
class Article < ActiveRecord::Base
has_one :header_image, :as => :imageable, :class_name => 'Image', :dependent => :destroy
has_many :images, :as => :imageable, :dependent => :destroy
end
then on the other end...
class Image < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end