I'm developing a form that links recipes, recipe_entries (has_many_though join table) and ingredients with jQuery autocomplete in Rails 4. I'm using a combination of of the simple_form, cocoon and rails4-autocomplete gems. The idea is that users can create a recipe and dynamically add and edit associated ingredients via autocomplete (the quantity for each ingredient is stored on the join table).
I have most functionality working, however the only issue that still bugs me is that I had to create an f.input for :ingredient that when editing the recipe shows values, such as the below image and not the desired name of the associated ingredient.
Other than that, I can dynamically create, delete and update all ingredient associations. Any tips are much appreciated. Here my code:
Gemfile
gem 'rails', '4.0.2'
gem 'jquery-rails'
gem 'jquery-ui-rails'
gem 'simple_form'
gem "cocoon"
gem 'rails4-autocomplete'
Recipe.rb
class Recipe < ActiveRecord::Base
has_many :recipe_entries, :dependent => :destroy
has_many :ingredients, through: :recipe_entries
accepts_nested_attributes_for :recipe_entries,:allow_destroy => true
accepts_nested_attributes_for :ingredients
end
RecipeEntry.rb
class RecipeEntry < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
Ingredient.rb
class Ingredient < ActiveRecord::Base
has_many :recipe_entries
has_many :recipes, through: :recipe_entries
end
Recipes form:
<%= simple_form_for(#recipe) do |f| %>
<% if #recipe.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>
<ul>
<% #recipe.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.input :name %>
<%= f.input :description %>
<div id="recipe_entries">
<%= f.simple_fields_for :recipe_entries, :input_html => { :class => "form_inline" } do |entry|%>
<% render 'recipe_entry_fields', :f => entry %>
<% end %>
<%= link_to_add_association 'add recipe entry', f, :recipe_entries%>
</div>
<%= f.button :submit %>
<% end %>
recipe_entry_partial
<div class="nested-fields">
<% #it=f.options[:child_index] %>
<%= f.input :ingredient_id, as: :hidden, input_html: {id: "ingredient_id#{#it}"} %>
<%= f.input :ingredient, :url => autocomplete_ingredient_name_recipes_path, :as => :autocomplete, :input_html => {id_element: "#ingredient_id#{#it}"}, placeholder: "Enter ingredient..." %>
<%= f.input :quantity %>
<%= link_to_remove_association "remove entry form", f %>
</div>
Update
I believe that the easiest way of solving the edit display issue is to pre-insert the correct value in the f.input containing the autocomplete. e.g.
<%= f.input :ingredient, :url => autocomplete_ingredient_name_recipes_path, :as => :autocomplete, :input_html => {id_element: "#ingredient_id#{#it}", value: f.object.ingredient.name}, placeholder: "Enter ingredient..." %>
Unfortunate, this only works up to the recipe entries association. This means that I can still get values for e.g. f.object.ingredient_id which returns the correct result. However, I can't get the associated ingredient any more. I seem to be able to get the associated ingredient but when I try to e.g. call the .name method on it, I receive a nil error.
Strangely, I can get the ingredient_id by calling f.object.ingredient_id and can retrieve an Ingredient from the Model by calling e.g. Ingredient.get(1).name. BUT, I cannot dynamically link the two values. Ingredient.get(f.object.ingredient_id) returns a nil error. Also with to_string or to_integer calls.
OK, the solution to a full autocomplete within the nested form is as follows:
<div class="nested-fields">
<% #it=f.options[:child_index] %>
<%= f.input :ingredient_id, as: :hidden, input_html: {id: "ingredient_id#{#it}"} %>
<%= f.simple_fields_for :ingredients, f.object.ingredient do |ii| %>
<%= ii.input :name, :url => autocomplete_ingredient_name_recipes_path, :as => :autocomplete, :input_html => {id_element: "#ingredient_id#{#it}"}, placeholder: "Enter ingredient..." %>
<% end %>
<%= f.input :quantity %>
<%= link_to_remove_association "remove entry form", f %>
</div>
The trick was to place the f.input autocomplete within another fields_for tag and make sure to specify the context for each recipe_entry with a f.object.ingredient. Other than this partial, I didn't have to make any other changes!
Related
I'm using Rails 6.0.2.2, Foundation, and wanted to do some nested forms using Cocoon. Here are my models:
class Adventure < ApplicationRecord
alias_attribute :pcs, :player_characters
has_many :player_characters, dependent: :destroy
accepts_nested_attributes_for :player_characters
end
class PlayerCharacter < ApplicationRecord
belongs_to :adventure
end
And here my views:
# _form.html.erb
<%= simple_form_for #adventure do |f| %>
<%= f.input :email %>
<%= f.input :test %>
<h3>PCs</h3>
<%= f.simple_fields_for :player_characters do |player_character| %>
<%= render 'player_character_fields', f: player_character %>
<% end %>
<div class='links'>
<%= link_to_add_association 'Add PC', f, :player_characters %>
</div>
<%= f.submit %>
<% end %>
# _player_character_fields.html.erb
<div class='nested-fields'>
<%= f.input :path %>
<%= f.input :race %>
<%= f.input :name %>
<%= f.input :player_name %>
<%= link_to_remove_association "remove PC", f %>
</div>
Neither the render nor the link_to display anything. I tried creating a different partial and if I render it outside of the simple_fields_for it works normally, but as soon as move it inside, it stops.
I tried all the solutions mentioned here https://github.com/nathanvda/cocoon/blob/master/app/assets/javascripts/cocoon.js:
create a file in app/javascript/src/cocoon.js
yarn add cocoon-js
yarn add github:nathanvda/cocoon#c24ba53
but nothing worked, and I didn't get any error messages in the console log.
I have three models in my rails 5 application... What I want to do is to be able to create a note on the person's "show" view, then the note should automatically link to that person and any other person I choose to attach the note to.
Here's where I got up to
Note
class Note < ApplicationRecord
has_many :person_notes
has_many :people, through: :person_notes
accepts_nested_attributes_for :person_notes
end
Person Note (Join table)
class PersonNote < ApplicationRecord
belongs_to :person
belongs_to :note
end
Person
class Person < ApplicationRecord
has_many :person_notes
has_many :notes, through: :person_notes
accepts_nested_attributes_for :person_notes
accepts_nested_attributes_for :notes
end
In my "show" section of my "people" controller I have the following:
def show
#notep = Note.new()
#person.notes << #notep
end
This is creating a join of a new note to my person record every time I open the page (obviously i only want the join to happen on the note I have created.)
I have rendered this in a modal as a partial in my "show" view (there is an "end" and a submit button but it's in between 3 divs and I know they weren't the cause of the issue so i didn't include them.:
<%= simple_form_for(#notep, remote: true) do |f| %>
<%= f.error_notification %>
<%= f.input :subject %>
<%= f.input :note %>
<%= f.input :date, html5: true %>
<div class="input-field">
<%= f.check_box :isalert, :id => "alert" %>
<%= f.label :isalert, :for => "alert" %>
</div>
<div class="input-field">
<%= f.check_box :archived, :id => "archived" %>
<%= f.label :archived, :for => "archived" %>
</div>
<div class="input-field">
<%= f.check_box :deleted, :id => "deleted" %>
<%= f.label :deleted, :for => "deleted" %>
</div>
<%= f.input :datecreated, html5: true %>
<%= f.input :user_id %>
<%= f.simple_fields_for :person_notes do |builder| %>
<%= builder.association :person, label_method: :lstfstfullname %>
<%= builder.input :deleted %>
<%= builder.input :datecreated, html5: true %>
<%= builder.input :user_id %>
<% end %>
You can probably tell I'm a total newb, but I'd appreciate any help I can get.
Thanks,
Leah
If I'm understanding this correctly, you'll want a separate Notes controller and create action to handle note creation. Then in your People show view, you can add a form and input field that submits to NotesController#create.
I am working on a dynamically nested form using the cocoon gem. I have two models
class CrossTable < ActiveRecord::Base
attr_accessible :title, :table_name, :database, :folder_label_id, :foreign_fields_attributes
belongs_to :folder_label
has_many :foreign_fields
accepts_nested_attributes_for :foreign_fields
validates :title, :table_name, :database, :folder_label_id, presence: true
end
class ForeignField < ActiveRecord::Base
attr_accessible :cross_table_id, :column_name, :description
belongs_to :cross_table
has_many :filter_sets
end
I have cocoon and jquery-rails in the gemfile
I added //=require cocoon to the application.js file
And here is my form partial
<%= simple_form_for #table do |f| %>
<%= f.input :title %>
<%= f.input :folder_label_id, :collection => #folders, :label_method => :title, :value_method => :id %>
<br><br>
<%= f.input :table_name %>
<%= f.input :database %>
<%= f.simple_fields_for :foreign_fields do |fields| %>
<%= render 'foreign_field_fields', :f => fields %>
<div id='links'>
<%= link_to_add_association 'Add Field', f, :foreign_fields %>
</div>
<% end %>
<%= f.button :submit %>
<% end %>
#table is an instance of the cross table model. Nothing in the foreign_field_fields partial shows up and link_to_add_association does nothing, and I get no errors. How can I start debugging this? Does anyone spot an error?
You wrote the link_to_add_association inside the simple_fields_for, which will loop over all :foreign_fields and execute the given block. So if there are no foreign-fields yet, the link_to_add_association is never shown.
You should write your view as follows (as documented):
<%= simple_form_for #table do |f| %>
<%= f.input :title %>
<%= f.input :folder_label_id, :collection => #folders, :label_method => :title, :value_method => :id %>
<br><br>
<%= f.input :table_name %>
<%= f.input :database %>
<%= f.simple_fields_for :foreign_fields do |fields| %>
<%= render 'foreign_field_fields', :f => fields %>
<% end %>
<div id='links'>
<%= link_to_add_association 'Add Field', f, :foreign_fields %>
</div>
<%= f.button :submit %>
<% end %>
Hope this helps.
I'm actually trying cocoon gem(by nathanvda) along with simple_form gem to dynamically add and remove fields on a form .I don't understand why the nested form doen't show up on my forms.Here's the code.(I'm newbie of course).Please can I have some help.
menu/_form.html.erb
<%= simple_form_for #menu do |f| %>
<%= f.input :name %>
<%= f.input :price %>
<%= f.simple_fields_for :drinks do |drink| %>
=render "drink_fields", :f => drink.links %>
<%= link_to_add_association "Add drink"%>
<%end%>
<%= f.button :submit %>
<%end%>
menu/_drink_fields.html.erb
.nested-fields
<%= f.input :name %>
<%= link_to_remove_association "remove drink", f %>
these are the models
class Drinks < ActiveRecord::Base
belongs_to :menu
end
class Menu < ActiveRecord::Base
attr_accessible :drinks_attributes
has_many :drinks, :dependent => :destroy
accepts_nested_attributes_for :drinks
end
in assets/javascripts/application.js
//= require cocoon
in layouts/application.html.erb
<%= javascript_include_tag :cocoon %>
Your nested form is wrong, you are mixing haml and erb. If you have no personal preference yet, I would recommend using haml, I find it much easier and cleaner than erb.
All that aside, your menu/_drink_fields.html.erb should look as follows:
<div class='nested-fields'>
<%= f.input :name %>
<%= link_to_remove_association "remove drink", f %>
</div>
Also, inside your menu/_form.html.erb you should write the following:
<%= f.simple_fields_for :drinks do |drink| %>
<%= render "drink_fields", f: drink %>
<% end %>
<%= link_to_add_association "Add drink", f, :drinks %>
Hope this helps.
Thanks for the head ups nathanvda.I finally find a gem to handle nested fields, Here is the link: https://github.com/lailsonbm/awesome_nested_fields.
Update: answered here.
There are lots of good questions and answers here and on the interweb about getting nested_form, collection_select, accepts_nested_attributes_for and fields_for to play nicely together, but I'm still stumped. Thanks in advance if you can help me.
Aim: To generate a new isbn record. An isbn can have many contributors. I am successfully using the ryanb nested_form gem to dynamically produce new contributor fields on a form, as required. One of these fields uses a collection_select drop down of all the name records in Contributor. When the new record is created, the many contributor ids need to be written to the join table (contributors_isbns).
I have got bits of this working, but only to the point where I can save a single contributor ID to the new record in the isbns table. I can't seem to get anywhere in writing any data to the join table.
I have three models. Contributors and Isbns have a many to many relationship, which I've done using has_many :through. An isbn can have many contributors, and a contributor can have many isbns. They are joined via contributors_isbn.
isbn.rb
attr_accessible :contributor_id
has_many :contributors, :through => :contributors_isbns
has_many :contributors_isbns
accepts_nested_attributes_for :contributors
accepts_nested_attributes_for :contributors_isbns
contributor.rb
attr_accessible :isbn_id
has_many :contributors_isbns
has_many :isbns, :through => :contributors_isbns
accepts_nested_attributes_for :isbns
contributors_isbn.rb
class ContributorsIsbn
attr_accessible :isbn_id, :contributor_id
belongs_to :isbn
belongs_to :contributor
accepts_nested_attributes_for :contributors
In the isbns controller:
def new
#isbn = Isbn.new
#title = "Create new ISBN"
1.times {#isbn.contributors.build}
#isbn.contributors_isbns.build.build_contributor
end
(obviously I can't make my mind up on which build method to use.)
In the isbns new.html.erb view:
<%= nested_form_for #isbn, :validate => false do |f| %>
<h1>Create new ISBN</h1>
<%= render 'shared/error_messages', :object => f.object %>
<%= render 'fields', :f => f %>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
In the _fields partial, a version with a very plain text_field:
<%= field_set_tag 'Contributor' do %>
<%= f.link_to_add "Add Additional Contributor", :contributors %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
</li>
<%= f.fields_for :contributors_isbns, :validate => false do |contrib| %>
<li>
<%= contrib.label :id, 'contributors_isbns id' %>
<%= contrib.text_field :id %>
</li>
<% end %>
<li>
<%= f.label 'Contributor Role' %>
<%= f.text_field :descriptivedetail_contributor_contributorrole %>
</li>
<% end %>
And here, a fancier version which doesn't work either:
<%= f.fields_for :contributors_isbns, :validate => false do |contributors| %>
<li>
<%= f.label :personnameinverted, 'Contributor Name' %>
<%= f.collection_select(:contributor_id, Contributor.all, :id, :personnameinverted ) %>
</li>
<% end %>
This code uses the answer from here. Both result in a 'Missing block" error on the nested_form_for #isbn line.
Thanks so much again in advance.
Update: here is some info about the nested_form gem which might come in handy for looking at this sort of problem. And here's a [2009 but still relevant post][4] on accepts_nested_attributes_for.
Update 2: well, here's a thing. I've been poking around on a cut-down version of this in two different models, not using collection_select or has_many through, but just on a simple belongs_to / has_many association. The parent model is Contract and the child model is Istc. I couldn't even create a record through the nested form. However, after looking in the stack and googling the error message "Warning. Can't mass-assign protected attributes" I've just added :istcs_attributes to my :attr_accessible line and now I can add records. A rather crucial bit missing, and a case of RTFM, as it's right there in the gem readme. I'll update later to see if this works on the more complicated has_many through association.
Update 4: [Here][5] is another useful post about how to deal with a nil record error message.
Update 5: Slight detour - When I dynamically added a new set of fields to the form, one one of the child records was being created. Duh - I had the "Add" link inside the child forms area. Here's the before:
<%= f.fields_for :istcs do |istc_form| %>
<h4> Istc</h4>
<%= istc_form.label "istc name" %>
<%= istc_form.text_field :title_title_text %>
<%= istc_form.link_to_remove "[-] Remove this istc"%>
<%= f.link_to_add "[+] Add an istc", :istcs %>
<% end %>
and here's the after:
<%= f.fields_for :istcs do |istc_form| %>
<h4> Istc</h4>
<%= istc_form.label "istc name" %>
<%= istc_form.text_field :title_title_text %>
<%= istc_form.link_to_remove "[-] Remove this istc"%>
<% end %>
<%= f.link_to_add "[+] Add an istc", :istcs %>
Update 6, post-answer:
Oh noes. The collection_select isn't working. It's adding new contributor records, not using an existing one from the contributor model. Someone else had this problem too. Any ideas?
Huzzah! Here's the code which makes all this work. Bit verbose but didn't want to leave anything out. My main learnings:
you need to make the child attributes attr_accessible in the parent model
you need to make the parent and child ids attr_accessible in the join table model
it makes life easier if you build at least one child instance in the parent controller.
contributor.rb model
class Contributor < ActiveRecord::Base
attr_accessible #nothing relevant
has_many :contributors_isbns
has_many :isbns, :through => :contributors_isbns
isbn.rb model
class Isbn < ActiveRecord::Base
attr_accessible :contributors_attributes, :contributor_id, :istc_id #etc
belongs_to :istc
has_many :contributors, :through => :contributors_isbns
has_many :contributors_isbns
accepts_nested_attributes_for :contributors #if you omit this you get a missing block error
contributors_isbn model
class ContributorsIsbn < ActiveRecord::Base
belongs_to :isbn
belongs_to :contributor
attr_accessible :isbn_id, :contributor_id
isbn controller
def new
#isbn = Isbn.new
#title = "Create new ISBN"
1.times {#isbn.contributors.build}
end
new.html.erb
<td class="main">
<%= nested_form_for #isbn, :validate => false do |f| %>
<h1>Create new ISBN</h1>
<%= render 'shared/error_messages', :object => f.object %>
<%= render 'fields', :f => f %>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
_fields.html.erb
<%= field_set_tag 'Identifier Details' do %>
<li>
<%= f.label 'Title prefix' %>
<%= f.text_field :descriptivedetail_titledetail_titleelement_titleprefix %>
</li>
<li>
<%= f.label 'Title without prefix' %>
<%= f.text_field :descriptivedetail_titledetail_titleelement_titlewithoutprefix %>
</li>
<li>
<%= f.label 'ISTC' %>
<%= f.collection_select(:istc_id, Istc.all, :id, :title_title_text, :prompt => true) %>
</li>
<% end %>
<%= field_set_tag 'Contributor' do %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
</li>
<%= f.fields_for :contributors, :validate => false do |contributor_form| %>
<li>
<%= contributor_form.label :personnameinverted, 'Contributor Name' %>
<%= contributor_form.collection_select(:isbn_id, Contributor.all, :id, :personnameinverted ) %>
</li>
<%= contributor_form.link_to_remove "[-] Remove this contributor"%>
<% end %>
<%= f.link_to_add "[+] Add a contributor", :contributors %>
<li>
<%= f.label 'Contributor Role' %>
<%= f.text_field :descriptivedetail_contributor_contributorrole %>
</li>
<% end %>