3

I've got a standard has_many through relationship. Humans have many Orcs through a join table Interactions. The interactions is just a table and model; no controller or views.

Using the simpleform gem in Rails 4, I want to make a form from the humans page, in order to select multiple orcs out of the pool of all orcs. Once submitted, I want it to create/update as many records in the interactions table, each with the human id, and as many orc ids were selected. :

AKA list notation

  1. Make a form from one end (humans)
  2. List out all the orcs in the form
  3. Select multiple orcs from that list
  4. Save as many records into the interactions table with human_id and orc_id as there were orcs chosen from that list. (The human_id will be the same in these records, since it started from a given human's form page)

I'll code out as much of the entire story as I have. Please feel free to ask for clarifications, and fix anything wrong for accomplishing this.

Tables

humans
  integer "id"

interactions
  integer "human_id"
  integer "orc_id"


  index ["human_id", "orc_id"] 
  # This is the primary key. no normal id.
  # Is it better to have a primary id for this join table, or does it not matter?

orcs
  integer "id"

Models

/models/human.rb

class Human < ActiveRecord::Base
  has_many :interaction
  has_many :orcs, through: :interactions

  accepts_nested_attributes_for :interactions
end

/models/interaction.rb

# Purely a join model and table. No controller, no scaffold.
class Interaction <ActiveRecord::Base
  belongs_to :human
  belongs_to :orc

  accepts_nested_attributes_for :orc 
  # Singular to match what was specified in the belongs_to relationship?
  # Do I even need this if I'm only trying to read orcs to save their id into the interactions table, and not trying to modify orcs?
end

/models/orc.rb

class Orc< ActiveRecord::Base
  has_many :interactions
  has_many :humans, through: :interactions

end

Controllers

/controllers/humans_controller.rb

class HumansController < ApplicationController

  before_action :set_human,         only: [:show, :edit, :update, :destroy]
  before_action :build_interaction, only: [:new, :edit]

  private
    def set_human
      @human = Human.find(params[:id])
    end

    def human_params
      params.require(:human).permit(
                                    interaction_attributes: [:human_id, 
                                                             :orc_ids,   # Is plural here correct?
                                                             :_destroy]
      )
    end

    def build_interaction
      @interaction = @human.interactions.build
                     # Is the human instance variable valid here?
                     # How many interactions are being built here?
                     # How do I ensure there are as many interaction builds as there will be selected orcs (i.e. as many interaction records to be saved or updated)?
    end
end

/controllers/orcs_controller.rb

class OrcsController < ApplicationController

  before_action :set_orc,   only: [:show, :edit, :update, :destroy]

  private
    def set_orc
      @orc = Orc.find(params[:id])
    end

    def orc_params
      params.require(:orc).permit()
    end

end

Views

/views/humans/_form.html.haml

= simple_form_for(@human, html: { multipart: true }) do |f|
  = f.simple_fields_for :interactions do |i|
    = i.hidden_field :human_id, value: @human.id
    = i.association :orc, collection: Orc.all
                    ^                        
                    # Should this be :orc_id?
                    # Does this code automatically extract the orc_id when saving to the interactions table?

Thank you.

What am I missing? when I submit, I confirmed that there no record created in the interactions join table.

I think some of the challenges are

  1. creating multiple records reusing the single hidden input field.
  2. obtaining a list of orcs when the interactions table must be a singular orc (since it was defined with belongs_to :orc in the interactions model)

Also, where can I find out more about how using the plural form of model ids works (i.e. simply using orc_ids instead of orc_id, and what concrete consequences that entails)?

ahnbizcad
  • 10,491
  • 9
  • 59
  • 85

1 Answers1

5

Turns out that with simple_form this is actually rather simple (who knew?). It handles the magic of all the intermediary tables (together with Rails's awesomeness). All you need to do is this:

= simple_form_for(@human, html: { Pmultipart: true }), do |f|
  = f.association :orcs

I don't really use HAML so I'm not sure about that comma before do |f|. Here is what I'm trying to say in ERB HTML

<%= simple_form_for(@human) do |f| %>
  <%= f.association :orcs %>
<% end %>

Then in your controller's param sanitizer:

def orc_params
  params.require(:orc).permit(orc_ids: [])
end

And finally in your model:

class Human < ActiveRecord::Base
  ...
  accepts_nested_attributes_for :orcs
end

And that's it! It will automatically create the join objects. Don't you just love magic?

This will generate a multi-select field populated by all orcs. You can easily change this to checkboxes by using f.association :orcs, as: :check_boxes.

Xavier
  • 3,423
  • 23
  • 36
  • I know that works for a has many, but it also works for a has many through? No `fields for` required? So doing a nested attributed for orcs on the human model means the data gets fanned in the interactions join table? Hard to believe. That would be very magical indeed. I'll try right away. – ahnbizcad Aug 03 '14 at 20:09
  • 1
    I made a test app to try it out before I posted. :) – Xavier Aug 03 '14 at 20:10
  • You're right about the comma. They was also a typo for `multipart `. But those were just typos in the question, not the real issue in my app. – ahnbizcad Aug 03 '14 at 20:12
  • OMG THANK YOU SO MUCH you have no idea how embarrassingly long i hacked away at this problem. <3 SO community. Through you answer, I answered all my questions about how `*_ids` syntax works, what SimpleForm requires for has many through relationships. Reverse engineer learning ftw! – ahnbizcad Aug 03 '14 at 20:51
  • That was pretty unintuitive, and I didn't see any documentation on how to use simple form with ahs many through in the simpleform readme. If I wanted to know how to do this in default rails forms, should I ask it as a separate question? – ahnbizcad Aug 03 '14 at 20:59
  • 1
    That's the standard procedure, yeah. Although you should take a look at these: http://stackoverflow.com/questions/13506735/rails-has-many-through-nested-form http://stackoverflow.com/questions/2182428/rails-nested-form-with-has-many-through-how-to-edit-attributes-of-join-model Typically, you only need a nested form if you're CREATING the associated object. If you're just trying to define an association from existing object, it shouldn't be needed.http://guides.rubyonrails.org/form_helpers.html#select-boxes-for-dealing-with-models – Xavier Aug 03 '14 at 21:32
  • I am also using select2. Your answer, plus the select2 instructions allows me to create, destroy, or update records in the join table (and the array of selected orc ids) via a single form without any `fields_for` or nesting. – ahnbizcad Aug 03 '14 at 22:19
  • I saw the 2nd question link, but that one involved building, and you'd have to somehow control the number of built objects, which doesn't seem good for selecting a varying number of options. Your solution worked well. – ahnbizcad Aug 03 '14 at 22:24