Rails Associations with Modules - ruby-on-rails

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

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.

Rails: Adding polymorphic associations between three models

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.

An equivalent to first_or_create on a nested attribute in Rails 4

I'm using a has many through pattern with these 3 models
class User < ActiveRecord::Base
has_many :user_topics
has_many :topics, through: :user_topics
end
class Topic < ActiveRecord::Base
validates_presence_of :name
validates :name, :uniqueness => true
end
class UserTopic < ActiveRecord::Base
belongs_to :user
belongs_to :topic
accepts_nested_attributes_for :topic
end
At the moment a new topic model is trying to be created every time a new user_topic is created. I'd like to create a new topic model only if the topic name doesn't already exist, otherwise if it does, use the existing topic_id.
So something like:
class UserTopic < ActiveRecord::Base
belongs_to :user
belongs_to :topic
accepts_nested_attributes_for :topic, :first_or_create(:name)
end
Is it possible to do something similar to this?

Should I use the foreign_key clause both in has_many as in belongs_to?

I have two models. Office and Employee. Employee has office_id as foreign_key. The tables was generated under a namespace. So, which is the correct?
class MyNamespace::Office < ActiveRecord::Base
has_many :employees, foreign_key: 'office_id'
end
class MyNamespace::Employee < ActiveRecord::Base
belongs_to :office, foreign_key: 'office_id'
end
Or
class MyNamespace::Office < ActiveRecord::Base
has_many :employees
end
class MyNamespace::Employee < ActiveRecord::Base
belongs_to :office, foreign_key: 'office_id'
end
I think the second example is the correct, because to me, doesn't makes sense declare the foreign_key in a has_many relation. A coworker thinks the first example is the correct. But I haven't found too many references to this subject. So, does Anybody know which is the correct example and why?
You can specify prefixes for proper mapping to table names in DB and remove foreign_keys and MyNamespace at all.
class Office < ActiveRecord::Base
self.table_name_prefix = 'namespace_'
has_many :employees
end
class Employee < ActiveRecord::Base
self.table_name_prefix = 'namespace_'
belongs_to :office
end

Dynamic has_many class_name using polymorphic reference

I am trying to associate a polymorphic model (in this case Product) to a dynamic class name (either StoreOnePurchase or StoreTwoPurchase) based on the store_type polymorphic reference column on the products table.
class Product < ActiveRecord::Base
belongs_to :store, polymorphic: true
has_many :purchases, class_name: (StoreOnePurchase|StoreTwoPurchase)
end
class StoreOne < ActiveRecord::Base
has_many :products, as: :store
has_many :purchases, through: :products
end
class StoreOnePurchase < ActiveRecord::Base
belongs_to :product
end
class StoreTwo < ActiveRecord::Base
has_many :products, as: :store
has_many :purchases, through: :products
end
class StoreTwoPurchase < ActiveRecord::Base
belongs_to :product
end
StoreOnePurchase and StoreTwoPurchase have to be separate models because they contain very different table structure, as does StoreOne and StoreTwo.
I am aware that introducing a HABTM relationship could solve this like this:
class ProductPurchase < ActiveRecord::Base
belongs_to :product
belongs_to :purchase, polymorphic: true
end
class Product < ActiveRecord::Base
belongs_to :store, polymorphic: true
has_many :product_purchases
end
class StoreOnePurchase < ActiveRecord::Base
has_one :product_purchase, as: :purchase
delegate :product, to: :product_purchase
end
However I am interested to see if it is possible without an extra table?
Very interesting question. But, unfortunately, it is impossible without an extra table, because there is no polymorphic has_many association. Rails won't be able to determine type of the Product.purchases (has_many) dynamically the same way it does it for Product.store (belongs_to). Because there's no purchases_type column in Product and no support of any dynamically-resolved association types in has_many. You can do some trick like the following:
class Product < ActiveRecord::Base
class DynamicStoreClass
def to_s
#return 'StoreOnePurchase' or 'StoreTwoPurchase'
end
end
belongs_to :store, polymorphic: true
has_many :purchases, class_name: DynamicStoreClass
end
It will not throw an error, but it is useless, since it will call DynamicStoreClass.to_s only once, before instantiating the products.
You can also override ActiveRecord::Associations::association to support polymorphic types in your class, but it is reinventing the Rails.
I would rather change the database schema.

Resources