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 :)
Related
I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)
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.
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
....
In my Ruby on Rails project I have a User model and a Content model.
A User has_many :contents and a Content belongs_to :user.
Now, I want to create the idea of playlist. There will be more than one playlists, and each one will have some contents in some order. At this moment, it doesn't matter if a user owns a playlist or not, they'll be general.
A playlist doesn't have any kind of association with a User. They will be general, owned by the system.
I think the solution will be something like having a model Playlist and another table with these attributes: playlist_id:integer content_id:integer order:integer. But do I really need to create all the MVC parts to this new relationship?
As I looked into Rails associations, I got confused and I don't know how to do this, if using the through property, using has_and_belongs_to_many in both Content and Playlist or even how to create this new relationship.
I'd be glad if someone could help me, as you can see, I'm a bit confused.
The solution for you is use has_many through
class User < ActiveRecord::Base
... user code in here with no association
end
class Playlist < ActiveRecord::Base
has_many :content_playlists
has_many :contents, through: :content_playlists
end
class Content < ActiveRecord::Base
has_many :content_playlists
has_many :playlists, through: :content_playlists
end
class ContentPlaylist < ActiveRecord::Base
belongs_to :content
belongs_to :playlist
end
The migration:
class CreateAll < ActiveRecord::Migration
def change
create_table :contents do |t|
t.string :name
t.timestamps
end
create_table :playlists do |t|
t.string :name
t.timestamps
end
create_table :content_playlists do |t|
t.belongs_to :content
t.belongs_to :playlist
t.integer :order
t.timestamps
end
add_index(:content_playlists, :content_id)
add_index(:content_playlists, :playlist_id)
end
end
Now, you can assign a order integer on content_playlists, and in the future you can reorder your playlist changing the value on contents_playlists.
To add a new content_playlist:
c = Content.create(name: "Song 2")
p = Playlist.create(name: "My Playlists2)
ContentPlaylist.create(content: c, playlist: p, order: 1)
Reference:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
You can see (fork, clone, do whatever you want) here:
https://github.com/bdfantini/hmt_example
I'm guessing something like this is what you want:
class User < ActiveRecord::Base
...
has_many :contents
has_many :playlists
has_many :playlisted_contents, :through => :playlists
...
end
class Playlist < ActiveRecord::Base
...
has_many :contents
...
end
class Content < ActiveRecord::Base
...
belongs_to :user
belongs_to :playlist
...
end
I'd start there, and write some tests to see if it's behaving as you want. If your design has other constraints, we might need to adjust this some.
I am customizing RailsAdmin for one of my project. I am trying to achieve multi select box like below for many to many association
My Class are different (No Teams) , I have tour and programs
Tour Class
class Tour < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :programs
end
Program Class
class Program < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :tours
end
Joint Table
class ProgramsTours < ActiveRecord::Migration
def change
create_table :programs_tours, :id => false do |t|
t.integer :program_id
t.integer :tour_id
end
end
end
This association creating an Multiple Add form with tabs as below , I am not sure how I can get that multi select box, any suggestions will be helpful.
class Tour < ActiveRecord::Base
attr_accessible :name, :program_ids
has_and_belongs_to_many :programs
end
class Program < ActiveRecord::Base
attr_accessible :name, :tour_ids
has_and_belongs_to_many :tours
end