Daniel Andersen
Daniel Andersen

Reputation: 225

Why do I need to specify the id instead of just the ActiveRecord object as a parameter in this case?

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

Answers (2)

OutlawAndy
OutlawAndy

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

iouri
iouri

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

Related Questions