Reputation: 213
Using the simple_forms gem in Rails 3.2.2, I have built a form to create objects in a nested model (kudos again to Ryan Bates over at railscasts.com) but I am struggling to master the complexity of a has_many through relationship without adding extra code to the create method of my Games controller.
I am using accepts_nested_attributes_for :participants in my Game model with the hope of being able to mass assign the 2 required participant objects.
I have three classes: a Game, a Team, and a through table Participant
Participant exists so a team can be noted as the home or away team in a game.
Currently, I am using the select_tag in the form to create a couple of psuedo params that I then use to create the participants in the create method in the controller after I have saved the new @game object I am creating a @game.participant for the home team and the away team. This all works but is a bit too heavy lifting and can't be extended if I wanted, for example, to create a tournament of games without writing more controller code.
So now I build 2.times participants in the new method and am trying to get create and update to work without adding extra code and removing the creating @games.participants in the create method.
Ok ... the problems...
builder.input :team_id, :collection => Team.all
This puts the object id in the selector, I want the Team.full_name as is produced in the current select_tag "home", options_from_collection_for_select(Team.all, :id, :full_name) and the id to be pushed into the team_id param. I am struggling to find decent simple_forms documentation and looking through the gem source has given me a headache (I'm not good enough to decode it)
I need to differentiate between the two participants created in the new Game object. The simple_form f.simple_fields_for :participants do |builder| won't allow me to do :participants.first or :participants.last. I want to create a hidden field setting the :home_team to true for the first participant.
If I can get these two pieces working then I think my solution would work. Any help or clues are, as always, hugely appreciated.
Game Model:
class Game < ActiveRecord::Base
belongs_to :venue
belongs_to :game_type
has_and_belongs_to_many :tournaments
has_many :participants, :dependent => :destroy
has_many :teams, :through => :participants
validates :kickoff, :venue_id, :presence => true
accepts_nested_attributes_for :participants,
:reject_if => lambda {|a| a[:game_id].blank? },
:allow_destroy => :true
def home_team
self.teams.joins(:participants).where("participants.home_team = ?", true).first
end
def away_team
self.teams.joins(:participants).where("participants.home_team = ?", false).first
end
end
Participant Model:
class Participant < ActiveRecord::Base
belongs_to :game
belongs_to :team
end
Team Model:
class Team < ActiveRecord::Base
has_many :participants, :dependent => :destroy
has_many :games, :through => :participants
validates :full_name, :presence => true
validates :short_name, :presence => true, :length => { :within => 1..5 }
end
Games Controller (New and Create Methods)
class GamesController < ApplicationController
def new
@game = Game.new
### This section is not currently implemented
# 2.times { @game.participants.build }
### End of unimplemented section
respond_to do |format|
format.html # new.html.erb
end
end
def create
@game = Game.new(params[:game])
respond_to do |format|
if @game.save
@game.participants.create(:team_id => params[:home], :home_team => true)
@game.participants.create(:team_id => params[:away], :home_team => false)
format.html { redirect_to games_path, notice: 'Game was successfully created.' }
else
format.html { render action: "new" }
end
end
end
Games View Form Partial (actually has another partial but included for ease of reading)
<%= simple_form_for(@game) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= label_tag "home", "Home Team"%>
<%= select_tag "home", options_from_collection_for_select(Team.all, :id, :full_name)%>
<!-- This section is not working properly -->
<!-- <%=# f.simple_fields_for :participants do |builder| %> -->
<!-- <%=# builder.input :team_id, :collection => Team.all, :selected => :full_name %> -->
<!-- <%# end %> -->
<!-- End of not working properly section -->
<%= f.input :home_goals, :input_html => {:maxlength => 5, :size => 5 }%>
<%= f.input :away_goals, :input_html => {:maxlength => 5, :size => 5 }%>
<%= select_tag "away", options_from_collection_for_select(Team.all, :id, :full_name)%>
<%= label_tag "away", "Away Team"%>
<%= f.input :kickoff %>
<%= f.input :completed %>
<%= f.input :game_type_id %>
<%= f.input :venue_id ,:collection => Venue.all, prompt: 'Choose the venue'%>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Upvotes: 2
Views: 3846
Reputation: 213
Okay ... in case anyone was interested I solved the two issues with the following:
Used the collect capabilities on the class rather than any simple_form helpers, so the Games View Form Partial had the following changes to the collection line:
<%= builder.input :team_id, :collection => Team.all.collect{ |t| [t.full_name, t.id ]}%>
I used a count comparison to set the veriable noting the value of the hidden field. This is not very object code but means that the code works using simple_form helpers and the controller just uses the .save (in create) and update_attributes (in update) without any further code required. Very nice. Also slightly amended the accepts_nested_attributes_for to look for blank team_id rather than game_id (which would never be blank!). So the code now looks as follows...
Game Model:
class Game < ActiveRecord::Base
belongs_to :venue
belongs_to :game_type
has_and_belongs_to_many :tournaments
has_many :participants, :dependent => :destroy
has_many :teams, :through => :participants
validates :kickoff, :venue_id, :presence => true
accepts_nested_attributes_for :participants,
:reject_if => lambda {|a| a[:team_id].blank? },
:allow_destroy => :true
def home_team
self.teams.joins(:participants).where("participants.home_team = ?", true).first
end
def away_team
self.teams.joins(:participants).where("participants.home_team = ?", false).first
end
end
Games Controller (New and Create Methods)
class GamesController < ApplicationController
def new
@game = Game.new
2.times { @game.participants.build }
respond_to do |format|
format.html # new.html.erb
end
end
def create
@game = Game.new(params[:game])
respond_to do |format|
if @game.save
format.html { redirect_to games_path, notice: 'Game was successfully created.' }
else
format.html { render action: "new" }
end
end
end
end
Games View Form Partial (actually has another partial but included for ease of reading)
<%= simple_form_for(@game) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<% count = 0 %>
<%= f.simple_fields_for :participants do |builder| %>
<% if count == 0 %>
<% value = true %>
<% count += 1 %>
<% else %>
<% value = false%>
<% end %>
<%= builder.input :home_team, :as => :hidden, :input_html => {:value => value} %>
<%= builder.input :team_id, :collection => Team.all.collect{ |t| [t.full_name, t.id ]}%>
<% end %>
<%= f.input :home_goals, :input_html => {:maxlength => 5, :size => 5 }%>
<%= f.input :away_goals, :input_html => {:maxlength => 5, :size => 5 }%>
<%= f.input :kickoff %>
<%= f.input :completed %>
<%= f.input :game_type_id %>
<%= f.input :venue_id ,:collection => Venue.all, prompt: 'Choose the venue'%>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Hope this is of use to someone. I think it's a good solution but would be interested if anyone has suggestions on improving the logic in the View.
Regards
Peter
Upvotes: 3