I have a model representing a Content
item that contains some images. The number of images are fixed as these image references are very specific to the content. For example, the Content
model refers to the Image
model twice (profile image, and background image). I am trying to avoid a generic has_many
, and sticking to multiple has_one
's. The current database structure looks like:
contents
- id:integer
- integer:profile_image_id
- integer:background_image_id
images
- integer:id
- string:filename
- integer:content_id
I just can't figure out how to setup the associations correctly here. The Content
model could contain two belongs_to
references to an Image
, but that doesn't seem semantically right cause ideally an image belongs to the content, or in other words, the content has two images.
This is the best I could think of (by breaking the semantics):
class Content
belongs_to :profile_image, :class_name => 'Image', :foreign_key => 'profile_image_id'
belongs_to :background_image, :class_name => 'Image', :foreign_key => 'background_image_id'
end
Am I way off, and there a better way to achieve this association?
They essentially do the same thing, the only difference is what side of the relationship you are on. If a User has a Profile , then in the User class you'd have has_one :profile and in the Profile class you'd have belongs_to :user .
In Ruby on Rails, a polymorphic association is an Active Record association that can connect a model to multiple other models. For example, we can use a single association to connect the Review model with the Event and Restaurant models, allowing us to connect a review with either an event or a restaurant.
The simple answer is to setup your associations in reverse of what you have, like so:
# app/models/content.rb
class Content < ActiveRecord::Base
has_one :profile_image, :class_name => 'Image'
has_one :background_image, :class_name => 'Image'
end
# app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :content
end
You don't need the foreign keys 'background_image_id' and 'profile_image_id' in the content table at all.
However, there's a more elegant solution: single table inheritance. Set it up now in case you want background and profile images to behave even slightly differently in the future, plus it will clarify your code today.
First, add a column to your images table called type:
# command line
script/generate migration AddTypeToImages type:string
rake db:migrate
Now setup your models like this:
# app/models/content.rb
class Content < ActiveRecord::Base
has_one :profile_image
has_one :background_image
end
# app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :content
end
# app/models/background_image.rb
class BackgroundImage < Image
# background image specific code here
end
# app/models/profile_image.rb
class ProfileImage < Image
# profile image specific code here
end
Now you can do all kinds of things like getting a list of all background images:
# script/console
BackgroundImage.all
This is more true to the data model you're trying to create, allows the easiest expandability in the future, and gives you some cool new methods today.
UPDATE:
I've since created a blog article called Single-Table Inheritance with Tests that goes into more detail, and covers testing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With