How to use conditional attributes with jsonapi-serializers - ruby-on-rails

I am working on a Ruby on Rails project with ruby-2.5.0 and Rails 5. i am working on api part, i have used jsonapi-serializers gem in my app. I want to add conditional attribute in serializer.
Controller:
class RolesController < ApplicationController
def index
roles = Role.where(application_id: #app_id)
render json: JSONAPI::Serializer.serialize(roles, is_collection: true)
end
end
Serializer:
class RoleSerializer
include JSONAPI::Serializer
TYPE = 'role'
attribute :id
attribute :name
attribute :application_id
attribute :application do
JSONAPI::Serializer.serialize(object.application)
end
end
Here application is a model which has_many roles and roles belongs to application. I want to add application details in some conditions. I also tried like:
Controller:
class RolesController < ApplicationController
def index
roles = Role.where(application_id: #app_id)
render json: JSONAPI::Serializer.serialize(roles, is_collection: true, params: params)
end
end
Serializer:
class RoleSerializer
include JSONAPI::Serializer
TYPE = 'role'
attribute :id
attribute :name
attribute :application_id
attribute :application do
JSONAPI::Serializer.serialize(object.application), if: #instance_options[:application] == true
end
end
But #instance_options is nil. Please help me how i can fix it. Thanks in advance.

In the jsonapi-serializers this is what is said about custom attributes:
'The block is evaluated within the serializer instance, so it has access to the object and context instance variables.'
So, in your controller you should use:
render json: JSONAPI::Serializer.serialize(roles, is_collection: true, context: { application: true })
And in your serializer you should use context[:application] instead of #instance_options

Related

Include current_user in ActiveModel::Serializer

I have an api-only rails application using active_model_serializers 0.10. I have a current_user attribute in my ApplicationController and am trying to access it from my serializers in order to restrict the data shown. I can do it by passing it to scope manually like this ExerciseSerializer.new(#exercise, scope: current_user), but would like to have a general solution.
This is my ApplicationController:
class ApplicationController < ActionController::API
include Response
include ExceptionHandler
serialization_scope :view_context
# called before every action on controllers
before_action :authorize_request
attr_reader :current_user
def check_access_rights(id)
#current_user.id == id
end
def check_admin_rights
if !#current_user.admin
raise(ExceptionHandler::AuthenticationError, Message.unauthorized)
end
end
private
# Check for valid request token and return user
def authorize_request
#current_user = (AuthorizeApiRequest.new(request.headers).call)[:user]
end
end
This is one of my serializers:
class ExerciseSerializer < ActiveModel::Serializer
attributes :id, :name, :description, :image_url, :note
delegate :current_user, :to => :scope
has_many :exercise_details
end
And this is how I present the objects:
def json_response(object, status = :ok)
render json: object, status: status
end
When serializing I get the following error:
** Module::DelegationError Exception: ExerciseSerializer#current_user delegated to scope.current_user, but scope is nil:
When I try accessing the current_user from within the Serializer, I get the following error:
*** NameError Exception: undefined local variable or method `current_user' for #<ExerciseSerializer:0x007ff15cd2e9c0>
And obviously scope is nil.
Any ideas would be helpful. Thanks!
Found it randomly after countless times unsuccessfully reading the offical docs : https://www.driftingruby.com/episodes/rails-api-active-model-serializer
So here is the interesting part:
def current_user_is_owner
scope == object
end
So current_user is saved in the scope variable by default, you don't need to add code in the controller to retrieve it.
Works in 0.10 and available since 0.08:
https://github.com/rails-api/active_model_serializers/search?utf8=%E2%9C%93&q=scope&type=

Is there access to the `include` directive inside an active model serializer class?

Given the following controller code:
def show
render json: #post, include: 'author', adapter: :json_api
end
Inside the serializer do I have access to the include directive?
class PostSerializer < ActiveModel::Serializer
attributes :title, :body, :foo
belongs_to :author
def foo
# can I gain access to the include_directive here somehow?
end
end
I have looked in #attributes, #instance_options, #object, #root #scope and #serializer_class (all the instance variables I can see with pry) with no luck.
I am using active_model_serializers (0.10.2).
The include option is passed to the Adapter. The adapter uses it essentially by passing it to serializable_hash in each serializer. So, you could redefine that method, inspect it, and call super
Otherwise I'm not sure what you're trying to do.

Rails API versioning, AMS doesn't use custom serializers

I'm working on a Rails application and I'm versioning the API.
Following RailsCast #350 I have this:
routes.rb
namespace :v1 do
#resources for version 1
end
namespace :v2 do
#resources for version 2
end
I use active_model_serializer and I have app/serializers/v1/ and .../v2/ with:
(for /v1)
module V1
class ResourceSerializer < ActiveModel::Serializer
attributes :id
end
end
(for /v2)
module V2
class ResourceSerializer < ActiveModel::Serializer
attributes :id, :data
end
end
But Rails doesn't call my custom serializer.
module V1
class ResourcesController < ApplicationController
def show
#resource = Resource.find(params[:id])
render json: #resource
end
end
end
OUTPUT for .../v1/resources/1
{"id":1,"name":"...","city":"...","created_at":"...","updated_at":"2..."}
instead of
{"id":1}
If I put render json: #resources, serializer: ResourceSerializer it retrieves undefined method 'read_attribute_for_serialization'
Any help would be appreciated. Thanks!
EDIT: Namespaces are valid!
I got this problem also, I have tried many solutions, but didn't work for me
the only solution that works is calling the serializer class directly:
render json: V1::ResourceSerializer.new(#resource)
If your problem only "undefined method 'read_attribute_for_serialization'", include ActiveModel::Serialization into your ActiveModel sub class
module V1
class ResourceSerializer < ActiveModel::Serializer
include ActiveModel::Serialization
attributes :id
end
end
I finally got a solution using each_serializer: V1::UserSerializer for collections and serializer: V2::UserSerializer for normal objects.
Thanks to all.

Should I use controller methods from a form object?

I'm using Sorcery gem.
I have a sessions_controller which handles login/logout. I want to extract controller-unrelated code like form validation and signing the user into a form object.
However, to login user, I need an access to the sessions_controller, as it requires access to the controller context. Obviously, I can pass controller into the form object, but I don't like tying them at all. Here's how it looks:
# SessionsController:
# ...
def create
#form = SignInForm.new(session_params)
#form.set_controller self
if #form.valid?
redirect_signed_in_users
else
render :new, status: :unauthorized
end
end
# SignInForm
( unnecessary stuff omitted
class SignInForm
include ActiveModel::Model
include Virtus.model
attribute :email, String
attribute :password, String
attribute :controller, ActionController::Base, writer: :private
validates_presence_of :email, :password
validate :valid_credentials
def set_controller(controller)
self.controller = controller
end
private
def valid_credentials
errors.add(:base) unless controller.login(self.email, self.password, self.remember_me)
end
end
Any ideas?

How to configure the JSON response when using rocket pants?

I am using rocket pants to render my JSON API.
I'm trying to change the way it renders the JSON by overriding as_json in my model, but somehow, it seems not to change anything in the rocket pants response.
in my controller:
class Api::V1::ProjectsController < RocketPants::Base
...
def show
expose Project.find(params[:id])
end
...
end
And in my model:
class Project < ActiveRecord::Base
...
def as_json(options = {})
{"this" => "is not working!"}
end
...
end
What am I missing?
In addition, there is a first set of options that can be sent to the expose block. Depending on the source, you can pass options through to serializable_hash method. For example:
expose user, only: [:name, :email]
This will call serializable_hash on the object with name and email.
You can also specify eager loading in this set of options. For example:
expose uploads, :include => { :user => { :only => :username } }.
This will expose your uploads and eager load the belongs_to association with user.
Source: https://github.com/filtersquad/rocket_pants/issues/20#issuecomment-6347550
I've figured out how to do that. The way rocket pants work is by looking at the serializable_hash method. Overriding it results in a change in the response.
Edit:
The solution I got to:
In the model where I need to add some attributes: simply override the attributes method:
# Overriding this method is required for the attribute to appear in the API
def attributes
info = {} # add any logic that fits you
super.merge info
end
In the controller that needs to expose the API I've created a new Model class (this is only needed in order to keep different API versions), and overridden the serializable_hash method:
class Location < ::Location
def serializable_hash(options = {})
super only: [:id, :lat, :long],
include: [user: {only: ...your attributes here...}]
end
end
For nested things:
paginated #matches, include: { listing: { include: { company: { only: [:name, :email] } } } }

Resources