Strong params, nested attributes: Can't mass-assign - ruby-on-rails

I'm using Rails 4.0.2 and can't get strong parameters to work with nested attributes.
# Model
has_one :availability
accepts_nested_attributes_for :availability
# Controller
def base_content_params
params.require("base_content").permit(:enabled, :language, :title, :description,
availability_attributes: [:duration, :slots])
end
# View
form_for [app, base_content] do |form|
form.fields_for :availability do | a |
a.select 'duration', duration_values
end
form.fields_for :availability do | a |
a.select 'slots', [*(1..10)]
end
But I keep getting:
Can't mass-assign protected attributes for BaseContent: availability_attributes
>> base_content_params
=> {"enabled"=>"false", "title"=>"test", "description"=>"", availability_attributes"=>{"duration"=>"30", "slots"=>"10"}}
# request parameters
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"---", "base_content"=>{"enabled"=>"false", "language_id"=>"12938", "title"=>"test", "description"=>"", "content"=>"", "terms"=>"", "category"=>"product", "category_mode"=>"appy_booking", "responder_email"=>"", "price"=>"111.00", "price_per"=>"unit", "availability_attributes"=>{"start_at(5i)"=>"17:45:00", "id"=>"1", "duration"=>"30", "slots"=>"10"}, "reward_points"=>"100", "hash_tags"=>"", "lat"=>"", "lng"=>""}, "commit"=>"Save & Continue edit", "geocoder_lat"=>"0.0", "geocoder_lng"=>"0.0", "pac-input"=>"", "action"=>"update", "controller"=>"backend/base_contents", "app_id"=>"1898", "id"=>"16108"}
What am I missing here?
EDIT
# BaseContent Model
class BaseContent < ActiveRecord::Base
attr_accessible :enabled, :price, :price_per, :app, :menu,
# App Model
class App < ActiveRecord::Base
attr_accessible :name, :allow_search, :display_logo_badge, #... etc

This should be a comment, but it was too long.
From your comments, it's now clearer what the problem is. If you're using Rails 4.0.2, you have to switch to using strong_params in your controller:
#app/controllers/your_controller.rb
class YourController < ApplicationController
def create
#model = Model.new model_params
#model.save
end
private
def model_params
params.require(:model).permit(:attribute1, :attribute2)
end
end
I would strongly recommend you go through your models, remove any attr_accessible references, get rid of the protected_attributes gem and rebuild the functionality for strong params.
--
Another issue I can see is the way you're calling your form:
form_for [app, base_content] do |form|
Why are you nesting base_content (which should be an instance variable) under app? If anything, I'd expect something along the lines of...
form_for #base_content do |form|

Related

InvalidAuthenticityToken from form in Rails 5.2

I had a working Rails5.2 application that needed a database restructure to improve the organisation of some tables. Since this change, I've found that some of my forms fails consistently on my local machine with this error:
ActionController::InvalidAuthenticityToken in PayBandsController#create
I've had to change a lot in the application as part of the refactor, of course, but the tests are all passing.
I can avoid the issue by setting skip_before_action :verify_authenticity_token on the relevant controller (per previous SO threads) but this isn't a good fix, of course. I'd like to know what the root cause is and eliminate it.
The form works when it's on its own page, placed with render in the HTML.
The form works when I set the skip_before_action.
The form fails with an AuthenticityToken error otherwise - even though the same structure worked before.
This is all tested on my local machine with a valid session.
The form is created within a service object as the final line of a data table (so that I can add new lines to the table). Is this a potential cause of the problem - it's rendered in the service object rather than in the ERB? Although this approach worked fine before the refactor...
_table_data += ApplicationController.render(partial: 'datasets/pay_band_numbers_form', locals:{ _dataset_id: _dataset.id, _namespace: _key })
The partial is as follows and, as I said, works if placed on a page by itself (e.g. pay_bands/new) but not where it needs to be (on dataset/edit):
<%= form_for (PayBand.new), namespace: _namespace do |f| %>
<%= f.hidden_field :dataset_id, :value => _dataset_id %>
<%= f.text_field :label, :tabindex => 11, :required => true %>
<%= f.number_field :number_women, :tabindex => 12, :required => true %>
<%= f.number_field :number_men, :tabindex => 13, :required => true %>
<%= f.submit "✔".html_safe, :tabindex => 14, class: "button green-button checkmark" %>
<% end %>
The models involved are Dataset and PayBand, which are defined as follows.
class Dataset < ApplicationRecord
belongs_to :organisation
has_many :pay_bands, inverse_of: :dataset, dependent: :destroy
accepts_nested_attributes_for :pay_bands
default_scope -> { order(created_at: :desc) }
validates :name, presence: true
validates :organisation_id, presence: true
end
class PayBand < ApplicationRecord
belongs_to :dataset
has_many :ages_salaries_genders, inverse_of: :pay_band, dependent: :destroy
validates :dataset_id, presence: true
validates :label, presence: true
end
The relevant parts of the dataset controller are:
class DatasetsController < ApplicationController
protect_from_forgery with: :exception
load_and_authorize_resource
before_action :authenticate_user!
.
.
.
def edit
#dataset = Dataset.find(params[:id])
end
def update
#dataset = Dataset.find(params[:id])
if #dataset.update_attributes(dataset_params) && dataset_params[:name]
flash[:notice] = "Survey updated"
redirect_back fallback_location: dataset_path(#dataset)
else
flash[:alert] = "Attempted update failed"
redirect_to edit_dataset_path(#dataset)
end
end
.
.
.
private
def dataset_params
params.require(:dataset).permit(:name)
end
end
The pay-band controller is:
class PayBandsController < ApplicationController
protect_from_forgery with: :exception
# skip_before_action :verify_authenticity_token #DANGEROUS!!!!!
load_and_authorize_resource
before_action :authenticate_user!
def create
#pay_band = PayBand.new(pay_band_params)
if #pay_band.save
flash[:notice] = "Data saved!"
redirect_to edit_dataset_path(Dataset.find(#pay_band.dataset_id))
else
flash[:alert] = "There was an error. Your data was not saved."
redirect_back fallback_location: edit_dataset_path(Dataset.find(pay_band_params[:dataset_id]))
end
end
private
def pay_band_params
params.require(:pay_band).permit(:label, :number_women, :number_men, :avg_salary_women, :avg_salary_men, :dataset_id)
end
end
All of the above controller structure is identical to the working version of the app, before I fiddled with the data models!
===
Edit
I decided to check the headers, after some more research, so see if I could see the token value. However, the token value in the meta csrf-token header tag doesn't match any of the 3 different tokens in the 3 different forms on this page. But one works anyway (the simple form that's just rendered in the ERB template) and the other two don't (the ones rendered in the service object). Which all leaves me just as confused as I was before!
Also, I tried removing Turbolinks from my GemFile and application.js file, re-running bundle and rails s, but it made no difference.
For future reference for anyone else having the same problem (AuthenticityToken errors for a form that's been built in a helper or service object), I moved the form directly into the view and rendered it there - and the problem disappeared. I've removed the skip_before_action :verify_authenticity_token and all is now well!

Unpermitted parameters on belong_to association

I have an association as follows:
class Membership < ApplicationRecord
belongs_to :user
end
and
class User < ApplicationRecord
has_many :memberships
end
Now I have a form to create a new membership. Within this form I also want to input new user information. I am using SimpleForm. My basic structure is as follows (using HAML not erb files):
= simple_form_for #membership do |f|
...
= simple_fields_for #user do |uf|
.field= uf.input :firstname, label: 'First Name', required: true
...
= f.button :submit, 'Submit'
#user here is an instance variable set in the new action on the Memberships controller (#user = User.new). I would like to keep it this way so I can use SimpleForm's inference on user attributes (i.e., uf.input :firstname maps to the firstname attribute on the User model)
Now given this background, when I hit submit the goal is to create a new membership and a new user associated to that membership. How can I permit parameters for the single associated user?
At the moment I have:
# Never trust parameters from the scary internet, only allow the white list through.
def membership_params
params.require(:membership).permit(users_attributes: [:id, :firstname] )
end
Here is the request:
Started POST "/memberships" for ::1 at 2018-10-15 15:08:42 -0600
Processing by MembershipsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"omitted==", "membership"=>{"user"=>{"firstname"=>""}}, "commit"=>"Submit"}
Unpermitted parameter: :user
Unpermitted parameter: :user
Unpermitted parameter: :user
##################### {}
No template found for MembershipsController#create, rendering head :no_content
Completed 204 No Content in 264ms (ActiveRecord: 0.0ms)
I have no template rendering on purpose for now.
Given this parameter structure:
"membership"=>{"user"=>{"firstname"=>""}}
I have also tried the following for permitted parameters:
params.require(:membership).permit(user_attributes: [:id, :firstname] )
params.require(:membership).permit(user: [:id, :firstname] )
Notice I the ################ {}. This is a manual puts I have in the create action. puts '################ ' + membership_params.to_json. As you can see it yields nothing. Also why do I get the 'Unpermitted parameters' logged three times?
UPDATE 1 controller code:
class MembershipsController < ApplicationController
load_and_authorize_resource
# GET
def new
#membership_plans = Plan.active.sort_by { |plan| plan.sequence }
#user = #membership.build_user
end
# POST
def create
debug_puts(membership_params.to_json)
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def membership_params
params.require(:membership).permit(user_attributes: [:id, :firstname, :lastname] )
end
end
UPDATE 2
I don't deem this as an acceptable answer (which is why I'm not formally "answering" my question), but what I have decided to do is invert my form. The relationship is still the same among Memberships and Users, but the parent form is for a User:
= simple_form_for #user do |f|
...
= f.simple_fields_for :memberships_attributes do |mf|
...
This means I put accepts_nested_attributes_for :membership on the User model (a has_many association with memberships) and all the rendering and param permitting is done within the Users Controller
# new action in users_controller.rb
def new
#membership_plans = Plan.active.sort_by { |plan| plan.sequence }
#user = User.new
#user.build_membership
end
# Never trust parameters from the scary internet, only allow the white list through.
def user_params
params.require(:user).permit(memberships_attributes: [:id, :field1, :field2] )
end
You need to build the user for membership in the new action of memberships_controller.
def new
#membership = Membership.new
#user = #membership.build_user
end
and make sure you have user_attributes not users_attributes in the membership_params
params.require(:membership).permit(user_attributes: [:id, :firstname] )
Update:
There is one more important piece of code which need to be fixed.
This
= simple_fields_for #user do |uf|
should be
= f.simple_fields_for #user do |uf|

Duplicate two linked models Ruby

I want to duplicate my models 'Formulaire' and 'Question'. They have a has_may/belongs_to relation.
I'm able to duplicate the first model but I have a " NoMethodError in FormulairesController#duplicate
undefined method `save' for # " when I duplicate the second
My models :
Formulaire.rb
class Question < ActiveRecord::Base
belongs_to :formulaire
validates :nom, presence: true
end
Question.rb
class Formulaire < ActiveRecord::Base
has_many :questions, dependent: :destroy
end
formulaire_controller.rb
def duplicate
template = Formulaire.find(params[:id])
#formulaire= template.dup
#formulaire.save
#for question in #formulaire.questions
# question.dup
# question.save
#end
template2 = Question.where(formulaire_id: 47)
#question = template2.dup
#question.save
redirect_to #formulaire, notice: "Formulaire dupliqué"
end
def formulaire_params
params.require(:formulaire).permit(:name, :description,
questions_attributes: [:id, :nom, :typequestion, :image, '_destroy', photos_attributes:[:id],
answers_attributes:[:id, :content,'_destroy']]) if params[:formulaire]
#puts YAML::dump params
end
My view
formulaire/show.html.erb
<li class="Dupliquer"><%= link_to 'Dupliquer', duplicate_formulaire_path(#formulaire) %> </li>
routes.rb
resources :formulaires do
member do
get 'duplicate'
end
end
Thank you
Here in stackoverflow, I found it here:
If you want to copy an activeRecord object you can use its attributes to create new one like
you can have an action in your controller which can be called on link,
def create_from_existing
#existing_post = Post.find(params[:id])
#create new object with attributes of existing record
#post = Post.new(#existing_post.attributes)
render "your_post_form"
end
what's here: Rails clone copy or duplicate
I never used this gem, but it's for this, I'm going to leave it there and you'll see what you want.
https://github.com/amoeba-rb/amoeba
And also on reddit
def clone
#location = Location.find(params[:id]).dup
............
render :new
end
link: https://www.reddit.com/r/rails/comments/6phfy4/form_how_to_implement_a_clone_action/

Unpermitted parameters nested attributes - rails

I'm trying to submit a form to 2 tables but somehow I got this syntax error unexpected '\n' at this line joins: ['sources'], :landslide_id and found unpermitted parameter: sources in landslide params. Here's all the files
Models
class Landslide < ApplicationRecord
has_many :sources, dependent: :destroy
accepts_nested_attributes_for :sources
class Source < ApplicationRecord
belongs_to :landslide
end
landslides_controller.rb
def new
#landslide = Landslide.new
#landslide.sources.build
end
# POST /landslides
def create
#landslide = Landslide.new(landslide_params)
#landslide.save
end
private
# Use callbacks to share common setup or constraints between actions.
def set_landslide
render json: Landslide.find(params[:total_id]),
joins: ['sources'], :landslide_id
end
# Only allow a trusted parameter "white list" through.
def landslide_params
params.require(:landslide).permit(:start_date, :continent, :country, :location, :landslide_type, :lat, :lng, :mapped, :trigger, :spatial_area, :fatalities, :injuries, :notes, sources_attributes: [ :url, :text ])
end
sources_controller.rb
def set_source
#source = Source.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def source_params
params.require(:source).permit(:url, :text)
end
_form.html.haml
= form_for :landslide, :url => {:controller => 'landslides', :action => 'create'} do |f|
#something
%fieldset
%legend Source
= f.fields_for :sources do |s|
.form-group.row
%label.col-sm-2.col-form-label{for: "textinput"}URL
.col-sm-10
= s.text_field :url, class: "form-control"
.form-group.row
%label.col-sm-2.col-form-label{for: "textinput"}Text
.col-sm-10
= s.text_field :text, class: "form-control"
Request
{"utf8"=>"✓",
"authenticity_token"=>"W3m2dLTGyuPCbP6+pStWDfgpIbPzGdl4tvf01vMAbyozzkimqlXH4B/RtwBcsLb+iiBqms7EHagY+Anbpo4zNg==",
"landslide"=>
{"start_date(3i)"=>"27",
"start_date(2i)"=>"4",
"start_date(1i)"=>"2017",
"continent"=>"Africa",
"country"=>"Country",
"location"=>"Location",
"landslide_type"=>"1",
"lat"=>"1",
"lng"=>"1",
"mapped"=>"False",
"spatial_area"=>"1",
"fatalities"=>"1",
"injuries"=>"1",
"notes"=>"1",
"trigger"=>"1",
"sources"=>{"url"=>"url", "text"=>"text"}},
"button"=>""}
found unpermitted parameter: sources
Based on your form, it looks like sources are inside a param called sources rather than sources_attributes. Edit your landslide_params, changing sources_attributes to sources.
May I ask what set_landslide is trying to render, or correct me if I am wrong below? Placing joins on a new line causes the error. I am thinking you're trying to do something like:
landslide = Landslide.find(params[:total_id])
render json: landslide.to_json(:include => { :sources => { landslide_params[:sources] }})
Which would give you a json with the landslide object and a sources array. The landslide id should be within the landslide object. This of course assumes that's what you were going for.

Rails 4 Strong Params has_many with JSON

I'm attempting to pass json up on the client side and have rails take care of handling the object creation.
Here are my models:
class Order < ActiveRecord::Base
has_many :order_items, :autosave => true
belongs_to :menu_session
end
class OrderItem < ActiveRecord::Base
belongs_to :order
has_one :menu_item
end
Controller
class OrderController < ApplicationController
#POST /order/create
def create
#order = Order.new(order_params)
#order.save
end
private
def order_params
params.require(:order).permit(:comments, :menu_session_id, :order_items => [:menu_item_id])
end
end
The json data:
{'order': {'comments': 'none', 'menu_session_id': '9', 'order_items':[{'menu_item_id': '5'}, {'menu_item_id': '5'}]}};
The javascript
var data = {};
data.order = {'comments': 'none', 'menu_session_id': '9', 'order_items':[{'menu_item_id': '5'}, {'menu_item_id': '5'}]};
$.post('http://localhost:3000/order/create', orders, function(){}, 'json');
Finally, the error log:
Started POST "/order/create" for 127.0.0.1 at 2013-07-10 22:30:36 -0400
Processing by OrderController#create as JSON
Parameters: {"order"=>{"comments"=>"none", "menu_session_id"=>"9", "order_items"=>{"0"=>{"menu_item_id"=>"5"}, "1"=>{"menu_item_id"=>"5"}}}}
Completed 500 Internal Server Error in 52ms
ActiveRecord::AssociationTypeMismatch (OrderItem(#28109220) expected, got Array(#16050620)):
app/controllers/order_controller.rb:5:in `create'
Clearly, either my json is messed up or the ruby .permit is wrong. However, I've been playing with variations of this for a while now and cannot get it to work. The official documentation doesn't seem to venture into this, and every example I have found here deals with forms.
Anyone have any idea what is going on? I can't be the first to try this approach.
UPDATE:
Worked around it by making the following changes:
class OrderController < ApplicationController
#POST /order/create
def create
#order = Order.new(order_params)
order_items = order_item_params
order_items.each do |item|
#order.order_items << OrderItem.new(menu_item_id: item)
end
#order.save
end
private
def order_params
params.require(:order).permit(:comments, :menu_session_id)
end
def order_item_params
params.require(:order_items)
end
end
json: {"order":{"comments":"none","menu_session_id":"9"},"order_items":["5","5"]}
I don't think this would be the best way to do it, so I'm going to leave the question unanswered for now in hopes there is a best practice.
The workaround is not necessary in this case. ActiveRecord provides an automagic way of creating child elements directly through the params hash. In order to accomplish this, follow the steps bellow:
Configure Nested Attributes in the model
class Order < ActiveRecord::Base
# autosave is already enabled with accepts_nested_attributes_for
has_many :order_items
belongs_to :menu_session
accepts_nested_attributes_for :order_items
end
Include a *_attributes key in your JSON. In your case, change the order_items key to order_items_attributes
{'order': {'comments': 'none', 'menu_session_id': '9', 'order_items_attributes':[{'menu_item_id': '5'}, {'menu_item_id': '5'}]}};
In your controller, make permit accept your new key
def order_params
params.require(:order).permit(:comments, :menu_session_id, :order_items_attributes => [:menu_item_id])
end
There is some more awesomeness possible to accomplish with Nested Attributes. For further information, see ActiveRecord::NestedAttributes at Rails API

Resources