How to create default options for all controller render calls - ruby-on-rails

I'm writing a Rails API using ActiveModel::Serializers. I am following the JSON API spec and would like to include some data about current_user for authorization and authentication in the response's top-level meta key. With ActiveModel::Serializers, top-level meta information is specified like this:
render json: #posts, meta: { 'current-user': #current_user }
But I would like to have this information available on all JSON responses. It's a big hassle to define this information every single time I call render in each of my controllers.
Is there any way I can pass the meta: option to all my controller's render calls by default, say somewhere in my ApplicationController or something?

Create a def and append to before_action in application controller
This might work for you
Create this def in application controller
def append_user_to_json(data)
user = current_user.to_json
data = data.to_json
return data = data+user
end

Related

Rails - AMS - Add key to every json response from controller

I'm updating my Rails app to active_model_serializers 0.10.4 but I'm having trouble updating a necessary feature: the ability to add request information to every json response from the controller.
In AMS 0.9.x I used to do it by using default_serializer_options but that funcionality is gone, and apparently the only way to achieve this is manually adding the meta key to EVERY request.
Has anyone found a workaround to make this work?
in config/initializers/active_model_serializer.rb file add this line:
ActiveModel::Serializer.config.adapter = :json
The example below will add a data attribute at the top level, and render the #users objects under data key in your response.
In your controller:
def index
#users = User.all
render json: #users, root: "data"
end
If you're going for a JSON API schema with a meta tag and a data tag with objects for every response, simply change initializer to
ActiveModel::Serializer.config.adapter = :json_api
and controller to
def index
#users = User.all
#extra_meta = {"totalCount": #users.size}
render json: #users, root: "data", meta: default_meta_attributes(#users, #extra_meta)
end
In my case default_meta_attributes is in the base controller and merges in some details about the request like current_user_id, etc with the option to add additional details in each method
Could you give a more specific example of what you're trying to do? You could make an ApplicationSerializer and set the serialization_scope to :view_context then do whatever you want in the serializer. You could also customize the JSON adapter. Or, if you want, you can prepend a module to SerializableResource to add options.

Create object in rails if request params present

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.

Rails API Best Practice, JSON responses

I am working on a Rails API backend with a separate Rails/Angular front-end codebase. The responses from the Rails API must be structured in a certain way to match with the front-end flash messages. A(n) (slightly boiled down) example controller response is
render json: {status: "Unauthorized", code: "AUTH.UNAUTHORIZED", fallback_msg: "Unauthorized request"}
so basically all my controllers are full of this, and sometimes when there are 5 possible responses for a method (ex: if a user resets their email the response can be invalid password, invalid email, email is already registered etc). A coworker suggested abstracting these methods out into the model, so the model is response for sending back these messages and then the controller can just have
def ctrl_method
user = User.update(password: password)
render json: user, status(user)
end
(where status is another method that provides the HTTP status code based on the object's status attribute)
My question is is this best practice? I understand Rails MVC and feel like the responsibility of sending the json message belongs to the controller not the model.
I say you're both right. The controller should have the responsibility of sending the message, and the methods should be abstracted out--just not into the model, more like a class that lives in /lib.
This should make testing easier as well.
If you want to deal with ActiveRecord errors I think you use errors.full_messages and use the same code and status for such errors (status: 'Forbidden', code: '').
Note that you should customize your messages in locale files see guide. But it's useful if you want to translate your app in different languages.
Success messages can be inferred from the controller and the action (see below).
class ApplicationController < ActionController::Base
...
def render_errors(object)
render json: {status: 'Forbidden', code: 'WRONG_DATA', fallback_msg: object.errors.full_messages.join(", ")}
end
def render_success(message = nil)
message ||= I18n.t("#{self.controller_name}.message.#{self.action_name}"
render json: {status: 'OK', code: 'OK', fallback_msg: message}
end
end
class SomeController < ApplicationController
def update
if #some.update(params)
render_success
else
render_errors(#some)
end
end
end
Without knowing any more details, I would think about utilizing concerns to deal with the statuses. This allows business logic to be encapsulated without muddying up your models. So you could have
module Concerns
module Statuses
def deal_with_show
# business logic goes here to generate the status
end
def deal_with_index
# business logic goes here to generate the status
end
def deal_with_create
# business logic goes here to generate the status
end
def default_status
# set a default status here
end
end
end
and then in the controllers
class MyController < ApplicationController
include Concerns::Statuses
def index
render json: deal_with_index
end
end
Of course, that breakdown of statuses in the concern is arbitrary. It can be handled however makes sense: by method, by verb, or by some other distinction.
This is a good bit on concerns.

writing raw html to a response object in rails (using ActionDispatcher::Response)

How to write the response from the controller using the ActionDispatch::Response object. There seems to be no api that does http://api.rubyonrails.org/classes/ActionDispatch/Response.html.
The below code works which does not use any view. Is the same can be achived using a response object. The reason being having a necessity to write some binary data to html(which is required for the the current rails app being written)
class HelloController < ApplicationController
def index
render :text => "hello" # want to use ActionDispatch::Response object instead of this
end
end
Have you taken a look at send_data? It may be what you're looking for.

How do I expose data in a JSON format through a web service using Rails?

Is there an easy way to return data to web service clients in JSON using Rails?
Rails resource gives a RESTful interface for your model. Let's see.
Model
class Contact < ActiveRecord::Base
...
end
Routes
map.resources :contacts
Controller
class ContactsController < ApplicationController
...
def show
#contact = Contact.find(params[:id]
respond_to do |format|
format.html
format.xml {render :xml => #contact}
format.js {render :json => #contact.json}
end
end
...
end
So this gives you an API interfaces without the need to define special methods to get the type of respond required
Eg.
/contacts/1 # Responds with regular html page
/contacts/1.xml # Responds with xml output of Contact.find(1) and its attributes
/contacts/1.js # Responds with json output of Contact.find(1) and its attributes
http://wiki.rubyonrails.org/rails/pages/HowtoGenerateJSON
Rails monkeypatches most things you'd care about to have a #to_json method.
Off the top of my head, you can do it for hashes, arrays, and ActiveRecord objects, which should cover about 95% of the use cases you might want. If you have your own custom objects, it's trivial to write your own to_json method for them, which can just jam data into a hash and then return the jsonized hash.
There is a plugin that does just this,
http://blog.labnotes.org/2007/12/11/json_request-handling-json-request-in-rails-20/
And from what I understand this functionality is already in Rails. But go see that blog post, there are code examples and explanations.
ActiveRecord also provides methods to interact with JSON. To create JSON out of an AR object, just call object.to_json. TO create an AR object out of JSON you should be able to create a new AR object and then call object.from_json.. as far as I understood, but this did not work for me.

Resources