Rails: Adding polymorphic associations between three models - ruby-on-rails

I have three models : User, Agency and Client.
Currently,
class User < ActiveRecord::Base
has_one :agency
has_one :client
end
class Client < ActiveRecord::Base
belongs_to :users
end
class Agency < ActiveRecord::Base
belongs_to :users
end
I want to change associations and create a polymorphic association such as this:
User belongs_to :role , :polymorphic => true
and
Client has_one :user, as: :role
Agency has_one :user, as: :role
I am a novice rails developer. How can I achieve this? HOw to write a migration?

You need to add two fields, role_id and role_type in user model. You can create new migration as follows
rails g migration addNewFieldsToUsers role_id:integer role_type:string
After running rake db:migrate you need to modify the associations as follows
class User < ActiveRecord::Base
belongs_to :role, polymorphic: true
end
class Client < ActiveRecord::Base
has_one :user, as: :role, class_name: 'User'
end
class Agency < ActiveRecord::Base
has_one :user, as: :role, class_name: 'User'
end
Now restart the rails server.

A migration is not needed. There are no associations between models in the database (which is what a migration would change).
What you need to do is change the models app/models/user.rb and app/models/location.rb. simply remove the belongs_to: from user and add it in loction: belongs_to: user.

Related

Can't get STI to act as polymorphic association on model

I have a User model that can have an email and a phone number, both of which are models of their own as they both require some form of verification.
So what I'm trying to do is attach Verification::EmailVerification as email_verifications and Verification::PhoneVerification as phone_verifications, which are both STIs of Verification.
class User < ApplicationRecord
has_many :email_verifications, as: :initiator, dependent: :destroy
has_many :phone_verifications, as: :initiator, dependent: :destroy
attr_accessor :email, :phone
def email
#email = email_verifications.last&.email
end
def email=(email)
email_verifications.new(email: email)
#email = email
end
def phone
#phone = phone_verifications.last&.phone
end
def phone=(phone)
phone_verifications.new(phone: phone)
#phone = phone
end
end
class Verification < ApplicationRecord
belongs_to :initiator, polymorphic: true
end
class Verification::EmailVerification < Verification
alias_attribute :email, :information
end
class Verification::PhoneVerification < Verification
alias_attribute :phone, :information
end
However, with the above setup I get the error uninitialized constant User::EmailVerification. I'm unsure of where I'm going wrong.
How I structure this so that I can access email_verifications and phone_verifications on the User model?
When using STI you don't need (or want) polymorphic associations.
Polymorphic associations are a hack around the object-relational impedance mismatch problem used to setup a single association that points to multiple tables. For example:
class Video
has_many :comments, as: :commentable
end
class Post
has_many :comments, as: :commentable
end
class Comment
belongs_to :commentable, polymorphic: true
end
The reason they should be used sparingly is that there is no referential integrity and there are numerous problems related to joining and eager loading records which STI does not have since you have a "real" foreign key column pointing to a single table.
STI in Rails just uses the fact that ActiveRecord reads the type column to see which class to instantiate when loading records which is also used for polymorphic associations. Otherwise it has nothing to do with polymorphism.
When you setup an association to a STI model you just have to create an association to the base inheritance class and rails will handle resolving the types by reading the type column when it loads the associated records:
class User < ApplicationRecord
has_many :verifications
end
class Verification < ApplicationRecord
belongs_to :user
end
module Verifications
class EmailVerification < ::Verification
alias_attribute :email, :information
end
end
module Verifications
class PhoneVerification < ::Verification
alias_attribute :email, :information
end
end
You should also nest your model in modules and not classes. This is partially due to a bug in module lookup that was not resolved until Ruby 2.5 and also due to convention.
If you then want to create more specific associations to the subtypes of Verification you can do it by:
class User < ApplicationRecord
has_many :verifications
has_many :email_verifications, ->{ where(type: 'Verifications::EmailVerification') },
class_name: 'Verifications::EmailVerification'
has_many :phone_verifications, ->{ where(type: 'Verifications::PhoneVerification') },
class_name: 'Verifications::PhoneVerification'
end
If you want to alias the association user and call it initiator you do it by providing the class name option to the belongs_to association and specifying the foreign key in the has_many associations:
class Verification < ApplicationRecord
belongs_to :initiator, class_name: 'User'
end
class User < ApplicationRecord
has_many :verifications, foreign_key: 'initiator_id'
has_many :email_verifications, ->{ where(type: 'Verifications::EmailVerification') },
class_name: 'Verifications::EmailVerification',
foreign_key: 'initiator_id'
has_many :phone_verifications, ->{ where(type: 'Verifications::PhoneVerification') },
class_name: 'Verifications::PhoneVerification',
foreign_key: 'initiator_id'
end
This has nothing to do with polymorphism though.

How to do has_many and has_one association in same model?

I need to do two associations in the same model. Where:
Team has_many User
Now, I want that Team has_one Leader
This "Leader" will be a User
Im trying to use has_one throught but I think that association isn't work.
Leader.rb
class Leader < ActiveRecord::Base
belongs_to :user
belongs_to :team
Team.rb
class Team < ActiveRecord::Base
has_one :user, through: :leader
end
User.rb
class User < ActiveRecord::Base
belongs_to :team
has_one :captain
end
and the get following error around line 27:
NoMethodError in TeamsController#create
26 def create
**27 #team = current_user.teams.create(team_params)**
28 #team.save
29 respond_with(#team)
30 current_user.update(team_id: #team.id)
In this case I think you need 2 model are enough
1). User model
class User < ActiveRecord::Base
belongs_to :team
end
2). Team model
class Team < ActiveRecord::Base
has_many :users
belongs_to :leader, class_name: 'User', foreign_key: :leader_id
end
How about setting a boolean flag in users table called leader. And then your association can become:
class Team < ActiveRecord::Base
has_many :users
has_one :leader, class_name: 'User', -> { where leader: true }
end
Team has_many User Now, I want that Team has_one Leader
This "Leader" will be a User
Use inheritance (also called sub-classing), Leader is a User.
class User < ActiveRecord::Base
belongs_to :team
end
class Leader < User
end
class Team < ActiveRecord::Base
has_many :users
has_one :leader
end
Your users table is also important. Ensure that users has t.belongs_to :team and t.string :type in its create_table method. Note that a Leader is a User and does not need a separate table, however you do need to allow ActiveRecord to record its type so it can return the correct Model later.
References:
inheritance specifically you need 'single table inheritance'
belongs_to scroll down for has_one and has_many, the three relationships used here.
current_user.teams.create(team_params)
Teams is for a has_many association, you want current_user.create_team(team_params)
You have has_one association between user and team. Try this:
current_user.create_team(team_params)
Also, you should add proper back association from team to leader.
class Team < ActiveRecord::Base
belongs_to :leader
has_one :user, through: :leader
end

Buid two associations between two tables

I have a strange requirement in my project. Actually, I have two tables (users,galleries). I would like to access galleries table with has_one as well as has_many associations. The main purpose is to get the profile snap of the user by using has_one relation and to get the personally uploaded pictures by using has_many relation.
Initially I go with polymorphic association to resolve this (FYI, please find the below code snippet) .But I think it is not the right approach for this problem.
Would anybody explain how to handle this case in an efficient way.
class User < ActiveRecord::Base
attr_accessible :name
has_many :galaries, as: :imageable
has_one :galary, as: :imageable
end
class Galary < ActiveRecord::Base
attr_accessible :name
belongs_to :imageable, polymorphic: true
end
You need to add a column user_id in galaries table in order to link the the user to the galary (profile snap).
rails generate migration add_user_id_to_galaries user_id:string
This will generate the migration:
class AddUserIdToGalaries < ActiveRecord::Migration
def change
add_column :galaries, :user_id, :integer
end
end
Then run rake db:migrate
Moving to our Models now:
class User < ActiveRecord::Base
attr_accessible :name
has_many :galaries, as: :imageable
has_one :galary #profile snap
end
class Galary < ActiveRecord::Base
attr_accessible :name
belongs_to :imageable, polymorphic: true
belongs_to :user #profile snap user
end
Can be done with scoped has_one association. Although not necessary, you can define which gallery is selected in the has_one using a scope. If no scope is given, the has_one would return the first match.
class User < ActiveRecord::Base
attr_accessible :name
has_many :gallaries
has_one :gallery, -> { where primary: true }
end
class Gallery < ActiveRecord::Base
#user_id, primary (boolean variable to select the gallery in has one association)
attr_accessible :name
belongs_to :user
end

Rails Associations with Modules

With Rails 4.1 I can't seem to get my rails associations to work when using modules.
I have Objects within the FG module:
module FG
class Object < ActiveRecord::Base
belongs_to :user
has_one :email
has_one :phone
end
end
And Emails in the global space:
class Email < ActiveRecord::Base
belongs_to :object, class_name: 'FG::Object'
has_many :objects, class_name: 'FG::Object'
end
When I try
email.objects << object
I get the following error:
ActiveModel::MissingAttributeError
can't write unknown attribute `object_id'
Am I missing something in the association setup?
You could write your Email code this way:
class Email < ActiveRecord::Base
has_many :objects, class_name: 'FG::Object', foreign_key: 'email_id'
end
This will only work if you have an email_id in your objects table. You can not use has_many and belongs_to referring to the same class. That would mean you have an object_id in the one table and an email_id in the other.
You could also write:
class Email < ActiveRecord::Base
belongs_to :object, class_name: 'FG::Object', foreign_key: 'object_id'
end
That depends on your database construction.
I was thinking of the relationships in a conflicting way.
In order for the associations to make sense, I needed to organize them in the following way:
module FG
class Object < ActiveRecord::Base
belongs_to :user
belongs_to :email
belongs_to :phone
end
end
class Email < ActiveRecord::Base
has_many :objects, class_name: 'FG::Object'
end

rails creating model with multiple belongs_to, with attr_accessible

My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)

Resources