I have a nested model form with parent Foo and child Bar.
I followed http://railscasts.com/episodes/196-nested-model-form-revised through getting this setup for me. It was worked great. I can add and delete Bars easily, through javascript (per the railscast)
Background:
I have a required field of "name" in Bar.
Problem:
If the user leaves the name field blank and then deletes that Bar (through javascript), it does not let me save the form. I do not get any sort of notification. I believe because of the client side validation has kicked it on the required field that I deleted, the form won't let me submit to the server.
Foo.rb
validates :title, presence: true
has_many bars
accepts_nested_attributes_for :workouts, :allow_destroy => true
Bar.rb
validates :name, presence: true
views/foos/_form.html.haml
= simple_form_for(#foo) do |f|
.form_inputs
= f.input :title
= f.simple_fields_for :bars do |p|
= render "bar_fields", f: p
%br
= link_to_add_fields "Add Bar", f, :bars
%br
= f.button :submit
views/foos/_bar_fields.html.haml
%h4 Bar
= f.input :name
= f.input :description
= f.hidden_field :_destroy
= link_to "Delete Bar", '#'
helpers/application_helper.rb
def link_to_add_fields(name, f, association, css_class = "add_fields btn btn-sm btn-info icon-plus")
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: css_class, data: {id: id, fields: fields.gsub("\n", "")})
end
application.js
function remove_fields_(link) {
$(link).prev("input[type='hidden']").val("true");
$(link).closest(".fields").hide();
}
What am I doing wrong? Any workaround?
Don't see 'accepts_nested_attributes_for :bars' in your code example, be sure you use this.
Related
I've been following this railscast: https://www.youtube.com/watch?v=t_FNKR7jahM for learning how to implement nested attributes into my application. When I implement his code in the certain areas necessary I always get the same NoMethod error on the fields.
On the events show page where you can add songs, I am trying to add a 'Add Field' link that will add another group of songs(a field for artist, title, and genre).
Here is my EventController:
class EventsController < ApplicationController
def new
#event = Event.new
#event.songs.build
end
def index
#songs = Song.all
end
def show
#event = Event.find(params[:id])
#songs = #event.songs.paginate(page: params[:page])
end
def create
#event = current_user.events.build(event_params)
if #event.save
flash[:success] = "Event Created!"
redirect_to user_path(#event.user)
else
render 'welcome#index'
end
end
def destroy
end
private
def event_params
params.require(:event).permit(:name, :partycode, song_attributes: [:artist, :title, :genre])
end
end
Here is my Event model:
class Event < ApplicationRecord
belongs_to :user
has_many :songs, dependent: :destroy
accepts_nested_attributes_for :songs, allow_destroy: true
validates :name, presence: true
validates :partycode, presence: true, length: {minimum: 5}
end
Here is the show page in which the songs are added:
<section class="song_form">
<%= render 'shared/song_form' %>
<%= form_for #event do |f| %>
<%= f.fields_for :fields do |builder| %>
<%= render 'events/songs_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add Field", f, :fields %>
<% end %>
</section>
Here is the song_fields file:
<fieldset>
<%= f.select :field_type, %w[text_field check_box] %>
<%= f.text_field :artist, placeholder: "Artist" %>
</fieldset>
Here is the ApplicationHelper file :
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
Finally, here is my events coffee script file;
$(document).on 'click', 'form .add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
Sorry for the lengthy post, an answer to my question would be GREATLY appreciated. Let me know if there is anything else you need from me(note I am using rails 5). Cheers :)
Should be
f.fields_for :songs do |builder|
I have a model of a brand that can have many products that can have many categories. I have a nested form to create products that permit nested attributes for creating categories. But I can make it work.
class Brand < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :products, dependent: :destroy
validates :name, presence: true,
length: { maximum: 50 }
end
class Product < ActiveRecord::Base
belongs_to :brand
has_many :categories, dependent: :destroy
accepts_nested_attributes_for :categories
default_scope -> { order(created_at: :desc) }
validates :brand_id, presence: true
validates :name, presence: true,
length: { maximum: 50 }
private
def product_params
params.require(:product).permit(:name,
categories_attributes: [:name, :price])
end
end
class Category < ActiveRecord::Base
belongs_to :product
has_many :units, dependent: :destroy
validates :price, presence: true
validates :product_id, presence: true
validates :name, presence: true,
length: { maximum: 50 }
end
So my product controller is:
class ProductsController < ApplicationController
def new
#product = current_brand.products.new
#product.categories.build
end
def create
#product = current_brand.products.build(product_params)
if #product.save
redirect_to root_url
else
render 'new'
end
end
and my new view is like this:
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#product) do |f| %>
<%= render 'shared/error_messages_products' %>
<%= f.label :name, "Name:" %>
<%= f.text_field :name, class: 'form-control' %>
<%= link_to_add_fields "Add Category", f, :categories %>
<%= f.submit "Add Product", class: "btn btn-primary" %>
<% end %>
</div>
</div>
and my category partial is:
<fieldset>
<%= f.label :name, "Category Name" %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :price, "Price" %>
<%= f.text_field :price, class: 'form-control' %>
<hr>
</fieldset>
I have the link_to_add_fields helper in my application helper:
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
That allows me to use some Javascript to add category fields with:
jQuery ->
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
But when I try to add a product with any number of categories in this example 2, I fail to create the products and the categories. I get the error from my form and object error:
The form contains 1 error:
Categories product can't be blank
The params I get from this submition are:
{"utf8"=>"✓", "authenticity_token"=>"IO8GFcv1auFVh/ZNypONI78XQrY2Ntm07cMrrjmq51ogwppbsb1sNyN/ynKY+Pdb/lyniED9O6jFRkLKsvu2jQ==", "product"=>{"name"=>"Product Example", "categories_attributes"=>{"1467231299616"=>{"name"=>"Category Example 1", "price"=>"1234"}, "1467231300745"=>{"name"=>"Category Example 2", "price"=>"1234"}}}, "commit"=>"Agregar Producto", "controller"=>"products", "action"=>"create"}
I don't understand why the category and the product are not associating correctly.
After a while of experimenting I found that the answer is to remove the validation of product_id from the category model. Like this:
class Category < ActiveRecord::Base
belongs_to :product
has_many :units, dependent: :destroy
validates :price, presence: true
validates :name, presence: true,
length: { maximum: 50 }
end
I am in the early stages of creating an app, and am just putting some basic code in place. Here is the current code...
app/views/cards/front.html.erb
<%= form_for(front_of_card_path) do |f| %>
<%= f.fields_for :competency_templates do |builder| %>
<%= render 'add_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add New Tag", f, :skill %>
<% end %>
routes
controller :cards do
get '/front', action: 'front', as: 'front_of_card'
post '/save', action: 'create', as: 'save_card'
get '/my_contact_info', action: 'back', as: 'back_of_card'
put '/save', action: 'update', as: 'save_card'
get '/my_card', action: 'show', as: 'card'
end
controller
def create
#skill= Skill.new(params[:skill])
#tag = Tag.new(params[:tag])
#tag.save
#skill.tag_id = #tag.id
#skill.save
redirect_to front_of_card_path, notice: 'Skill was successfully created.'
#get user/session
#save skills & tags
end
cards.js.coffee
jQuery ->
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
app_helper
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
So right now this code gives me two text fields. One for the a tag name and another for a tag weight, and the controller inserts everything in the DB. I would like use some javascript to dynamically add as many of these tag/weight fields as I like. Everything I've found seems to focus on nested attributes. Any ideas appreciated.
Update
Added more code to flesh this out. The issue I am having is the 3rd variable I am passing in on this line...
<%= link_to_add_fields "Add New Tag", f, :skill %>
It does not like ':skill', but I am not sure what I should be passing here.
So here is what I came up with...here are my two models...
class Skill < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :weight
end
class Tag < ActiveRecord::Base
has_many :skills
attr_accessible :name
end
I'm calling a partial from app/views/skills/_form.html.erb and using a js tag to add new fields. Also note that I am re-rendering the partial, then hiding it in the last div tag.
<div id="skillSet">
<%= render partial: "skills_form" %>
</div>
Add New Tag
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<div class="hide" id="new_skills_form">
<%= render partial: "skills_form", locals: {skill: false} %>
</div>
The partial is pretty simple. All I am doing here is storing the values in an array...
<div class="skillsForm">
<%= label_tag 'tag' %>
<%= text_field_tag 'tags[]' %>
<%= label_tag 'weight' %>
<%= text_field_tag 'weights[]' %>
</div>
...here is the javascript...real straight forward, just say when #addNewTag is clicked, appeand #new_skills_form to #skillSet
$(document).ready(function(){
$("#addNewTag").click(function(){
$("#skillSet").append($("#new_skills_form").html());
});
});
...and finally the controller action decontructs the arrays, and saves them...
def create
#skill = Skill.new(params[:skill])
tags = params[:tags]
weights = params[:weights]
tags.each_with_index do |tag, index|
tag = Tag.create :name => tag
Skill.create :tag_id => tag.id, :weight => weights[index]
end
end
UPDATE 6/21/12
I have a form in rails that is working similar to an e-commerce checkout.
The user selects a start hour, end hour, date, time and price preference(hourly, weekly, daily, etc.) in the form. The product already has a set price in the database that I'm converting to (hourly, weekly, daily, etc.) that I'd like to change based on the price preference and then get submitted with the rest of the form.
I've been following Ryan's screencast on dynamic select menus but I'm wasn't sure how I'd fit his demo into my application since I don't want the user to select the price just the measure (daily/hourly/etc.)
Gear has_many line_items
line_items belongs to Gear and Carts
Below is my code:
Gear Model (where I've created the price variables)
class Gear < ActiveRecord::Base
attr_accessible :title, :size, :price, :sub_category_id, :user_id, :image, :image_a, :remote_image_url, :color, :year, :latefee, :cancellation, :minrental, :policy, :about, :address, :city, :state, :zip
belongs_to :user
belongs_to :sub_category
has_one :category, :through => :sub_category
has_many :comments, :dependent => :destroy
has_many :line_items
has_many :calendars
require 'carrierwave/orm/activerecord'
mount_uploader :image, GearpicUploader
mount_uploader :image_a, GearpicUploader
before_destroy :ensure_not_referenced_by_any_line_item
validates :title, presence: true
validates :size, presence: true
validates :price, presence: true
validates :sub_category_id, presence: true
validates :user_id, presence: true
def hourly_price
price/24
end
def weekly_price
price*7
end
def monthly_price
weekly_price*4
end
end
View
<div class="gearside_date_top">
<%= form_for LineItem.new do |f| %>
<p><%= render :partial => "price" %> /
<%= f.select :day_or_hour, [['day', 1], ['hour', 2]], :id => 'day_or_hour' %>
</p>
</div>
Partial Given to View
<em>from</em>
<span id="gear_daily" style="font-size: 190%; color: #94cbfc;">$<%= #gear.price %></span>
<span id="gear_hourly" style="font-size: 190%; color: #94cbfc; display:none;">$<%= #gear.hourly_price %></span>
Javascript
$('#day_or_hour').change(function() {
var $index = $('#day_or_hour').index(this);
if($('#day_or_hour').val() = '2') {
$('#gear_hourly').show();
}
else {
$('#gear_daily').show();//else it is shown
}
});
I can't seem to get it working.
You can put your price in a partial:
<div class="gearside_date_top">
<%= form_for LineItem.new do |f| %>
<%= render :partial => "price" %>
<%= f.select :day_or_hour, [['day', 1], ['hour', 2]], :id => 'day_or_hour' %>
_price.html.erb
<em>from</em><span style="font-size: 190%; color: #94cbfc;">$<%= #gear.price %></span>
In your line_item.js you put the code to update the price:
line_item.js
$('#day_or_hour').change(function() {
$.get("/cart/update_price", {option: $(this).val()}, function(data) {
}
}
On cart_controller.rb you create the method update_price that makes a call to line_item.rb total_price.
Hope you understand.
edit
It would look like something like this.
def update_price
#gear.price = LineItem.total_price(#gear)
render :partial => 'price'
end
attempting to use this railscast as a guide:
http://railscasts.com/episodes/197-nested-model-form-part-2?view=asciicast
and running into this error:
`#search[queries_attributes][new_queries][queries' is not allowed as an instance variable name
models:
#search.rb
class Search
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :user
field :name, :type => String
embeds_many :queries
accepts_nested_attributes_for :queries, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
#query.rb
class Query
include Mongoid::Document
field :columns, :type => String
field :types, :type => String
field :keywords, :type => String
embedded_in :search, :inverse_of => :queries
end
searches controller :
def new
#search = Search.new
#search.queries.build
#3.times { #search.queries.build }
end
_form.html.haml partial:
= form_for(#search) do |f|
= f.label 'Name this search'
= f.text_field :name, :class => 'text_field'
= render :partial => 'query', :collection => #search.queries, :locals => { :f => f }
= link_to_add_fields "Add Query", f, :queries
.actions
= f.submit
_query.html.haml partial:
.fields
= f.fields_for "queries[]", query do |q|
= q.label 'Search Datatype'
= q.select :types, Query::TYPES
= q.label 'In Column'
= q.select :columns, #search.record_columns
= q.label 'For Keywords:'
= q.text_field :keywords, :class => 'text_field'
= q.hidden_field :_destroy
= link_to_function "remove", "remove_fields(this)"
searches helper:
module SearchesHelper
def link_to_add_fields(name, f, association)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(association.to_s.singularize , :f => builder)
end
link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
end
end
javascript:
function remove_fields(link) {
$(link).prev("input[type=hidden]").val("1");
$(link).closest(".fields").hide();
}
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g");
$(link).parent().before(content.replace(regexp, new_id));
}
when the line:
= link_to_add_fields "Add Query", f, :queries
is commented out, it works as expected, but I need to be able to add additional queries
via this helper.
for testing multi queries I am triggering the creation in the controller 3.times
also in the error message the last "]" is stripped off.. not sure what I am missing
sorry for all the tags, but not sure where the issue lies
looks like this was the fix:
= f.fields_for :queries, query do |q|
Two thoughts:
I would name the Query class something else, it probably conflicts with some stuff inside mongoid as per the error message you specified:
#search[queries_attributes][new_queries][queries' is not allowed as an instance variable name]
Also googling your problem I came across this:
http://www.jtanium.com/2009/11/03/rails-fields_for-is-not-allowed-as-an-instance-variable-name/
Something must be nil where it shouldn't be.