polymorphic association and creating a table for it - ruby-on-rails

Say I have this:
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
Does this require me to define a table exactly in the following way?
class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
t.timestamps
end
add_index :pictures, [:imageable_type, :imageable_id]
end
end
Or may I define a bit differently, with different columns or types, for example, that is, in a way I see more efficient? Will the polymorphic association remain functioning?

The polymorphic association only relates to the _type and _id pair of columns. Everything else is up to you.
So yes, you can add additional metadata if you like.

Related

How associations can be set with non id foreign key?

Currently I have following migrations:
class CreateDevices < ActiveRecord::Migration[5.0]
def change
create_table :devices do |t|
t.string :name
t.string :abbr
t.timestamps
end
end
end
class CreateVendors < ActiveRecord::Migration[5.0]
def change
create_table :vendors do |t|
t.string :name
t.string :abbr
t.timestamps
end
end
end
class CreateDeviceVendors < ActiveRecord::Migration[5.0]
def change
create_table :device_vendors do |t|
t.string :device
t.string :vendor
t.timestamps
end
end
end
There is many to many relationship between device and vendor, so DeviceVendors table is getting used for that. Both tables abbr column (which is unique) is getting saved in this table as device and vendor respectively.
I am using this kind of table structure so that I can seed the data and don't have to check for ids in the primary tables.
How can I set the association in all three models so that I can access in better way. Something like this:
class Device < ApplicationRecord
has_many :device_vendors
has_many :vendors, through: device_vendors
end
class Vendor < ApplicationRecord
has_many :device_vendors
has_many :devices, through: device_vendors
end
class DeviceVendor < ApplicationRecord
belongs_to :device
belongs_to :vendor
end
I know I have to apply foreign_key: :abbr to belongs_to in models but not sure in which ones. Also whether I need to change/add the migration for this?
The foreign_key, as you point out, is on the belongs_to table, but you need to specify both primary_key and foreign_key (since none is the default id) in all associations:
class Device < ApplicationRecord
has_many :device_vendors, primary_key: "abbr", foreign_key: "device"
has_many :vendors, through: device_vendors
end
class Vendor < ApplicationRecord
has_many :device_vendors, primary_key: "abbr", foreign_key: "vendor"
has_many :devices, through: device_vendors
end
class DeviceVendor < ApplicationRecord
belongs_to :device, primary_key: "abbr", foreign_key: "device"
belongs_to :vendor, primary_key: "abbr", foreign_key: "vendor"
end
Also notice that the foreign key is not abbr, that's the primary key in both device and vendor; the foreign key is the one in the table with belongs_to (i.e. device and vendor in device_vendors).

Rails many to many associations and has_many, through:

I created my models a week ago, but I didn't know many things that I know now so it is time to create it from scratch.
What I want to accomplish is to create:
Lab model that can have many offers
Offer model that can have many labs.
#MIGRATION FILES BELOW:
class CreateLabs < ActiveRecord::Migration[5.0]
def change
create_table :labs do |t|
t.string :name
...
t.timestamps
end
end
end
class CreateOffers < ActiveRecord::Migration[5.0]
def change
create_table :offers do |t|
t.string :name
...
t.timestamps
end
end
end
# Join table:
class CreateLabChain < ActiveRecord::Migration[5.0]
def change
create_table :lab_chain do |t|
t.references :lab, foreign_key: true
t.references :offer, foreign_key: true
t.timestamps
end
end
end
And here is how the model files look like:
class Lab < ApplicationRecord
has_many :offers, through: :lab_chain
has_many :lab_chains
end
class Offer < ApplicationRecord
has_many :labs, through: :lab_chain
has_many :lab_chains
end
class LabChain < ApplicationRecord
belongs_to :lab
belongs_to :offer
end
I just want to know if I wrote it all correctly as I am not sure about all those tutorials I have watched and read.
Bonus question is what if I want my Offer to have many sections, and section to have many offer_items? Should I just add:
to Offer:
has_many :sections
has_many :offer_items, through: :section
and then to Section:
has_many :offer_items
belongs_to :offer
and the to OfferItem:
belongs_to :section
?
As I mentioned before, I volunteered as a guy that would make a website for our school project as I was the only one that had something to do with code (different language). It is harder than I thought.
EDIT
How would I also correctly add an self join in Section, so a section can have a subsection and so on?
Self joins addded to Section model
has_many :child_sections, class_name: "Section", foreign_key: "section_id"
belongs_to :parent_section, class_name: "Section"
Added to migration file
t.references :parent_section, foreign_key: "section_id"
That does look like a correct many to many association. You second also looks correct.
As an aside, it helps to draw a diagram of any database with more than 3 tables and if you plan on doing this as a job, it's well worth it to really grasp the whole table relationships fully as it's core to writing good model code.

Polymorphic Associations - rails

I have some trouble understanding polymorphic associations in rails.
How is
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
different from
class Picture < ApplicationRecord
belongs_to :employee
belongs_to :product
end
class Employee < ApplicationRecord
has_many :pictures
end
class Product < ApplicationRecord
has_many :pictures
end
In the second case you will need to add two foreign keys i.e employee_id and product_id in pictures table.
Where as in first case t.references :imageable, polymorphic: true in your pictures migration will add two fields in pictures table i.e
t.integer :imageable_id
t.string :imageable_type
imagable_type field will be the name of class with whom you are associating this model to and imagable_id will hold the id of that record.
e.g,
Typical rows of picture table will look like
id | name | imagable_id | imagable_type |
1 | pic1 | 1 | Employee |
2 | pic2 | 3 | Product |
So here, Picture of first row will belong to Employee model holding the picture of Employee with id 1. Second row will belong to Product model holding the picture of product with id 3
Advantages of first Approach is you can associate picture model any other model in future without having to add foreign key to it.
Just add the line
has_many :pictures, as: :imageable
and association will be set.
There's not much difference other than the schemas and syntax required to support it. If you have some relatively large number of belongs_to :imageable relations it would begin to make sense as a use case. Using the standard naming approach they will reduce n number of fields necessary to represent multiple belongs_to associations to two, "MODEL_able" and an id that references the id of the targeted model. This is in favor to having a MODEL_id for each belongs_to model. It's fairly rare for them to be a huge win but a good thing to be familiar with.
Without Polymorphic
20160902065429_create_employee_images
class CreateEmployeeImages < ActiveRecord::Migration[5.0]
def change
create_table :employee_images do |t|
t.integer :employee_id
t.string :image
t.timestamps
end
end
end
20160902065445_create_product_images
class CreateProductImages < ActiveRecord::Migration[5.0]
def change
create_table :product_images do |t|
t.integer :product_id
t.string :image
t.timestamps
end
end
end
app/model/employee.rb
class Employee < ApplicationRecord
has_many :employee_images
end
app/model/product.rb
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
app/model/employee_image.rb
class EmployeeImage < ApplicationRecord
belongs_to :employee
end
app/model/product_image.rb
class ProductImage < ApplicationRecord
belogs_to :product
end
With Polymorphic
db/migrate/20160902063459_create_products.rb
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.string :name
t.timestamps
end
end
end
db/migrate/20160902063513_create_employees.rb
class CreateEmployees < ActiveRecord::Migration[5.0]
def change
create_table :employees do |t|
t.string :name
t.timestamps
end
end
end
db/migrate/20160902063602_create_pictures.rb
class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :picture
t.integer :imageable_id
t.string :imageable_type
t.string :image
t.timestamps
end
end
end
app/model/employee.rb
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
app/model/picture.rb
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
app/model/product.rb
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
1- Without polymorphic. We have used two different tables to do the same thing.
2- With polymorphic we have create only a single model to store the images of two or more than two models in third polymorphic model.
Here we need two coulms in third table, one is for stroe model class name and second one for record id .
we can take any two column but column name should be same like imageable+id imageable+type
here we can use any name insted of imageable like we can use poly_id poly_type but we have to make sure what we are using in model.
if we use poly_id and poly_type then we have to use belogs_to :poly, polymorphic:true in picture.rb and has_many :pictures, as: :poly in employee.rb and product.rb both
in both.
Imagin you have these models : Post,Idea,Article and you want to have comment model for all the three!
You can have three tables for comments like :
PostComment,
IdeaCommnet,
ArticleComment
or you can have one General Comment Model and store each comment based on it's related model.
As #Karan Purohit mentioned :)

How to set up an admin user in rails

I have a simple relationship
class School < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :schools
end
A user can be part of many schools but at the same time a user might be the admin of a number of schools. I set up a many-to-many relationship to represent this however I'm not sure how I would distinguish between admins and simple users.
I initially thought of setting a table which has a school_id and a user_id and every entry will represent the school id and the user id of any admins that the school has however I'm not sure how I would represent this in rails or if it's the best way to solve this problem? And if it is, how do I access the table without a model associated to it?
What I mean by what I said above:
school_id user_id
1 3
1 4
Which means that the school with id 1 has 2 admins (3 and 4)
What you are looking for is a more complex many_to_many relationship between school and user called has_many :through. This relationship allows you to have many to many relationship with access to the table that represents the relationship. If you use that relationship, your models should look something like this:
class User < ActiveRecord::Base
has_many :school_roles
has_many :schools, through: :school_roles
end
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
end
class School < ActiveRecord::Base
has_many :school_roles
has_many :users, through: :school_roles
end
And the migrations of those tables would look something like this:
class CreateSchoolRoles < ActiveRecord::Migration
def change
create_table :schools do |t|
t.string :name
t.timestamps null: false
end
create_table :users do |t|
t.string :name
t.timestamps null: false
end
create_table :school_roles do |t|
t.belongs_to :school, index: true
t.belongs_to :user, index: true
t.string :role
t.timestamps null: false
end
end
end
I would suggest to make the "role" field in the "school_roles" migration an integer and then use an enum in the model like so:
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
enum role: [ :admin, :user ]
end
which allows you to add more roles in the future, but it's your call
combining polymorphic association with has_many :through in my opinion is best option.
Let's say you create supporting model SchoolRole, which
belongs_to :user
belongs_to :school
belongs_to :rolable, polymorphic:true
This way:
class School ...
has_many :administrators, :as => :schoolroles
has_many :users, :through => :administators
#school.administrators= [..., ...]
It is quite agile.
#user=#school.administrators.build()
class User
has_many :roles, :as => :rolable
def admin?
admin=false
self.roles.each do |r|
if r.role_type == "administator"
admin=true
break
end
end
admin
end
....

has_one relationship is resulting in ActiveModel::MissingAttributeError , what am I missing here?

I feel like I am overlooking something obvious here. I can create a story model, and a category model, but I can't relate a story to a category.
Here is how I reproduce the error:
s = Story.new(title: "test", picture_url: "www.google.com")
c = Category.last
s.category = c
error: ActiveModel::MissingAttributeError: can't write unknown attribute `story_id'
Story model
class Story < ActiveRecord::Base
has_many :chapters, dependent: :destroy
has_many :users, through: :story_roles
has_one :category
end
Story migration file
class CreateStories < ActiveRecord::Migration
def change
create_table :stories do |t|
t.string :title
t.string :picture_url
t.integer :category_id
t.timestamps
end
end
end
Category model
class Category < ActiveRecord::Base
belongs_to :story
validates_presence_of :body
end
Category migration
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.string :body
t.timestamps
end
end
end
In your story model, change has_one :category to belongs_to :category. A rule of thumb is if you have a foreign key for a model, you declare the association as belongs_to. In this example, you have category_id in the story model so you use belongs_to :category in the story model. This makes perfect sense since a story should really belong to a category and a category has_many stories.
You miss t.references :story in your migration. The belongs_to method on category requires story_id.
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.references :story
t.string :body
t.timestamps
end
end
end
You are missing a foreign_key story_id in your Category Model. Add that column in your categories table and migrate it.That would resolve your issue.
Note: Before migrating the changes,roll back the previous migration.
OR
The best way is what #bekicot suggested. Just add t.references :story. This includes story_id,so that it will be added to your categories table by default.

Resources