ActiveRecord has many through with both being polymorphic - ruby-on-rails

I have the association setup like the below, but I'm getting errors/exceptions thrown from Rails telling me that I don't have the associations set up properly.
Here's what I have:
class Case
has_many :case_accesses, as: :policy, inverse_of: :case, dependent: :destroy
has_many :agents, through: :case_accesses, source: :ownable, source_type: 'Agent'
end
class CaseAccess
belongs_to :policy, polymorphic: true
belongs_to :ownable, polymorphic: true
end
class Agent
has_many :case_accesses, as: :ownable, dependent: :destroy
has_many :cases, through: :case_accesses
end
The error from Rails:
Could not find the source association(s) "case" or :cases in model CaseAccess. Try 'has_many :cases, :through => :case_accesses, :source => '. Is it one of policy, connection, or ownable?
I tried setting the source to ownable and it's causing problems in my query. How should I be setting up this association? It's a traditional has many through, except on one side the policy can be either of type Case or Ppae, and the ownable can be of type Agent or User.
Columns for tables:
Case
-id
CaseAccess
-id
-policy_id
-policy_type
-ownable_id
-ownable_type
Agent
-id

A Case has many case_accesses as policy. Is the CaseAccess the policy, or is the Case the policy for the CaseAccess?
I believe you need to change
has_many :case_accesses, as: :policy, inverse_of: :case, dependent: :destroy
to
has_many :case_accesses, inverse_of: :policy, dependent: :destroy

This is what did it:
class Case
has_many :case_accesses, as: :policy, inverse_of: :policy, dependent: :destroy
has_many :agents, through: :case_accesses, source: :ownable, source_type: 'Agent'
has_many :users, through: :case_accesses, source: :ownable, source_type: 'User'
end
class CaseAccess
belongs_to :policy, polymorphic: true
belongs_to :ownable, polymorphic: true
end
class Agent
has_many :case_accesses, as: :ownable, dependent: :destroy
has_many :cases, through: :case_accesses, source: :policy, source_type: 'Case'
end

Related

ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation

I am running a rake task to copy over some live data for demo purposes. Before each re-copy, I want to delete the old records. But when I try to Host.where(organization_id: target_org_id, customer_slug: target_customer_slug).destroy_all, I get the following err:
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: update or delete on table "hosts" violates foreign key constraint "fk_rails_f66d1a8a7f" on table "event_attendees"
Models look something like this:
class Host
has_many :event_attendees, inverse_of: :host, dependent: :destroy
has_many :events, -> { distinct }, through: :event_attendees
end
class Attendee
has_many :event_attendees, inverse_of: :attendee, dependent: :destroy
has_many :events, through: :event_attendees
end
class Event
has_many :event_attendees, inverse_of: :event, dependent: :destroy
has_many :attendees, through: :event_attendees
end
class EventAttendee
belongs_to :event, inverse_of: :event_attendees
belongs_to :attendee, inverse_of: :event_attendees
belongs_to :host, inverse_of: :event_attendees
end
I thought dependent: :destroy should remove the associations?

How do target polymorphic model through many models

I have 4 classes:
class User < ApplicationRecord
has_many :memories
has_many :playlists
has_many :items, as: 'playlist_items', through: :playlists
has_many :items, as: 'memory_items', through: :memories
end
class Item < ApplicationRecord
belongs_to :itemable, polymorphic: true, optional: true
end
class Playlist < ApplicationRecord
belongs_to :user
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, allow_destroy: true
end
class Memory < ApplicationRecord
belongs_to :user
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, allow_destroy: true
end
I would like to be able to get from current_user to the items of either type, i.e. Memory or Playlist. But right now I can only get to 1 set.. if I have this in User:
has_many :items, through: :playlists
I can't currently figure out how to do both in a list. 'As' seems to have no effect. Any suggestions would be very helpful?
You can't have both associations with the same name, you can specialize specifying the source. Try like this:
class User < ApplicationRecord
has_many :memories
has_many :playlists
has_many :playlist_items, through: :playlists, source: :items
has_many :memory_items, through: :memories, source: :items
end
Then of course you use user.playlist_items and user.memory_items instead.

Rails has_one association expected Model got String

Having the following associations between 3 models:
workout.rb
class Workout < ActiveRecord::Base
has_and_belongs_to_many :workout_sets, :join_table => :workout_sessions
belongs_to :warmup, :class_name => :WorkoutStep, :foreign_key => "workout_step_id"
accepts_nested_attributes_for :workout_sets, allow_destroy: true
accepts_nested_attributes_for :warmup, allow_destroy: true
end
workout_set.rb
class WorkoutSet < ActiveRecord::Base
has_and_belongs_to_many :workout_steps, :join_table => :sets_steps, dependent: :destroy
has_and_belongs_to_many :workouts, :join_table => :workout_sessions
accepts_nested_attributes_for :workout_steps, allow_destroy: true
has_one :intro_video_usage, class_name: 'VideoUsage::Intro', as: :parent, dependent: :destroy
has_one :intro_video, through: :intro_video_usage, source: :video
accepts_nested_attributes_for :intro_video
has_one :get_ready_video_usage, class_name: 'VideoUsage::GetReady', as: :parent, dependent: :destroy
has_one :get_ready_video, through: :get_ready_video_usage, source: :video
has_one :congrats_video_usage, class_name: 'VideoUsage::Congratulations', as: :parent, dependent: :destroy
has_one :congrats_video, through: :congrats_video_usage, source: :video
end
and
workout_step.rb
class WorkoutStep < ActiveRecord::Base
has_and_belongs_to_many :workout_sets, :join_table => :sets_steps
has_many :main_video_usage, class_name: 'VideoUsage::Main', as: :parent
has_many :main_videos, through: :main_video_usage, source: :video
accepts_nested_attributes_for :main_videos
end
And using simple_form and cocoon to handle nested models creation on the top level model (Workout) I'm having troubles building the form for sets and steps - more concise, when associating a workout_set with an intro_video (and whitelisting the params) I'm having the following error:
Video(#70285207226600) expected, got String(#70285080848240)
The params object after sending looks like this:
"workout"=>{"title"=>"",
"workout_sets_attributes"=>{"0"=>{"_destroy"=>"false",
"intro_video"=>"70",
"title"=>""}}},
"image"=>"",
"sound_logo"=>"",
"intro_video"=>"",
"commit"=>"Create workout"}
Thanks in advance.
Your parameters are passing a string ("70") to intro_video= but association accessors like that expect you to pass an actual instance of the associated class (in this case Video).
You should instead be assigning to intro_video_id. The accessor will convert the string to an integer for you.

How to use a named_association for has_many_through relationship?

How to write this:
has_many :sales, foreign_key: :buyer_id, dependent: :destroy
has_many :purchased_books, class_name: 'Book', through: :sales, source: :book
as this:
has_many :purchases, class_name: 'Sale', foreign_key: :buyer_id, dependent: :destroy
has_many :purchased_books, class_name: 'Book', through: :sales, source: :book
It gives me the following error at the moment:
Could not find the association :sales in model User (ActiveRecord::HasManyThroughAssociationNotFoundError)
And writing it has_many :sales is grammatically incorrect when foreign_key: :buyer_id.
The :through key must reference an association that is defined.
has_many :purchases, class_name: 'Sale', foreign_key: :buyer_id, dependent: :destroy
has_many :purchased_books, class_name: 'Book', through: :purchases, source: :book

Rails has_many :through --> How do you set up an association to pull from multiple join models?

I'd like to set up multiple has_many :through relationships in parallel. Here are my 2 standard and 2 join models:
User.rb
has_many :ownerships, dependent: :destroy
has_many :devices, through: :ownerships
has_many :bookings, dependent: :destroy
has_many :devices, through: :bookings
Ownership.rb
belongs_to :user, touch: true, counter_cache: :devices_count
belongs_to :device, touch: true
Booking.rb
belongs_to :user, touch: true, counter_cache: :bookings_count
belongs_to :device, touch: true, counter_cache: :bookings_count
Device.rb
has_many :ownerships, dependent: :destroy
has_many :users, through: :ownerships
has_many :bookings, dependent: :destroy
has_many :users, through: :bookings
This current setup is NOT working as expected, there seems to be crosstalk between the join models. I want the join models to be independent and in parallel (i.e. Users can have relationships - Ownerships - with devices independently of being able to book them). I am not looking for a nested has_many :through relation here.
When I change the User Ownerships of a Device that seems to alter the number of Bookings and vice versa... any ideas on how should I be setting this up correctly?
I think the first error you've got is you're calling two associations by the same name (users / devices)
To help any further respondents, the real question is --> how do you set up an association to pull from multiple join models?
Quick Fix
Rails associations are named primarily by their class, but because of conflicts, you should refrain from setting them twice. This is why you're seeing the current issue. A simple resolution will be to call the associations by different names:
User.rb
has_many :ownerships, dependent: :destroy
has_many :owner_devices, through: :ownerships, class_name: "Device", foreign_key: "ownership_id"
has_many :bookings, dependent: :destroy
has_many :booking_devices, through: :ownerships, class_name: "Device", foreign_key: "booking_id"
I am still looking for information on how you could set an association to use two models
This appears be a working solution following Rich Peck's suggestions:
User.rb
has_many :ownerships, dependent: :destroy
has_many :device_ownerships, through: :ownerships, class_name: "Device", foreign_key: "device_id", source: :device
has_many :bookings, dependent: :destroy
has_many :device_bookings, through: :bookings, class_name: "Device", foreign_key: "device_id", source: :device
Booking.rb (Join model)
belongs_to :user, touch: true, counter_cache: :bookings_count
belongs_to :device, touch: true, counter_cache: :bookings_count
Ownership.rb (Join model)
belongs_to :user, touch: true, counter_cache: :devices_count
belongs_to :device, touch: true, counter_cache: :users_count
Device.rb
has_many :ownerships, dependent: :destroy
has_many :user_ownerships, through: :ownerships, class_name: "User", foreign_key: "user_id", source: :user
has_many :bookings, dependent: :destroy
has_many :user_bookings, through: :bookings, class_name: "User", foreign_key: "user_id", source: :user
To be honest, I'm a bit confused over why the foreign_key's need(?) to be set up as they are, so I'll have to do a bit more reading about it. Otherwise it appears to be functional, I don't see crosstalk between these join models anymore.

Resources