How to stop create if a parameter value is empty/missing? - ruby-on-rails

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

Related

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 - Object attributes accessible individually but not by inspect method

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".

json rendering issue in rails

I have added following code in user.rb.
def as_json(options={})
h = super(:only => [:id, :content, :created_at, :updated_at])
h
end
But in another api I have to get username and address as well. But producing these four fields when rendering json. How can I get both outputs. Thanks in advance
Let the two API be
this API should return username and address too
def api1
User.first.as_json(user_info: true)
end
this does not need to return username and address
def api2
User.first.as_json(user_info: false)
end
let the user.rb be
class User < ApplicationRecord
def as_json(options = {})
if options[:user_info] == true
user = super(:only => [:id, :content, :created_at, :updated_at, :username, :address])
else
user = super(:only => [:id, :content, :created_at, :updated_at])
end
user
end
end
I don't have any better idea but I think you can use except like:
def as_json(options={})
h = super(:only => [:id, :content, :created_at, :updated_at, :username, :address])
h
end
Then when rendering you can use #yourVariable.as_json(except: [:username, :address]) when you don't need those or you can use only instead of except

Strong parameters and nested attributes

I am upgrading from Rails 3 to 4, and so I am adopting strong parameters. It seems that nested attributes are not being passed successfully. I have read several related SO questions and blog posts, and I'm still stumped.
An Event has many Occurrences. When I submit the form to create a new Event and one or more Occurrences, I get "1 error prohibited this class from being saved: Occurrences start can't be blank." However, start is not blank, as I confirmed by looking at the Parameters that are posted:
{"utf8"=>"✓",
"authenticity_token"=>"aPRLtUbjW7EMxO2kWSzCEctHYZgvBvwuk2QUymfiwkM=",
"event"=>{"name"=>"some name",
"photo"=>#<ActionDispatch::Http::UploadedFile:0x007f9e9bc95310 #tempfile=# <File:/var/folders/rz/1p7tmbmj2t5fbfv2wjhvwcsh0000gn/T/RackMultipart20140927-52743-10pcxtg>,
#original_filename="10435871_671176211140_3488560836686101357_n.jpg",
#content_type="image/jpeg",
#headers="Content-Disposition: form-data; name=\"event[photo]\"; filename=\"10435871_671176211140_3488560836686101357_n.jpg\"\r\nContent-Type: image/jpeg\r\n">,
"summary"=>"",
"facebook_event_link"=>"",
"occurrences_attributes"=>{"0"=>{"start"=>"09/30/2014",
"_destroy"=>"0"}},
"special"=>"0",
"prose"=>""},
"commit"=>"Create Event"}
Here are the relevant sections of the models and controller.
app/models/event.rb:
class Event < ActiveRecord::Base
has_many :occurrences, :dependent => :destroy
accepts_nested_attributes_for :occurrences, allow_destroy: true, reject_if: proc {|o| o[:start].blank? }
app/models/occurrence.rb:
class Occurrence < ActiveRecord::Base
belongs_to :event
validates_presence_of :start
app/controllers/events_controller.rb:
class EventsController < ApplicationController
def new
#event = Event.new
#event.occurrences.build
#event.passes.build
end
def create
#event = Event.create(event_params)
if #event.save
redirect_to #event, notice: 'Event was successfully created.'
else
render :action => "new"
end
end
...
private
def event_params
params.require(:event).permit(
:name, :expiration, :prose, :special, :summary, :photo, :link, :price, :student_price, :registration_switch, :facebook_event_link,
:occurrences_attributes => [:id, :start, :end, :_destroy])
end
end
Why isn't the information about Occurrences being passed correctly?
I'm not sure, but I think that strong parameters requires you permit the foreign key on associated models. So, perhaps you are missing event_id in your occurrences_attributes?
Not even close to 100% sure, but this could be it:
def event_params
params.require(:event).permit(
:name, :expiration, :prose, :special, :summary, :photo, :link, :price, :student_price, :registration_switch, :facebook_event_link,
:occurrences_attributes => [:id, :start, :end, :_destroy, :event_id])
end
Perhaps you need to ensure that your form has multipart: true

Rails 4 strong params get permit from nested model

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

Resources