using serializer inside concern rails - ruby-on-rails

I am using serializer to format json response of my rails-api projector.
I am using a concern to format final response.
My code snippets are as follows
entry_controller.rb
class EntriesController < ApplicationController
include Response
def index
#entries = #current_user.entries
json_response(#entries)
end
end
concerns/response.rb
module Response
def json_response(response, error = nil, message = 'Success', code = 200)
render json: {
code: code,
message: message,
error: error,
response: response
}
end
end
application_serializer.rb
class ApplicationSerializer < ActiveModel::Serializer
end
entry_serializer.rb
class EntrySerializer < ApplicationSerializer
attributes :title, :content, :is_encrypted, :entry_date
end
In entries#index if i use json_response(#entries) my final response of request is not formatted and each entry is as in database. instead if i use render json: #entries. Im getting as per serializer. I want to use concern method json_response(#entries) along with serializers. Can someone suggest a way to use serializers in concern methods in a generic way as multiple controllers use same concern method.
Thanks in advance.

Something related to serializer params is what you want to customise your response.
class EntriesController < ApplicationController
include Response
def index
#entries = #current_user.entries
render json: #entries, serializer_params: { error: nil, message: "Success", code: 200}
end
end
class EntrySerializer < ApplicationSerializer
attributes :title, :content, :is_encrypted, :entry_date
params :error, :message, :code
def attributes
super.merge(:error, :message, :code)
end
end

I’m not sure I fully understand your question, but I don’t believe render :json calls to_json recursively if it is given a hash, like in this case. So you may be looking for something like this in your concern:
module Response
def json_response(response, error = nil, message = 'Success', code = 200)
render json: {
code: code,
message: message,
error: error,
response: response.to_json
}
end
end

Related

Serializing errors to JSON in Rails 6

I’ve been trying to find and an easy way to serialize all of the errors to JSON in Rails 6. Not writing the custom ones, but just add a standard JSON format for them. Wrote a serializer like:
class ErrorSerializer
def initialize(error)
#error = error
end
def to_h
serializable_hash
end
def to_json(_payload=nil)
to_h.to_json
end
private
def serializable_hash
{
errors: [error.serializable_hash].flatten
}
end
attr_reader :error
end
And in my User controller, tried to render error with:
render json: ErrorSerializer.new(#user.errors.to_hash), status: :bad_request
But getting an error:
NoMethodError (undefined method `serializable_hash')
Has anyone encountered something similar? And, in general, what is the best/easiest way to serialize all errors to JSON in Rails 6 (ideally, if there won’t be a need to raise/render errors on each endpoint in the controller)
In case someone needs some simple errors serializer. This worked fine for me:
class ActiveRecordErrorsSerializer
attr_reader :object
def initialize(object)
#object = object
end
def to_h
serializable_hash
end
def as_json(_payload = nil)
{ data: { type: 'errors' }, attributes: to_h }
end
private
def serializable_hash
object.errors.messages.flat_map do |field, errors|
errors.flat_map do |error_message|
{
source: { pointer: "/data/attributes/#{field}" },
details: error_message
}
end
end
end
end

Rails API render all responses in camelCase

I'm trying to get my Rails API to render all JSON responses in camelCase. Currently I am using Netflix Fast JSON API for my serializer and rendering errors like so:
render json: { errors: command.errors }, status: :unauthorized
For the Netflix Fast JSON API serializers I've been adding set_key_transform :camel_lower to every serializer, which seems to do the trick (although if anyone knows how to make that a default it would be much appreciated).
For rendering errors however I'm not sure the best way to go about camel casing. If anyone has any experience with this please let me know how you go about it! Ideally there is a method of doing this that doesn't add too much syntax to every render call being made.
UPDATE
In serializing errors I added a helper method on the application controller:
def render_error(errors_params, status)
render json: {
errors: errors_params
}.deep_transform_keys { |key| key.to_s.camelize(:lower) }, status: status
end
For the Netflix Fast JSON API I took the suggestion of #spickermann and added an application serializer for the other serializers to inherit from:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class SomeSerializer < ApplicationSerializer
attributes :attribute, :other_attribute
end
You could create an ApplicationSerializer and all other serializers could inherit from it:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class FooBarSerializer < ApplicationSerializer
attributes :buzz, :fizz
# ...
end
You could monkey patch the serializer
Rails.application.config.to_prepare do
FastJsonapi::ObjectSerializer.class_eval do
set_key_transform :camel_lower
end
end
and for handling errors you can probably create an error serializer
render serializer: ErrorSerializer, json: {status: : unauthorized, errors: resource.errors
Have a look here and here

ActiveModel Serializer - Passing params to serializers

AMS version: 0.9.7
I am trying to pass a parameter to an ActiveModel serializer without any luck.
My (condensed) controller:
class V1::WatchlistsController < ApplicationController
def index
currency = params[:currency]
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer
end
My serializer:
class V1::WatchlistOnlySerializer < ActiveModel::Serializer
attributes :id, :name, :created_at, :market_value
attributes :id
def filter(keys)
keys = {} if object.active == false
keys
end
private
def market_value
# this is where I'm trying to pass the parameter
currency = "usd"
Balance.watchlist_market_value(self.id, currency)
end
I am trying to pass a parameter currency from the controller to the serializer to be used in the market_value method (which in the example is hard-coded as "usd").
I've tried #options and #instance_options but I cant seem to get it work. Not sure if its just a syntax issue.
AMS version: 0.10.6
Any options passed to render that are not reserved for the adapter are available in the serializer as instance_options.
In your controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer, currency: params[:currency]
end
Then you can access it in the serializer like so:
def market_value
# this is where I'm trying to pass the parameter
Balance.watchlist_market_value(self.id, instance_options[:currency])
end
Doc: Passing Arbitrary Options To A Serializer
AMS version: 0.9.7
Unfortunately for this version of AMS, there is no clear way of sending parameters to the serializer. But you can hack this using any of the keywords like :scope (as Jagdeep said) or :context out of the following accessors:
attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
Though I would prefer :context over :scope for the purpose of this question like so:
In your controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists,
each_serializer: WatchlistOnlySerializer,
context: { currency: params[:currency] }
end
Then you can access it in the serializer like so:
def market_value
# this is where I'm trying to pass the parameter
Balance.watchlist_market_value(self.id, context[:currency])
end
Try using scope in controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer, scope: { currency: params[:currency] }
end
And in your serializer:
def market_value
Balance.watchlist_market_value(self.id, scope[:currency])
end
You can send your params to your serializer like this
render json: #watchlists, each_serializer: WatchlistOnlySerializer, current_params: currency
and in your serializer you can use this to get the value
serialization_options[:current_params]

Rails 5: attr_accessor throwing NoMethodError (undefined method `keys' for nil:NilClass):

I have 2 non database attributes in my model. If one of them has a value, I need to return the other one in the json response:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def attributes
if !self.track.nil?
super.merge('max_speed_on_track' => self.max_speed_on_track)
end
end
end
The problem is that the line 'if !self.track.nil?' throws an error when the controller tries to return the json
Perhaps there is a better way as I read that using attr_accessor is a code smell.
What I am trying to do is if the user passes me a track value as a query parameter, then I pass that value to the model and it uses it to calculate the max_speed_on_track, and return that value.
Obviously if no track is provided by the user then I don't want to return max_speed_on_track in the json.
The controller method is very basic for now (I still need to add the code that checks for the track param). The code throws the error on the save line.
def create
#car = Car.new(car_params)
if #car.save
render json: #car, status: :created
else
render json: #car.errors, status: :unprocessable_entity
end
end
Try this out:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def as_json(options = {})
if track.present?
options.merge!(include: [:max_speed_on_track])
end
super(options)
end
end
Since Rails uses the attributes method, and you're only needing this for json output, you can override the as_json method just like in this article. This will allow you to include your max_speed_on_track method in your json output when the track is present (not nil).

Rails: return model (not bound to active record) as xml

I have a model class that is not bound to Active record.
class ProcessingStatus
attr_accessor :status, :timestamp
end
The model acts as a processing status holder and will eventually be returned to the calling method.
Since this is invoked as an active resource method, this needs to go back (serialized) as xml.
Here is my action method:
def activate
#process_status = ProcessingStatus.new
if Account.activate(params[:account])
#process_status.status = "success"
else
#process_status.status = "fail"
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #process_status }
end
end
This doesn't seem to return a valid xml though.
If I try and output the #process_status like below
return render :text => "The object is #{#process_status}"
this is what I get:
The object is #<ProcessingStatus:0x00000005e98860>
Please tell me what I am missing.
Edit #1,
Based on the comment below, I modified my code to include the serialization libraries.
class ProcessingStatus
include ActiveModel::Serialization
include ActiveModel::Serializers::JSON
include ActiveModel::Serializers::Xml
attr_accessor :status
def attributes
#attributes ||= {'status' => 'nil'}
end
end
I am getting closer:) Now get the output as follows for .xml request.
but the value that I assigned is not reflected.
#process_status.status = "success" / "fail"
<processing-status><status>nil</status></processing-status>
but when i make a json request, it is appearing correct!
{"processing_status":{"status":"success"}}
You need to define method to_xml in your model, or include Serialization module as below:
class ProcessingStatus
include ActiveModel::Serialization
attr_accessor :status, :timestamp
end
Here you've got more info: http://api.rubyonrails.org/classes/ActiveModel/Serialization.html

Resources