I'm having almost exactly the same problem as here:
Rails has_many :through saving additional fields
However I can't figure out what's missing in my code. The basic structure is following: there are Products and they may have additional services (like cleaning or sharpening). The basic price for each service is same, however products may have different coefficients. Let's say, Product 1 may have sharpening basic price * 1.5 and Product 2 may have sharpening basic price * 2. The join table is called somewhat stupid - servization, I couldn't figure out a decent name.
products_controller.rb:
def update
#product.update!(product_params)
redirect_to #product
end
def product_params
params.require(:product).permit(
:title, :description, :advertising_text, :fancy_quote, :hot, :hotpic, :product_size_ids,
{ volume_ids: [] }, { color_ids: [] }, { addservice_ids: [] }, :category_id, :subcategory_id,
options_attributes: [:size, :weight, :price, :material, :product_id],
images_attributes: [ :image, :product_id ],
servizations_attributes: [:coefficient, :product_id, :addservice_id]
)
end
product model (i cut out the irrelevant parts) :
class Product < ActiveRecord::Base
has_many :servizations
has_many :addservices, through: :servizations
accepts_nested_attributes_for :servizations
before_destroy :ensure_not_referenced_by_any_line_item
end
additional service model:
class Addservice < ActiveRecord::Base
has_many :servizations
has_many :products, through: :servizations
end
servization model
class Servization < ActiveRecord::Base
belongs_to :product
belongs_to :addservice
end
Here is my edit.html.haml:
=form_for #product do |f|
=f.label 'Title'
=f.text_field :title
%p
=f.label 'Colors'
=collection_check_boxes(:product, :color_ids, Color.all, :id, :value )
%p
=f.label 'Additional services'
=collection_check_boxes(:product, :addservice_ids, Addservice.all, :id, :title)
=f.fields_for :servization do |s|
=s.label 'Coefficient'
=s.text_field :coefficient
%p
=f.submit class: 'btn btn-default'
I have no issues saving plain services or just the colors, however when I try to edit and save the coefficient, then I receive following error:
Processing by ProductsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"jLN59PIOgpaIQtg2/rVYcK/xUOCFzSMLosfUhOslY9tNNhjFbh16Aa1/qT/8u7LHuhNTLGr2SB7b/BitekuDJQ==", "product"=>{"title"=>"Power MI", "color_ids"=>["1", "2", "3", ""], "addservice_ids"=>["1", ""], "servization"=>{"coefficient"=>"1.5"}}, "commit"=>"Update Product", "id"=>"7"}
Product Load (0.3ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT 1 [["id", 7]]
Unpermitted parameter: servization
Servization structure:
Servization(id: integer, product_id: integer, addservice_id: integer, coefficient: float, created_at: datetime, updated_at: datetime)
I don't get it... my options are saved well, images too. But additional services refuse to save.
Edit 1:
I think I might be close to the solution. The problem is the correct linking in the form. This is what I came up with so far:
=form_for #product do |f|
%p
=f.label "Additional Services"
%br
-Addservice.all.each do |addservice|
=check_box_tag "product[addservice_ids][]", addservice.id, #product.addservice_ids.include?(addservice.id)
=addservice.title
=text_field_tag "product[servizations][coefficient][]", addservice.servizations.where(product_id: #product.id)[0].coefficient
%br
%p
=f.submit class: 'btn btn-default'
This is the patch request:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"wD8uMSu0TxvumAWcs1V6GtAFeNgYhbEPEh8HuV8RWucBuk8At6e3jMuldJWxW5Ctxed7FPe+2hprJMuQzn+6GQ==", "product"=>{"title"=>"Флешеченка на тристапяцот гигов", "color_ids"=>["1", "2", "3", ""], "addservice_ids"=>["1", "2"], "servizations"=>{"coefficient"=>["4", "5"]}}, "commit"=>"Update Product", "id"=>"5"}
However now I'm getting again
Unpermitted parameter: servizations
Error
The problem is here in this line
=f.fields_for :servization do |s|
which should be
=f.fields_for :servizations do |s|
Update:
You should permit :id for update to work correctly
def product_params
params.require(:product).permit(
:title, :description, :advertising_text, :fancy_quote, :hot, :hotpic, :product_size_ids,
{ volume_ids: [] }, { color_ids: [] }, { addservice_ids: [] }, :category_id, :subcategory_id,
options_attributes: [:size, :weight, :price, :material, :product_id],
images_attributes: [ :image, :product_id ],
servizations_attributes: [:id, :coefficient, :product_id, :addservice_id]
)
end
Related
Unpermitted parameter is displayed when I try to create new data.
Althogh there are many similar questions, I can't find out how to solve.
What I'd like to do is to save the value in the amounts table when create schedule.
log
Processing by SchedulesController#create as HTML
Parameters: {"utf8"=>"?", "authenticity_token"=>"xxx", "schedule"=>{"title"=>"test title", "departure_date"=>"2016-07-06", "rooms_attributes"=>{"0"=>{"schedule_id"=>"", "amounts_attributes"=>{"0"=>{"schedule_id"=>"", "room_id"=>""}}}}}, "commit"=>"Create my schedule"}
Parameters: {"utf8"=>"?", "authenticity_token"=>"xxx", "schedule"=>{"title"=>"test title", "departure_date"=>"2016-07-06", "rooms_attributes"=>{"0"=>{"schedule_id"=>"", "amounts_attributes"=>{"0"=>{"schedule_id"=>"", "room_id"=>""}}}}}, "commit"=>"Create my schedule"}
User Load (120.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
User Load (120.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Unpermitted parameter: amounts_attributes
models
schedule.rb
class Schedule < ActiveRecord::Base
belongs_to :user
has_many :rooms, inverse_of: :schedule, dependent: :destroy
has_many :amounts, inverse_of: :schedule, dependent: :destroy
accepts_nested_attributes_for :rooms, allow_destroy: true
accepts_nested_attributes_for :amounts, allow_destroy: true
rooms.rb
class Room < ActiveRecord::Base
belongs_to :schedule, inverse_of: :rooms
has_many :events, inverse_of: :room, dependent: :destroy
has_many :amounts, inverse_of: :room, dependent: :destroy
accepts_nested_attributes_for :events, allow_destroy: true
accepts_nested_attributes_for :amounts, allow_destroy: true
amount.rb
class Amount < ActiveRecord::Base
belongs_to :schedule, inverse_of: :amounts
belongs_to :room, inverse_of: :amounts
belongs_to :event, inverse_of: :amounts
end
controller
schedule_controller.rb
def new
#schedule = Schedule.new
room = #schedule.rooms.build
room.amounts.build
end
def create
#schedule = current_user.schedules.build(schedule_params)
if #schedule.save
flash[:success] = "schedule created!"
redirect_to schedule_path(#schedule)
else
render 'new'
end
end
...
def schedule_params
params.require(:schedule).permit(
:title, :departure_date,
rooms_attributes: [
:id, :_destroy, :room, :room_address, :schedule_id, :day,
amounts_attributes: [
:schedule_id, :room_id, :event_id, :id, :_destroy, :ccy, :amount
]
]
)
end
view
/schedules/new.html.erb
...
<%= form_for(#schedule) do |f| %>
<%= render 'schedule_form', f: f %>
<%= f.submit "Create my schedule", class: "btn btn-primary" %>
...
/schedules/ _schedule_form.html.erb
It works before adding 4 lines for <%= room.fields_for(:amounts) do |amount| %>.
...
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
...
<%= f.fields_for(:rooms) do |room| %>
<%= room.hidden_field :schedule_id %>
<%= room.fields_for(:amounts) do |amount| %> # my app works before add these 4 lines.
<%= amount.hidden_field :schedule_id %> #
<%= amount.hidden_field :room_id %> #
<% end %> #
<% end %>
It would be appreciated if you could give me any suggestion.
You haven't closed the room_attributes array parameter.Please close it and open the amount's attribute nested parameters. Also the schedule_id in amount's attributes.
def schedule_params
params.require(:schedule).permit(:title,
:departure_date,{rooms_attributes: [:id, :_destroy, :room, :room_address, :schedule_id, :day,
{ amounts_attributes: [:itinerary_id, :room_id, :event_id, :id, :_destroy, :ccy, :amount]}
]}
)
end
Following from the logged params, your amounts_attributes is an array of objects and should be declared as such in your permit method argument:
def schedule_params
params.require(:schedule).permit(
:title, :departure_date,
rooms_attributes: [{
:id, :_destroy, :room, :room_address, :schedule_id, :day,
amounts_attributes: [{
:itinerary_id, :room_id, :event_id, :id, :_destroy, :ccy, :amount
}]
}]
)
end
The rooms_attributes follows similar structure and needs to be corrected as well.
Take a look at the nested parameters example in ActionController::Parameters docs.
I think your params should look like this:
def schedule_params
params.require(:schedule).permit(
:title, :departure_date,
rooms_attributes: [
:id, :_destroy, :room, :room_address, :schedule_id, :day,
amounts_attributes: [ :id, :_destroy,
:itinerary_id, :room_id, :event_id, :id, :_destroy, :ccy, :amount]
]
)
end
So, for your nested model, even if it's deeply nested, you still need :id and :_destroy.
Let me know how that goes for you.
I'm following this tutorial - http://alagram.github.io/blog/2013/11/04/uploading-multiple-files-with-carrierwave-and-a-nested-form/
And receive this error:
TypeError at /products
no implicit conversion of String into Integer
Better errors highlights this line:
#product = Product.new(product_params)
This is my POST log:
Started POST "/products" for ::1 at 2015-08-13 11:59:24 +0300
Processing by ProductsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"sYWHbXoRQtxsUTU+JmJ4fZagrSoQjE779QPrgQdI1Fn0C0O2sa5vPdU7CSnMSkbH2ulDQcTjrKknFLPEIw7NXw==", "product"=>{"title"=>"hdgsdf", "description"=>"", "advertising_text"=>"", "fancy_quote"=>"", "category_id"=>"2", "subcategory_id"=>"1", "volume_ids"=>[""], "options_attributes"=>{"0"=>{"price"=>"", "size"=>"", "weight"=>"", "material"=>""}, "option_pics_attributes"=>[{"product_image"=>"00026708.jpg"}, {"product_image"=>"475614.jpg"}]}}, "commit"=>"Create Product"}
Unpermitted parameter: 0
Completed 500 Internal Server Error in 2ms
I assume, that general logic is correct since there are indeed two images in my params. My further assumption is, that my strong params are somewhat not correct. I've read, that to allow arrays you should use a hash in the strong params and to allow hash you should use an array. I tried all possible combinations and right now I'm confused.
Strong params:
def product_params
params.require(:product).permit(
:title, :description, :advertising_text, :fancy_quote, :product_size_ids, { volume_ids: [] }, :category_id, :subcategory_id,
options_attributes: [:size, :weight, :price, :material, :product_id,
option_pics_attributes: [ :product_image, :option_id ] ])
end
Rest of my code for the record:
new.html.haml - form
=form_for #product, url: products_path do |f|
%p
=f.label :title
=f.text_field :title
%br
=f.label :description
=f.text_area :description
%br
=f.label :advertising_text
=f.text_area :advertising_text
%br
=f.label :fancy_quote
=f.text_area :fancy_quote
%br
=f.label :category_id
=f.collection_select :category_id, Category.all, :id, :title, { prompt: 'Please select category' }
%br
=f.label :subcategory_id
=f.collection_select :subcategory_id, Subcategory.all, :id, :title, { prompt: 'Please select sub-category' }
%br
=f.label 'Product Size'
=f.collection_check_boxes(:volume_ids, Volume.all, :id, :value)
%p
= f.fields_for :options do |builder|
=builder.label :price
=builder.text_field :price
%br
=builder.label :size
=builder.text_field :size
%br
=builder.label :weight
=builder.text_field :weight
%br
=builder.label :material
=builder.text_field :material
%br
=fields_for :option_pic, OptionPic.new, html: { multipart: true, id: "fileupload" } do |op|
= op.label 'Upload image'
= op.file_field :product_image, multiple: true, name: "product[options_attributes][option_pics_attributes][][product_image]", id: 'fileupload'
=f.submit id: 'submit-data'
products.coffee:
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
jQuery ->
$('#fileupload').fileupload()
add: (e, data) ->
data.context = $("#submit-data")
data.submit()
Models:
class OptionPic < ActiveRecord::Base
mount_uploader :product_image, ProductImageUploader
belongs_to :option
end
class Option < ActiveRecord::Base
belongs_to :product
has_many :option_pics
accepts_nested_attributes_for :option_pics
end
class Product < ActiveRecord::Base
belongs_to :category
belongs_to :subcategory
has_many :options
has_many :voluminazations
has_many :volumes, through: :voluminazations
accepts_nested_attributes_for :options
end
The basic logic for my app is following: I have a product (say, a t-shirt "mercury t-bone", this t-shirt can have many options (say, green, blue, yellow) and for each option I want to upload multiple pics (thus, option_pics - say 4 pics for the green option, 5 for blue option and 2 pictures for the yellow option)
I've followed Cocoon's instructions for creating a nested form for a has_many association but there's one thing that I can't seem to figure out. I'm able to add an existing genre to my project but when it comes to creating a completely new one the parameters for my genres aren't being permitted. When I refer to my rails console to debug the issue this is the message I receive upon update:
Unpermitted parameters: genre_attributes
What's strange is I've permitted both my join table genreships and my genre model along with having my movie model accept their nested attributes but still no luck. There has to be something I'm not doing in my movie_params method but not able to find what. I've literally tried to permit every possible attribute within this method and completely out of ideas on what could be the solution
Models
class Movie < ActiveRecord::Base
has_many :genreships
has_many :genres, through: :genreships
accepts_nested_attributes_for :genres, :reject_if => :all_blank, :allow_destroy => true
accepts_nested_attributes_for :genreships, :reject_if => :all_blank, :allow_destroy => true
extend FriendlyId
friendly_id :title, use: :slugged
end
class Genreship < ActiveRecord::Base
belongs_to :movie
belongs_to :genre
accepts_nested_attributes_for :genre, :reject_if => :all_blank
end
class Genre < ActiveRecord::Base
has_many :genreships
has_many :movies, through: :genreships
extend FriendlyId
friendly_id :name, use: :slugged
end
Movies Controller
def movie_params
params.require(:movie).permit(:title, :release_date, :summary, genreships_attributes: [:id, :genre_id, :_destroy], genres_attributes: [:id, :_destroy, :name])
end
Form
= simple_form_for #movie do |f|
.field
= f.input :title
.field
= f.input :release_date, label: 'Release Date', order: [:month, :day, :year], start_year: 1901
.field
= f.input :summary, as: :text
#genres
= f.simple_fields_for :genreships do |genreship|
= render 'genreship_fields', :f => genreship
= link_to_add_association 'add a genre', f, :genreships
.actions = f.submit 'Save', class: 'btn btn-default'
Genreship Partial
.nested-fields
#genre_from_list
= f.association :genre, :collection => Genre.order(:name), :prompt => 'Choose an existing genre'
= link_to_add_association 'or create a new genre', f, :genre
= link_to_remove_association "remove genre", f
Genre Partial
.nested-fields
= f.input :name
Here's what's shown in the rails console right after posting a new movie:
Started POST "/movies" for 127.0.0.1 at 2014-11-19 08:17:42 -0500
Processing by MoviesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"2/wqXhn/x73AdOOrSo49Q/OPAmrcVk0rLViJJNpIhps=", "movie"=>{"title"=>"", "release_date(2i)"=>"11", "release_date(3i)"=>"19", "release_date(1i)"=>"2014", "summary"=>"", "genreships_attributes"=>{"1416403056574"=>{"genre_attributes"=>{"name"=>"Comedy"}, "genre_id"=>"", "_destroy"=>"false"}}}, "commit"=>"Save"}
Unpermitted parameters: genre_attributes
You use genre_attributes while genres_attributes (plural form) is defined in your controller. Change it to:
def movie_params
params.require(:movie).permit(:title, :release_date, :summary, genreships_attributes: [:id, :genre_id, :_destroy], genre_attributes: [:id, :_destroy, :name])
end
I created a solution that in a sense deviates from Nathan's example code somewhat but gets the job done nonetheless. Simply moving this line from genreship_fields.html.slim to _form.html.slim did the trick:
link_to_add_association 'create a new genre', f, :genres
My partials now look like this:
Form
= simple_form_for #movie do |f|
.field
= f.input :title
.field
= f.input :release_date, label: 'Release Date', order: [:month, :day, :year], start_year: 1901
.field
= f.input :summary, as: :text
#genres
= f.simple_fields_for :genreships do |genreship|
= render 'genreship_fields', :f => genreship
| #{link_to_add_association 'add a genre', f, :genreships} | #{link_to_add_association 'create a new genre', f, :genres}
.actions = f.submit 'Save', class: 'btn btn-default'
Genreship Partial
.nested-fields
#genre_from_list
= f.association :genre, :collection => Genre.order(:name), :prompt => 'Choose an existing genre'
= link_to_remove_association "remove genre", f
Genre Partial
.nested-fields
= f.input :name
= link_to_remove_association "remove form", f
I'm using Rails 4.0.1 with Ruby 2.0.0.
I have a simple functionality of categories. Each category could belong to another one or be a root category (no parent). But if choose blank ('None') value from select, category can not be saved. Where is my mistake?
category.rb
class Category < ActiveRecord::Base
has_many :items
has_ancestry
validates :name, presence: true, length: { minimum: 3 }
mount_uploader :icon, IconUploader
end
categories_controller.rb
def create
#category = Category.new(category_params)
if #category.save
redirect_to admin_categories_path, notice: "Category was successfully created!"
else
render action: "new"
end
end
_form.html.slim
= form_for [:admin, #category] do |f|
= f.label :name
= f.text_field :name
= f.label :ancestry
= f.select :ancestry, Category.all.map {|p| [ p.name, p.id ] }, include_blank: 'None'
= f.label :icon
= f.file_field :icon
= f.submit nil
Transaction log
Started POST "/admin/categories" for 127.0.0.1 at 2014-01-11 12:18:35 +0600
Processing by Admin::CategoriesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"aZS2bO2HEy65cf2jQmm5BTy1VS/1Na1LBN4mHR3FYy4=", "category"=>{"name"=>"Example", "ancestry"=>""}, "button"=>""}
(0.1ms) begin transaction
(0.1ms) rollback transaction
I just came across the same problem. Instead of trying to set the ancestry field on your model, use parent_id instead.
So in your form, use this:
= f.label :parent_id
= f.select :parent_id, Category.all.map {|p| [ p.name, p.id ] }, include_blank: 'None'
Then in your controller, edit the category_params function to allow the attribute to be assigned:
def category_params
params.require(:category).permit(:name, :parent_id)
end
For root categories, the ancestry attribute must be nil, rather than an empty string, hence is why you can't save the category.
I have campaigns and messages. Campaigns have many messages and messages belongs to campaigns. I setup nested attributes for the models and in my view and i'm trying to create a message associated with the model. Here's the code:
class Campaign < ActiveRecord::Base
attr_accessible :message_id, :name, :group_id, :user_id, :messages_attributes
has_many :messages
belongs_to :group
belongs_to :user
accepts_nested_attributes_for :messages
end
class Message < ActiveRecord::Base
attr_accessible :body, :sent, :sent_at
belongs_to :user
belongs_to :campaign
has_many :responses
end
and the form:
= form_for #campaign, :html => {:class => 'form-horizontal'} do |f|
...removed error output code...
%legend
Enter the campaign information
.field
.control-group
%label.control-label
Campaign Name
.controls
= f.text_field :name
= f.collection_select(:group_id, current_user.groups.all, :id, :name, :include_blank => true)
= f.fields_for :message do |m|
= m.text_area :body, :rows => 3
.form-actions= f.submit "#{params[:action] == 'new' ? 'Create New Campaign' : 'Save Campaign'}", :class => 'btn btn-success'
I know it's probably something really simple but I keep getting a mass assignment issue with message. Here's the error:
ActiveModel::MassAssignmentSecurity::Error (Can't mass-assign protected attributes: message):
app/controllers/campaigns_controller.rb:18:in `update'
and finally the params that get created from the form:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"(removed)", "campaign"=>{"name"=>"Weekly Poker", "group_id"=>"1", "message"=>{"body"=>"s"}}, "commit"=>"Save Campaign", "id"=>"1"}
Because it's a has_many association it should be in plural form:
= f.fields_for :messages do |m|
Also, in the controller you will need:
def new
#campaign = Campaign.new
#campaign.messages.build
end