I'm having an issue tracking down why my update method isn't getting the needed arguments. I have a similar test for show and the payloads are working. In this scenario the route in question is invoice/invoice_id/trip/id. If you can help me spot the error and give any suggestions on how to troubleshoot this type of problem in the future that'd be great.
This is the update method.
def update
if #trip.update(#trip.trip_id, trip_params)
head :no_content
else
render json: [#invoice, #trip].errors, status: :unprocessable_entity
end
end
With the following private methods.
private
def set_trip
#trip = Trip.where(:invoice_id => params[:invoice_id], :trip_id => params[:id] )
end
def trip_params
params.require(:trip).permit(:trip_id, :depart_airport, :arrive_airport, :passenger_first_name, :passenger_last_name, :passenger_count, :departure_date, :merchant_id)
end
def load_invoice
#invoice = Invoice.find(params[:invoice_id])
end
end
My failing test looks like this.
test "should update trip" do
put :update, invoice_id: #invoice.invoice_id, id: #trip,
trip: {arrive_airport: #trip.arrive_airport,
depart_airport: #trip.depart_airport,
departure_date: #trip.departure_date,
passenger_count: #trip.passenger_count,
passenger_first_name: #trip.passenger_first_name,
passenger_last_name: #trip.passenger_last_name}
assert_response 204
end
if you are calling set_trip in before_action then update() method should look like this
def update
if #trip.update(trip_params)
head :no_content
else
render json: [#invoice, #trip].errors, status: :unprocessable_entity
end
end
update() is an instance method that can be called using object, you only need to pass trip_params into it, Hope that helps!
You can get this error message when the method is calling another which is being passed the wrong number of arguments.
update takes a hash as its one and only argument, but you are are passing two arguments (#trip.trip_id, trip_params) in the update method. This is why you are getting the "Wrong number of arguments (1 for 2) for update method" error message. as #RSB said, just pass in the trip_params and the Trip instance will be updated.
RSB was right on the money. It turned out in this case that my issue was at the database level. The table didn't have a primary key so I was using
#trip = Trip.where in the private method and this was causing it to come back with an array of possible rows rather than the specific one. I changed things at the database level to have a primary key and updated the private method. VoilĂ RSB's code worked!
Related
I have a controller that accepts three params, title, users and project_type. I want to make all the params required
I have seen people do things like
def project_params
params.require(:title,:project_type, :users)
.permit(:title, :project_type, :users)
end
And then do Project.new(project_params), but I need to work a little with the params first. How can I make this possible?
I make a post request in postman like this:
module Api
module V1
class ProjectsController < ApplicationController
def create
admins = params[:admins]
users = get_user_array()
project_type = ProjectCategory.find_by(name: params[:project_type])
project = Project.new(
title: params[:title],
project_category: project_type,
project_users: users)
if project.save
render json: {data:project}, status: :ok
else
render json: {data:project.errors}, status: :unprocessable_entity
end
end
...
end
end
end
{
"title": "Tennis",
"project_type": "Sports",
"users": [{"name": "john Dow", "email": "johnDoe#gmail.com"}],
}
I would say that you are using ActionController::Parameters#require wrong. Its not meant to validate that the all the required attributes are present - thats what model validations are for. Rather you should just use params.require to ensure that the general structure of the parameters is processable.
For example if you used the rails scaffold you would get the following whitelist:
params.require(:project)
.permit(:title, :project_type)
This is because there is no point in continuing execution if the project key is missing from the params hash since this would give you an empty hash or nil.
ActionController::Parameters#require will raise a ActionController::ParameterMissing error which will return a 400 - Bad Request response which is the wrong response code for what you are doing. You also should not use exceptions for normal application flow. A missing attribute is not an exceptional event.
Instead if you want to use a flat params hash you should whitelist it with:
def project_params
params.permit(:title, :project_type, users: [:name, :email])
end
I think that if you don't have to get anything from the frontend to run get_user_array(), you could only allow and require title and project_type.
def create
users = get_user_array()
project = Project.new(project_params)
project.users = users
if project.save
render json: {data:project}, status: :ok
else
render json: {data:project.errors}, status: :unprocessable_entity
end
end
private
def project_params
params.require(:project).permit(:title, :project_type).tap do |project_params|
project_params.require(:title, :project_type)
end
end
If you need to process something before creating the project, you can do this:
project_category = ProjectCategory.find_by(name: project.project_type)
I'm having a problem with validations at rails, i have a parameter call propose that cannot be blank, when I try to create it work, but when update do not, this is my validations:
class Propose < ActiveRecord::Base
validates_presence_of :propose, :date, :meeting_type
end
For some reason, this only works when a object is create, then propose cannot be blank, but if I try update for empty it works when should not. I tried this solution, but did not work as well:
validates_length_of :propose, :minimum => 1, allow_blank: false
In both cases, my rspec returns this
Failure/Error: expect(response.body).to be_empty
expected `"{\"id\":508,\"propose\":\"\",\"business_id\":442,\"date\":\"2017-05-24T00:00:00.000-03:00\",\"meetin...\":\"2017-05-24T14:13:42.120-03:00\",\"updated_at\":\"2017-05-24T14:13:42.120-03:00\",\"status\":0}".empty?` to return true, got false
This is the test:
it "Should not be able to update propose because propose is blank" do
#propose = Fabricate(:propose, company: #current_user.return_company)
put :update, propose_id: #propose.id, propose: ''
expect(response.body).to be_empty
expect(response.status).to eq(422)
end
This is my controller:
def update
begin
propose = Propose.find(params[:propose_id])
propose.update_attributes(update_params)
render(json: propose.to_json, status: :ok)
rescue => e
render(json: {error: e.message}, status: :unprocessable_entity)
end
end
private
def update_params
params.permit(:propose, :date, :meeting_type, :status)
end
So, that is the problem, propose cannot be blank when update or create, but the validations appears only to work for create. I know that I can use something like:
raise "empty propose" if params[:propose].empty?
But I want to use rails methods if possible to the code don't be fill with manually validations when there is a way to do using rails already.
While you could use "dangerous" .update! which raises an exception if the record is invalid:
def update
begin
propose = Propose.find(params[:propose_id])
propose.update!(update_params)
render(json: propose.to_json, status: :ok)
# Never use rescue without an exception type!
rescue ActiveRecord::RecordInvalid => e
render(json: {error: e.message}, status: :unprocessable_entity)
end
end
This is not a good practice since exceptions should be used for exceptional events. Not the normal application flow.
Instead use the safe .update method and check the return value:
def update
propose = Propose.find(params[:propose_id])
if propose.update(update_params)
render(json: propose.to_json, status: :ok)
else
render(json: { errors: propose.errors.full_messages }, status: :unprocessable_entity)
end
end
First of all response.body will never be empty based on what you have and generally shouldn't be as it offers no context to the situation.
Secondly you are not checking that update_attributes returned false you are just returning the object. That object is in an invalid state and does not reflect the persisted object since validation failed.
update_attributes will call assign_attributes before save so the in memory propose will reflect the new assignment with propose.propose being blank but that is not actually what is stored in the database.
Consider changing update to
def update
begin
propose = Propose.find(params[:propose_id])
if propose.update_attributes(update_params)
render(json: propose.to_json, status: :ok)
else
# modify this portion as you see fit
render(json: {error:{errors: propose.errors.full_messages}}.to_json,status: :unprocessable_entity)
end
end
end
If you want an Error to be raised as your initial intent implies then use
# expanded based on #ma_il's answer
def update
begin
propose = Propose.find(params[:propose_id])
propose.update_attributes!(update_params) #notice the bang (!)
render(json: propose.to_json, status: :ok)
# notice we are only rescuing ActiveRecord::RecordInvalid rather than
# Exception which is considered poor form and could expose information in the json that you don't intend
rescue ActiveRecord::RecordInvalid => e
render(json: {error: e.message}, status: :unprocessable_entity)
end
end
You're calling update_attributes but never checking its return value. Since it looks like you're expecting an error to be raised, you should use update_attributes! (with an exclamation mark at the end) instead.
Note that it is advisable to rescue only from those errors you're actually expecting to be thrown. In this case, you should rescue ActiveRecord::RecordInvalid => e.
Alternatively, you can also use the rescue_from method to execute error handling in a separate method. This is helpful if your error handler is more complex, for example because it has its own respond_to block.
Quoting Rails documentation on update a.k.a update_attribute
Updates a single attribute and saves the record. This is especially
useful for boolean flags on existing records. Also note that
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
This method raises an ActiveRecord::ActiveRecordError if the attribute
is marked as readonly.
So there's that, update skips the validation steps, if you want to validate your models either call valid? yourself or use save instead of update.
I'm currently using the below to add one product that has a name and a brand via API call. I would like to be able to submit an array of 'products' and then add then to my DB.
Could anyone suggest:
1) How would I do this in the controller?
2) How would I structure the API POST body?
Current call looks like:
http://localhost:3000/api/v1/products?brand=brand&name=name
My Controller:
def create
#newProduct = Product.create(product_params)
if #newProduct.save
render json: {message: "Product created"}
else
render json: {error: "Failed to create product"}
end
end
private
def product_params
params.permit(:name, :brand)
end
Thanks
Add a new route in routes file with line below
get 'create_multiple_products'
Send data in an array
{"products":[
{"name":"playstation"},
{"name":"xbox"},
{"name":"blueray"}
]}
then add a new method in controller and call the create in a loop
def create_multiple_products
response["products"].each do |p|
Product.create( p )
end
end
The above is pseudocode, you might want to try a test driven approach setting up expected api and matching with returned data with rspec. http://matthewlehner.net/rails-api-testing-guidelines/
There is the following code in Comments controller:
def create
current_user.comments.create!(place: current_place, content: params[:content])
render json: encode_result
end
private
def current_place
Place.find(params[:place_id])
end
This code creates a new comment for a current user and a current place; if the current place doesn't exist than this code throws exception RecordNotFound. I can send 'place_id' instead of 'place' for 'create' method, but I need to check if place exists before creating a new comment. Please, tell me, is my solution good or there is any better way? Thanks in advance.
This is doing what it's supposed to do. Your code is perfect as it's written now. You specifically want to halt execution if a specified record doesn't exist. All you need to do is handle the exception.
You can do this for your entire controller with rescue_from, or your entire application by placing rescue_from in your ApplicationController.
You can do like this.........
def create
current_user.comments.create!(place: current_place, content: params[:content]) if current_place
render json: encode_result
end
private
def current_place
Place.find(params[:place_id])
end
I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end