I need to escape NotFound exception for nested form associations, when there is one relation isn't found.
For example, i have
class User < ActiveRecord::Base
has_many :user_selection_criteria
accepts_nested_attributes_for :user_selection_criteria, :reject_if => lambda { |t| t['brand_id'].nil? }, :allow_destroy => true
end
and
if #user.update_attributes user_params
render
else
render json: #user.errors, status: :unprocessable_entity
end
Which updates attributes, params do permit this, and everything is ok.
I use nested form, say, with those attributes.
user_selection_criteria_attributes[0][id]
user_selection_criteria_attributes[0][brand_id]
user_selection_criteria_attributes[0][_destroy]
user_selection_criteria_attributes[1][id]
user_selection_criteria_attributes[1][brand_id]
user_selection_criteria_attributes[1][_destroy]
user_selection_criteria_attributes[2][id]
user_selection_criteria_attributes[2][brand_id]
user_selection_criteria_attributes[3][_destroy]
etc...
Everything is OK, when i:
Leave id blank - a new record is created
Use id of existing record - corresponding record is updated.
But when i use non-existing record id, for example when another user already deleted this record, i get an error Couldn't find UserSelectionCriterium with ID=13 for User with ID=12
When i use
begin
if #user.update_attributes user_params
render
else
render json: #user.errors, status: :unprocessable_entity
end
escape
render
end
Error is escaped, but attributes aren't saved. But that's expected.
Question: how do i squelch/escape that error, ignore that record does not exist any more, and save any other valid relations?
I.e. when nested relation with id 13 doesn't exist, but relation with id 14 exists, relation 13 is ignored and 14 is processed normally.
You can filter these ids which's record does not exsited, like this(ideally, use your own code):
def some_params
params.require(:user).permit(user_selection_criteria_attributes: [:id, :brand_id]).tap do |white_list|
white_list[:user_selection_criteria_attributes].each do |key, value|
if value[:id].present? && UserSelectionCriteria.find_by(id: value[:id]).blank?
white_list[:user_selection_criteria_attributes].delete(key)
end
end
end
end
Related
Basically I want to update an array of objects that my api recieves in a single command. I have done it when I was inserting but I couldn't find a way to do update it.
Here is m create method for multiple insertions:
def create_all
if Attendance.create attendance_params
render json: { message: "attendance added" }, status: :ok
else
render json: { message: "error in creation" }, status: :bad_request
end
end
Params:
def attendance_params
params.require(:attendance).map do |p|
p.permit(
:student_id,
:id,
:attendance
)
end
end
I tried to do similar thing with update but it generates this error:
Completed 500 Internal Server Error in 11ms (ActiveRecord: 2.7ms)
Argument Error (When assigning attributes, you must pass a hash as an argument.)
my update method is this:
def update_attendance
if Attendance.update attendance_params
render json: { message: "attendance updated" }, status: :ok
end
end
ActiveRecord Create can take an array of hashes and create multiple records simultaneously.
However, ActiveRecord Update cannot.
You could potentially create an "update_batch" method on your model that allows for an array of hashes. You would have to send an array of hashes and each hash would have to include the id of the record you are updating (and allow that in your strong parameters definition). Then in your update_batch method you would have to grab the id from each hash and update each:
class Attendance < ActiveRecord
def update_batch(attendance_records)
attendance_records.each do |record|
Attendance.find(record[:id]).update(record.except(:id))
end
end
end
Please check this example and try applying it:
Attendance.where(:student_id => [23,45,68,123]).update_all(:attendance => true)
Or if you're trying to update all Attendance records:
Attendance.update_all(:attendance => true)
Also, please check this link:
https://apidock.com/rails/ActiveRecord/Relation/update_all
I have 2 non database attributes in my model. If one of them has a value, I need to return the other one in the json response:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def attributes
if !self.track.nil?
super.merge('max_speed_on_track' => self.max_speed_on_track)
end
end
end
The problem is that the line 'if !self.track.nil?' throws an error when the controller tries to return the json
Perhaps there is a better way as I read that using attr_accessor is a code smell.
What I am trying to do is if the user passes me a track value as a query parameter, then I pass that value to the model and it uses it to calculate the max_speed_on_track, and return that value.
Obviously if no track is provided by the user then I don't want to return max_speed_on_track in the json.
The controller method is very basic for now (I still need to add the code that checks for the track param). The code throws the error on the save line.
def create
#car = Car.new(car_params)
if #car.save
render json: #car, status: :created
else
render json: #car.errors, status: :unprocessable_entity
end
end
Try this out:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def as_json(options = {})
if track.present?
options.merge!(include: [:max_speed_on_track])
end
super(options)
end
end
Since Rails uses the attributes method, and you're only needing this for json output, you can override the as_json method just like in this article. This will allow you to include your max_speed_on_track method in your json output when the track is present (not nil).
I am trying to create a web API that allows creation of FriendShip by email or phone_number.
class Api::FriendshipsController < Api::BaseController
respond_to :json
def create
friend = User.where("email = ? OR phone_number = ?", params[:emailOrPhone], params[:emailOrPhone]).first # create a friend by email or phone_number
if friend.valid? # check if the friend exists, if it does we create our new friendship
friendship = Friendship.new
friendship.user = current_user
friendship.friend = friend
if friendship.valid? # check if friendship is valid
friendship.save # if it is, we save and return a success JSON response
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
end
end
Here is my FriendShip model
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
validates_uniqueness_of :user_id, scope: :friend_id, :message => '%{friend_id} is already a friend'
validate :check_friend_and_user # prevent user trying to add him/herself as friend.
def check_friend_and_user
errors.add(:friend, "can't be the same as user") if user == friend
end
end
Whenever the uniqueness constrain is violated, an error missing interpolation argument :friend_id in "%{friend_id} is already a friend" ({:model=>"Friendship", :attribute=>"User", :value=>2} given) with error code 500
How do I make it not throw an error but instead proceed to give back a 'fail json response' with status code 400
I want the caller of this API to know that they are trying to add someone that is already a friend. Getting back a status code 500 and a bunch of html does not seems to uniquely identify it. So I would like to throw a error in the form of JSON and status 200
What you seem to be trying to do is determine if the friend is already associated to the User via the friendship class. Which you can simplify with a has_many :friendships association on the User object.
Also, the way you're looking up by email OR phone is IMO unnecessarily ambiguous and will be problematic if you want to separately track one or the other for other purposes. Which you seem to be wanting to do since you have them broken out into separate database columns. I think you could just put two form inputs email or phone number and only pass one along to the controller. If you must have only one then you could determine what the form submits in Javascript or something.
In that case, you'd be better off sending the type of identifier with your initial data from your form, so you can look for one or the other. So your form would explicitly send the column lookup identifier, e.g. the params would be equivalent to the ruby hash
{friendship: {email: "someone#somewhere.com"}}
With that in the params then you could do what your trying with this code
# assuming you're passing via a params hash that would look like
# one or the other of the following
# {friendship: {email: "john#doe.com"}}
# {friendship: {phone_number: "123-123-1234"}}
def create
if current_user.friendships.find_or_create_by(friendship_params)
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
protected
def friendship_params
require(:friendship).permit(:email, :phone_number)
end
I have the following form:
When the user selects a product from the dropdown, a ajax is triggered to find the inventory of the single product to append the details to a table.
The user can attach a product detail to the order.
Finally I get something like that:
{"utf8"=>"✓", "authenticity_token"=>"xmlzMouWp0QGUnpKeawQ8OCPJ/GlF2bp0kn97ra2Qyb7TgsCkXmJEGD1l/oZitn+VPVJRc8x79/kTUtgbbDr0A==", "order"=>{"customer_search"=>"", "customer_id"=>"2", "product_search"=>"", "order_detail"=>[{"product_id"=>"10", "product_detail_id"=>"13", "price_id"=>"12"}, {"product_id"=>"1", "product_detail_id"=>"8", "price_id"=>"11"}], "subtotal"=>"111990", "tax"=>"0", "comission"=>"0", "total"=>"111990"}, "product_list"=>"1", "button"=>""}
My code to create the order is working, but I can not add the details.
Orders controller
def create
# Creates the order removing the order details from the hash
#order = Order.create(order_params.except!(:order_detail))
# Set all the details into an empty array
order_details_attributes = order_params[:order_detail]
order_details_attributes.each do |order_detail_attributes|
# Fill the params with order_id and creates the detail
order_detail_attributes["order_id"] = #order.id
#order_detail = OrderDetail.create(order_detail_attributes)
end
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
def order_params
params.require(:order).permit(:customer_id, :subtotal, :tax, :comission, :total, :invoice, :shipping_id, :order_detail, order_details_attributes: [:product_id, :product_detail_id, :price_id])
end
I'm getting this error:
undefined method `delete' for nil:NilClass
order_details_attributes = order_params[:order].delete(:order_detail)
What could be bad? I really need help :(
Thanks!!
order_params doesn't have key :order, it only has keys you specified in permit method when defined order_params. Actually, you don't need to manually create children records, as Rails can do this automatically. Check this out: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
You just need to add accepts_nested_attributes_for :order_details in Order model, fill order_details_attributes param when creating an order (currently you fill order_detail, you need to fix you form for it to be order_details_attributes, because this is one of Rails conventions, or you can use fields_for helper for this). Then you just create Order in standard way, like #order = Order.new(order_params) and #order.save, and you'll get order and order details created together.
This is a really messy thing in Rails. I can only recommend you to read the link I posted and use Google to find some tutorials.
As to the error you get:
undefined method `delete' for nil:NilClass
There is no :order key in order_params. It is in params, but not in order_params, because you called require(:order). So order_params returns only the keys you specified in permit method. But :order_detail will be empty, as you didn't describe it as an array with certain keys (like you did for order_details_attributes).
So, your problem is that you tried to implement nested attributes, but you pass :order_detail instead of :order_details_attributes (hm, but you still have it in strong params) and try to create children relations manually. Don't do this, just use what Rails provides to you.
There are 2 ways:
You continue to use order_detail param. In this case you need to change order_params in controller to look like so:
params.require(:order).permit(:customer_id, :subtotal, :tax, :comission, :total, :invoice, :shipping_id, order_detail: [:product_id, :product_detail_id, :price_id])
(just replace order_details_attributes with order_detail)
Then instead of
order_details_attributes = order_params[:order].delete(:order_detail)
you do
order_details_attributes = order_params[:order_detail]
(you don't need delete here as order_params is a method that returns a hash)
And leave rest controller code as it is now. And you don't need nested attributes, as you don't use it (bad way).
You fully use nested attributes. I described in a comment below how to do this. You also need to tweak you jquery code to generate order_details_attributes instead of order_detail.
I'm a relative newbie at Rails, so forgive me if my question's got an obvious answer.
I'm trying to include a field in a Rails form which isn't in the model/controller or the migration associated with the view.
The form is a simple public contact form, and I can validate against most of the fields easily enough. Eg name, email etc.
The model is form_submission.rb
However, I have a field in the contact form - captcha - which isn't mirrored in the form_submissions db table, etc.
There is a separate table, model etc for captcha which is captcha_answer.rb (etc)
The attributes for captcha_answer in the migration are: answer and is_correct.
The table simple contains a list of answers to a predefined question, some of which are true and some which are false.
Eg, the captcha question might be:
Which is these is an animal?
With the options of: cat, dog, tree, rabbit .. in a select.
What I want to be able to do is to validate that:
a) The captcha field exists in the POST (return message of "no captcha given" if not)
b) The answer given has a value in captcha_answers.is_correct of true (return message of "you gave a wrong answer" if not)
The capcha_answers.answer is always unique, so I want to do the equivalent of a SQL query which gets the first record where captcha_answers.answer = and returns the value of captcha_answers.is_correct
Like I say, if the attribute was in form_submissions then I'd be able to validate it no problem, but I can't figure out how I can validate a field against something in another model.
Any ideas?
You can just add for example a hidden field and catch it in the controller:
in your form:
<%= hidden_field(:signup, :pass_confirm, :value => 'abcd') %>
then in the controller:
params[:signup]
There you can access a different model and validate the answer.
Action in the controller like:
def update
#company = Company.find(params[:id])
puts "extra field:"
puts params[:signup]
respond_to do |format|
if #company.update_attributes(params[:company])
format.html { redirect_to #company, :notice => 'Company was successfully updated.' }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => #company.errors, :status => :unprocessable_entity }
end
end
end
Define accessors for the extra field and use usual ActiveRecord validations:
class MyModel < ActiveRecord::Base
attr_accessor :extra_field
validates :extra_field, :presence => true
validate :custom_validation_method
def custom_validation_method
errors.add :extra_field, :invalid unless extra_field == "correct"
end
end