Rails: Unpermitted parameter in Rails 5 - ruby-on-rails

First of all I want simply get an object inside the current object that I'm sending to my backend.
I have this simple JSON (generated from a form):
{
"name": "Project 1",
"project_criteria": [
{
"name": "Criterium 1",
"type": "Type 1",
"benefit": "1"
},
{
"name": "Criterium 2",
"type": "Type 2",
"benefit": "3"
}
]
}
My classes:
class Project < ApplicationRecord
has_many :project_criteria
accepts_nested_attributes_for :project_criteria
end
class ProjectCriterium < ApplicationRecord
belongs_to :project
end
ProjectsController:
def project_params
params.require(:project).permit(:name, project_criteria: [] )
end
But I still can't access project_criteria parameter as you can see below:
Started POST "/projects" for 127.0.0.1 at 2016-08-19 16:24:03 -0300
Processing by ProjectsController#create as HTML
Parameters: {"project"=>{"name"=>"Project 1", "project_criteria"=>{"0"=>{"benefit"=>"1", "name"=>"Criterium 1", "type"=>"Type 1"}, "1"=>{"benefit"=>"3", "name"=>"Criterium 2", "type"=>"Type 2"}}}}
Unpermitted parameter: project_criteria # <-----------
Note:
By the way, I already tried to use criterium instead of criteria(which - in my opinion - is the correct since it should be pluralized) in has_many and accepts_nested_attributes_for, but it also doesn't work.
Does someone have a solution for this?

It's not the inflection of the word "criteria" that's giving you problems (although you can add a custom inflector to get the singular and plural versions you prefer if you really want).
The issue is that you have to explicitly permit the fields of nested objects.
Change your current params:
params.require(:project).permit(:name, project_criteria: [] )
To this (for a single nested object):
params.require(:project).permit(:name, project_criteria: [:name, :type, :benefit] )
Your case is somewhat compounded by the fact that you're dealing with multiple nested objects, so you'll have to pass a hash instead:
params.require(:project).permit(:name, { project_criteria: [:name, :type, :benefit]} )

I had this issue when working on a Rails 6 application.
My application consists of a User model that has a one-to-one relationship a Personal_Info model
My original code was this:
User Model
class User < ApplicationRecord
has_one :personal_info, class_name: 'PersonalInfo', dependent: :destroy
accepts_nested_attributes_for :personal_info, allow_destroy: true
end
Personal Info Model
class PersonalInfo < ApplicationRecord
belongs_to :user
end
User Controller
class UsersController < ApplicationController
def index
#users = User.all
end
.
.
def user_params
params.require(:user).permit(:email, :password, :password_confirmation,
personal_info_attributes: [:first_name,
:last_name, :phone, :gender, :dob,
:address, :city, :state, :country])
end
end
The issue was that I did not add the Personal_Info id to the accepted user params (parameters).
Here's how I fixed it:
I simply had to add the Personal_Info id to the UsersController params this way:
User Controller
class UsersController < ApplicationController
def index
#users = User.all
end
.
.
def user_params
params.require(:user).permit(:email, :password, :password_confirmation,
personal_info_attributes: [:id, :first_name,
:last_name, :phone, :gender, :dob,
:address, :city, :state, :country])
end
end
Another way is to add the update_only option to the Users Model this way:
class User < ApplicationRecord
has_one :personal_info, class_name: 'PersonalInfo', dependent: :destroy
accepts_nested_attributes_for :personal_info, update_only: true, allow_destroy: true
end
That's all.
I hope this helps

Related

Nested association not saving

I have a nested association for a customer. The setup is like this:
A customer has one address
An address has a physical_address
An address has a postal_addrsss
Address model
belongs_to :customer, optional: true # Yes, optional
has_one :physical_address, dependent: :destroy
has_one :postal_address, dependent: :destroy
accepts_nested_attributes_for :physical_address
accepts_nested_attributes_for :postal_address
Postal Address model
# same for physical_address.rb
belongs_to :address
Customer controller:
def create
#customer = current_user.customers.build(customer_params)
if #customer.save
return puts "customer saves"
end
puts #customer.errors.messages
#redirect_to new_customer_path
render :new
end
private
def customer_params
params.require(:customer).permit(
address_attributes: address_params
)
end
def address_params
return ([
postal_address_attributes: shared_address_params,
#physical_address_attributes: shared_address_params
])
end
def shared_address_params
params.fetch(:customer).fetch("address").fetch("postal_address").permit(
:street, etc...
)
end
Customer model:
has_one :address, dependent: :destroy
accepts_nested_attributes_for :address
A customer is created ok but not the address. Here's the form, for example:
<form>
<input name="customer[address][postal_address][street]" value="Foo Street" />
</form>
Logging "params", I see all the values but address is not creating. I believe the error lies in shared_address_params. Any ideas?
I think you just managed to lose yourself in layers of indirection and complexity in that parameters whitelist.
What you basically want is:
def customer_params
params.require(:customer)
.permit(
address_attributes: {
physical_address_attributes: [:street, :foo, :bar, :baz],
postal_address: [:street, :foo, :bar, :baz]
}
)
end
As you can see here you need the param key customer[address_attributes] not just customer[address].
Now lets refactor to cut the duplication:
def customer_params
params.require(:customer)
.permit(
address_attributes: {
physical_address_attributes: address_attributes,
postal_address: address_attributes
}
)
end
def address_attributes
[:street, :foo, :bar, :baz]
end
As you can see there should be very little added complexity here any and if you need to make it more flexible add arguments to the address_attributes method - after all building the whitelist is just simple array and hash manipulation.
If you want to handle mapping some sort of shared attributes to the two address types you really should do it in the model instead of bloating the controller with business logic. Like for example by creating setters and getters for a "virtual attribute":
class Address < ApplicationController
def shared_address_attributes
post_address_attributes.slice("street", "foo", "bar", "baz")
end
def shared_address_attributes=(**attrs)
# #todo map the attributes to the postal and
# visiting address
end
end
That way you would just setup the form and whitelist it like any other attribute and the controller doesn't need to be concerned with the nitty gritty details.
def customer_params
params.require(:customer)
.permit(
address_attributes: {
shared_address_attributes: address_attributes,
physical_address_attributes: address_attributes,
postal_address: address_attributes
}
)
end

Persist nested data to database in one transaction

I have 3 models:
class Address < ApplicationRecord
has_one :company_address
end
class CompanyAddress < ApplicationRecord
belongs_to :address, dependent: :destroy
belongs_to :address_type
end
class Company < ApplicationRecord
has_many :company_addresses
end
I am getting JSON data from another application.
The data consists of attributes of a company and one/none or many company_address which consists of only one address each
I want to be able to insert and update the data automatically and if anything fails I want to role the migration back
When I set require on strong_params I don't receive the array of company_addresses, however when I only use permit it works fine
This doesn't work:
params.require(:company)
.permit([
:short, :name, :company_legal_form_id,
:company_role_id, :parent_id, :email,
:fax, :phone, :description,
:comment, :changed_by,
company_addresses: [
:company_id, :address_type_id, :addition,
:comment, :changed_by,
address: [
:street, :zip, :city,
:country_id, :other1, :other2,
:other3, :comment, :changed_by
]
]
])
This works:
params.permit([
:short, :name, :company_legal_form_id,
:company_role_id, :parent_id, :email,
:fax, :phone, :description,
:comment, :changed_by,
company_addresses: [
:company_id, :address_type_id, :addition,
:comment, :changed_by,
address: [
:street, :zip, :city,
:country_id, :other1, :other2,
:other3, :comment, :changed_by
]
]
])
So I created a Form-Object called CompanyForm with these methods.
class CompanyForm
include ActiveModel::Model
attr_accessor(
:company_attributes
)
def save
#company_id = company_attributes.delete('id')
company_addresses_attributes = company_attributes.delete('company_addresses')
company_attributes[:changed_by] = 'user'
company.update!(p company_attributes)
#company_id = company.id
if company_addresses_attributes.empty?
company.company_addresses.destroy_all
end
company_addresses_attributes.each do |company_address_attributes|
#company_address_id = find_company_address_id(company_address_attributes)
address_attributes = company_address_attributes.delete('address')
#address_id = find_address_id(address_attributes)
address_attributes[:changed_by] = 'user'
address.assign_attributes(p address_attributes)
#address_id = address.id
company_address[:changed_by] = 'user'
company_address.build_address(#address.attributes)
company_address.assign_attributes(p company_address_attributes)
company.company_addresses.update!(p company_address.attributes)
end
end
private
def company
#company ||= Company.find_by(id: #company_id) || Company.new()
end
def address
#address ||= Address.find_by(id: #address_id) || Address.new()
end
def company_address
#company_address ||= CompanyAddress.find_by(id: #company_address_id) || CompanyAddress.new()
end
def find_company_id(params)
params.dig(:id)
end
def find_company_address_id(params)
params.dig(:id)
end
def find_address_id(params)
params.dig(:id)
end
end
The first question is: why can't I get company_address as well when I set require on :company?
The second question is, how could I get my code to work without problems? I know that the code is really ugly, however I am new to Rails and Ruby in general.
It looks like an issue with the JSON itself - it would help if you provided actual example of JSON sent in that request. The structure could be different than you expect (eg 'company' nested inside of another key).
Try using binding.pry at the first line of the controller which handles that request and investigate what are returns from params and params.require(:company) it might lead you to the answer.

Rails ActiveModel::ForbiddenAttributesError - ActiveModel::ForbiddenAttributesError в simple_form nested_fields

I experience a trouble while saving simple_form.fields_for - forbidden attributes error
'create' action in bookings controller looks so:
def create
...
new_params = params[:booking]
new_params[:user_attributes] = new_params[:user_attributes].merge({"password"=>"osmsmsmsm32"}) # password is temp stuff to bypass User save validation
#booking = Booking.new
#booking.update(params)
# however #booking.user.update(params[:booking][:user_attributes]) gives the same error
...
end
...
def booking_params
params.require(:booking).permit(:arrived_at, :departured_at, :arrival_address,
:departure_address, :arrival_city, :departure_city,
:reservation_cost, :total_additional_cost, :user_attributes, :user_id, :garage_id,
user_attributes: [:id, :name, :surname, :email, :phone],
garage_attributes: [:id]
)
end
===========================
Booking:
belongs_to :user
accepts_nested_attributes_for :user
===========================
##In model User:
has_many :bookings
However #booking.user.save & #booking.save in irb console with same params are successfully saveable and true is passed, without any Forbidden Attribute error.
Where is this Forbidden attribute come from? I am sure I allowed all the attrs I send in the form, and I think I use accepts_nested_attributes_for properly, isn't it?
Just Define your user_attributes inside controller private method as per below:
private
def user_params
params.require(
:user
).permit(
:first_name,
:last_name,
:job_title
)
end
if you are working with nested filed just add nested attributes inside this attributes like below:
private
def user_params
params.require(
:user
).permit(
:first_name,
:last_name,
:job_title,
addresses_attributes: [:address_1, :address_2]
)
end
write nested attributes by take in mind your model associations,
Hope this will work for you. :)

Create a model by passing a relational id from strong parameters

I'm building an API with Rails 4. There are Org and User models which are related. (below)
Now, I'd like to create new user from parameters like this.
POST /users parameter:
{
"name": "Rails",
"org_id": 1 // existing Org.id, Org.name == 'Ruby'
}
and response should be like this:
{
"name": "Rails",
"org": {
"name": "Ruby"
}
}
I'd like to pass org_id to User.create in order to relate a new user and the existing org. How can I do efficiently with strong parameters?
CODES
models/org.rb
class Org < ActiveRecord::Base
has_many :users
end
models/user.rb
class User < ActiveRecord::Base
belongs_to :org
validates :org_id, presence: true
validates :name, presence: true
end
controllers/users_controller.rb
class API::UsersController < API::ApplicationController
def create
#user = User.create! create_params # can I do this?
end
private
def create_params
params.permit(:name, :org_id)
end
end
Change app/models/user.rb:
class User < ActiveRecord::Base
belongs_to :org
validates :org_id, presence: true
validates :name, presence: true
accepts_nested_attributes_for :org
end
Now, your params should be like:
params = { "name" => "User name here!", "org_attributes" => { "id"=> "1", "name" => "Ruby" } }
Here, passing id in org_attributes will update the existing record for user, when you do:
#user = User.create! user_params
inside your controller, method user_params:
private
def user_params
params.permit(:name, org_attributes:[:id, :name])
end
And now, #user = User.create! user_params should create a user with existing org only if you pass id inside org_attributes params.

can't convert symbol into integer error when saving user

So here is my issue. I have been working with users I created at the beginning of my project for a month now. Today I switched from sqllite to sqlserver to meet client requirements and when I went to use my registration form to create a new user I got the following error:
can't convert Symbol into Integer
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"51nF50CYGNqz3N4o7TUYSyWeTadulXojQBPqERjvlcY=",
"user"=>{
"email"=>"test#blizzardlabs.com",
"login"=>"bgarrison",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"profile_attributes"=>{
"prefix"=>"",
"first_name"=>"Bill",
"last_name"=>"Garrison",
"suffix"=>"",
"birthday"=>"1983-06-01",
"phone_numbers_attributes"=>{
"0"=>{
"info"=>"1234567890",
"label"=>"Cell"
}
}
}
},
"commit"=>"Register"}
I have a feeling that at some point I messed up the registration process but I can't for the life of me figure out where. User-> has_one profile-> has_many phone_numbers.
User Controller:
def create
#user = User.new(params[:user])
if #user.save
#profile = #user.profile
flash[:notice] = "Your account has been created."
redirect_to(#user)
else
flash[:notice] = "There was a problem creating you."
render :action => :new, :layout => 'logged_out'
end
end
User Model:
class User < ActiveRecord::Base
# Accessible attributes
attr_accessible :login,
:email,
:password,
:password_confirmation,
:profile_attributes,
:active
# Associations
has_one :profile, dependent: :destroy, autosave: true
# Allows for a profile hash in user creation (stored in :profile_attributes)
accepts_nested_attributes_for :profile
Profile Model:
class Profile < ActiveRecord::Base
# Accessible Attributes
attr_accessible :birthday,
:company_id,
:first_name,
:last_name,
:prefix,
:suffix,
:phone_numbers_attributes,
:addresses_attributes
# Model Associations
has_many :phone_numbers, :as => :contactable, :class_name => "PhoneNumber", autosave: true
accepts_nested_attributes_for :phone_numbers, allow_destroy: true, reject_if: :all_blan
Any help would be appreciated. Thanks!
Update:1 Also, I have tested some and realized if I leave out the phone number then it works.....if I then update using the same form and add a phone number everything works fine.
Nested attributes should be passed in as Array:
"user"=>{
"email"=>"test#blizzardlabs.com",
"login"=>"bgarrison",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"profile_attributes"=>[
{
"prefix"=>"",
"first_name"=>"Bill",
"last_name"=>"Garrison",
"suffix"=>"",
"birthday"=>"1983-06-01",
"phone_numbers_attributes"=>{
"0"=>{
"info"=>"1234567890",
"label"=>"Cell"
}
}
}
]
}
So, after a couple days of banging my head against the wall I have finally figured this out. However to understand it I need to explain my model's a bit better.
Basically, from above you can see that a User has a profile which has many phone_numbers and addresses through a polymorphic association (:as => :contactable ). However, contactable is actually a base class called ContactInformation which uses STI to contain contactable information of all types.
At one point I decided that having 4 extra fields for addresses was cluttering up the STI relationship but I still wanted to keep it. My solution was to serialize all those fields into the "info" field of ContactInformation. Right now, phone numbers only have "number" as a field that is serialized and stored into "info" but if I ever want to seperate it out into "area code" "extension" etc the implementation will be simple.
This leads to the problem. On my registration form I was using label / info for my phone_number fields instead of label / number. I had edited my edit form but not my new form (yes i know they should be the same one but I have a special ajax form for editing).
Here is the code for ContactInformation / PhoneNumber / Address
class ContactInformation < ActiveRecord::Base
attr_accessible :contactable_id, :contactable_type, :info, :label, :type
belongs_to :contactable, :polymorphic => true
end
class PhoneNumber < ContactInformation
attr_accessible :number
stash :number, in: :info
#----------------------------------Validations--Start-------------------------
validates :number, presence: true
#----------------------------------Validations--End---------------------------
end
class Address < ContactInformation
attr_accessible :street_address, :city, :state, :postal
stash :street_address, :city, :state, :postal, in: :info
#----------------------------------Validations--Start-------------------------
validates :street_address, :city, :state, :postal, presence: true
#----------------------------------Validations--End---------------------------
end

Resources