Understanding Rails Model Associations - ruby-on-rails

I'm relatively new to Ruby on Rails and I'm trying to understand the way how active record associations work. So far, I thought I figured it out, but not sure anymore.
Anyway, I'm building my very own CMS and apart from all, I'll focus on my main problem. I'm having a table pages and pictures:
class CreatePages < ActiveRecord::Migration
def change
create_table :pages do |t|
t.string :name
t.integer :headline_image_id
t.timestamps
end
create_table :pictures do |t|
t.string :name
t.string :description
t.string :image
t.timestamps
end
end
end
With this I have my models:
class Page < ActiveRecord::Base
validates :name, presence: true
validates :headline_image_id, presence: true
belongs_to :headline_image, class_name: Picture, foreign_key: :headline_image_id
end
class Picture < ActiveRecord::Base
mount_uploader :image, ImageUploader
end
And that's it. Now after I create a picture and a page which has the id of a picture in the headline_image_id attribute, I can fetch that headline_image with #target_page.headline_image. Perfect, but the thing that is bothering me is the readability of the code. Wouldn't it make much more sense if I associated the two models in the Page model like this:
class Page < ActiveRecord::Base
validates :name, presence: true
validates :headline_image_id, presence: true
has_one :headline_image, class_name: Picture, foreign_key: :headline_image_id
end
If I do it like this and run #target_page.headline_image I get a SQL Constraint exception that tells me there is no headline_image_id in the pictures table.
I read all the Active Record Association tutorial on Ruby on Rails Guides and watched all the codeschool Rails courses, and I was pretty sure that everything was going to work with a has_one association...but it didn't.
Can someone please explain?
Thanks!

Rails Guides provides an explanation as to why you're experiencing the problem. Essentially, when you declare a belongs_to relationship, the foreign key appears on the table for the class declaring it. When you declare a has_one relationship, the foreign key is on the table for the class in the declaration.
Example
In this scenario, the pictures table would require a page_id foreign key.
class Page < ActiveRecord::Base
has_one :picture
end
class Picture < ActiveRecord::Base
belongs_to :page
end
In this scenario, the pages table would require a picture_id foreign key.
class Page < ActiveRecord::Base
belongs_to :picture
end
class Picture < ActiveRecord::Base
has_one :page
end
If you wanted to use a has_one association, just remove the headline_image_id column from your pages table and add a page_id column to your pictures table. You can do this in one migration. After you run the migration, change your model definitions as per my above example. Hope this helps.

Related

Rails: Create migrations for has_many and has_one

I have Question, Option and Answer models as follows:
class Question < ApplicationRecord
belongs_to :user
has_many :options
has_one :answer
end
class Option < ApplicationRecord
belongs_to :question
has_many :answers
end
class Answer < ApplicationRecord
belongs_to :question
belongs_to :option
end
I have one migration files for Question and Option models like this:
class CreateQuestions < ActiveRecord::Migration[5.2]
def change
create_table :questions do |t|
t.text :body
t.references :user, foreign_key: true
t.timestamps
end
end
end
class CreateOptions < ActiveRecord::Migration[5.2]
def change
create_table :options do |t|
t.references :question, foreign_key: true
t.timestamps
end
end
end
If my understanding is correct, I have a migration here for belongs_to association. My doubt is, are these migration files enough to create has_many associations or do I need to add any extra conditions in migrations files? If yes, please tell me what to add. I referred the following link:
[https://stackoverflow.com/questions/35771847/rails-survey-style-application-show-all-answers-on-option][1]
[1]: Rails survey style application - Show all answers on option but I did not understand whether I need to add extra line for has_many and has_one associations.
Your migrations are correct, because if you think of your models as in database tables, you will never store the 'has_many' option somewhere. That is merely for the human understanding, as well as for ActiveRecord.
So an option in your example belongs to a question, hence we have to store the ID of that question in the record of the answer. In the question migration however, we don't store any information regarding the option, it is enough that the option "knows" which question it belongs to. (And same for user and question).
Only in the model you can then specify - as you did - the 'has_many' options. This will allow you later to call 'question.options` to retrieve all options that belong to a question.

Has_many of different models association in Rails

I have a few different models which I would like to add multiple images to.
I have an image model with belongs_to associations set up to the different owning models (each of these owning models has has_many :images defined).
I would like to know what's the appropriate migration I should create in order to add an image_ids column to each of owning models.
I assume something like this...
rails g migration AddImagesToBusinesses images businesses image_ids:integer
However, I'm confused as I believe that you can only make one association this way and it would need to be completed by adding a column to the images table to identify the id of the model it belongs to (here there are a few different models).
Thank you for your help.
As you concern about relationship of image to other model. You should try polymorphic associations like this.
Generate the Image model:
class CreateImages < ActiveRecord::Migration
def change
create_table :images do |t|
t.string :file_id
t.boolean :featured
t.references :imageable, polymorphic: true, index: true
t.timestamps null: false
end
end
end
Update the Image model:
class Image < ActiveRecord::Base
attachment :file
belongs_to :imageable, polymorphic: true
end
Add association to other models like this
class Model < ActiveRecord::Base
has_many :images, as: :imageable, dependent: :destroy
accepts_attachments_for :images, attachment: :file
end
For more details you Ruby on Rails Guide.
I think you need Polymorphic associations.
See documentaions here.

Creating and enforcing has_one relationship in rails relational database

Working on a table of viewed profiles. I kind of have an issue setting it up, is this correct? I am confused about the has_many and has_one relationship. Because this is one table that has a row for each visited relationship, I decided to go with has_one.
Does this look correct, also is there a way to enforce the relation in ActiveRecord?
model
class ViewedProfile < ActiveRecord::Base
validates :viewed_profile_id, presence: true
validates :profile_id, presence: true
has_one :profile_id
has_one :viewed_profile_id
end
migration
class CreateViewedProfile < ActiveRecord::Migration
def change
create_table :viewed_profiles do |t|
t.integer :profile_id
t.integer :viewed_profile_id
end
end
end
edit
Also when I go to my console and I type ViewedProfile nothing comes up. Any idea as to why? =c the schema should normally show up!
Firstly, you are confused in between the terms Model names and attributes(specially Foreign keys).Model will have attributes and the associations will be set to models.
You have to set your models like this
class ViewedProfile < ActiveRecord::Base
has_one :profile
end
Class Profile < ActiveRecord::Base
belongs_to :viewed_profile
validates :viewed_profile_id, presence: true
validates :viewed_profile_id, uniqueness: true
end
And your corresponding migration files should look like this
class CreateViewedProfile < ActiveRecord::Migration
def change
create_table :viewed_profiles do |t|
t.string :name
end
end
end
class CreateProfile < ActiveRecord::Migration
def change
create_table :profiles do |t|
t.integer :viewed_profile_id
end
end
end
I would recommend to read these Guides articles before getting started.
Associations
Migrations
Validations

referencing two models to each other

i'm going through one of ror beginners books, and it has this example on references:
class Article < ActiveRecord::Base
attr_accessible :body, :excerpt, :location, :published_at, :publisher, :title
validates :title, :presence => true
validates :body, :presence => true
belongs_to :user
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :articles
end
so these two models are meant to be connected by habtm in both ways, just like in the code above. but, the book also tells me i should provide such a migration:
class CreateArticlesCategories < ActiveRecord::Migration
def up
create_table :articles_categories, :id => false do |t|
t.integer :article
t.integer :category
end
end
def down
drop_table :articles_categories
end
end
My question: why? why do i need to create this migration and not a model articles_categories?
tl;dr; You need the extra model when you're using a has_many through, you don't need it for the habtm association
The has_and_belongs_to_many association is meant to be used when you don't really care about the table in the middle (here it's articles_categories). What I mean by "don't" really care is that, there are no extra data needed in this table. It's only here to tell you which categories are related to a given article, and which articles are related to a given category.
For that purpose the has_and_belongs_to_many association is great cause you don't have to create the model, it only needs the join table.
If somehow you need some extra data in that table, like tags, dates, status, or anything, then it would be really handy to have a dedicated model, so you could explicitly manipulate it.
For those cases, you should use the has_many :through association
If you need more information, I suggest you read the rails guide about association, it's a good start. And of course if my explanation is not clear enough, please tell me, and I'll add more details.

Configure my model so that a field can reference many different types of classes

Let's say that I have a Company model and a Product model, both of which have an UserUploadedImage associated with them. I want to create my UserUploadedImage in a way such that I can write image.parent and that will reference either the Product or the Company, whichever is appropriate in that case.
I realize I can store a second column in UserUploadedImage with either Product or Company and have a conditional to look up the appropriate value. I am, however, not sure where the optimal place to put this code is, or whether there is a cleaner way of achieving my goal. Thanks!
What you need to look at is Polymorphic Associations
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Polymorphic Associations
A slightly more advanced twist on associations is the polymorphic association. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model.
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
You can think of a polymorphic belongs_to declaration as setting up an interface that any other model can use. From an instance of the Employee model, you can retrieve a collection of pictures: #employee.pictures.
Similarly, you can retrieve #product.pictures.
If you have an instance of the Picture model, you can get to its parent via #picture.imageable. To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface:
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
t.timestamps
end
end
end
This migration can be simplified by using the t.references form:
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
t.references :imageable, :polymorphic => true
t.timestamps
end
end
end

Resources