Reputation: 225
From my understanding of Ruby on Rails and ActiveRecord, I am able to use the ActiveRecord model itself instead of its ID when a parameter is looking for an ID. For example, if I have a Foo
model that belongs_to
a Bar
model, then I could write bar = Bar.new(foo_id: foo)
instead of bar = Bar.new(foo_id: foo.id)
. However, in the model I am making now (for a Go game application), this does not seem to be the case.
Here is the relevant code from the model:
class User < ActiveRecord::Base
.
.
.
has_many :participations, dependent: :destroy
has_many :games, through: :participations
end
class Game < ActiveRecord::Base
attr_accessible :height, :width
has_many :participations, dependent: :destroy
has_many :users, through: :participations
def black_player
self.users.joins(:participations).where("participations.color = ?", false).first
end
def white_player
self.users.joins(:participations).where("participations.color = ?", true).first
end
end
class Participation < ActiveRecord::Base
attr_accessible :game_id, :user_id, :color
belongs_to :game
belongs_to :user
validates_uniqueness_of :color, scope: :game_id
validates_uniqueness_of :user_id, scope: :game_id
end
(color is a boolean where false=black, true=white)
If I have created two Users
, black_player
(id=1) and white_player
(id=2), and a Game
game
, I can do this:
game.participations.create(user_id: black_player, color: false)
And game.participations
and black_player.participations
both show this new Participation
:
=> #<Participation id: 1, game_id: 1, user_id: 1, color: false, created_at: "2012-10-10 20:07:23", updated_at: "2012-10-10 20:07:23">
However, if I then try:
game.participations.create(user_id: white_player, color: true)
then the new Participation
has a user_id
of 1 (black_player's id). As I validate against duplicate players in the same game, this is not a valid Participation
and is not added to the database:
=> #<Participation id: nil, game_id: 1, user_id: 1, color: true, created_at: nil, updated_at: nil>
However, if I do:
game.participations.create(user_id: white_player.id, color: true)
Then it does work:
=> #<Participation id: 2, game_id: 1, user_id: 2, color: true, created_at: "2012-10-10 20:34:03", updated_at: "2012-10-10 20:34:03">
What is the cause of this behavior?
Upvotes: 1
Views: 1431
Reputation: 336
ActiveRecord will handle the association for you. However, you have 2 problems that I can from the code above. First, You are trying to assigning the object 'user' to the attribute 'user_id'. Second, that 'user' is not available for mass_assignment on instances of the Participation class. Assigning attributes to an object by passing a hash into the call to create, is known as "mass_assignment" and can be used by hackers to assign attributes that you might not want them too. For that reason, rails provides the "attr_accessible" method. You must explicitly declare the attributes that your users are allowed to set during 'mass_assignment' by passing them as symbols into the 'attr_accessible' method in your class definition.
attr_accessible :game, :user, :color
Now you can
game.participations.create(:user => my_player_object)
note that if you still want to assign the "Id" in some situations, than you have to pass that into attr_accessible as well
attr_accessible :game, :user, :color, :game_id, :user_id
Upvotes: 0
Reputation: 2929
It seems that any parent object passed directly to :belongs_to child object gets converted into boolean and thus results in id being 1. One of the solutions would be to initiate the object first, and then set parent objects directly before saving it:
@participation = Participation.new
@participation.game = @game
@participation.user = @user
@participation.color = false
@participation.save
Other would be to pass IDs instead of objects, just the way you are doing it now.
EDIT: Below is not the case, leaving for info:
Try using user
instead of user_id
:
game.participations.create(user: white_player, color: true)
I think what might be happening is that it is trying to come up with an integer from your user object, because you are explicitly specifying column name instead of a relation. "#<User:0x107f8a2f2584e0>"
then becomes 1 or whatever first valid digits it finds in the string, you can try it with User.find(2).to_s.to_i
it should return 1 in your case.
Upvotes: 2