Rails has_one vs belongs_to semantics

12,388

Solution 1

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.

Solution 2

Based on the AR associations guide, I think you should use has_one. It doesn't make sense for an image to own a Content... the Content surely owns the image. From the guide:

The distinction is in where you place the foreign key (it goes on the table for the class declaring the belongs_to association), but you should give some thought to the actual meaning of the data as well. The has_one relationship says that one of something is yours – that is, that something points back to you.

Finally, I'm not sure you need both contents and images to have foreign keys. As long as the images references the content_id, I think you're alright.

Share:
12,388

Related videos on Youtube

Chance
Author by

Chance

Updated on May 29, 2020

Comments

  • Chance
    Chance almost 4 years

    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?

  • Chance
    Chance over 14 years
    Single table inheritance works perfectly for my problem. Do you know how would I integrate STI with polymorphic associations? For example, if along with the content, there were some images that were part of the document metadata, and both - content & metadata images were stored in the images table.
  • Chance
    Chance over 14 years
    thanks a lot for the article. that has to be the best article on Single Table Inheritance for Rails on the net!
  • Weblance
    Weblance almost 12 years
    For the 'simple answer', how would Content know which Images to reference if there are no foreign keys in its db table?
  • de.
    de. almost 3 years
    The link is dead unfortunately.