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.
Related
I am trying to implement post action using httparty gem and this is what I have. I am running everything in docker and I have code below that will run as active job. I is in one service and I am trying to make post to api in other service. I am able to do get but not having any luck with post. I looked and searched a lot online but I am not sure what is it I am doing wrong. I always get error 403 at self.class.post line. I also tried to do a postman call to api and I am able to hit the api but with the code below its not even reaching to the other service.
Any help is appreciated. Thanks.
require 'uri'
class CustomerProductAPI
include HTTParty
format :json
def initialize(customer_product_id)
#customer_product = CustomerProduct.find(customer_product_id)
#customer = Customer.find(#customer_product.student_id)
#product = Product.find(#customer_product.product_id)
self.class.base_uri environment_based_uri + '/customer_product_api'
end
def create_customer_product
uri = URI(self.class.base_uri + "/customer/#{customer.id}")
self.class.post(uri, body: body_hash).response.value
end
private
attr_reader :customer_product, :customer, :product
def body_hash
{
token: ENV['CUSTOMER_PRODUCT_API_TOKEN'],
customer: customer.name,
product: product.name,
}
end
def environment_based_uri
ENV['CUSTOMER_PRODUCT_URL']
end
end
While we can't actually be sure of exactly what the server thats accepting the response expects you're definately doing quite a few non-idiomatic things here which will aggrevate trouble shooting.
base_uri should just be set in the class body. Not in initialize for each instance. You also do not need to construct a URI with HTTParty. Just pass a path and it will construct the request uri relative to the base_uri.
When getting configuration from ENV use ENV.fetch instead of the bracket accessors as it will raise a KeyError instead of just letting a nil sneak through.
Your HTTP client class should not be concerned with querying the database and handling the potential errors that can occur if the records cannot be found. That should be the responsibility of the controller/job/service object that calls the client. Since you're only actually using three simple attributes it doesn't actually need records at all as input and its actually better that it doesn't have to know about your models and their assocations (or lack thereof in this case).
class CustomerProductAPI
# lets you stub/inspect the constant
CUSTOMER_PRODUCT_URL = ENV.fetch('CUSTOMER_PRODUCT_URL') + '/customer_product_api'
include HTTParty
format :json
base_uri CUSTOMER_PRODUCT_URL
def initialize(id:, product_name:, customer_name:)
#id = id
#product_name = product_name
#customer_name = customer_name
end
def create_customer_product
self.class.post("/customer/#{#id}", body: {
token: ENV.fetch('CUSTOMER_PRODUCT_API_TOKEN'),
customer: #customer_name,
product: #product_name
})
# don't return .response.value as it will make error handling impossible.
# either handle unsuccessful responses here or return the whole response
# for the consumer to handle it.
end
end
If a parameter that's required is missing using strong parameters, the Rails server will respond with an HTTP 500.
This does not give me control over giving the user feedback with what exactly went wrong. Does it not make sense to be able to send them back a message such a required parameter is missing?
What is the "Rails way" of giving appropriate user feedback on ActionController::ParameterMissing? Is one supposed to capture the exception and handle your request response there? It seems wrong to do that in every controller.
You can use
rescue_from ActionController::ParameterMissing do |e|
render 'something'
end
in your ApplicationController (or whatever your parent controller is).
As to whether you should inform users or not, I think it depends on what your controllers are doing. If they are API controllers, it definitely makes sense to handle that gracefully, as the users are responsible for preparing the input.
If they are accepting data from your HTML forms it's, in my opinion, not that important as the missing parameters probably mean that the user tinkered with the HTML, or something went really wrong inside the browser.
Since you mention wanting to communicate the error specifics back to the user, you could do something like the following:
# app/controllers/application_controller.rb
rescue_from ActionController::ParameterMissing do |exception|
render json: { error: exception.message }, status: :bad_request
end
You can also define a method to handle a specific exception, if you'd prefer to break up the handling logic:
# app/controllers/application_controller.rb
rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing
def handle_parameter_missing(exception)
render json: { error: exception.message }, status: :bad_request
end
Both of the above examples will return a JSON response like so: {"error"=>"param is missing or the value is empty: [field_name]"}
For an API-only application, I think this is valuable information to pass on.
More info:
Rails API rescue_from documentation
Handling Errors in an API Application the Rails Way
I have a factor controller which takes factor values for two different types of factors
Primary Factor
Secondary Factor
And I pass them as params as follows:
class FactorController < AdminController
def create
if primary_factor_params ## LINE 5
do something
elsif secondary_factor_params
do something else
end
end
def primary_factor_params
params.require(:primary).permit(:user_id, ## LINE 70
:primary_factors)
end
def secondary_factor_params
params.require(:secondary).permit(:user_id,
:secondary_factors)
end
end
But in the above whenever I try to pass a secondary_factor I get the following error:
ActionController::ParameterMissing (param is missing or the value is empty: primary):
app/controllers/factors_controller.rb:70:in `primary_factor_params' app/controllers/api/v1/admin/factors_controller.rb:5:in `create'
So to me it seems that this error is coming up because I didn't have any values for primary_factor_params in this condition and that's way it throws the error because of the first if condition.
I've tried:
primary_factor_params.exists?
primary_factor_params.has_key?(:primary_factors)
....
But all of them throw the same error since primary_factor_params doesn't exist. Is there a way to test this without throwing an error for missing params?
The problem is this line params.require(:primary) saying that the parameter is required, so attempting to do like params.require(:primary).exists? wont help you if you do not have a :primary param at all because the require already failed.
You need to check its existance on params itself. For example params.has_key?(:primary).
Dependening on your use case, you might also use params.permit(:primary) directly on params as well. See the API docs for detailed information on how ActionController::Parameters (the class for params) can be used.
http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit
you can check your params existence in your case by params[:primary].present?
I think this is the easiest way
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
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.