how to save data to two models from one form
I have a scenario where I would like to save data to two models from one form.
Basically I have a player
which belongs to many teams
. So in the new
action of players_controller
I'd like to have a multiple select box that contains all the teams. then the user can select 2 or 3 of them..click save and they will be saved.
player
belonging to many teams
is done by a table called playerizations
it contains a player_id
and team_id
columns
so if I want to get all the teams a player belongs to. I just say player.teams
all this relationship is working fine. I would just like to know how to save to playerizations
table when new player
is created
What I have (it is basically scaffolding model):
def new
@player = Player.new
@teams = Team.all
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @p开发者_如何学JAVAlayer }
end
view:
<% form_for(@player) do |f| %>
<%= f.error_messages %>
...
....
All Teams<br/>
<%= select_tag 'selected_teams[]', options_for_select(@teams.map {|t| [t.team_name, t.id]}), :multiple => true%>
end
Can I get some hints please? I took a look at railscast regarding this but not much help.
You're describing a has-and-belongs-to-many ("HABTM") relationship but you have not defined it according to Rails convention, so Rails isn't sure how it should update your models.
Player model should say:
has_and_belongs_to_many :teams
Team model should say:
has_and_belongs_to_many :players
This has the happy side effect that not only does "player.teams" give a list of a player's associated teams, but also "team.players" gives the list of the players in a given team.
Your join table must be called "players_teams", because the Rails convention is to use the name of the two models in plural form and joined together in ascending alphabetical order. Renaming your "playerizations" table should be sufficient since it sounds like the table columns are correct.
Your select menu code is almost there; you need something like:
select_tag(
:player_team_ids,
options_for_select( @teams.map { | t | [ t.team_name, t.id ] } ),
{
:multiple => true,
:name => 'player[team_ids][]'
}
)
It's the "name" assignment that contains the 'magic' to get your team IDs array assigned. The first parameter to "select_tag" is just the form field's name of "player[team_ids][]" with the square brackets turned into underscores or stripped off if at the end of the string, thus generating a recognisable and unique ID for use in the output HTML.
You can then save your player model or update its attributes with standard calls to save() or update_attributes() - no need for additional code per se however Rails falters on validations. If you are editing an existing player's details, then a call to "update_attributes" will result in the teams association being updated first. Then the player is updated; if its validations fail, the team changes will have been saved anyway. It's quite simple to patch around; wrap your call to update_attributes() in a transaction and roll back if update_attributes returns 'false' indicating failure.
success = Player.transaction do
player.update_attributes( params[ :player ] ) ) or raise ActiveRecord::Rollback
end
The value of 'success' will end up being 'true' for success or 'nil' for failure. This works because the Rollback exception is caught by the transaction block and does not propagate. Setting 'success' to the evaluated result of the block rather than trying to use local variables means that the code is both Ruby 1.8 and Ruby 1.9-friendly.
This is only necessary when updating existing records with HABTM relationships. It is not required when creating new records.
All of the above code is untested and may contain typing errors, so please use with due care and attention.
For more on HABTM:
- http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
For more on transactions:
- http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
精彩评论