2

I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.

class Student < ActiveRecord::Base
  has_many :evaluations
  has_many :assessments, through: :evaluations

  # ... etc
end

class Evaluation < ActiveRecord::Base
  belongs_to :student
  belongs_to :assessment

  # ... etc
end

class Assessment < ActiveRecord::Base
  has_many :evaluations
  has_many :students, through: :evaluations

  accepts_nested_attributes_for :evaluation, reject_if: :all_blank
  # ... etc
end

When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).

{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}

I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.

What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?

charlie
  • 181
  • 13
  • Please include the relevant controller – max Oct 24 '15 at 06:45
  • @max The controller doesn't have any special logic (atm) than whitelisted parameters and a call to `@assessment.new(assessment_params)` and then `@assessment.save` – charlie Oct 24 '15 at 08:46
  • Yeah, but you usually build the associated records in the controllers new action - that way we don't have to speculate. – max Oct 24 '15 at 08:48
  • I want to avoid manually building these things. If I am creating new records via the view (adding new records for each student - what I want to automate), it structures them as above, and I don't need any special controller logic beyond using `new` and using nested forms and `accepts_nested_attributes_for`. I just pass in the whitelisted parameters to the `new` method and Rails creates the parent `assessment` and associated `evaluation` objects. – charlie Oct 24 '15 at 08:57

2 Answers2

3

"??"

Never a good sign in your params.

With Cocoon I see that requests have some number in the space where the id would go

That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).


From your setup, here's what I'd do:

#app/models/student.rb
class Student < ActiveRecord::Base
   has_many :evaluations
   has_many :assessments, through: :evaluations
end

#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
  belongs_to :student
  belongs_to :assessment
end

#app/models/assessment.rb
class Assessment < ActiveRecord::Base
   has_many :evaluations
   has_many :students, through: :evaluations
   accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end

This will allow you to do the following:

#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
   def new
      @assessment = Assessment.new
      @students = Student.all
      @students.each do
         @assessment.evaluations.build
      end
   end
end

Allowing you:

#app/views/assessments/new.html.erb
<%= form_for @assessment do |f| %>
   <%= f.fields_for :evaluations, @students do |e| %>
       <%= e.hidden_field :student_id %>
       <%= e.text_field :grade %>
   <% end %> 
   <%= f.submit %>
<% end %>

As far as I can tell, this will provide the functionality you need.

Remember that each evaluation can connect with existing students, meaning that if you pull @students = Student.all, it will populate the fields_for accordingly.

If you wanted to add new students through your form, it's a slightly different ballgame.


Cocoon

You should also be clear about the role of Cocoon.

You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.

Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...

enter image description here

Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).

Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.

Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • Many thanks @rich-peck. Yep, the `??` were replaced by me especially for this question, so they are not coming through in the request :) – charlie Oct 24 '15 at 11:00
  • (pressed enter too quickly) I know Cocoon is all front-end, but I wasn't sure what logic created the ids it was passing back via the request. Do they just not matter, as long as they are unique? Or do they have to be anything that do not correspond to ids for `evaluation`s that already exist? – charlie Oct 24 '15 at 11:03
  • 1
    The ids are `Time.now.to_i` ;) – Richard Peck Oct 24 '15 at 11:06
  • I thought they might be! This totally answers my question. Many thanks for your time. – charlie Oct 24 '15 at 11:06
  • http://stackoverflow.com/questions/20436526/rails-accepts-nested-attributes-for-with-f-fields-for-and-ajax - or at least the way I like to do it – Richard Peck Oct 24 '15 at 11:07
  • 1
    Yeah it's time - https://github.com/nathanvda/cocoon/blob/master/app/assets/javascripts/cocoon.js#L5 – Richard Peck Oct 24 '15 at 11:08
0

Thanks to @rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)

assessments/new.html.haml (just raw, no fancy formatting)

= form_for @assessment do |f|
  = f.fields_for :evaluations do |ff|
    .meaningless-div
      = ff.object.student.name
      = ff.hidden_field :student_id, value: ff.object.student_id
      = ff.label :comment
      = ff.text_field :comment
      %br/

assessments_controller.rb

def new
  @assessment = Assessment.new
  @students = Student.all
  @students.each do |student|
    @assessment.evaluations.build(student: student)
  end
end
charlie
  • 181
  • 13