I have a product, and a product can have many images. This is through an associations table. However, I would like a product to have one main image.
I know this is very easy to do with a method in the model, but I want it to be an association so that I can preload it in the query by using include.
Models:
class Product < ActiveRecord::Base
has_many :image_associations, :as => :imageable
has_many :images, :through => :image_associations
end
class ImageAssociation < ActiveRecord::Base
belongs_to :image
belongs_to :imageable, :polymorphic => true
end
class Image < ActiveRecord::Base
has_many :image_associations
end
In the ImageAssociation table, there is a boolean column called 'feature' which associates an image as the 'main' image.
One of the ways I've thought about doing this is adding a main_image_id column to the products table, and then adding to the Image model:
belongs_to :image, :class => "Image", :foreign_key => "main_image_id"
However, this doesn't allow for any fallback to the other has_many images if the main image is nil -- which I'd like the association to load.
Which is why I was hoping for something in the images model like:
has_one :image, :through => :images, :conditions => 'feature = true', :order => 'created_at DESC'
But that gives me an association error.
Is there any way I can edit that has_one, or do I really need to run a rake task to push an image into every main_image_id field, and then add a validation for future products to make sure a main image has been added?
EDIT:
Should I be using a has_and_belongs_to_many association instead?
You're almost there, I think, although I'm not so clear on polymorphic associations. I think you want the following:
has_one :main_image_assoc, class => "ImageAssociation", :conditions => 'feature = true', :order => 'created_at DESC', :as => :imageable
has_one :image, :through => :main_image_assoc
Related
I have 3 tables Collections, Tracks and ProductContributors
Association of them is as follows
class Collection < ActiveRecord::Base
has_many :product_contributors, :as => :product
has_many :tracks, :through => Product_contributors, :as=> :product
end
class Track < ActiveRecord::Base
has_many :product_contributors, :as => :product
has_many :collections, :through => Product_contributors, :as => :product
end
class ProductContributor < < ActiveRecord::Base
belongs_to :product, :polymorphic => true
belongs_to :collection
belongs_to :track
end
whenever i hit the url for product contributor i get the following error :
Expected /app/models/track.rb to define TRACK
I've gone through this url but didnt help me in any case. I dont have the autoload issue, all my models are loaded properl
Any help would be highly appreciated..!!
I dare say its because of the typo in your Track class.
has_many :collections, :through => Product_contributors, :as => :product
is not valid. Try:
has_many :collections, :through => :product_contributors, :as => :product
Basically, it is trying to load the Model, but its finding the typo in the association, and it is then not loading, causing it to seem like the class is not there. I assume you will have a similar situation with the Collection class as well.
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.
I've found an old plugin called acts_as_habtm_list - but it's for Rails 1.0.0.
Is this functionality built in acts_as_list now? I can't seem to find any information on it.
Basically, I have an artists_events table - no model. The relationship is handled through those two models specifying :has_and_belongs_to_many
How can I specify order in this situation?
I'm assuming that you have two models - Artist and Event.
You want to have an habtm relationship between them and you want to be able to define an order of events for each artist.
Here's my solution. I'm writing this code from my head, but similar solution works in my case. I'm pretty sure there is a room for improvement.
I'm using rails acts_as_list plugin.
That's how I would define models:
class Artist < ActiveRecord::Base
has_many :artist_events
has_many :events, :through => :artist_events, :order => 'artist_events.position'
end
class Event < ActiveRecord::Base
has_many :artist_events
has_many :artists, :through => :artist_events, :order => 'artist_events.position'
end
class ArtistEvent < ActiveRecord::Base
default_scope :order => 'position'
belongs_to :artist
belongs_to :event
acts_as_list :scope => :artist
end
As you see you need an additional model ArtistEvent, joining the other two. The artist_events table should have two foreign ids and additional column - position.
Now you can use acts_as_list methods (on ArtistEvent model, unfortunately) but something like
Artist.find(:id).events
should give you a list of events belonging to specific artist in correct order.
Additional update for the accepted answer: for Rails 4 and Rails 5:
has_many :events, -> { order 'artist_events.position ASC' }, through: :artist_events
has_many :artists, -> { order 'artist_events.position ASC' }, through: :artist_events
I trying with self-referencing like that
class Product < ActiveRecord::Base
has_many :cross_sales
has_many :cross_sales_products, :through => :cross_sales, :order => 'cross_sales.position'
end
class CrossSale < ActiveRecord::Base
default_scope :order => 'cross_sales.position'
belongs_to :product
belongs_to :cross_sales_product, :class_name => "Product"
acts_as_list :scope => :product
end
create_table :cross_sales, :force => true, :id => false do |t|
t.integer :product_id, :cross_sales_product_id, :position
end
But the field cross_sales.position is never updated ...
An idea ?
Update: Ok the field 'id' it's necessary in the case of additional model with has_many :through option. It's work well now
In the accepted answer, note that :order => 'artist_events.position' is referencing the table artist_events and not the model.
I ran into this minor hiccup when moving from a habtm association to has_many :through.
I'm modeling "featuring" based on my plan in this question and have hit a bit of a stumbling block.
I'm defining a Song's primary and featured artists like this:
has_many :primary_artists, :through => :performances, :source => :artist,
:conditions => "performances.role = 'primary'"
has_many :featured_artists, :through => :performances, :source => :artist,
:conditions => "performances.role = 'featured'"
This works fine, except when I'm creating a new song, and I give it a primary_artist via new_song.performances.build(:artist => some_artist, :role => 'primary'), new_song.primary_artists doesn't work (since the performance I created isn't yet saved in the database).
What's the best approach here? I'm thinking of going with something like:
has_many :artists, :through => :performances
def primary_artists
performances.find_all{|p| p.role == 'primary'}.map(&:artist)
end
I think you're overcomplicating it. Just because things have similarities doesn't mean you should put them all in the same box.
class Song < ActiveRecord::Base
has_one :artist # This is your 'primary' artist
has_and_belongs_to_many :featured_artists, :source => :artist # And here you make a featured_artists_songs table for the simple HABTM join
validates_presence_of :artist
end
Poof, no more confusion. You still have to add song.artist before you can save, but that's what you wanted. Right?
There's not much that you can do about the association not being recognized until you save. Arguably, it doesn't really exist until you save, validations pass, and the relevant transaction(s) are completed.
Regarding your question of cleaning up your primary_artist method, you could model it something like this.
class Song < ActiveRecord::Base
has_many :performances
has_many :artists, :through => :performances
has_one :primary_artist, :through => :performances, :conditions => ["performances.roll = ?", "primary"], :source => :artist
end
It's unclear if you want one or many primary artists, but you can easily switch that has_one to has_many as needed.
You've nailed the source of your problem with build vs. create.
As for finding the primary artist of a song. I would add a named_scope on artist to select only featured/primary artists.
class Artist < ActiveRecord::Base
...
named\_scope :primary, :joins => :performances, :conditions => "performances.role = primary"
named\_scope :featured, :joins => :performances, :conditions => "performances.role = featured"
end
To get the primary artist for a song, you would do #song.artists.primary or if you prefer your primary_artists method in song.
def primary_artists
artists.primary
end
However, after looking at your initial question, I think your database layout is insufficient. It's workable but not clear, I've posted my suggestions there, where it belongs.
These named scopes I would also work under my proposed scheme as well.
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