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.
Related
I'm trying to create an event with some nested params, but I want to return a 400 if certain parameters are empty. Is there some built in way to do this without checking params and returning early?
for exmaple:
event = Event.create! params.require(:event).permit(
:name,
:owner_id,
attachments: [],
location_attributes: [
:custom,
:building,
:street_1,
:street_2,
:city,
:state,
:postal,
:country,
:latitude,
:longitude,
],
)
Let's say I want to reject if latitude and longitude are empty - what's the best way to do that?
You can use require on your desired attributes too, but it's a bit tricky. Check the last example here https://edgeapi.rubyonrails.org/classes/ActionController/Parameters.html#method-i-require
In your case it would be something like:
params.require(:event).permit(
:name,
:owner_id,
attachments: [],
location_attributes: [
:custom,
:building,
:street_1,
:street_2,
:city,
:state,
:postal,
:country,
:latitude,
:longitude,
],
).tap do |event_params|
event_params[:location_attributes].require(:latitude, :longitude)
end
I'm not sure if that's the final syntax since the doc example does not have nested attributes, but I hope it points you in the right direction.
While you can just call require several times:
def event_params
params.require(:event).require(:location_attributes).tap do |p|
p.require(:latitude)
p.require(:longitude)
end
# ...
params.require(:event).permit(
:name,
:owner_id,
attachments: [],
location_attributes: [
:custom,
:building,
:street_1,
:street_2,
:city,
:state,
:postal,
:country,
:latitude,
:longitude,
]
)
end
This really bloats your controller.
And I think you are really just overcomplicating something that should be handled by using the non-bang persistence methods (the ones that don't raise an error and that don't end with !) and validations on the model layer. Exceptions should not be used for the normal controller flow.
The bang methods such as create! should only really be used in a non-interactive context (such as seed files) or when you are using a transaction and need to rollback the transaction on failure.
So you use can use .require to bail early if the params are completely unusable but for the more "normal" case where a one the attributes is missing or blank should be handled by validations.
This lets you actually provide feedback to the user/client about what is missing and the 422 response is actually more appropriate.
def create
#event.new(event_params)
if #event.save
respond_to do |f|
f.html { redirect_to #event }
f.json { status :created, location: #event }
end
else
respond_to do |f|
f.html { render :new}
f.json { status :unprocessable_entity }
end
end
end
class Event < ApplicationRecord
has_many :locations
accepts_nested_attributes_for :locations
validates_associated :locations
end
class Location < ApplicationRecord
belongs_to :event
validates_presence_of :latitude, :longitude
end
Sorry I'm new to rails but can't wrap my head around this one.
I have an Order object with various attributes - no references
In my controller I can print out the attributes individually via their attr_accessor and see them in the console via puts.
But when I call .inspect they are all nil! any suggestions?
class Order < ApplicationRecord
attr_accessor :name, :email, :phone, :date, :dessert_type, :size, :quantity, :dessert, :comments, :total
validates :name, :date, :quantity, presence: true
validates :quantity, numericality: { only_integer: true, greater_than: 0}
validate :contact_provided?
private
def contact_provided?
if :email.blank? || :phone.blank?
errors.add(:base, "Please provide either phone or email so we can contact you!")
end
end
end
Controller
def create_order
puts "create_order object"
#order = Order.new order_params
if #order.valid?
puts #order.inspect
#everything is null here
#order.attributes.each do |attr_name, attr_value|
puts "#{attr_name}: #{attr_value}"
end
#this prints out fine!
puts "dessert: #{#order.dessert}"
end
end
Parameters
Parameters: {"utf8"=>"✓", "authenticity_token"=>"randomtoken", "order"=>{"name"=>"jim", "email"=>"test#email.com", "phone"=>"12345678", "dessert_type"=>"Cake", "size"=>"25.0", "dessert"=>"Chocolate Caramel", "date"=>"2018-04-15", "quantity"=>"1", "comments"=>""}, "commit"=>"Submit Order"}
Any insight much appreciated!
That's because this line:
attr_accessor :name, :email, :phone, :date, :dessert_type, :size, :quantity, :dessert, :comments, :total
is overriding the Order attributes in the way Rails works with them. As working with Rails you don't need that declaration, so you can remove them as attr_accessor.
As Order is an ActiveRecord model, then the getters and setters are already generated by ActiveRecord for all of your object attributes.
What you're doing right now is defining all of your attributes, with the attr_accessor as virtual attributes which are attributes on the model that don't persist in the database, just "an attribute not corresponding to a column in the database".
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. :)
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
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