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
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=
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.
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.
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?
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] } } } }