How do I call update action from another action in rails 3? - ruby-on-rails

So I'm writing a basic member modifying action, and I figured, lets stay DRY and just modify the params hash then pass along to our update method but it doesn't seem to work. I guess there is some rails magic going on that I can't find... From what I've read this should work. I'm using Rails 3.2.
Here's an example of what I'm trying to do:
# POST /tasks/1/toggle_done
def toggle_done
#task = Task.find(params[:id])
puts "<<<<<", params
# invert done bool value
params[:done] = !(#task.done)
# thought maybe update_attributes retured a full set of
# attributes in the params...
#params[:name] = #task.name + "...test."
# thought maybe the method call to update was getting
# filtered or something. Doesn't seem to help.
#params[:_method] = "put"
# redirect to update with these new params
puts ">>>>>", params
# Why bother rewriting task.done = x; task.save;
# redirect_to show; etc when update already does that.
update
end
# PUT /tasks/1
# PUT /tasks/1.json
def update
#task = Task.find(params[:id])
puts "======", params
respond_to do |format|
if #task.update_attributes(params[:task])
format.html { redirect_to #task, notice: 'Task was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end
end
I get the following console output:
<<<<<
{"_method"=>"post", "authenticity_token"=>"CVqzsJfSVgM7Bq/kXlrjzkWVoA7Pbne4GNEHqbQB42s=", "action"=>"toggle_done", "controller"=>"tasks", "id"=>"1"}
>>>>>
{"_method"=>"put", "authenticity_token"=>"CVqzsJfSVgM7Bq/kXlrjzkWVoA7Pbne4GNEHqbQB42s=", "action"=>"toggle_done", "controller"=>"tasks", "id"=>"1", "done"=>false, "name"=>"Put Done button in index view...test."}
======
{"_method"=>"put", "authenticity_token"=>"CVqzsJfSVgM7Bq/kXlrjzkWVoA7Pbne4GNEHqbQB42s=", "action"=>"toggle_done", "controller"=>"tasks", "id"=>"1", "done"=>false, "name"=>"Put Done button in index view...test."}
So it seems like the params array is set right. It renders the regular show view with the flash message "Task was successfully updated.", so it seems like the whole method gets executed but non of the model properties are getting changed. I guess something inside update_attributes is failing. Can anyone shed some light on this for me?
Also is this a crazy thing to do? Should I be setting and saving inside my toggle_done method instead of chaining to update?

Rails saves the attributes for the task object in the hash params[:task]. So you in your toggle_done method you need to save the result in params[:task][:done] otherwise rails cannot associate the done attribute with the task.
def toggle_done
#task = Task.find(params[:id])
params[:task] = { done: !(#task.done) }
update
end
But with calling the update method you make 3 database queries where only 2 are neccessary - And the first 2 are identically because you load the Task with the ID in the toggle_done method as well as in update.
To avoid this you can put the save and redirect part into a protected method and call it when you want to save it. Like this:
def toggle_done
#task = Task.find(params[:id])
params[:task] = { done: !(#task.done) }
save_updated
end
def update
#task = Task.find(params[:id])
save_updated
end
protected
def save_updated
respond_to do |format|
if #task.update_attributes(params[:task])
format.html { redirect_to #task, notice: 'Task was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #task.errors, status: :unprocessable_entity }
end
end

You're passing params[:task] to update_attributes, which doesn't exist. Try:
params[:task] = {:done => !(#task.done)}

Related

Creating 2 Models in Controller Action With Transaction - Rails 4

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.

No route matches in show action- missing required keys: [:id]

I looked at a few different answers similar to this question, but they all are getting the id passed into the params hash, it is just coming back as a nil value. I cant seem to get the id to be whitelisted and I cant figure out why.
This is my create and show action, also includes the params. I explicitly whitelisted id although I dont know if it is necessary.
def show
#board = Board.find(params[:id])
end
def create
#board = Board.new(board_params)
if #board.save
# flash[:notice] = 'You successfully created your post!'
redirect_to board_path
else
render 'new'
# flash[:danger] = 'One or more errors occurred when processing your post'
end
end
private
def board_params
# params.require(:board).permit!
params.require(:board).permit(:id, :project_name, :project_description, :postition_title, :project_url, :project_headquarters, :application_process)
end
The output of my routes show action
board GET /boards/:id(.:format) boards#show
And the output of the params hash on board#create
Parameters: {"utf8"=>"✓", "authenticity_token"=>"k+KKRhBk4Dnba1vxtFCanI2grfhNXFSgJpfEBLnhPdablfOsXRi1wBehPpM1qgx1pySrqxVHTeiwkneluXwRIQ==", "board"=>{"project_name"=>"sdf", "postition_title"=>"sdf", "project_headquarters"=>"sdf", "project_url"=>"fsd", "project_description"=>"dfs", "application_process"=>"dfs"}, "commit"=>"Create Board"}
Can anyone see what I need to change in order to get the id passed in correctly?
Try this.
Take :id out of params.
def show
end
def create
#board = Board.new(board_params)
respond_to do |format|
if #board.save
format.html { redirect_to #board, notice: 'Board was successfully created.' }
format.json { render :show, status: :created, location: #board }
else
format.html { render :new }
format.json { render json: #board.errors, status: :unprocessable_entity }
end
end
end
in create action...
change ..
redirect_to board_path
to...
redirect_to board_path(#board)
###OR
### this calls the show action
redirect_to #board

find results from table after update, where (attribute integer) is equal to the (primary id integer)

I am updating an object in a table with attributes : :primary_id, :zucode_number, :zucode_email
The updated row object does NOT have the attributes :zucode_number and :zucode_email .
Other different rows in the same table do have the attributes i need, :zucode_number and :zucode_email.
:zucode_number(integer) can be equal to, is sometimes equal to the :primary_key(integer)
How to get the rows where :zucode_number is equal to :primary_key and send email to :zucode_email on those rows.
Hope this makes sense...
Ive been, and i am, struggling with this, and can't get it to work. Thanks in advance for your help.
Asked another question similar yesterday but think it wasn't clearly explained.
I am updating (successfully) the object(zucode), sending it through a link to:
def changezu
zucode = Zucode.where( id: params[:id], secret2: params[:secret2] ).first
if zucode
respond_to do |format|
format.html { redirect_to edit_zucode_path, notice: 'Make changes!.' } #edit_zucode_path
format.json { head :no_content }
end
end
end
my update method is :
def update
respond_to do |format|
if #zucode.update(zucode_params)
format.html { redirect_to #zucode, notice: 'Zu was successfully updated.' }
format.json { render :show, status: :ok, location: #zucode }
else
format.html { render :edit }
format.json { render json: #zucode.errors, status: :unprocessable_entity }
end
end
end
UPDATE::
added to controller :.
after_action :trynew, only: [:update]
and
def trynew
#zucode = Zucode.where("zucode_number=id")
#ZuMailer.mymail.(zucode).deliver
end
with the mailer commented out, it does not give error but nothing happens. Looking into log the correct rows are not being selected or mentioned. I only see reference to the updated row. If i run with the mailer i get error for "wrong arguments 0 of 1"
As I can see, that you need to use callbacks of object lifecycyle.
I am assuming that you have a model called Zucode .
Inside that model add after_update method.
class Zucode
after_update :method_to_call
def method_to_call
zucodes = Zucode.where('zucode_number=id')
#perform action on zucodes
end
end
The method method_to_call will get called each time a Zucode is updated.

can't update field in rails 4

When i update my revisor_id from my Petition model, i want to automatically update my revisor_id from my Post model.
So, in my petitions_controller.rb i added this line within the update method:
#petition.post.revisor_id = #petition.revisor_id
With the debugger i can see that after the line is executed, both are set correctly. But it seems like it's not saved in the database or something, because when i request to show all the posts with revisor_id set to 1, it doesnt show anything.
I believe it has something to do with strong parameters since i just chaged to rails 4, but not sure.
Any idea?
#petitions_controller.rb
...
def update
respond_to do |format|
if #petition.update(petition_params)
#petition.post.revisor_id = #petition.revisor_id
format.html { redirect_to #petition, notice: 'Petition was successfully updated.' }
format.json { render :show, status: :ok, location: #petition }
else
format.html { render :edit }
format.json { render json: #petition.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_petition
#petition = Petition.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def petition_params
params.require(:petition).permit(:user_id, :category_id, :revisor_id, :status, :post_id)
end
You're not calling save.
#petition.post.update_attibute(:revisor_id, #petition.revisor_id)
Or
#petition.post.revisor_id = #petition.revisor_id
#petition.post.save
But it looks like you could redesign your database and you has_one through so you wouldn't need to do that.

Render does not run action

I am using almost the code from the regular scaffold. The only change is the 4.times block where I make 4 answer objects on the question. The reason is that I have the corresponding input fields in the view. Now, if the validation fails it renders the new.html.erb again, however after what I have been reading it does not invoke the "new" action again. However I am depending on the 4.times block because otherwise the loop in the view have no answers to loop through. How do I fix this? I tried redirecting but then the error messages disappered.
New action
def new
#question = Question.new
4.times do
#question.answers.build
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #question }
end
end
Create action
def create
#question = Question.new(params[:question])
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
format.json { render json: #question, status: :created, location: #question }
else
format.html { render action: "new" }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
Exactly as your title suggests: render does just render (the template belonging to) the action and not perform the action itself.
To get your prebuilt answers rendered again in a failed create call, I suggest removing the :reject_if condition from the nested attributes setup. This way, empty submitted answers are preserved. To prevent them from being written to the database, just add regular validations to the Question model...
What you need to look at is the #question in your create action. In theory it should contain the 4 newly built answers so redisplaying the form would also contain these.
If they are not written you may have to look at the accepts_nested_attributes_for to make sure it gets deserialized correctly from the request.

Resources