I'm trying to make an attendance sheet for my fishing club meetings which shows all the active members and allows you to put a checkbox next to their name, to record if they attended the meeting for a specific tournament. I created a "Meeting" scaffold and within the _form, I list out all the active members and allow the user to put a checkbox if the member attended the meeting for the selected tournament. However, I am having issues passing an array of hashes to my meetings_controller, and am quite confused.
I read a bunch of articles, but baselined my design off of this one: Submit array of hashes with rails
The article does not show what is in the create method, so I have this...
meetings_controller:
def create
# puts " OUTPUT TEXT: #{params} "
@meeting = params[:meetings][:meetings]
@meeting.each do |m|
#If there is no attendance key, its nil. Make it false
# if !m[:meeting].has_key?("attendance")
# m[:meeting].attendance = false
# end
puts "OUTPUT TEXT: #{m[:meeting]}" # OUTPUT TEXT: {"member_id"=>"1", "tournament_id"=>"2", "attendance"=>"1"}
@meeting = Meeting.new(member_id: m[:meeting][:member_id], tournament_id: m[:meeting][:tournament_id], attendance: m[:meeting][:attendance])
end
respond_to do |format|
if @meeting.save
format.html { redirect_to @meeting, notice: "Meeting was successfully created." }
format.json { render :show, status: :created, location: @meeting }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @meeting.errors, status: :unprocessable_entity }
end
end
end
_form inputs: (based on article linked above)
<% Member.where(active: true).each do |member| %>
<tr>
<td> <%= member.full_name %> </td>
<input multiple="multiple" value=<%=member.id %> type="hidden" name="meetings[meetings][]meeting[member_id]" />
<input multiple="multiple" value=<%=@tournament.id %> type="hidden" name="meetings[meetings][]meeting[tournament_id]" />
<td><input type="checkbox" value="1" name="meetings[meetings][]meeting[attendance]" /></td>
</tr>
<% end %>
When I click to submit the form its just taking me to the show page where only this is shown on a blank page...
{"controller"=>"meetings", "action"=>"show", "id"=>"18"}
Even when I have a redirect line in the show method
def show
redirect_to meetings_path
end
I've spent a lot of time reading, and doing trial and error attempts to get this to work. I am hoping the stackoverflow gods can help.
In the controller when you loop through meeting params Meeting
doesn't save to database and @meeting
variable gets overwritten until the last item in the array...
@meeting.each do |m|
@meeting = Meeting.new(member_id: m[:meeting][:member_id], tournament_id: m[:meeting][:tournament_id], attendance: m[:meeting][:attendance])
end
...and that's the one being saved.
if @meeting.save
Also not sure what's going on with your show
action, just don't redirect to it after save.
Working with arrays in rails forms is a bit iffy. But here it is.
Controller:
# params - submitted params from the form
# { "authenticity_token"=>"[FILTERED]",
# "meetings"=>[
# { "member_id"=>"1", "tournament_id"=>"1", attendance"=>"1" },
# ...
# ],
# "commit"=>"Save"
# }
# POST /meetings
def create
@meetings = meetings_params.map do |param|
Meeting.create_with(attendance: param[:attendance])
.find_or_create_by(member_id: param[:member_id], tournament_id: param[:tournament_id])
end
respond_to do |format|
if @meetings.detect{|m| m.id == nil } # id=nil if any meetings didn't save
# FIXME: @tournament not initialized here
format.html { render :new, status: :unprocessable_entity }
else
format.html { redirect_to meetings_url, notice: "Meetings were created." }
end
end
end
# Returns array
# [{member_id: 1, tournament_id: 1, attendance: 1}, ...]
def meetings_params
params.permit(meetings: [:member_id, :tournament_id, :attendance]).require(:meetings)
end
Form:
<%= form_with url: "/meetings" do |f| %>
<% Member.where(active: true).each do |member| %>
<%# TODO: if meeting doesn't save, there is no error to show %>
<%= text_field_tag "meetings[][member_id]", member.id %>
<%= text_field_tag "meetings[][tournament_id]", @tournament.id %>
<%= check_box_tag "meetings[][attendance]", Meeting.find_by(member: member, tournament: @tournament)&.attendance %>
<% end %>
<%= f.submit %>
<% end %>
Or using fields_for
helper:
<%= form_with url: "/meetings" do |f| %>
<% Member.where(active: true).each do |member| %>
<%# this nil does the [] thing %>
<%# | %>
<%= fields_for "meetings", nil, index: nil do |ff| %>
<%= ff.number_field "member_id", value: member.id %>
<%= ff.hidden_field "tournament_id", value: @tournament.id %>
<%= ff.check_box "attendance", value: Meeting.find_by(member: member, tournament: @tournament)&.attendance %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
Update
All of the above is really against the rails grain so to speak. Here is what I've come up with to make it super simple:
We need an Attendance
table to keep track of each member's attendance for each Meeting
:
# db/migrate/20221122034503_create_attendances.rb
class CreateAttendances < ActiveRecord::Migration[7.0]
def change
create_table :attendances do |t|
t.boolean :attended
t.references :meeting, null: false, foreign_key: true
t.references :member, null: false, foreign_key: true
end
end
end
# app/models/attendance.rb
class Attendance < ApplicationRecord
belongs_to :meeting
belongs_to :member
end
# app/models/meeting.rb
class Meeting < ApplicationRecord
has_many :attendances
# NOTE: let rails take care of nested attendances from the form
accepts_nested_attributes_for :attendances
end
MeetingsController
is default scaffold:
bin/rails g scaffold_controller meeting
just update new
action:
# app/controllers/meetings_controller.rb
def new
@meeting = Meeting.new
# NOTE: build a list of attendances for the form to render
Member.where(active: true).each do |member|
@meeting.attendances.build(member: member)
end
end
The form is super simple now:
<!-- app/views/meetings/_form.html.erb -->
<%= form_with model: @meeting do |f| %>
<%= f.fields_for :attendances do |ff| %>
<%= ff.number_field :member_id %>
<%= ff.check_box :attended %>
<% end %>
<%= f.submit %>
<% end %>
create
and update
work all by themselves.