1

I got some problems with saving records from a has_many :through association. I must have missed something important.

First things first:

  • Rails version: 5.1.4
  • Ruby version: 2.4.2

I got these three models:

class Event < ApplicationRecord
  has_many :events_timeslots
  has_many :timeslots, through: :events_timeslots
end

class Timeslot < ApplicationRecord
  has_many :events_timeslots
  has_many :events, through: :events_timeslots
end

class EventsTimeslot < ApplicationRecord
  belongs_to :event
  belongs_to :timeslot
end

According to this, every event has many timeslots and every timeslot has many events.

I want a multi select in my view:

<%= form_with(model: event, local: true) do |form| %>
  ...
  <% fields_for :events_timeslots do |events_timeslots| %>
    <%= events_timeslots.label :timeslots %>
    <%= events_timeslots.select(:timeslots, @timeslots.collect {|t| [t.name, t.id]}, {}, {multiple: true}) %>
  <% end %>
  ...
<% end %>

Is this the right approach to select multiple timeslots while creating a new event? The timeslots already exist at this time but when the event gets saved, it should also create the associated records in the events_timeslots table.

I also permit the timeslots attribute in strong parameters:

params.require(:event).permit(:date, timeslots: [])

Is there a magic Rails-Way to use the "scaffolded" controller actions to create the new event as well as the associated records in EventsTimeslot model? Related to this question I found an answer on another question, however I wasn't able to get it to work!

Maybe I missed a very stupid little thing, but anyways thanks for the help.


Edit

The (messed up) events_timeslots table in schema.rb:

create_table "events_timeslots", force: :cascade do |t|
  t.bigint "events_id"
  t.bigint "timeslots_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["events_id"], name: "index_events_timeslots_on_events_id"
  t.index ["timeslots_id"], name: "index_events_timeslots_on_timeslots_id"   
end
mabu
  • 286
  • 8
  • 26

3 Answers3

3

Assuming your foreign keys are the standard: event_id and timeslot_id...

Then perhaps try swapping timeslots with timeslot_ids in the permit method:

params.require(:event).permit(:date, timeslot_ids: [])

Then, rather than setting the attributes of the join table in a nested form, just update the timeslot_ids on the @event:

<%= form_with(model: event, local: true) do |form| %>

    <%= form.select(:timeslot_ids, Timeslot.all.collect {|t| [t.name, t.id]}, {}, {multiple: true}) %>

<% end %>
mwilshire
  • 96
  • 4
  • 1
    "Assuming your foreign keys are the standard" - turned out they weren't! I named them `events_id` and `timeslots_id` by mistake. Now it's perfectly working with `timeslot_ids` and the adjusted form. Thank you for the important hint to check the foreign key names! :) – mabu Nov 07 '17 at 08:34
2

fields_for is for when you are creating nested records with accepts_nested_attributes.

Its not needed when you are simply associating items:

<%= form_with(model: event, local: true) do |form| %>
  <%= f.collection_select(:timeslot_ids, Timeslot.all, :id, :name, multiple: true) %>
<% end %>

ActiveRecord creates a _ids setter method for has_many associations that takes an array of ids. This works hand in hand with the form helpers.

To whitelist an array param you need to pass it as a keyword to permit:

params.require(:event).permit(:foo, :bar, timeslot_ids: [])

Using [] permits any scalar value.

max
  • 96,212
  • 14
  • 104
  • 165
0

I think you’re looking for “autosave”, check here http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html