I have a Rails 5.2.3 API that is being consumed by a Vue application I maintain.
In the API, I have a Questions table, and each Question has a specific type. I'm creating a feature where my users can create their own custom questions out of a select few options of question types, for the sake of simplicity let's say they only have 2 options to choose from, an OpenEnded question and a StarRating question.
While I realize the risk of bad user input here is impossible (since on the client side of things I'm just giving them a checklist and supplying that checklist to the API), I'd still like to have some checks in place to ensure that the ONLY type permitted in the params are OpenEnded and StarRating, especially since I'm planning on expanding this API to be public-facing at some point in the future, where the risk of bad input is indeed possible.
What's the proper way of handling this so that my API returns a 400 or 422 code if the question type isn't one of the whitelisted options?
The current create method and question_params method are about as bog-standard as they can possibly get
def create
#question = Question.new(question_params)
if #question.save
render json: { question: #question }, status: :created
else
render json: { errors: #question.errors }, status: :bad_request
end
end
def question_params
params.permit(:content, :type)
end
What's the proper way of handling this so that my API returns a 400 or 422 code if the question type isn't one of the whitelisted options?
You would do this as a validation. Simplest way is to do it on the Question model.
class Question < ApplicationRecord
validates :type, inclusion: { in: %w(OpenEnded StarRating) }
end
The save will fail if the type is missing or incorrect.
Note that type has a special meaning, you may wish to consider a different column name if you're not using Single Table Inheritance.
Related
I need some help I have a controller with an action that queries two models.
Now I need to send both of them as json in order to be used on my angular views.
In the example bellow how should I send the "complex" and its "fields" in one json response?
Ex.
def complexes_and_fields
complex = Complex.find(params[:id])
search_params = {complex_id: complex._id}
fields = Field.where(search_params)
if !complex.nil?
render json: ???.to_json, status: :ok
else
render json: { error_description: 'no complex found' },status: :bad_request
end
An easy way to do this is to build a hash with your objects
complex = Complex.find(params[:id])
search_params = {complex_id: complex._id}
fields = Field.where(search_params)
render json: { complex: complex, fields: fields, search_params: search_params }, status: :ok
Another way would be to user a view such as some_view.json.erb where you render the objects as you are expecting it in your angular view. Also you can use can use ActiveModelSerializers, read on https://github.com/rails-api/active_model_serializers
Ideally what you will want to do is encapsulate this response into its object and make a single call in your controller that returns you the results
Without going into too much details something like this
results = MyComplexFieldsObj.response(params[:id])
render son: results, status: :ok
This is an extremely common requirement in Rails applications. This need is rarely restricted to a single model, or a single location. As a result, a variety of gems exist to provide this kind of functionality (in many cases, without altering the signature of your render lines substantially).
This post offers a good listing. Personally, I've had a good experience with active_model_serializers and an acceptable experience with grape-entity. It's reasonable to review their documentation and decide which is best for you.
I try to stick to principles of thin controller and thin model. And I believe that business logic related code should be in the service classes.
I write a Rails back-end application accepting JSON requests. And I need to validate, that the one parameter is present. Let's assume that I have:
class UserController
def change_status
user = User.find(params[:id])
render json: UserStatusChanger.new(user, params[:status]).perform!
end
end
class UserStatusChanger
attr_reader :user, :status
def initialize(user, status)
#user = user
#status = status
end
def perform!
# complex logic here
{result: 'ok'}
end
end
And now let's suppose that I need to receive non-blank params[:status]. Sure, in the real world it is much more complex with more parameters. :)
My question is: Where should I put validation of params[:status]?
My thoughts are:
If I put it in the controller, I need an integration test to test the validation. But there are some good looking solutions, as rails_params gem. But I can face with problem of big controller method, having many validations. And also in the unit test my service will work wrong if some input parameter is nil without validation before performing complex logic.
If I put is in the service, the test will be more lightweight. But I should catch exceptions via ApplicationController#rescue_from and this will be not tested.
I think, that you should do it in controller. As for me, i usually do it in before_action method. According to MVC pattern, all params and routing logic must be in controller.
Is there any way to remove sensitive fields from the result set produced by the default ActiveRecord 'all', 'where', 'find', etc?
In a small project that I'm using to learn ruby I've a reference to User in every object, but for security reasons I don't want to expose the user's id. When I'm using a simple HTML response it is easy to remove the user_id simply by not using it. But for some task I'd like to return a json using something like:
def index
#my_objects = MyObject.all
respond_to do |format|
...
format.json { render json: #my_objects, ...}
...
end
end
How do I prevent user_id to be listed? Is there any way to create a helper that removes sensitive fields?
You can use the as_json to restrict the attributes serialized in the JSON response.
format.json { render json: #my_objects.as_json(only: [:id, :name]), ...}
If you want to make it the default, then simply override the method in the model itself
class MyObject
def serializable_hash(options = nil)
super((options || {}).merge(only: [:id, :name]))
end
end
Despite this approach is quick and effective, it rapidly becomes unmaintainable as soon as your app will become large enough to have several models and possibly different serialization for the same type of object.
That's why the best approach is to delegate the serialization to a serializer object. It's quite easy, but it will require some extra work to create the class.
The serializer is simply an object that returns an instance of a model, and returns a JSON-ready hash. There are several available libraries, or you can build your own.
questions_controller.rb
def index
#questions = Question.all(app_params)
end
private
def app_params
params.require(:questions).permit(:question, :answer)
end
end
question.rb
class Question < ActiveRecord::Base
end
I am completely new to ruby-on-rails. I was following a guide and it said I should take care of some "loopholes" or "security issues" and it used attr_accessible, but on Rails 4, they suggest strong parameters, so now I'm trying to use them. I'm confused on how to define the :questions params, because I'm currently getting an error saying that :questions param is not found.
:questions is pretty much something that I will define myself as the web developer.
So for example, I will define questions = "How are you?", "What is your name?". I'm basically starting very simply. I want questions that I have created to be displayed on my webpage. Ultimately, I plan to make a website what is basically a list of questions and, with answer options. After the user clicks "submit" I want to store the information into my database.
Am I supposed to even be requiring this as a param? I'm completely lost..
Do you have a dump of the params we could look at? They are shown when your app encounters an error, and typically shows you the params array which rails will pass through
Strong Params In Rails 4
Strong Params allow you to allow certain parameters for use in the controller, protecting against any malicious assignment client-side. They replaced attr_accessible in Rails 4.0
Strong Params is only for user-submitted content, as it's designed to protect the params hash. To that end, it's mostly used with the create and find functions:
class PeopleController < ActionController::Base
# Using "Person.create(params[:person])" would raise an
# ActiveModel::ForbiddenAttributes exception because it'd
# be using mass assignment without an explicit permit step.
# This is the recommended form:
def create
Person.create(person_params)
end
# This will pass with flying colors as long as there's a person key in the
# parameters, otherwise it'll raise an ActionController::MissingParameter
# exception, which will get caught by ActionController::Base and turned
# into a 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap { |person|
person.update!(person_params)
}
end
private
# Using a private method to encapsulate the permissible parameters is
# just a good pattern since you'll be able to reuse the same permit
# list between create and update. Also, you can specialize this method
# with per-user checking of permissible attributes.
def person_params
params.require(:person).permit(:name, :age)
end
end
params.require
The params.require function works by taking this params hash:
params{:question => {:question => "1", :answer => "5"}}
That's why people asked what your params hash looks like, because the require function can only work if the :question hash is present.
Possible Solutions For You
Question.all(app_params)
Regardless of what you're trying to achieve, don't use all. The where function is better for receiving an array of data based on certain values. I believe all is depreciated anyway.
def index
#questions = Question.where("value = ?", variable)
end
What data is being passed?
I will define questions = "How are you?", "What is your name?"
This is okay, but typically in rails, you'd call data by using an ID in the database. If you're defining these questions in a form, you'd use the strong params system; but you'd need a form to submit the data to
Further Additions
The rails way is to keep all your data in a database, and use the application to manipulate that data, either by showing it, or allowing people to input more.
The "params" variables are basically there to help the rails controllers & models accept & process data from end users, and consequently allow you to keep the system growing. Instead of having to write custom code to accommodate all sorts of different data, the params give you a rigid structure to work with. Here is a good explaination of how MVC (and params) works for you: How does an MVC system work?
I think you're getting confused with how your app should work
Your "questions" should be stored in a questions table / model, and can be accessed by calling their ID's with the find function. This code would be like this:
#app/controllers/questions_controller.rb
def show
#question = Question.find(params[:id])
end
If you want to add new questions, you'll be best to add them to the questions table, like this:
#app/controllers/questions_controller.rb
def new
#question = Question.new
end
def create
#question = Question.new(question_params)
#question.save
end
private
def question_params
params.require(:question).permit(:question)
end
#app/views/questions/new.html.erb
<%= form_for #question do |f| %>
<%= f.text_field :question %>
<% end %>
This will give you a central store of your questions, which you'll then be able to access when you need them, either with a helper or with your ".all" call :)
Give it a shot with question (singular):
params.require(:question).permit(:text, :answer)
Assuming question is your model and text (which I made up) is the wording of the question.
I have a Rails 2.x app that I'm building a RESTful interface for.
For some (good) reason, I've chosen to expose some of the fields on my ActiveRecord models through the API using different names than the underlying fields in MySQL.
For example, a lot of my MySQL field were prefixed with the model name (user_type, user_first_name, etc.). My request/responses use the names without prefixes (type, first_name).
The action methods look like (and yes, this is already a source of pain for maintenance)
def create
u = User.new
u.user_type = params[:type]
u.user_first_name = params[:first_name]
u.save!
end
My problem comes when rescuing ActiveRecord::RecordInvalid. I translate the errors collection on the record into a key that the client app can make some sense of. For example:
validation_error.blank.user_first_name
via:
rescue_from ValidationError, ActiveRecord::RecordInvalid do |e|
errors = []
e.record.errors.each_error do |attr, error|
errors << {:key => "validation_error.#{error.type.to_s}.#{attr}"}
end
errors.uniq!
respond_to do |format|
format.xml { render_xml_error errors, :unprocessable_entity}
end
end
The problem is that the "user_first_name" wasn't a field that the client code knows anything about -- yes, you can figure it out because you're a human, but it doesn't match any of the fields that were passed in exactly
So, the more general question is: how can I maintain this type of incongruous mapping between my public API facade and the underlying SQL model without doing hand-to-hand combat with every action method and error rescue translating the field names?
Would switching to Rails 3 help me in this specific regard?
One idea suggested was maintaining a separate I18n localization file (en.yml) for the API resources so that I can map the field names -- but that still feels heavy and fragile