There are several questions for strong params, but I couldn't find any answer for achieving my goal. Please excuse any duplicates (and maybe point me in the right direction).
I'm using strong params in a model that has several 'has_one' associations and nested attributes with 'accepts_attributes_for'.
In my routes I have: (updated for better understanding)
resources :organisations do
resources :contact_details
end
So, i.e. for one associated model I have to use
def organisation_params
params.require(:organisation).permit(:org_reference, :supplier_reference, :org_type, :name, :org_members, :business, :contact_person, contact_detail_attributes: [:id, :contactable_id, :contactable_type, :phone, :fax, :mail, :state, :province, :zip_code, :street, :po_box, :salutation, :title, :last_name, :first_name, :description])
end
This works, but I have to retype all my permitted params for each associated model. When I modify my permitted attributes for contact_details , I have to change it in several locations (every model that has the polymorphic association).
Is there a way to get the parameter whitelist of contact_details and include it into the parent whitelist?
Something like:
def organisation_params
my_params = [:org_reference, :supplier_reference, :org_type, :name, :org_members, :business, :contact_person]
contact_params = #get permitted params, that are defined in contact_details_controller
params.require(:organisation).permit(my_params, contact_params)
end
I don't want to workaround security, but I had already defined the permitted attributes for the contact_details and don't want to repeat it in every associated "parent" model (because it's exhausting and very prone to stupid mistakes like omitting one attribute in one of several parent models).
Use a method defined inside ApplicationController, or a shared module:
ApplicationController:
class ApplicationController
def contact_details_permitted_attributes
[:id, :contactable_id, :contactable_type, ...]
end
end
class ContactDetailsController < ApplicationController
def contact_details_params
params
.require(contact_details)
.permit(*contact_details_permitted_attributes)
end
end
class OrganisationsController < ApplicationController
def organisation_params
params
.require(:organisation)
.permit(:org_reference, ...,
contact_detail_attributes: contact_details_permitted_attributes)
end
end
Shared module:
module ContactDetailsPermittedAttributes
def contact_details_permitted_attributes
[:id, :contactable_id, :contactable_type, ...]
end
end
class ContactDetailsController < ApplicationController
include ContactDetailsPermittedAttributes
def contact_details_params
params
.require(contact_details)
.permit(*contact_details_permitted_attributes)
end
end
class OrganisationsController < ApplicationController
include ContactDetailsPermittedAttributes
def organisation_params
params
.require(:organisation)
.permit(:org_reference, ...,
contact_detail_attributes: contact_details_permitted_attributes)
end
end
Rails has even dedicated directories for shared modules, concerns inside app/controllers and app/models; indeed, in your case you should use app/controllers/concerns
I don't see why not. In your ApplicationController you could have
def contact_attributes
[:id, :contactable_id, :contactable_type, :phone, :fax,
:mail, :state, :province, :zip_code, :street, :po_box,
:salutation, :title, :last_name, :first_name, :description]
end
Then in your organisation_params
def organisation_params
my_params = [:org_reference, :supplier_reference, :org_type, :name, :org_members, :business, :contact_person]
params.require(:organisation).permit(*my_params, contact_detail_attributes: contact_attributes)
end
In some other location you might do...
def contact_params
params.require(:contact).permit(*contact_attributes)
end
Related
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
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.
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. :)
I have the following controller:
class BoilerplatesController < InheritedResources::Base
load_and_authorize_resource
private
def boilerplate_params
params.require(:boilerplate).permit(:title)
end
end
Then I extend it with this one:
class BoilerplateCopiesController < BoilerplatesController
defaults instance_name: 'boilerplate',
resource_class: BoilerplateCopy
private
def boilerplate_params
params.require(:boilerplate).permit(findings_attributes: [:id, :url])
super
end
end
It seems that the extending one doesn't add the new fields to the existing ones, but overrules them. How can this be implemented?
One possible solution (or workaround) would be:
BoilerplatesController:
def boilerplate_params
params.require(:boilerplate).permit(required_params)
end
def required_params
[:title,
:intro,
:lock_version,
:outro,
:resources,
:topic_id,
:tags,
:how_to_evaluate,
:status,
:priority]
end
BoilerplateCopiesController:
def required_params
super << {findings_attributes: [:id,
:url,
:description,
:screenshot,
:remove_screenshot]
}
end
I have a devise model that has a nested form (supp_form is the nested object) on sign up. When I submit the form I am getting the following error:
WARNING: Can't mass-assign protected attributes for Business: supp_form_attributes, terms_of_service
app/controllers/businesses/registrations_controller.rb:11:in `create'
I am using the nested_form gem and it seems as if my form is passing field data through to the console. My parameters after submit look like the following:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXX", "business"=>{"type"=>"Business", "supp_form_attributes"=>{"title"=>"mr.", "first_name"=>"jane", "last_name"=>"doe", "mobile_phone_number"=>"94034903", "loan_agreement_authorization"=>"1", "work_phone_number"=>"49034903", "business_industry"=>"Natural Resources and Mining", "legal_structure"=>"Sole Proprietorship", "employee_count"=>"5 to 10", "years_in_business"=>"5+ years", "business_address"=>"72 pentland rd", "business_city"=>"Waterdown", "business_postal_code"=>"l0r2h5", "business_province"=>"ON"}
business.rb
class Business < User
# Associations
has_one :supp_form
has_many :loan_applications
has_many :transactions
# Nested attributes
accepts_nested_attributes_for :supp_form, :loan_applications
# After save action
after_save :create_account
# Validations
validates_acceptance_of :terms_of_service
validate :terms_of_service, presence: true
end
supp_form.rb
class SuppForm < ActiveRecord::Base
# Associations
belongs_to :business
# Validations
validates_acceptance_of :terms
validates :business_id, :first_name, :last_name, :work_phone_number, :business_address, :business_postal_code, :business_city, presence: true
end
registraionts_controller.rb
class Businesses::RegistrationsController < Devise::RegistrationsController
before_filter :update_sanitized_params
def new
build_resource({})
resource.build_supp_form
respond_with self.resource
end
def create
super
resource.update_attribute(:railsid, '%010d' % rand(10 ** 10))
end
private
def update_sanitized_params
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, :type, :confirmed_at, :business_name, :terms, :railsid, :terms_of_service,
supp_form_attributes: [:business_id, :title, :loan_agreement_authorization, :first_name,
:last_name, :work_phone_number, :business_address, :business_postal_code,
:business_city, :business_name, :years_in_business, :legal_structure,
:business_industry, :employee_count, :mobile_phone_number, :business_province])}
end
def after_sign_up_path_for(resource)
business_root_path
end
end
supp_forms_controller.rb
class SuppFormsController < ApplicationController
before_filter :authenticate_user!
def new
#suppform = SuppForm.new(supp_form_params)
end
def create
#suppform = SuppForm.create(supp_form_params)
end
private
def supp_form_params
params.require(:supp_form).permit(:business_id, :title, :loan_agreement_authorization, :first_name,
:last_name, :work_phone_number, :business_address, :business_postal_code,
:business_city, :business_name, :years_in_business, :legal_structure,
:business_industry, :employee_count, :mobile_phone_number, :business_province)
end
end
You are using Rails 4 with strong parameters. And you get an error triggered by the protected_attributes gem (or default rails 3 app).
With strong_parameters on place you can remove safety the protected_attributes gem. And remove the configuration if you have it (config.active_record.whitelist_attributes).