1

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.

railsnoob
  • 51
  • 5

1 Answers1

1

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.

Alex
  • 16,409
  • 6
  • 40
  • 56
  • Thank you so much! I stepped away for a bit and was so happy to return to an answer. This is going to help me continue to progress with my website and i learned something new. I want to upvote it, but i still need a little more reputation. – railsnoob Apr 12 '22 at 00:56
  • so I have it working now with your code...however im struggling with the edit functionality. I pretty much just need 1 edit button for the array of member attendance for that tournament, however...doing it how I thought made sense gives me an error deep in the rails code that i dont understand. – railsnoob Apr 12 '22 at 02:09
  • What do I need to add to the controller in edit or update to get the update functionality to work? – railsnoob Nov 22 '22 at 00:56
  • @railsnoob ok, I've updated the answer, hope that'll point you in the right direction. – Alex Nov 22 '22 at 04:52