How do I customize the JSON output on creation of a devise User?
### User.rb ###
class User < ActiveRecord::Base
devise :database_authenticatable,
:registerable, ...
...
end
### Routes.rb ###
...
devise_for :users, :controllers => {:registrations => "registrations"}
...
I've got some extra fields in my User table that are secret, but they get returned in the JSON response when I do a User creation via JSON like this:
$ curl -H "Content-Type: application/json" -d '{"user" : {"username":"someone","email":"someone#somewhere.com","password":"awesomepass"}}' -X POST http://localhost:3000/users.json
which returns:
{"user":{"secret_field_1":"some value","secret_field_2":"some value","created_at":"2013-07-25T21:24:50-05:00","email":"someone#somewhere.com","first_name":null,"id":3226,"last_name":null,"updated_at":"2013-07-25T21:24:50-05:00","username":"someone"}}
I'd like to hide those secret fields, but don't know how to customize the JSON response.
I've tried a standard ActiveRecord serializer:
class UserSerializer < ActiveModel::Serializer
attributes :id, :created_at, :updated_at, :email, :first_name, :last_name, :username
end
to no avail, I'm guessing because of Devise.
I just ran into the same issue. I haven't pinpointed exactly why but it looks like respond_with in Devise's SessionsController (tested on Devise 3.0 and active_model_serializers 0.8.1) doesn't trigger the ActiveModel::Serializer.
So I overrode respond_with in my controller:
class SessionsController < Devise::SessionsController
def respond_with(resource, opts = {})
render json: resource # Triggers the appropriate serializer
end
end
It is, however, working in my RegistrationsController with respond_with. There I needed to do the following:
class RegistrationsController < Devise::RegistrationsController
respond_to :json
end
I recently ran into this and overriding respond_with didn't fix the issue. I ended up overriding to_json in user.rb like so:
def to_json(arg)
UserSerializer.new(self).to_json
end
Not sure what the extra arg is, but that seems to be required by one of the devise mixins.
I'm using the following:
Rails 4.2.0
Devise 3.4.1
Just a guess, but it sounds like rails is not finding your serializer and is using to_json(). Did you define active_model_serializer() in your model?
I just had the same problem, below is how I resolved it, very simple.
All these passed in active_model_serializers (0.9.5)
Override Devise registration method, and in your customize action:
def registration
//Some process, and you get a #user when registration is successful.
render :json => UserSerializer.new(#user)
end
If you want to pass some parameters to your customized Serializer(token for example), you can pass it in your action:
render :json => UserSerializer.new(#user).as_json({auth_token: your_token})
And in your serializer, just use:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :avatar_url, :auth_token
def auth_token
serialization_options[:auth_token]
end
end
Depending on what you are doing with that JSON, you simply have to remove attributes you don't want from your serializer.
For example :
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :username
end
I presume that, in your case, you just want to do that.
But you also have the possibility to include an attribute on a specific condition :
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :author
def include_author?
current_user.admin?
end
end
And finally you can override the attributes method to return the hash you need :
class PersonSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
def attributes
hash = super
if current_user.admin?
hash["ssn"] = object.ssn
hash["secret"] = object.mothers_maiden_name
end
hash
end
end
See README of ActiveModel::Serializers for more informations.
Related
I have ruby on rails app with user_controller generated via scaffold.
# app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < Api::V1::ApiController
skip_before_action :verify_authenticity_token
serialization_scope :view_context
def show
render json: #user
end
end
The model
# app/models/api/v1/user.rb
class Api::V1::User < Api::V1::ApiRecord
has_one_time_password
validates_presence_of :phone
end
And serializer:
# app/serializers/api/v1/user_serializer.rb
class Api::V1::UserSerializer < ActiveModel::Serializer
attributes :id, :phone, :first_name, :email, :dob, :last_name, :gender, :otp_code
def otp_code
object.otp_code
end
end
Everything is fine but i got stuck in configuration. /api/v1/users/2 gives me below response.
{
"api/v1/user": {
"id": 2,
"phone": "999999999",
"first_name": "Rajan",
"email": "sample#h.com",
"dob": "2000-01-01",
"last_name": "Verma",
"gender": "male",
"otp_code": "503036"
}
}
Did you saw the root key? why it is coming with full namespace? it should be { "user": { ...data } } only.
I don't want to apply and patch or hacks for this trivial one. I think i am missing any configuration which i am not able to find in documentation.
Please help.
So it seems ActiveModel::Serializer uses the full model name including modules as the root key, see
https://github.com/rails-api/active_model_serializers/blob/0-10-stable/lib/active_model/serializer.rb#L384-L391
So either you set the root key in your controller.
class UsersController < ApplicationController
def index
render json: #users, root: "users"
end
end
Or if you never want to include the full name in your serializer you could create a base serializer
# app/serializers/api/base_serializer.rb
class BaseSerializer < ActiveModel::Serializer
def json_key
object.class.model_name.to_s.demodulize.underscore
end
end
# app/serializers/api/v1/user_serializer.rb
class Api::V1::UserSerializer < BaseSerializer
attributes :id, :phone, :first_name, :email, :dob, :last_name, :gender, :otp_code
def otp_code
object.otp_code
end
end
The reason this happens is that your model is Api::V1::User which most probably was autogenerated by the scaffold.
Are you sure you'll be needing versioning in your model?
Maybe having a model User and apply the namespacing versioning to solely to your controllers and routes will be enough for your application.
If you do want to have the Vx namespace to your models as well, then you can override the json_key as Christian Bruckmayer suggests, either to all serializers or explicitly to Api::V1::UserSerializer
What is https://github.com/bsm/serialization_scopes doing in there? The line serialization_scope :view_context could be removed no?
I have User model with has_many :sessions. But for one particular request I want to send only one session based on request device platform (iOS, Android).
The problem is that I have separate UserSerializer and UserSessionSerializer and I want to pass UserSessionSerializer as option in UserSerializer for :session field. Like this:
# UsersController
render json: #user, serializer: UserSerializer, session: #session # #user's session founded by platform
# UserSerializer
attributes :id, :email, :username, :session
def session
#instance_options[:session], serializer: UserSessionSerializer
end
It is impossible, because I can pass serializer only in has_one and has_many as I understand. But I don't want to render all user sessions in JSON with has_many, only founded one.
Thanks for any help!
Your code is good just use UserSessionSerializer.new instead of serializer: UserSessionSerializer
render json: { user: UserSerializer.new(#user, session: #session) }
attributes :id, :email, :username, :session
def session
UserSessionSerializer.new(self.instance_options[:session]) if self.instance_options[:session].present?
end
I'm using Active Model Serializer 0.10.7 in rails 5
and I wanna know how to access devise current_user in serializer.
current_user is supposed to be set for scope by default.
according to doc
https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#controller-authorization-context
but my code doesn't work well...
anybody knows about this?
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
def is_reviewed
object.reviews.pluck(:user_id).include?(current_user.id)
end
end
and Book controller look like this.
class BooksController < ApplicationController
def index
#books = Book.order(created_at: :desc).page(params[:page])
respond_to do |format|
format.html
format.json {render json: #books, each_serializer: BookSerializer}
end
end
end
It is possible to pass a scope into your serializer when instantiating it in the controller (or elsewhere for that matter). I appreciate that this is for individual objects and not arrays of objects:
BookSerializer.new(book, scope: current_user)
Then in your Book Serializer you can do:
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
private
def is_reviewed
object.reviews.pluck(:user_id).include?(current_user.id)
end
def current_user
scope
end
end
Devise doesn't expose the current_user helper to models or serializers - you can pass the value to the model from the controller, or set it in a storage somewhere.
Some examples from other answers:
https://stackoverflow.com/a/3742981/385532
https://stackoverflow.com/a/5545264/385532
in application controller:
class ApplicationController < ActionController::Base
...
serialization_scope :view_context
end
in serializer:
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
def is_reviewed
user = scope.current_user
...
end
end
If you are using active_model_serializers gem, then it is straight forward.
In your serializer just use the keyword scope.
Eg:-
class EventSerializer < ApplicationSerializer
attributes(
:id,
:last_date,
:total_participant,
:participated
)
def participated
object.participants.pluck(:user_id).include?(scope.id)
end
end
You can also pass view context into serializer from controller when you want to intialize serializer your self instead of render json: book.
# controller
BookSerializer.new(book, scope: view_context)
# serializer
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
private
def is_reviewed
object.reviews.pluck(:user_id).include?(scope.current_user.id)
end
end
seems there is a typo, you have is_reviewed and you defined method has_reviewed
so it should be like this
class BookSerializer < ActiveModel::Serializer
attributes :id, :title, :url, :image, :is_reviewed
def is_reviewed
object.reviews.pluck(:user_id).include?(current_user.id)
end
end
My MembersController is in app/controllers/api/v1/members_controller:
class API::V1::MembersController < ApplicationController
include DeviseTokenAuth::Concerns::SetUserByToken
***before_action :authenticate_api_v1_member!***
def index
#members = Member.all
render json: #members
end
In my member.rb
class Member < ActiveRecord::Base
# Include default devise modules.
devise :database_authenticatable, :registerable
include DeviseTokenAuth::Concerns::User
end
In my member_serializer:
class MemberSerializer < ActiveModel::Serializer
attributes :email, :uid, :name, :nickname, :admin
end
My question is:
If I don't have the line before_action :authenticate_api_v1_member!, I can send request from Postman to '/api/v1/members' and I can get the JSON respond.error here
Blockquote
If I have the line above, with header include 'uid, client, access-token', I get the error below:
header 'client, access-token', the error is below:
error here
How can I send request to the 'members#index' with devise_token_auth??? Thanks in advance.
I've solved my problem and If anybody face the same issue, contact me by post an comment on this and I'll post answer.
I have the following method in an API controller:
module Api
module V1
class ArticlesController < Api::BaseController
def show
article = Article.find(params[:id])
render json: article
end
end end end
And while using the active_model_serializers gem, I have the following serializer:
class Api::V1::ArticleSerializer < ActiveModel::Serializer
attributes :id, :author_id, :title, :description
end
For all API requests the server should include information about the session, such as the api token and the current user. So in the above case the generated json shouldn't only include the attributes mentioned in the articles serializer but also for example the api token.
Where and how does one normally include this session information to send to the API front end? Is this perhaps in a separate serializer that is included in addition to in this case the articles serializer?
Since you want this information attached to all API responses, it makes sense to have a superclass serializer responsible for this, which all other serializers inherit from:
class SerializerWithSessionMetadata < ActiveModel::Serializer
attributes :token, :user
def token
# ...
end
def user
# ...
end
end
Then your serializers would inherit from this instead of ActiveModel::Serializer:
class ArticleSerializer < SerializerWithSessionMetadata
# ...
end
Alternatively, you could make it a module that you include in your serializers:
module SessionMetadataSerializer
def self.included(klass)
klass.attributes :token, :user
end
def token
# ...
end
# ...
end
Then:
class Api::V1::ArticleSerializer < ActiveModel::Serializer
include SessionMetadataSerializer
attributes :id, :author_id, :title, :description
end