So I'm connecting to a legacy database. I have two tables, Sites and States.
A Site has one State and a State can belong to many Sites
# Sites.rb
has_one :state, :primary_key => "StateKey", :foreign_key => "StateKey"
# States.rb
belongs_to :sites, :class_name => "Sites", :primary_key => "SiteKey", :foreign_key => "SiteKey"
As you can see I have to manually set the foreign keys and primary keys.
So this works:
Sites.first.state # one record returned (the state)
This does not:
States.first.sites # nil returned. Doesn't even appear to hit AR
What am I doing wrong?
Thanks.
You should use the pair has_many, belongs_to:
# Sites.rb
belongs_to :state, :primary_key => "StateKey", :foreign_key => "StateKey"
# States.rb
has_many :sites, :class_name => "Sites", :primary_key => "StateKey", :foreign_key => "StateKey"
Take a look at this guide.
When you have a one-to-many association it is standard practice to use belongs_to and has_many in the two model classes. has_one is a special case of has_many. belongs_to says that the foreign key is in the model declaring the association, and has_one, has_many say the foreign key is in the other model.
Related
In my model an Item is created by a User and can be purchased by many Users, and a User can purchase many Items.
User, Item, and Purchase are defined, using AcvtiveRecord with superfluous details snipped for brevity as follows:
class User < ActiveRecord::Base
# various other fields
has_many :items, :foreign_key => :creator_id
has_many :purchased_items, :through => :purchases, :source => :item
end
class Item < ActiveRecord::Base
# various other fields
belongs_to :creator, :class_name => 'User'
has_many :buyers, :through => :purchases, :source => :user
end
class Purchase < ActiveRecord::Base
belongs_to :item
belongs_to :user
# various other fields
end
and an rspec test also snipped as follows:
describe "user purchasing" do
it "should allow a user to purchase an item" do
a_purchase = Purchase.create!(:item => #item, # set up in `before :each`
:user => #user # set up in `before :each`
)
a_purchase.should_not eq(nil) # passes
#item.buyers.should include #user # fails
#user.purchased_items.should include #item # fails
end
end
This results in
1) Purchase user purchasing should allow a user to purchase an item
Failure/Error: #item.buyers.should include #user
ActiveRecord::HasManyThroughAssociationNotFoundError:
Could not find the association :purchases in model Item
Likewise if I swap around #file_item.buyers.should include #user and #user.purchased_items.should include #item I get the equivalent
1) Purchase user purchasing should allow a user to purchase an item
Failure/Error: #user.purchased_items.should include #item
ActiveRecord::HasManyThroughAssociationNotFoundError:
Could not find the association :purchases in model User
My migration looks like
create_table :users do |t|
# various fields
end
create_table :items do |t|
t.integer :creator_id # file belongs_to creator, user has_many items
# various fields
end
create_table :purchases do |t|
t.integer :user_id
t.integer :item_id
# various fields
end
What have I done wrong?
You have to specify the following.
class User < ActiveRecord::Base
has_many :purchases
has_many :items, :foreign_key => :creator_id
has_many :purchased_items, :through => :purchases, :source => :item
end
class Item < ActiveRecord::Base
# various other fields
has_many :purchases
belongs_to :creator, :class_name => 'User'
has_many :buyers, :through => :purchases, :source => :user
end
Only when you specify
has_many :purchases
the model will be able to identify the association.
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.
Using Ruby 1.9.3 and ActiveRecord 3.2.6.
I'm having an issue when trying to compare an ActiveRecord Object that has attr_accessible :property set on it, that is contained in an Array of associated Objects using include?(object).
These are my 2 ActiveRecord models, Account and Role.
Account:
class Account < ActiveRecord::Base
# Associations
#
has_many :role_assignments, :dependent => :destroy
has_many :roles, :through => :role_assignments
end
Role:
class Role < ActiveRecord::Base
attr_accessible :title
# Associations
#
has_many :role_assignments, :dependent => :destroy
has_many :accounts, :through => :role_assignments
end
If I then create a couple of Roles (say "Admin" and "Editor") and assign the "Admin" one to an Account, I would assume this would work:
role = Role.find_by_title("Admin")
account = Account.first # => The Account we assigned the "Admin" role to
account.roles.include?(role) # => Should be true but returns false
But this actually returns false!
If I remove the 'attr_accessible :title' from the Role model and repeat the above then it does return true.
So I guess my question is... why would attr_accessible cause this particular issue? or is it a case that I have to do my check to see if role does exist in account.roles a different way?
You could try that
account.role_ids.include?(role.id)
I have two models, Story and Chapter. A story has_many chapters, one of those is a chapter which serves as its first chapter. I used to have a foreign key start_id in the stories table to indicate which chapter is the first. Hovewer, the the database schema had to be changed a little, now every chapter has a code. If the code is '1a', then that is first chapter of the story which owns the chapter.
The following seems to work, including #create_start:
has_many :chapters, :dependent => :destroy, :inverse_of => :story
has_one :start, :class_name => 'Chapter', :foreign_key => 'story_id', :conditions => {:code => '1a'}
This way, the foreign key start_id of the stories table is unneeded, and #start still remains an association, with all the benefits (I need #start as an association, because I use CanCan with associations for authorization).
Does my approach has any drawbacks that I currently fail to realize, or I am relatively safe with it?
Relying on the code == '1a' to find the first chapter is a bit wonky. I'd probably add a flag to the chapters table like 'first_chapter' or something that was true or false indicating whether it was the first chapter. This way the first_chapter-ness of a chapter would survive a change to the code field, but this is a bit nitpicky.
Also, to avoid repeating yourself, you could change the declaration of has_one :start to something like:
has_one :start, :through => :chapters, :conditions => { :code => '1a' }
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.