I use find_or_initialize_by in my controller and then would like to apply the additional params to the record, whether new or existing. Is this possible to do in a Railsy way or do I have to loop through the parameters?
def create
re_evaluation = #client.re_evaluations.find_or_initialize_by(program_id: re_evaluation_params[:program_id], check_in_id: re_evaluation_params[:check_in_id])
# apply remaining re_evaluation_params here
if re_evaluation.save
render json: re_evaluation, status: :created
else
render json: re_evaluation.errors.full_messages, status: :unprocessable_entity
end
end
You should be able to assign the parameters as you'd do normally.
re_evaluation.assign_attributes(re_evaluation_params)
if re_evaluation.save
...
Related
There are multiple answers that explain how you can have nested resources, however, my use case is a bit different.
Batches belong to orders and an order has many batches.
I can understand how it works if you have a form for an order and can create batches within that form, but cannot comprehend a good way for my situation.
I have a form for a nested resource (batch) where the parent (order) may or may not exist. They may select whether or not it exists via radio buttons. If it exists, they then just simply select which order it belongs to .. simple. If it doesn't exist, I show fields for the order and submit order params alongside of batch params. I want to make sure to rollback the order creation if the batch does not save.
Here is the code I have thus far.
def create
#batch = Batch.new(batch_params)
Batch.transaction do
if params[:new_order] == "newOrder"
#order = Order.new(order_params)
#order.project_id = params[:batch][:project_id]
begin
#order.save!
rescue
respond_to do |format|
format.html { render action: 'new' }
format.json { render json: {order: #order.errors}, status: :unprocessable_entity }
format.js { render json: {order: #order.errors}, status: :unprocessable_entity }
end
raise ActiveRecord::Rollback
return
end
##batch.order_id = #order.id
end
respond_to do |format|
begin
#batch.save!
format.html { redirect_to #batch, notice: 'Batch was successfully created.' }
format.json { render json: #batch }
format.js { render json: #batch }
rescue
binding.pry
raise ActiveRecord.Rollback
format.html { render action: 'new' }
format.json { render json: {batch: #batch.errors}, status: :unprocessable_entity }
format.js { render json: {batch: #batch.errors}, status: :unprocessable_entity }
end
end
end
end
This isn't behaving quite like I want it and seems quite ugly. I have a feeling I'm making it more difficult than I need to. What's the best approach in a situation like this? Much appreciated!
It seems like this is a great opportunity to use a Service Object: https://www.engineyard.com/blog/keeping-your-rails-controllers-dry-with-services .
This pattern is very useful for keeping Model and Controllers clean, and making sure those parts of the application are keeping to the Single Responsibility Principle.
What I would do in this case is create a service class called CreateBatch that takes in the parameters and performs the correct logic for each case. You can then render the correct output in the controller. This will also help clean up the conditionals and early returns you have.
For example:
# app/controllers/batches_controller.rb
def create
project_id = params[:batch][:project_id]
new_order = params[:new_order]
result = CreateBatch.new(new_order, batch_params, order_params, project_id).call
if result.errors
# handle errors with correct format
else
# handle successful response with correct format
end
end
# app/services/create_batch.rb
class CreateBatch
def initialize(new_order, batch_params, order_params, project_id)
#new_order = new_order
#batch_params = batch_params
#order_params = order_params
#project_id = project_id
end
def call
if new_order?
create_new_order
else
add_batch_to_existing_order
end
end
private
def new_order?
#new_order
end
def create_new_order
order_params = #order_params.merge(project_id: #project_id)
Order.save(order_params)
end
def add_batch_to_existing_order
Batch.create(#batch_params)
end
end
I did not run this so it may take a bit of tweaking to work, however, I hope it's a good starting point. One of the awesome things about this refactor is you now have 1 conditional for the logic and 1 conditional for the response, no need for adding in Transaction blocks, and no early returns. It may make sense to break the call method into 2 different methods that you can call from the controller. Using service classes like this makes the code much easier to unit test as well.
Why not move error handling and response rendering outside of the transaction?
def create
#batch = Batch.new(batch_params)
Batch.transaction do
if params[:new_order] == "newOrder"
#order = Order.new(order_params)
#order.project_id = params[:batch][:project_id]
#order.save!
#batch.order_id = #order.id
#batch.save!
end
end
respond_to do |format|
format.html { redirect_to #batch, notice: 'Batch was successfully created.' }
format.json { render json: #batch }
format.js { render json: #batch }
end
rescue StandardError => error
#error = error
format.html { render action: 'new' }
format.json { render json: {error: #error, batch: #batch.errors}, status: :unprocessable_entity }
format.js { render json: {error: #error, batch: #batch.errors}, status: :unprocessable_entity }
end
It's still quite complex, but it's definitely more readable. The next step would be to extract the whole transaction block to a service.
I have a triple nested resource, which I am able to create new values for perfectly well. However when trying to edit the record, I am getting duplicated fields for the nested values, which is then creating multiple entries.
I am multiplying the nested fields by 3.
def new
#roast = Roast.new
3.times {#roast.countries.build.regions.build}
end
Edit method:
def edit
#roast = Roast.friendly.find(params[:id])
3.times {#roast.countries.build.regions.build}
end
Should I be removing the 'build' element here? I do want the user to be able to add new values if required however.
And create has nothing special for this:
def create
#roast = Roast.new(roast_params)
respond_to do |format|
if #roast.save
format.html { redirect_to #roast, notice: 'Roast was successfully created.' }
format.json { render :show, status: :created, location: #roast }
else
format.html { render :new }
format.json { render json: #roast.errors, status: :unprocessable_entity }
end
end
end
I obviously want the 3 nested fields to show on the edit page, but what am I doing wrong for it to keep repeating.
You don't need to add countries or regions in the edit. Just find the Roast.
#This is wrong
3.times {#roast.countries.build.regions.build}
When you edit a Roast, you can access its countries through #roast.countries
If you want, you can define an instance variable to use in the form (although not needed) #countries = #roast.countries
I have rails api with simple paperclip model:
def create
#photo = Photo.new(photo_params)
if #photo.save
render json: #photo, status: :created, location: #photo
else
render json: #photo.errors, status: :unprocessable_entity
end
end
private
def photo_params
params.require(:photo).permit(:image, :title)
end
My frontend framework send get like this
{"title"=>"simpletitletext", "photo"=>{"image"=>......}}
But it wrong, because rails waits following
{"photo"=>{"title"=>"simpletitle", "image"=>#...}}
I had been trying for different ways to fix angular for many hours, before wrote . May be it will be able to fix in rails
If your server has an incoming request that looks like this:
{"title"=>"simpletitletext", "photo"=>{"image"=>......}}
you can make it look like this:
{"photo"=>{"title"=>"simpletitle", "image"=>#...}}
The only difference between the two is that in the first, the title key is outside of the photo nested hash. but you want it to be inside.
So in your Rails controller,. you could write:
def photo_params
hash = params[:photo]
hash.merge(title: params[:title])
end
I've found that both methods work for saving data. Is there an advantage to using one method over the other? What are the advantages/disadvantages?
First version:
begin
#user.save!
render json: "User #{#user.email} added", status: :created
rescue StandardError => e
render json: #user.errors.full_messages, status: :unprocessable_entity
end
Second version:
if #user.valid?
#user.save!
render json: "User #{#user.email} added", status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
I would go with a third alternative, because on save, the model is validated automatically:
if #user.save
render json: "User #{#user.email} added", status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
Its comes down to performance. Your first approach is more expensive because the full call stack has to be unwound to construct the exception details and you don't even use it.
Exceptions are expensive
When you catch an exception, the exception has the full stack trace where exactly the error occurred and what was the sequence of method calls that led to that event. Building this information requires Ruby to go to the previous method call and then to the previous-to-previous method call and so on recursively. This is a fairly expensive operation and since you are already within the method, you don't really need this information.
Validating twice is unnecessary
So, out of your two approaches, the second version is better. However jvperrin's answer is even better. In your second approach, you call #user.isvalid? which runs through all the model validations. And when you call #user.save, it again runs through the same validations. Instead you could just call #user.save directly and look at the return value, which is true when everything went well and false when there were validation errors.
Hope that helps.
You can use like this
if #user.valid?
if #user.save!
render json: "User #{#user.email} added", status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
else
##some code
end
Because if user is not valid no need of entering save block.
Couldn't you just add validations to your User model as a 3rd, simpler alternative?
Something like,
class User < Activerecord::Base
validates :email, presence: true
end
I'm using javascript(coffeescript) as format for the response to an update action.
I can't figure out how to check if the record has been successfully updated in my js.coffee response.
For create I use .new_record?, for destroy we have .destroyed? to check that the record has been created/destroyed correctly, what about update?
What about returning different JSON objects, depending on the outcome of updating your object:
def update
#foo = Foo.find(params[:id])
respond_to do |format|
if #foo.update_attributes(params[:foo])
format.json { head :ok }
else
format.json { render json: #foo.errors, status: :unprocessable_entity }
end
end
end
Client side, you'd inspect the status property on the returned object.
If think the solution was too simple for me.
I can simply use .errors.empty?..