Trying to 'alias' a polymorphic has_many relationship - ruby-on-rails

I have a User model:
class User < ActiveRecord::Base
has_many :tracks, dependent: :destroy
has_many :tracked_locations, through: :tracks, source: :tracking, source_type: 'Location'
and a Track model (think of it as 'following'):
class Track < ActiveRecord::Base
belongs_to :user
belongs_to :tracking, polymorphic: true
end
The idea here is I will have many models to track / follow so I am using polymorphism. For example I have a Location model:
class Location < ActiveRecord::Base
has_many :tracks, :as => :tracking, :dependent => :destroy
has_many :users, through: :tracks
Now in the console Location.first.users works fine along with User.first.tracked_locations.
Now I will be adding another polymorphic relationship along the lines of Flagged. The user can 'flag' another model with a note etc. So if I add has_many :users, through: :flagged to the Location model for example I need to differentiate between tracking users and flagged users.
I tried:
has_many :tracking_users, through: :tracks, source: :tracking, source_type: 'User'
but I get:
NoMethodError: undefined method `evaluators_for' for #<Location:0x007ff29e5409c8>
Can I even do this or am I missing something simple here?
UPDATE
Based on the answer below I figured it out:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user

I'm not 100% on this, but you could try:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
Or you could also just create a class method and do it by hand.
def self.tracking_users
user_ids = tracks.collect(&:user_id)
User.where(id: user_ids)
end
edit: Had a brainfart, changed the "source" up there to :user. That tells what table to actually do the lookup in with the other attribute you've provided. of course it wouldn't be in :tracks

Related

Multiple polymorphic associations on the same object

I have a Flag model which is joined to multiple other objects with FlagInstance and a polymorphic flaggable on that table:
table 'flag_instances'
flag_id
flaggable_id
flaggable_type
.....
With has many_through I'm able to fetch any flaggable object like user.flags which is great.
However I'm trying to flag objects with errors and the notify other objects so I've added
table 'flag_instances'
flag_id
flaggable_id
flaggable_type
notifiable_id
notifiable_type
.....
The problem is, a User can have a flag and can be notified of a flag. So user.flags isn't specific enough to show me which is a flag and which is a notification of a flag.
I think I need to change the relationships:
user.rb
has_many :flag_instances, as: :flaggable, dependent: :destroy
has_many :flags, through: :flag_instances
has_many :flag_instances, as: :notifiable, dependent: :destroy
has_many :flags, through: :flag_instances
But I'm not sure what to change them to. Can someone please suggest a solution?
Note: both flags and notifications of flags can belong to multiple objects, so they both need to remain polymorphic.
Thanks
Association for notifiable needs to be changed. In this case user.rb:
has_many :flag_instances, as: :flaggable, dependent: :destroy
has_many :flags, through: :flag_instances
has_many :notifiables, as: :notifiable, dependent: :destroy, class_name: 'FlagInstance'
has_many :notifications, through: :notifiables, class_name: 'Flag'
Note: You might also need to provide foreign_key in case Rails association is not able to pick up the key itself.
Each association must have a unique name - otherwise the later definition will just overwrite the former.
Here the third line overwrites the first line:
has_many :flag_instances, as: :flaggable, dependent: :destroy
has_many :flags, through: :flag_instances
has_many :flag_instances, as: :notifiable, dependent: :destroy
To reference the correct associations we would need to setup the user model as so:
class User < ApplicationRecord
has_many :flag_instances_as_flaggable,
as: :flaggable
class_name: 'FlagInstance'
has_many :flags_as_flaggable,
through: :flag_instances_as_flaggable,
source: :flag
has_many :flag_instances_as_notifiable,
as: :notifiable
class_name: 'FlagInstance'
has_many :flags_as_notifiable,
through: :flag_instances_as_notifiable,
source: :flag
end
In your case you might want to use concerns to keep it dry:
module Flaggable
extend ActiveSupport::Concern
included do
has_many :flag_instances_as_flaggable,
as: :flaggable,
class_name: 'FlagInstance'
has_many :flags_as_flaggable,
through: :flag_instances_as_flaggable,
source: :flag
end
end
module Notifiable
extend ActiveSupport::Concern
included do
has_many :flag_instances_as_notifiable,
as: :notifiable,
class_name: 'FlagInstance'
has_many :flags_as_notifiable,
through: :flag_instances_as_notifiable,
source: :flag
end
end
class User < ApplicationRecord
include Flaggable
include Notifiable
end

Association from promotion rule to product not working

I'm trying to access products through promotion but can't.
In the command line: Promotion.last.promotion_rules.first.products
Returns an error of an uninitialized constant.
Here are my associations:
class Product
has_many :product_promotion_rules, class_name: 'ProductPromotionRule'
has_many :promotion_rules, through: :product_promotion_rules
end
class ProductPromotionRule
belongs_to :product
belongs_to :promotion_rule
end
class PromotionRule
has_many :product_promotion_rules, class_name: 'ProductPromotionRule', join_table: 'products_promotion_rules', foreign_key: :promotion_rule_id
has_many :products, through: :product_promotion_rules
belongs_to :promotion
end
class Promotion
has_many :promotion_rules
end
Within the Product model try:
has_many :product_promotion_rules, class_name: 'ProductPromotionRule', foreign_key: :product_id
has_many :promotion_rules, through: :product_promotion_rules, source: :promotion_rule
And within the PromotionRule model:
has_many :products, through: :product_promotion_rules, source: :product
Update: saw this, which you may want to remove/fix:
join_table: products_promotion_rules
Either change to:
join_table: product_promotion_rules
Or try removing it.

has_many (different objects) as: one type of object

I need a way of referrencing 2 different objects as 1.
I have a Message object with needs to keep track of Recipients. the problem is that Recipients could be a User or a Contact.
should the models be: ?
class Message < ActiveRecord::Base
has_many :users, as: :recipients
has_many :contacts, as: :recipients
end
class User < ActiveRecord::Base
belongs_to :recipient, polymorphic: true
end
class User < ActiveRecord::Base
belongs_to :recipient, polymorphic: true
end
because, I feel like polymorphic relationships are built to go the opposite way.
also, this way doesn't allow me to reference #message.recipients which is what I need.
I hope this makes sense
Thank you
What you have done is completely incorrect. I think you need many-to-many association. My association whould be that:
class Message < ActiveRecord::Base
has_many :recipient_links
has_many :users, through: :recipient_links, source: :recipient, source_type: 'User'
has_many :contacts, through: :recipient_links, source: :recipient, source_type: 'Contact'
end
class Contact < ActiveRecord::Base
has_many :recipient_links, as: :recipient
has_many :messages, through: :recipient_links
end
class User < ActiveRecord::Base
has_many :recipient_links, as: :recipient
has_many :messages, through: :recipient_links
end
# fields: message_id, recipient_id, recipient_type
class RecipientLink < ActiveRecord::Base
belongs_to :recipient, polymorphic: true
belongs_to :message
end
... I can't add comments yet so i give here solution for: When use answer from up to receive all #message.recipients every type in only 2 request:
RecipientLink.includes(:recipient).where(message_id: #message.id).collect(&:recipient)

Cannot have a has_one :through association ' where the :through association is a collection

I have the following associations. PropertyOwner is a join model which belongs to a property and polymorphically belongs to an owner, which in the below example is a ForeclosureDefense. Everything works well, until I had the has_one :main_property. The idea is the ForeclosureDefense model can have many properties, but the last property is the main property:
class ForeclosureDefense < ActiveRecord::Base
has_many :property_owners, as: :owner
has_many :properties, through: :property_owners
has_one :main_property, through: :property_owners, source: :property, order: 'created_at desc'
end
class PropertyOwner < ActiveRecord::Base
belongs_to :property
belongs_to :owner, polymorphic: :true
end
class Property < ActiveRecord::Base
has_many :property_owners
has_many :owners, through: :property_owners
has_many :foreclosure_owners, through: :property_owners, source: :owner, source_type: "ForeclosureDefense"
has_many :folder_owners, through: :property_owners, source: :owner, source_type: "Folder"
end
Unfortunately, when I try to use that has_one :main_property association, I get the following error:
ActiveRecord::HasOneThroughCantAssociateThroughCollection: Cannot have a has_one :through association 'ForeclosureDefense#main_property' where the :through association 'ForeclosureDefense#property_owners' is a collection.
What am I doing wrong?
My solution was just to add it as a class-level macro:
def main_property
properties.order('created_at desc').first
end

Get attribute value from the join in a many-to-many relationship

I have a many-to-many relation between User and "Link".
The join model is called LinkAddress and besides for saving the IDs of the other two models, it has an attribute called address - information it collects at creation.
How can I access the address attribute for a certain link in a request scenario like the following: User.first.links.first.address ?
Models:
class User < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :links, through: :link_addresses
accepts_nested_attributes_for :link_addresses, allow_destroy: true
end
class LinkAddress < ActiveRecord::Base
belongs_to :user
belongs_to :link
end
class Link < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :users, through: :link_addresses
end
You could access it through User since it's a has_many ... :through relation:
User.first.link_addresses.first.address
Or, if you'd like to go through links then:
User.first.links.first.link_addresses.first.address
SQL Aliases
I had this exact question: Rails Scoping For has_many :through To Access Extra Data
Here's the answer I got:
#Images
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy
This uses SQL Aliases which I found at this RailsCast (at around 6:40 in). It allows us to call #user.image.caption (even though .caption is in the join model)
Your Code
For your query, I'd use this:
class User < ActiveRecord::Base
has_many :link_addresses, dependent: :destroy
has_many :links, -> { select("#{Link.table_name}.*, #{LinkAddress.table_name}.address AS address") }, through: :link_addresses
accepts_nested_attributes_for :link_addresses, allow_destroy: true
end
This will allow you to write #user.links.first.address, and gracefully handles an absence of the address record

Resources