Complex Rails form with Nested Attributes (Rails 5) - ruby-on-rails

I have a form, that is saving the main attribute, but not its nested attributes. I have dug into a lot of documents, and seem to be doing things correctly, but still get an error that my nested attributes "must exist".
My interview attributes are saving correctly to the database, but my logs show "Unpermitted parameters: student, parents"
My code is modified for brevity, but I will still try to be thorough enough to get some direction as to what might be going wrong ...
Models (which I include accepts_nested_attributes for :student, :parents)
:student is singular since it has a has_one relationship
:parents is plural since it has a has_many relationship
class Interview < ApplicationRecord
has_one :student
has_many :parents
accepts_nested_attributes_for :student, :parents
end
class Student < ApplicationRecord
belongs_to :interview
end
class Parent < ApplicationRecord
belongs_to :interview
end
Controller
class InterviewsController < ApplicationController
def index
#interviews = Interview.all
end
def show
#interview = Interview.find(params[:id])
end
def new
#interview = Interview.new
#interview.build_student
2.times { #interview.parents.build }
end
def create
#interview = Interview.new(interview_params)
if #interview.save
redirect_to #interview
else
render :action => 'new'
end
end
private
def interview_params
params.require(:interview).permit(:date_today, :date_contact, :purpose_of_call, :problems_start_date, :cause, :violence, :running_away, :police_contact, :suicide, :self_harm, :other_info, :testing, :hospitalization, :medications, :school_problems, :teacher_relationships, :parent_goals, :notes,
student_attributes: [:id, :name, :age, :height, :weight, :dob, :interview_id],
parents_attributes: [:id, :name, :relationship, :parentage, :address, :phone_home, :phone_work, :phone_mobile, :phone_mobile, :email, :employer, :notes, :interview_id] )
end
end
Form (important bits)
<%= form_for(#interview) do |f| %>
<p class="inline">
<%= f.label :date_today, 'Today\'s Date' %>
<%= f.date_select(:date_today, :order => [:month, :day, :year], :start_year => 2000, :end_year => Date.today.year) %>
</p>
<p class="inline float_right">
<%= f.label :date_contact, 'Initial Contact' %>
<%= f.date_select(:date_contact, :order => [:month, :day, :year], :start_year => 2000, :end_year => Date.today.year) %>
</p>
<%= f.fields_for :student do |student_form| %>
<p><%= student_form.text_field :name, placeholder: 'Name' %></p>
<p class="inline">
<%= student_form.label :age %>
<%= student_form.text_field :age %>
</p>
<p class="inline">
<%= student_form.label :height %>
<%= student_form.text_field :height %>
</p>
<p class="inline">
<%= student_form.label :weight %>
<%= student_form.text_field :weight %>
</p>
///// removed for brevity /////
<% end %>
<%= f.fields_for :parents do |parent_form| %>
<%= render 'parents', :f => parent_form %>
<% end %>
Parent Partial
<p>
<%= f.label :name, 'Name' %>
<%= f.text_field :name %>
</p>
//// and more of the same /////
Routes
resources :interviews do
resources :student
resources :parents
end
The website form (at it's current state) can be found here: www.compassconsultingwi.com/interviews/new
and the link to the github can be found here: https://github.com/plantoteachme/compassconsultingwi
Params returns this ..
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nU4WM2RO5GJd36eaSLHMxhRQCOnY8EPjDhUdFBHlYGkcw6H7/Oc5y7kFx0HMU9nm5cc47ZZZBDW6oQ2QNF5yhA==", "interview"=>{"date_today(2i)"=>"11", "date_today(3i)"=>"16", "date_today(1i)"=>"2016", "date_contact(2i)"=>"10", "date_contact(3i)"=>"23", "date_contact(1i)"=>"2016", "student"=>{"name"=>"John", "age"=>"12", "height"=>"5 feet", "weight"=>"123 lbs", "dob(2i)"=>"3", "dob(3i)"=>"13", "dob(1i)"=>"2004", "strengths"=>"Great with his siblings", "weaknesses"=>"Lazy", "likes"=>"Food", "dislikes"=>"Chores", "medical_prolems"=>"ADD", "religous_training"=>"Catholic", "ethnic_issues"=>"none", "grade_level"=>"6"}, "parents"=>{"name"=>"Jamie", "relationship"=>"Mom", "parentage"=>"Strict", "address"=>"Miwaukee Wi", "phone_home"=>"555-1000", "phone_work"=>"555-1001", "phone_mobile"=>"555-1002", "email"=>"jj#jj.com", "employer"=>"Googleer", "notes"=>"PhD in Computer Science"}, "purpose_of_call"=>"Depression causing suicidal tendencies", "problems_start_date"=>"When we moved from Nigeria last year", "cause"=>"Relocating", "violence"=>"none", "running_away"=>"no", "police_contact"=>"no", "suicide"=>"Hasn't acted on it, but talks about it", "self_harm"=>"Minor bruising from \"sports\"", "other_info"=>"", "testing"=>"Yes, for ADD", "hospitalization"=>"no", "medications"=>"Regeline", "school_problems"=>"Getting bullied", "teacher_relationships"=>"Strained", "parent_goals"=>"Improve self awareness", "notes"=>"Our family was in Nigeria for mission work"}, "button"=>""}
Unpermitted parameters: student, parents

Try to use cocoon gem.
You can build a model object using link_to_add_association method of cocoon gem.
Also, you can remove object using link_to_remove_association
A fully working example here: https://github.com/nathanvda/cocoon/wiki/ERB-examples

Related

Rails - How to avoid using hidden_fields in the view to pass values to controller?

Is there a way I can avoid the hidden_field method of passing values in the view to a controller? I would prefer a controller method for security reasons. Unfortunately value pairing #variables is not supported in strong_parameters.
EDIT 6/18 1:00 PM EST
I've renamed my garages controller to appointments
cars_controller no longer creates a new appointment (formally garages). A new appointment is created in the
appointments_controller
My current structure
routes
Rails.application.routes.draw do
resources :techs, only: [:index, :show], shallow: true do
resources :cars, only: [:new, :create]
end
resources :appointments
#For searchkick
resources :cars, only: [:show] do
collection do
get 'search'
end
end
root "home#index"
end
models
tech.rb
class Tech < ActiveRecord::Base
searchkick
has_many :appointments
has_many :customers, :through => :appointments
has_many :service_menus
has_many :services
has_many :cars
end
service.rb
class Service < ActiveRecord::Base
belongs_to :tech
belongs_to :service_menu
has_many :cars, dependent: :destroy
accepts_nested_attributes_for :cars, :reject_if => :all_blank, :allow_destroy => true
end
car.rb
class Car < ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
end
appointment.rb
class Garage < ActiveRecord::Base
belongs_to :customer
belongs_to :tech
belongs_to :car
end
controllers
cars_controller
def new
#car = Car.find(params[:id])
#tech = Tech.find(params[:tech_id])
#appointment = Garage.new
end
appointments_controller
def create
#appointment = current_customer.appointments.build(appointment_params)
if #appointment.save
redirect_to appointments_path, notice: "You car has been added to this appointment."
else
redirect_to appointments_path, notice: "Uh oh, an error has occured."
end
end
private
def appointment_params
params.require(:appointment).permit(:tech_id, :service_id, :car_id, ...and a bunch of other keys here)
end
views
cars.new.html
Please note this form passes hidden values to the appointment_controller.
Value from #car.name and other alike are not from a text_field but rather a pre-defined value based on selections from a previous page which is store in the cars db.
<%= simple_form_for(#appointment, { class: 'form-horizontal' }) do |f| %>
<%= f.hidden_field :tech_id, value: #tech.id %>
<%= f.hidden_field :car_id, value: #car.id %>
<%= f.hidden_field :service_id, value: #car.service.id %>
<%= f.hidden_field :customer_car, value: current_customer.car %>
<%= f.hidden_field :customer_street_address, value: current_customer.street_address %>
<%= f.hidden_field :customer_city, value: current_customer.city %>
<%= f.hidden_field :customer_state, value: current_customer.state %>
<%= f.hidden_field :customer_zip_code, value: current_customer.zip_code %>
<%= f.hidden_field :service_name, value: #car.service.service_menu.name %>
<%= f.hidden_field :car_name, value: #car.name %>
<%= **And a bunch of other hidden values here which are too long to list** %>
<%= f.submit "Add to appointment", class: 'btn btn-default' %>
<% end %>
service.html
<%= render 'form' %>
_form.html
<%= simple_form_for #service do |f| %>
<div class="field">
<%= f.label "Select service category" %>
<br>
<%= collection_select(:service, :service_menu_id, ServiceMenu.all, :id, :name, {:prompt => true }) %>
<%= f.fields_for :cars do |task| %>
<%= render 'car_fields', :f => task %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'Add New Car', f, :cars, class: 'btn btn-default' %>
</div><br>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
_car_fields.html
<div class="nested-fields">
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :hours %>
<%= f.select :hours, '0'..'8' %>
<%= f.label :minutes %>
<%= f.select :minutes, options_for_select( (0..45).step(15), selected: f.object.minutes) %><br>
<%= f.label :price %><br>
<%= f.text_field :price, :value => (number_with_precision(f.object.price, :precision => 2) || 0) %> <br>
<%= f.label :details %><br>
<%= f.text_area :details %></div>
<%= link_to_remove_association "Remove Car", f, class: 'btn btn-default' %>
<%= f.hidden_field :tech_id, value: current_tech.id %>
<br>
<hr>
</div>
> Edit 7/14 1:30 pm EST
Brief Synopsis on this specific function of the application
A customer clicks through a list of services a tech has to offer
The customer selects a service for example brakes which is a service a tech has listed in his profile.
The attributes for brakes are listed in the cars db
cars belongs_to to techs
The customer can save brakes which is an attribribute of a techs car to a appointment
A good number of predefined values from tech, the customer's street address, etc..., and the car are pre-loaded in the form for storing in the appointments table.
appointment acts as a histories table. So if the tech decides to modify any one of his services in this example brakes, the appointments tables will remain untouched for the brakes entry.
Once the customer selects the Add to appointment button, it will save all of the predefined values from tech, customer, and car attributes (in this example brakes) to the appointments db.
Another approach to this would be to get rid of the strong parameters altogether and do the following:
def create
#appointment = Garage.create(tech_id: #car.service.tech.id,
customer_id: current_customer.id,
customer_street_address: current_customer.street_address,
customer_city: current_customer.city,
customer_state: current_customer.state,
customer_zip_code: current_customer.zip_code,
customer_phone_number: current_customer.phone_number,
customer_location_type: "WILL ADD LATER",
customer_latitude: current_customer.latitude,
customer_longitude: current_customer.longitude,
service_id: #car.service.id,
service_name: #car.service.name,
car_id: #car.id,
car_name: #car.name,
car_time_duration: #car.time_duration,
price: #car.price,
car_details: #car.details)
if #appointment.save
redirect_to techs_path, notice: "This service has been saved."
elsif
redirect_to tech_path, notice: "Uh oh, an error has occurred."
end
end
Please let me know if you require further details.
I can think of some methods you could use to avoid this form bloated with hidden_fields:
Share data between controllers in the user's session, pretty much like a shopping cart in an e-commerce application.
If you prefer to preserve the statelessness of the application, create a model to temporarily store these informations; this way you'll only need to include one hidden_field in the form.
Use JavaScript to make the requests, storing the data in local objects and passing them as JSON when needed (this is trivial using AngularJS).
Whichever method you choose, keep in mind that storing a lot of state in a web application usually is a code smell. You can always rethink your application so you don't need to keep so much context.
To resolve my issue, my latest edit from my initial post stated the following:
EDIT 6/18 1:00 PM EST
I've renamed my garages_controller to appointments_controller
cars_controller no longer creates a new appointment (formally garages). A new appointment is created in the appointments_controller
Only hidden_field i'm passing is the car_id in the appointments view /new.html.erb <%= f.hidden_field :car_id, value: #car.id %>.
In the appointments_controller, I'm assigning all the car attributes doing the following.
def create
#appointment = current_customer.appointments.build(appointment_params)
#appointment.tech_id = #appointment.car.service.tech.id
#appointment.price = #appointment.car.price
#appointment.car_name = #appointment.car.name
#appointment.car_details = #appointment.car.details
if #appointment.save
redirect_to appointments_path, notice: "Thank you booking your appointment."
else
redirect_to appointments_path, notice: "Uh oh, an error has occurred. Please try again or contact us for further assistance"
end
end
Thank you all for your responses.
I should've known better. :(
You could move that stuff into a callback and only pass the customer_id and car_id with the form. This way garage instance will know about it's customer and car parents and you can do something like:
class Garage < ActiveRecord::Base
before_create :copy_stuff
private
def copy_stuff
self.customer_street_address = customer.street_address
self.car_name = car.name
# and so on
end
end
Is there a way I can avoid the hidden_field method of passing values
in the view to a controller?
You can disable those fields in the HTML/view by adding attribute disabled: true to the hidden input field tags to achieve what you asked for.
Not sure about the syntax exactly, but should be something like this for example
f.hidden_field :tech_id, value: #tech.id, disabled: true

Using jQuery Tokeninput within a nested form partial

I'm using jQuery Tokeninput as shown in this Railscast. I'd like to combine this functionality in a nested form but get the error
undefined method `artists' for #<SimpleForm::FormBuilder:0x007febe0883988>
For some reason its not recognizing the track parameter in my form builder which is stopping me to get a hold of albums I have on record.
<div class="input">
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.artists.map(&:attributes).to_json} %>
</div>
Keep in mind this works in my track form but just not in my album form since its nested. What should I do to get this to work?
class ArtistsController < ApplicationController
def index
#artists = Artist.order(:name)
respond_to do |format|
format.html
format.json {render json: #artists.tokens(params[:q])}
end
end
end
Models
class Artist < ActiveRecord::Base
has_many :album_ownerships
has_many :albums, through: :album_ownerships
has_many :featured_artists
has_many :tracks, through: :featured_artists
def self.tokens(query)
artists = where("name like ?", "%#{query}%")
if artists.empty?
[{id: "<<<#{query}>>>", name: "Add New Artist: \"#{query}\""}]
else
artists
end
end
def self.ids_from_tokens(tokens)
tokens.gsub!(/<<<(.+?)>>>/) {create!(name: $1).id}
tokens.split(',')
end
end
class Albums < ActiveRecord::Base
attr_reader :artist_tokens
accepts_nested_attributes_for :tracks, :reject_if => :all_blank, :allow_destroy => true
has_many :albums_ownerships
has_many :artists, through: :albums_ownerships
def artist_tokens=(ids)
self.artist_ids = Artist.ids_from_tokens(ids)
end
end
class Track < ActiveRecord::Base
attr_reader :artist_tokens
belongs_to :album
has_many :featured_artists
has_many :artists, through: :featured_artists
def artist_tokens=(ids)
self.artist_ids = Artist.ids_from_tokens(ids)
end
end
class AlbumOwnership < ActiveRecord::Base
belongs_to :artist
belongs_to :album
end
class FeaturedArtist < ActiveRecord::Base
belongs_to :artist
belongs_to :track
end
Album Form
<%= simple_form_for(#album) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<h1>Tracks</h1>
<%= f.simple_fields_for :tracks do |track| %>
<%= render 'track_fields', :f => track %>
<% end %>
<div id='links'>
<%= link_to_add_association 'Add Field', f, :tracks %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Track Partial
<div class="field">
<%= f.input :name %><br>
</div>
<div class="input">
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.artists.map(&:attributes).to_json} %>
</div>
JS
$(function() {
$('#track_artist_tokens').tokenInput('/artists.json', {
prePopulate: $("#track_artist_tokens").data("pre"),
theme: 'facebook',
resultsLimit: 5
});
});
UPDATE
As mentioned by nathanvda, I needed to use f.object in order for the artists to be recognized. So in my partial I now have:
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.object.artists.map(&:attributes).to_json, class: 'test_class'} %>
In my js I also needed to call the token input method before/after insertion:
$(function() {
$('.test_class').tokenInput('/artists.json', {
prePopulate: $(".test_class").data("pre"),
theme: 'facebook',
resultsLimit: 5
});
$('form').bind('cocoon:after-insert', function(e, inserted_item) {
inserted_item.find('.test_class').tokenInput('/artists.json', {
prePopulate: $(".test_class").data("pre"),
theme: 'facebook',
resultsLimit: 5
});
});
});
The only remaining issue I have is the the tracks_attributes not being saved. I ran into an issue similar to this in the past in this post but the two main difference is the second level of nesting involved and that I used a join table within my nested form. I'm not entirely sure if or how any of that code would translate over but I believe this is most likely problem. As far as the permitted params of my albums_controller here's what they looks like.
def album_params
params.require(:album).permit(:name, :artist_tokens, tracks_attributes: [:id, :name, :_destroy, :track_id])
end
If you need to acces the object of a form, you need to write f.object, so I think you should just write f.object.artists.
Your "data-pre" => f.artists... is calling the artists method on f which is the form builder and doesn't have an #artists method.
Try this instead:
In the album form, change the render partial line to this:
<%= render 'track_fields', :f => track, :artists => #artists %>
And then use this in the track partial:
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => artists.map(&:attributes).to_json} %>
UPDATED
Let's back up a step. From your code it looks like you need to populate a data-pre attribute with the attributes of a collection of artists.
The problem is you're calling f.artists where f is the FormBuilder and doesn't know anything about artists. This is why you're getting undefined method 'artists'...
The solution is make a collection of artists available to the view and its partials. One way to do this:
class AlbumsController < ApplicationController
...
def new
#album = Album.new
#artists = Artist.order(:name) # or some other subset of artists
end
...
def edit
#album = Album.find params[:id]
#artists = Artist.order(:name) # or perhaps "#artists = #album.artists", or some other subset of artists
end
end
and then in new.html.erb and edit.html.erb, pass #artists to the form partial:
... # other view code
<%= render 'form', album: #album %>
... # other view code
and then in your form partial:
... # other view code
<%= f.simple_fields_for :tracks do |track_form| %>
<%= render 'track_fields', :f => track_form %>
<% end %>
... # other view code
finally, in your track partial:
... # other view code
<div class="input">
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => #artists.map(&:attributes).to_json} %>
</div>
... # other view code
Does that make sense?

How do I reference an existing instance of a model in a nested Rails form?

I'm attempting to build a recipe-keeper app with three primary models:
Recipe - The recipe for a particular dish
Ingredient - A list of ingredients, validated on uniqueness
Quantity - A join table between Ingredient and Recipe that also reflects the amount of a particular ingredient required for a particular recipe.
I'm using a nested form (see below) that I constructed using an awesome Railscast on Nested Forms (Part 1, Part 2) for inspiration. (My form is in some ways more complex than the tutorial due to the needs of this particular schema, but I was able to make it work in a similar fashion.)
However, when my form is submitted, any and all ingredients listed are created anew—and if the ingredient already exists in the DB, it fails the uniqueness validation and prevents the recipe from being created. Total drag.
So my question is: Is there a way to submit this form so that if an ingredient exists whose name matches one of my ingredient-name fields, it references the existing ingredient instead of attempting to create a new one with the same name?
Code specifics below...
In Recipe.rb:
class Recipe < ActiveRecord::Base
attr_accessible :name, :description, :directions, :quantities_attributes,
:ingredient_attributes
has_many :quantities, dependent: :destroy
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
In Quantity.rb:
class Quantity < ActiveRecord::Base
attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
And in Ingredient.rb:
class Ingredient < ActiveRecord::Base
attr_accessible :name
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
Here's my nested form that displays at Recipe#new:
<%= form_for #recipe do |f| %>
<%= render 'recipe_form_errors' %>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<h3>Ingredients</h3>
<div id='ingredients'>
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient_attributes do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name %>
<% end %>
<%= ff.label :amount %>
<%= ff.text_field :amount, size: "10" %>
<%= ff.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %><br>
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
</div><br>
<%= f.label :description %><br>
<%= f.text_area :description, rows: 4, columns: 100 %><br>
<%= f.label :directions %><br>
<%= f.text_area :directions, rows: 4, columns: 100 %><br>
<%= f.submit %>
<% end %>
The link_to and link_to_function are there to allow the addition and removal of quantity/ingredient pairs on the fly, and were adapted from the Railscast mentioned earlier. They could use some refactoring, but work more or less as they should.
Update: Per Leger's request, here's the relevant code from recipes_controller.rb. In the Recipes#new route, 3.times { #recipe.quantities.build } sets up three blank quantity/ingredient pairs for any given recipe; these can be removed or added to on the fly using the "Add ingredient" and "remove" links mentioned above.
class RecipesController < ApplicationController
def new
#recipe = Recipe.new
3.times { #recipe.quantities.build }
#quantity = Quantity.new
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
redirect_to #recipe
else
render :action => 'new'
end
end
You shouldn't put the logic of ingredients match into view - it's duty of Recipe#create to create proper objects before passing 'em to Model. Pls share the relevant code for controller
Few notes before coming to code:
I use Rails4#ruby2.0, but tried to write Rails3-compatible code.
attr_acessible was deprecated in Rails 4, so strong parameters are used instead. If you ever think to upgrade your app, just go with strong parameters from the beginning.
Recommend to make Ingredient low-cased to provide uniform appearance on top of case-insensitivity
OK, here we go:
Remove attr_accessible string in Recipe.rb, Quantity.rb and Ingredient.rb.
Case-insensitive, low-cased Ingredient.rb:
class Ingredient < ActiveRecord::Base
before_save { self.name.downcase! } # to simplify search and unified view
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
end
<div id='ingredients'> part of adjusted form to create/update Recipe:
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name, size: "10" %>
<% end %>
...
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
We should use :ingredient from Quantity nested_attributes and Rails will add up _attributes-part while creating params-hash for further mass assignment. It allows to use same form in both new and update actions. For this part works properly association should be defined in advance. See adjusted Recipe#new bellow.
and finally recipes_controller.rb:
def new
#recipe = Recipe.new
3.times do
#recipe.quantities.build #initialize recipe -> quantities association
#recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association
end
end
def create
#recipe = Recipe.new(recipe_params)
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
def update
#recipe = Recipe.find(params[:id])
#recipe.attributes = recipe_params
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
private
def prepare_recipe
#recipe.quantities.each do |quantity|
# do case-insensitive search via 'where' and building SQL-request
if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first
quantity.ingredient_id = quantity.ingredient.id = ingredient.id
end
end
end
def recipe_params
params.require(:recipe).permit(
:name,
:description,
:directions,
:quantities_attributes => [
:id,
:amount,
:_destroy,
:ingredient_attributes => [
#:id commented bc we pick 'id' for existing ingredients manually and for new we create it
:name
]])
end
In prepare_recipe we do the following things:
Find ID of ingredient with given name
Set foreign_key quantity.ingredient_id to ID
Set quantity.ingredient.id to ID (think what happens if you don't do that and change ingredient name in Recipe)
Enjoy!

Cannot get simple_nested_form to submit data

I am trying to build a simple_nested_form in my Ruby on Rails app. When I submit my form I am getting some unknown error because it is just redirecting back to the form to input again. Here is the output in the rails server console for when I submit the form. It looks like there is some random "0" => thrown in there.
Parameters: {"machine"=>{"name"=>"2134", "ip_adress"=>"2", "machine_employees_attributes"=>{"0"=>{"machine_id"=>"1", "employee_id"=>"2"}}}, "commit"=>"Create Machine"}
I have a machine model which has_many :machine_employees
and a machineemployee model which belongs_to :machine
Do you have any idea why this 0 => could be appearing because I think it is what is giving me the issues.
Here is the code for my models.
Machine
class Machine < ActiveRecord::Base
# Relationships
has_many :machine_employees
has_many :employees, :through => :machine_employees
accepts_nested_attributes_for :machine_employees, :reject_if => lambda{ |me| me[:employee_id].blank? }
attr_accessible :ip_adress, :name, :machine_employees_attributes
# Validations
validates_presence_of :name, :ip_adress
end
MachineEmployee
class MachineEmployee < ActiveRecord::Base
before_validation :set_default
# Relationships
belongs_to :machine
belongs_to :employee
attr_accessible :employee_id, :machine_id, :end_date, :start_date
# Validations
validates_presence_of :employee_id, :machine_id, :start_date
private
# Callback Methods
def set_default
self.start_date = Date.today
self.end_date = nil
end
end
New Machine Form
<div class="row-fluid">
<div class="span3">
<h1>Add a Machine</h1>
<br />
<%= simple_nested_form_for #machine do |f| %>
<%= render "machine_fields", :f => f %>
<%= f.button :submit %>
<%= link_to 'Back', machines_path %>
</div>
<div class="span4">
<h4>Assign an Employee to This Machine</h4>
<%= f.simple_fields_for :machine_employees do |me_form| %>
<!-- render nested machine_employee fields-->
<%= render "machine_employee_fields", :f => me_form %>
<% end %>
</div>
<% end %>
</div>
Machine Employee Fields Partial
<%= f.input :machine_id, :as => :hidden, :input_html => { :value => #machine.id } %>
<%= f.input :employee_id, collection: #employees, :id => :name, :prompt => "Select ..." %>
The 0 is thrown in there because the machine model has_many machine_employees. When you use nested forms, it passes a pseudo-array for has_many relations. So, if you tried to submit 2 machine employees, your hash would look like this:
Parameters: {"machine"=>{"name"=>"2134", "ip_adress"=>"2", "machine_employees_attributes"=>{
"0"=>{"machine_id"=>"1", "employee_id"=>"2"},
"1"=>{"machine_id"=>"1", "employee_id"=>"3"}
}
}, "commit"=>"Create Machine"}
This way you can access the machine_employees passed from the form by doing params[:machine][:machine_employees_attributes][0] or params[:machine][:machine_employees_attributes][1]. Note that if this was a has_one relationship, then the machine_employees_attributes key would be changed to machine_employee_attributes and there would be no numerical index.
I suspect the problem is that your machine model must accept_nested_attributes_for :machine_employees and must also have attr_accessible :machine_employees_attributes.

Rails model - how to differentiate different types

I am currently working on a nested model form.
I have a subject model.
This subject model has lessons of 3 different types - tutorial, lecture and laboratory.
I am able to get the nested form working with https://github.com/ryanb/nested_form.
But I want to fix it such that in the form only 3 forms for the child(lesson model) will be produced and that their first field (lesson_type field) will be automatically filled in and fixed.
I am not too sure on how to model such a situation on Rails.
These are the codes I have so far.
Any advice on what I could try out or point out the mistakes I have made would be appreciated.
This is the form.
Right now I could get the form to show up three times on my controller but I am not sure how I could generate different values for the fields. They are all showing lecture as of now.
<%= nested_form_for(#subject, :remote=>true) do |f| %>
<div class="field">
<%= f.label :subject_code %><br />
<%= f.text_field :subject_code %>
</div>
<%= f.fields_for :lessons do |lesson_form| %>
<%= lesson_form.label :lesson_type %><br/>
<%= lesson_form.text_field :lesson_type, :value=> "lecture"%><br/>
<%= lesson_form.label :name %><br/>
<%= lesson_form.text_field :name %><br/>
<%= lesson_form.fields_for :lesson_groups do |lesson_group_form| %>
<%= lesson_group_form.label :group_index %><br/>
<%= lesson_group_form.text_field :group_index %>
<%= lesson_group_form.link_to_remove "Remove this task" %>
<% end %>
<p><%= lesson_form.link_to_add "Add a lesson_group",:lesson_groups,:id=>"open-lesson"%></p>
<% end %>
<% end %>
This is the controller. The creation will happen on the index page.
def index
#subjects = Subject.all
#subject = Subject.new
lecture = #subject.lessons.build
lecture.lesson_groups.build
lecture.destroy
tutorial = #subject.lessons.build
tutorial.lesson_groups.build
tutorial.destroy
laboratory = #subject.lessons.build
laboratory.lesson_groups.build
laboratory.destroy
respond_to do |format|
format.html # index.html.erb
format.json { render json: #subjects }
format.js
end
end
The subject model
class Subject < ActiveRecord::Base
attr_accessible :subject_code, :lessons_attributes
has_many :lessons, :dependent => :destroy
accepts_nested_attributes_for :lessons, :allow_destroy => :true, :reject_if => lambda { |a| a[:lesson_type].blank? }
end
And the lesson model
class Lesson < ActiveRecord::Base
belongs_to :subject
attr_accessible :lesson_type, :name, :subject, :lesson_groups_attributes
has_many :lesson_groups, :dependent => :destroy
accepts_nested_attributes_for :lesson_groups, :allow_destroy => true
end
Okay, I am not sure if this is to the Rails convention but I got it working according to what I want. Added the following lines in the subject model: Basically assigning the lesson type field in the model.
lecture = #subject.lessons.build
lecture.lesson_type = "lecture"
lecture.lesson_groups.build
lecture.destroy
tutorial = #subject.lessons.build
tutorial.lesson_type = "tutorial"
tutorial.lesson_groups.build
tutorial.destroy
laboratory = #subject.lessons.build
laboratory.lesson_type = "laboratory"
laboratory.lesson_groups.build
laboratory.destroy
And to make it such that they can't change the lesson type I made it read only
<%= lesson_form.text_field :lesson_type, :readonly=>true%><br/>

Resources