ActiveRecord::HasManyThroughAssociationPolymorphicSourceError - ruby

I need a player to have many structures and the structure to belong to the player. Structure is a polymorphic relationship.
class Player < ActiveRecord::Base
has_many :player_structures
has_many :structures, :through => player_structures
end
class PlayerStructures < ActiveRecord::Base
belongs_to :structure, polymorphic: true
belongs_to :player
end
class StructureA < ActiveRecord::Base
has_one :player_structure, :as => :structure
has_one :player, :through => :player_structure
end
class StructureB < ActiveRecord::Base
has_one :player_structure, :as => :structure
has_one :player, :through => :player_structure
end
But if I pull out Player.first and ask for its structures, it gives:
ActiveRecord::HasManyThroughAssociationPolymorphicSourceError: Cannot have a has_many :through association 'Player#structures' on the polymorphic object 'Structure#structure'.
But it should be able to generate a SQL query where it finds all player_structures with its id, then fetches the structure based on the structure_id and structure_type. Why does this fail and how can I validly construct a polymorphic join table?
UPDATE
If I do what I want it to do manually, it works:
player_structures.collect(&:structure)
Rails, y u no do that?

I think you need to be more specific in defining your relationships in your Player model. For example:
class Player < ActiveRecord::Base
has_many :player_structures
has_many :structureas, :through => player_structures, :source => :structure, :source_type => 'StructureA'
has_many :structurebs, :through => player_structures, :source => :structure, :source_type => 'StructureB'
end
Then you can make a method that'll return all the structures defined in the relationships instead of having to access each one individually.

Related

database schema for like entities that can be combined pairwise (ActiveRecord)

I am designing a database of woodwind instrument sounds, and would like to create a table that joins pairs of sounds that a performer can combine into, for example, a trill. Such relations are transitive: if sound A has a 'Sound Relation' with sound B, then sound B has that same 'Sound Relation' with sound A.
I am familiar with join tables, but I've never seen them used to join 'like' objects, only to join 'unlike' objects, such as tags and posts, so I'm wary of going that direction.
I realize the example below looks extremely dubious, but it gives an idea of what I'm after. What is a better way of doing it? (Using ActiveRecord syntax)
Models
class Sound < ActiveRecord::Base
has_many :linked_sounds, through: :sound_relations, class_name: "Sound", foreign_key: ???
end
class Sound_Relation < ActiveRecord::Base
has_many :sounds
end
Migration
class CreateSoundRelations < ActiveRecord::Migration
def change
create_table :sound_relations do |t|
t.integer first_sound_id # This cannot possibly be right.
t.integer second_sound_id # Surely the transitivity of the
# relationship should be more evident?
end
end
end
You might try something like:
class Set < ActiveRecord::Base
has_many :sound_sets
has_many :sounds, :through => :sound_sets
end
class SoundSet < ActiveRecord::Base
belongs_to :sound
belongs_to :set
end
class Sound < ActiveRecord::Base
has_many :sound_sets
has_many :sets , :through => :sound_sets
has_many :set_sound_sets, :through => :sets , :source => :sound_sets
has_many :set_sounds , :through => :set_sound_sets, :source => :sound
end
So, no more "sound_1" and "sound_2" ... they are both just sounds. For every sound you can also use the set_sounds method to retrieve all of the sounds associated with it.
This would also allow more than two sounds in a relation, and you might like to put a "type" on the sets model.
Edit: If you look at the query generated, you'll find that sound_sets is mentioned in there twice, once with a different alias. The key to eliminating "self" joins is to include a clause in the association along the lines of:
has_many :set_sounds ,
{where("sound_sets.sound_id != set_sound_sets_sound_set_sounds.sound_id")},
:through => :set_sound_sets,
:source => :sound
... where "sound_set_sounds" is the table alias. If you can post the query in the comments I can update this with the actual alias.

Trouble with cascading in Active Record models

I have these active record models in my application
class Artist < ActiveRecord::Base
has_many :albums, :dependent => :delete_all, autosave: true
end
class Album < ActiveRecord::Base
attr_accessor :album_artist
has_many :tracks, :dependent => :delete_all, autosave: true
has_many :covers, :dependent => :delete_all, autosave: true
belongs_to :artist
end
class Track < ActiveRecord::Base
belongs_to :album
end
class Cover < ActiveRecord::Base
belongs_to :album
end
I'm trying, in my application, when i delete an Artist, his albums and, in consequence, the tracks and covers of his albums, get all deleted, in a cascade reaction.
The way it's implemented today, when i delete the Artist, only the Album is deleted too, leaving orphan records in my database.
Am i doin' anything wrong?
Instead of :dependent => :delete_all you need to configure:
:dependent => :destroy_all
Because delete will just delete all its associated objects directly from the database without calling their destroy method (what break cascading).
You might want to read 4.1.2.4 in http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference

rails belongs_to through via association

I'm on rails 3.0 and trying to figure out what would be the proper way to setup a belong_to :through relationship (which) I know is not possible. Here's an example:
class ParentCompany < ActiveRecord::Base
has_many :subsidiaries
has_many :employees, :through => :subsidiaries
end
class Subsidiary < ActiveRecord::Base
belongs_to :parent_company
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :subsidiary
belongs_to :parent_company, :through :subsidiary # <-- I know this is invalid
end
I know I can solve it by doing:
class Employee < ActiveRecord::Base
def parent_company
subsidiary.parent_company
end
end
However, I'd like to know if I can do the above via associations.
You can use delegate to accomplish this without using an association
class Employee < ActiveRecord::Base
belongs_to :subsidiary
delegate :parent_company, to: :subsidiary
end

Rails 3.1 has_many, :through => not working (joined model returns nil)

Update: This was all due to a stupid error: previously, I had defined a method with the same name as one of the methods ActiveRecord creates, which was masking the proper behaviour and breaking everything. I can't answer/close the question for a few more hours, apologies to anyone who looked into this!
I have an infuriating problem with a has_many, :through => relationship in my Rails 3.1 app.
It is infuriating because as far as I can see it is identical to two similar relationships which both work.
The owner of these relationships declares them like this:
has_many :user_skills, :dependent => :destroy
has_many :skills, :through => :user_skills
has_many :user_roles, :dependent => :destroy
has_many :roles, :through => :user_roles
has_many :conversation_users
has_many :conversations, :through => :conversation_users
(I am aware I have not followed standard nomenclature for join tables here - I only read about the convention of both-plural, names-alphabetical after setting this up, and I will refactor later)
The first two pairs of relationships (skills and roles) work just fine.
The final relationship (conversations) does not work fully. user.conversation_users returns the expected array, but user.conversations returns nil. Not an empty array, nil.
I may well be doing something stupid here, so I would be very grateful to anyone who can spot something wrong with the ConversationUser or Conversation models below.
conversation_user.rb
class ConversationUser < ActiveRecord::Base
belongs_to :user, :inverse_of => :conversation_users
belongs_to :conversation, :inverse_of => :conversation_users
validates_presence_of :user
validates_presence_of :conversation
end
conversation.rb
class Conversation < ActiveRecord::Base
has_many :messages, :dependent => :destroy
has_many :conversation_users, :dependent => :destroy
has_many :users, :through => :conversation_users
validates_presence_of :unique_id
end
(I am also aware that these are not really complex enough to justify has_many, :through => over has_and_belongs_to_many, but planned additional functionality will require join models.)
Answering to close question:
This was all due to a stupid error: previously, I had defined a method with the same name as one of the methods ActiveRecord creates, which was masking the proper behaviour and breaking everything.

ActiveRecord: Has many through (twice)

There are Things in Places which I'm looking to find. One Thing could be in many different Places, and many Things can be in one Place.
class Thing < ActiveRecord::Base
has_and_belongs_to_many :places
end
class Place < ActiveRecord::Base
has_and_belongs_to_many :things
end
I want to record the Finds of my Users so that I know where they found what.
class Find < ActiveRecord::Base
belongs_to :user
belongs_to :places_thing # Is this depluralization correct?
end
class User < ActiveRecord::Base
has_many :finds
# Now, how can I link in the Things the user has found? Like this?
has_many :found_things_in_places, :class_name => :places_things, :through => :finds
has_many :things, :through => :thought_things_in_places
end
Does this seem right? is it efficient? Thanks.
I think you were on the right track, the big change I'd make is that rather than having a join table (places_things) you should make it a proper model. I decided to call this an existence.
The data only exists in one place, so it's properly normalized. These relationships are clear and will be easy to manage. I think it's efficient.
class Place < ActiveRecord::Base
has_many :existences
has_many :things, :through => :existences
end
class Thing < ActiveRecord::Base
has_many :existences
has_many :places, :through => :existences
end
class Existence < ActiveRecord::Base
belongs_to :place
belongs_to :thing
end
class Find < ActiveRecord::Base
belongs_to :user
belongs_to :existence
end
class User < ActiveRecord::Base
has_many :finds
has_many :existences, :through => :finds
has_many :things, :through => :existences
end
You'll need rails 3.1 to do the nested has many through's like we did in User.
BTW the correct association declaration should be: belongs_to :places_things

Resources