I'have an json api where user can update a post and related details to that post. To simply the process the user just fill one hash and we are no exposing our architecture. The stuff I did here work but I would like to know if their is a better way. For me I don't know why but it smells...
def update
params['blog_post'] = params.delete 'post'
#blog_post.update_attributes blog_post_params
update_blog_post_groups if params['blog_post']['group_ids']
params['blog_post_details'] = params.delete 'blog_post'
#blog_post.blog_post_details.update_attributes blog_post_details_params
render :blog_post, status: 200
end
blog_post_params and blog_post_details_params are strong_parameter methods.
Related
I have a an json Api who received parameters to create a Device, like name, imei, etc. The Device can have one Blacklist object (has_one :blacklist). I would like to know what's the proper-way to create the blacklist object if a params is present in the post request of Device.
Exemple curl -X POST -d api_key=000000 -d device[name]='stack' -d device[blacklist]='true' https://www.example.com/api/devices.json
In the code for the moment I should have
def create
#device = Device.new
#device.update_attributes(strong_parameters)
if params[:device]['blacklist'] && params[:device]['blacklist'] == true
#blacklist = Blacklist.new(device_id: #device.id)
end
render :device, status: 201 # will render with jbuilder #device and #blacklist
end
But I don't like it that much :
Too much logic in one controller
Verifying parameters inside is a good practice?
If no parameters are given, how to handle the request? I know that strong parameters should return a 400, but what about #device I just created.
This controller smells for me.
Feedbacks welcome.
The result when doing a PATCH
class DevicesController
before_action :found_device, only: :blacklist # get `#device`
before_action :blacklist_device, only: :blacklist
def blacklist
render :device, status: 200
end
private
def blacklist_device
if (params[:device]['blacklisted'] and
params[:device]['blacklisted'] == true and
#blacklist = BlacklistedDevice.create(device_id: #device.id, organisation_id: current_store.organisation.id))
#device.reload
else
render json: { error: "Missing or incorrect 'blacklisted' parameter" }, status: :unprocessable_entity
end
end
end
Too much logic in the conrtoller ? No
I have also heard a lot 'too much logic in the controller is bad' but this is bullshit or rather I believe the words are not accurate enough.
What that phrase means for me, is that for example, model validations should not be in the controller, and the controller should remain light for very basic REST actions. Controller should only be a bridge between the HTML request and the model. Think of it this way : you may have several controllers modifying the same model. What you would write in EVERY controller, should most likely instead be written in the model as a validation.
But here you're dealing with specific requests (transforming a device[blacklist] == true as a Blacklist Model isn't something "natural", so yes in my opinion it should be in the controller.
Plus, a controller action of just 6 lines isn't what we could call "too much logic"
Verifying parameters inside is good Practice ? Yes/No
I assume by that you mean writing specific lines of codes in the controller like if params[xxx] == blabla or something equivalent
The way you did was good. You use specific code only for the special parameter (the blacklist) and the rest of the params go into the model as strong params, so the model validations will do the rest.
Verify parameters only if it's relevant to this particular controller (for example, if it was site-based, you could probably use a different implementation of the blacklist so the difference would have to be in the controller.
If no parameters are given, how to handle the request? I know that strong parameters should return a 400, but what about #device I just created.
This the part I don't quite like about your current implementation. You don't check for the success of your save operations. Here's what you could have written (check the result of every persistence operation result, and render appropriately)
def create
#device = Device.new
if #device.update_attributes(strong_parameters)
if (params[:device]['blacklist']
and params[:device]['blacklist'] == true
and #blacklist = Blacklist.create(device_id: #device.id))
# Handle stuff when everything is cool
render :device, status: 201 # will render with jbuilder #device and
else
# Handle stuff when there's no blacklist param true
end
else
# Handle error on model save
end
end
Inspecting params is well put in the controller - that's it's purpose - the model layer should not have knowledge of request parameters.
But you can put this info in a transient attribute with
class Device
attr_accessor 'create_blacklisted'
end
Then you can create an input field for that new attribute and an after_initialize callback in the Device model as well that can subsequently create the Blacklist entry.
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.
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.
I'm using RABL right now to generate JSON responses of an API in Rails, but I'm finding that while RABL is super handy for mapping models to responses, to create a consistent API I'm having to to duplicate that mapping logic in the update and create functions of my controller.
As a simple example, if I just want to change the attribute names in the response to a POST request, I can do this in RABL:
create.rabl
object #car
attributes car_id: :id, badly_named_legacy_column_that_means_color: :color
But if I want the client to be able to use these same "cleaned up" attributes in the JSON POST/PUT request itself (i.e. be able to send { "id": 1, "color": "red" } instead of { "car_id": 1, "badly_named_legacy_column_that_means_color": "red" }), I have to manually do this mapping again in the controller:
cars_controller.rb
def create
params[:car_id] = params.delete(:id)
params[:badly_named_legacy_column_that_means_color] = params.delete(:color)
#car = Car.create(params)
end
Now there are two places that I need to map car_id to badly_named_legacy_column_that_means_color. Not very DRY.
So far I haven't come across any way to handle this using RABL. Is there one that I'm missing? I also realize this might be outside the scope of RABL, which bills itself specifically as a templating system, so maybe is there another API builder that would allow me to do this? I love the idea of mapping messy database columns to a clean API but having to specify this mapping in both the view and the controller isn't very DRY. Any thoughts appreciated.
Update
The original answer is all about Ruby/Rails => JSON, the question is JSON => Ruby/Rails. This answer about associating columns should explain an approach:
alias_attribute :new_column_name, :column_name_in_db
Then you can just reference new_column_name in the RABL and Rails will handle the association on the create/update.
You should be able to call render from the create method and render any view. You could customize a response with a create specific template or reuse the generic show template. The trick is to re-use the object rabl template (app/views/car/car.rabl in this case), for example:
# POST /cars
def create
#car = Car.new(params)
if #car.save
render action: 'show'
else
respond_with #car
end
end
Where app/views/cars/car.rabl is
attributes :id, ...
and app/views/cars/show.rabl is
object #car
extends "cars/car"
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.