So I observed some weird behaviour while implementing an endpoint for a RESTful API I am creating for a mobile client. I am using a PUT method to update an attribute on the User model. I send the user's id as a URL parameter and the value to update inside a JSON object. Everything seems to work just fine but when I check the parameters via the rails logs I noticed something strange. For some reason there is an extra parameter being sent to the backend that I can't seem to explain. Here are the logs I am seeing when I call this endpoint from the mobile client:
Parameters: {"enable_security"=>true, "id"=>"7d7fec98-afba-4ca9-a102-d5d71e13f6ce", "user"=>{}}
As can be seen above an additional "user"=>{} is appended to the list of parameter entries. I see this when I print out the params object as well. I can't seem to explain where this is coming from. I also checked the mobile client just to be safe and there is no where in code where I send a parameter with a key user. This is very puzzling to me and makes me think I am missing something fairly simple. Why is there an empty object with the user key being sent to the backend RESTful API?
Update to Provide More Information
Here is the code that gets called when the user hits the endpoint that updates the user User model:
#PUT /:id/user/update_security_settings
def update_security_settings
#user = User.find_by_id(params[:id])
#user.advanced_security_enabled = params[:enable_security]
respond_to do |format|
if #user.save
response = {:status => "200", :message => "User's security settings updated."}
format.json { render json: response, status: :ok }
else
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Update in Response to User's Comments
Here are the routes that pertain to the user_controller, the view controller that defines all endpoints that deal with creating and updating the User model.
post '/user/upload_profile', to: 'user#upload_profile'
get '/:id/user', to: 'user#find_user'
put '/:id/user/update_security_settings', to: 'user#update_security_settings'
resources :user, :defaults => { :format => 'json' }
Does this comment really mirror your actual route?
#PUT /:id/user/update_security_settings
I'd expect it to be /user/:id/update_security_settings instead.
Can you show us your config/routes.rb - My wild guess is that your routes are somehow configured to expect an actual nested user param, which you don't send (of course) and therefor appears empty in the logs.
Update:
Some of your routes are unusual. You actually don't need the find_user route as it should be covered under resources :user as show action (provided you defined a show method in your controller, which is the default way to retrieve a single resource item; so no need for find_user)
For custom routes like your update_security_settings action I'd suggest to stick to the default pattern, like resource/:id/actionand nesting it in the default resourceful route. Putting the id before the resource is very unusual, confusing and may actually be related to your issue (thoguh I#m not sure about that). Try cleaning up your routes.rb liek this:
# notice that resources expects the plural form :users
resources :users do
member do
patch :update_security_settings
post :upload_profile
# any other custom routes
end
end
This will result in routes like GET /users (index), GET /users/1 (show) and PATCH /users/1/update_security_settings.
More on routing an be found here: Rails Guides | Routing From The Outside In
Please check if the changes above remove your empty user param.
Check your configuration in
config/initializers/wrap_parameters.rb
wrap_parameters format: [] this list should not contain json then it will wrap the parameters to the root of you controller for all the json request. Refer api docs
Related
I've read several | articles about using params.require(...) in Rails, but nothing that shows them in a non-trivial, real-world scenario.
Specifically, the following URL will be called:
GET http://myapp.example.com/widgets/{clientUuid}
Where {clientUuid} will be a string. I just want to check (from the proper controller action) whether the provided {clientUuid} is non-null and non-empty. I'm wondering if I can just do this:
if params.require(params[:clientUuid]) == null
response = { "error" => "bad client uuid" }
render json: response, status: :bad_request
return
end
And have non-nullness/non-emptiness enforced? If not, what can I do to achieve my desired result?
You're overcomplicating a simple GET request by messing up the route and using a method thats meant for a completely different use.
The idea is that .requires should be used for non-idempotent request methods (POST, PUT,PATCH) where the request contains a body with parameters. It lets you take a single key from the params and whitelist the params contained - which matches the Rails ideom of nesting inputs in a hash with the name of the resource as the root key.
In that case using .requires lets you return a response code to the client that indicates that the request cannot be processed (422 - Unprocessable Entity) as the request body does not have the right structure.
While you could potentially use it creatively on a GET request its wrong from a restful application engineering standpoint. In your case you should be returning a 404 - Not found response code if the clientUuid does not match a record. Usually in rails this is done by using .find which will raise a ActiveRecord::RecordNotFound exception which the framework catches.
Additionally if you have declared the route properly in the first place rails would actually give a 404 automatically as the request would not match if the id segment is missing.
class WidgetsController < ApplicationController
def show
#widget = Widget.find(params[:clientUuid])
end
end
If you want you could bail early so that the database is never queried if the param does not match a condition:
class WidgetsController < ApplicationController
def show
raise ActiveRecord::RecordNotFound if params[:clientUuid].blank?
#widget = Widget.find(params[:clientUuid])
end
end
You can just write:
if params[:clientUuid].blank?
response = { "error" => "bad client uuid" }
render json: response, status: :bad_request
return
end
With params.require it is a bit more difficult, because require raises a ActionController::ParameterMissing exception if the parameter is missing, but allows the parameter to return false (what I guess is still invalid in your example):
begin
uuid = params.require(:cliendUuid)
rescue ActionController::ParameterMissing
# nothing to do, just ensure the exceptions is rescued
end
unless uuid
# handle missing uuid
end
Or:
begin
uuid = params.require(:cliendUuid) || raise ActionController::ParameterMissing
rescue ActionController::ParameterMissing
# handle missing uuid
end
The article you posted re strong parameters is specifically about protecting your database data from user input, usually provided by forms.
params.require(:user).permit(:username)
The above code specifies that for the model User only allow the attribute username to be touched. If you try to update or create a user record in the user table with any other attribute e.g. email, you would get an error because the email attribute has not been 'permitted'. This is what is meant by whitelisting. You will only see the above code in create or update controller methods, or any other method that amends the data in some way. (An exception, of course, is deleting a record).
In your example, the parameter is provided as part of the url which you can also access via the rails provided params hash. However, as your method is not interacting with the db, you don't need to run it through the permit method.
This resource may help.
Currently I have a rails controller with an update method:
def update
respond_to do |format|
if #segment.update(segment_params)
format.html { redirect_to #segment, notice: 'Segment was successfully updated.' }
else
format.json { render json: #segment.errors, status: :unprocessable_entity }
end
end
end
The segment_params method:
def segment_params
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags)
end
I'm unsure as to how to make the actual request to be accepted by rails. I'm using Postman (Google Chrome) to make the request. I'm making a PUT request to localhost:3000/segments/2677.json with the segment info as JSON. Is this the correct way of doing it? Or would this be a URL param? Not quite sure how to format it as a URL param.
You should not make changes to server data via a GET request. For the reasoning behind this see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html section 9.1.1 - bascally GET and HEAD request should always be safe.
The method you are using right now is actually a good one. My only suggeston - if you really want to follow the HTTP spec would be to use PATCH instead of PUT for partial updates. The semantics for PATCH are essentially that rather than being idempotent like PUT it does delta updates like what you are doing. See : http://restful-api-design.readthedocs.org/en/latest/methods.html for more info on the topic.
You can check the server log after the PUT and you'll see something like this:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"zzzz", "segment"=>{"name"=>"My Segment Name", "description"=>"Something", "f_30_estimated_reach"=> "I dont know", "tags"=> "tag1 tag2 tag3" }
The key "segment" must be present with your data as value. I assume that "tags" isn't an array, or represent an associated model.
If not, let say 'tags is an array', you must call permit this way:
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags [])
If tags is an associated model and you update the 'name' field:
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, tags: {:name})
I hope it helps.
In Rails 4, the recommended HTTP action for an update action is the PATCH method. If you run rake routes in your Terminal, you'll see that the update action is linked to the PATCH action by default (assuming that you're using the resources method.
Rails is very opinionated about how you pass parameters in, and uses the concept of "strong params" to sanitize and validate your parameters as they're passed in from your form to your controller. This is a good primer on the topic (https://github.com/rails/strong_parameters), but I'm assuming you're trying to pass tags in as a collection (array in this case).
def segment_params
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags => [])
end
As Alejandro Babio said, if you're using an associated model then it you need to permit tags along with their model attributes that you've defined in your database migration file for the tags table. For example:
def segment_params
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags => {:name, :created_at, :updated_at})
end
It also might be a good idea to see what your params actually looks like by calling segment_params.inspect before #segment.update(segment_params). If :tags does not show up in the inspect method output, then it's not being passed into your strong params correctly.
Thanks for all the suggestions. This was an oversight on my part. I thought POSTMAN would specify the Content-Type HTTP Header as application/json because I was posting JSON.
A common pattern in a Rails controller action is to
Fetch a resource
Do something to the resource (optional)
Return the resource in a serialized format.
I am looking for a library that abstracts away the first step, so that my controller actions can assume a resource was successfully fetched and avoid checks for exceptional cases.
For example, here is a hypothetical show action:
def show
attrs = params.slice(:handle, :provider)
account = Account.find_by(attrs)
if account
respond_with account
else
head 404
end
end
And what I want is something more like this:
# controller
def show
respond_with resource
end
# some initializer (basically pseudocode)
resource do |params|
attrs = params.slice(:handle, :provider)
Account.find_by(attrs)
end
Where the library would handle returning a 404 if find_by returns nil, or 400 if the provided params are invalid (missing :handle key, include an extra :id key, etc.).
Does anyone know of a library that provides something like this? It is a great use case for a Rack middleware on top of Application.routes.
The gem platformatec/inherited_resources does something very close to this.
StackOverflow seems to have this style of routes for questions:
/questions/:id/*slug
Which is easy enough to achieve, both in routes and to_param.
However, StackOverflow seems to also redirect to that path when just an ID is passed.
Example:
stackoverflow.com/questions/6841333
redirects to:
stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/
Same goes for any variation of the slug
stackoverflow.com/questions/6841333/some-random-stuff
Will still redirect to the same URL.
My question is: Is this type of redirection typically handled in the controller (comparing the request to the route) or is there a way to do this in routes.rb?
The reason I wouldn't think this possible in the routes.rb file is that typically, you don't have access to the object (so you couldn't get the slug based off the ID, right?)
For anyone interested, Rails 3.2.13 and also using FriendlyID
Ok, so I think I've got this.
I was looking into doing something with middleware, but then decided that's probably not the place for this type of functionality (since we need to access ActiveRecord).
So I ended up building a service object, known as a PathCheck. The service looks like this:
class PathCheck
def initialize(model, request)
#model = model
#request = request
end
# Says if we are already where we need to be
# /:id/*slug
def at_proper_path?
#request.fullpath == proper_path
end
# Returns what the proper path is
def proper_path
Rails.application.routes.url_helpers.send(path_name, #model)
end
private
def path_name
return "edit_#{model_lowercase_name}_path" if #request.filtered_parameters["action"] == "edit"
"#{model_lowercase_name}_path"
end
def model_lowercase_name
#model.class.name.underscore
end
end
This is easy enough to implement into my controller:
def show
#post = Post.find params[:post_id] || params[:id]
check_path
end
private
def check_path
path_check = PathCheck.new #post, request
redirect_to path_check.proper_path if !path_check.at_proper_path?
end
My || in my find method is because in order to maintain resourceful routes, I did something like...
resources :posts do
get '*id' => 'posts#show'
end
Which will make a routes like: /posts/:post_id/*id on top of /posts/:id
This way, the numeric id is primarily used to look up the record, if available. This allows us to loosely match /posts/12345/not-the-right-slug to be redirected to /posts/12345/the-right-slug
The service is written in a universal fashion, so I can use it in any resourceful controller. I have't found a way to break it yet, but I'm open to correction.
Resources
Railscast #398: Service Objects by Ryan Bates
This Helpful Tweet by Jared Fine
I'm trying to work out how to get a custom route working with rails to perform a date query. I'd like to call this report if possible. So I access my route on the browser and get the following.
http://localhost:3000/trips/report
Couldn't find Trip with id=report
Like it's trying to read in report as an ID? I can't quite work out where I've gone wrong here? In my routes.rb file I've create the following entry.
match 'trips/report' => 'trips#report'
With the following in my trips controller.
def report
#trips.all :condition => ["DATE(date) = DATE(?)", Time.now]
respond_to do |format|
format.html
format.json { render json: #trips }
end
end
I'm probably doing something very silly! Hopefully someone can help me along the right track?
You certainly have declared a trips resource in your routes.rb, and the route GET 'trips/:id' generated by the resource have a higher priority then the match 'trips/report' defined later (Rails uses the first matching rule). If it's the case, declare your report route like this:
resources trips do
collection do
get 'report'
end
end
Look at this chapter in Rails Routing Guide for more information.