Rails Adding Attributes to JSON Serializer - ruby-on-rails

I had a model that should be rendered as JSON, for that I used a serializer
class UserSerializer
def initialize(user)
#user=user
end
def to_serialized_json
options ={
only: [:username, :id]
}
#user.to_json(options)
end
end
when I render json: I want though to add a JWT token and an :errors. Unfortunately I am having an hard time to understand how to add attributes to the serializer above. The following code doesn't work:
def create
#user = User.create(params.permit(:username, :password))
#token = encode_token(user_id: #user.id) if #user
render json: UserSerializer.new(#user).to_serialized_json, token: #token, errors: #user.errors.messages
end
this code only renders => "{\"id\":null,\"username\":\"\"}", how can I add the attributes token: and errors: so to render something like this but still using the serializer:
{\"id\":\"1\",\"username\":\"name\", \"token\":\"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.7NrXg388OF4nBKLWgg2tdQHsr3HaIeZoXYPisTTk-48\", \"errors\":{}}
I coudl use
render json: {username: #user.username, id: #user.id, token: #token, errors: #user.errors.messages}
but how to obtain teh same by using the serializer?

class UserSerializer
def initialize(user)
#user=user
end
def to_serialized_json(*additional_fields)
options ={
only: [:username, :id, *additional_fields]
}
#user.to_json(options)
end
end
each time you want to add new more fields to be serialized, you can do something like UserSerializer.new(#user).to_serialized_json(:token, :errors)
if left empty, it will use the default field :id, :username
if you want the json added to be customizable
class UserSerializer
def initialize(user)
#user=user
end
def to_serialized_json(**additional_hash)
options ={
only: [:username, :id]
}
#user.as_json(options).merge(additional_hash)
end
end
UserSerializer.new(#user).to_serialized_json(token: #token, errors: #user.error.messages)
if left empty, it will still behaves like the original class you posted

Change to_json to as_json, and merge new key-value.
class UserSerializer
def initialize(user, token)
#user=user
#token=token
end
def to_serialized_json
options ={
only: [:username, :id]
}
#user.as_json(options).merge(token: #token, error: #user.errors.messages)
end
end

i prefer to use some serialization gem to handle the serialize process like
jsonapi-serializer
https://github.com/jsonapi-serializer/jsonapi-serializer
or etc

Related

Rails API/Pundit: Strong parameters with ActiveModelSerializers

This section of Pundit section says that we could control which attributes are authorized to be updated. But it fails in case of the use of active_model_seriallizers gem:
def post_params
# originally geneated by scaffold
#params.require(:post).permit(:title, :body, :user_id)
#To deserialize with active_model_serializers
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params,
only: [:title, :body, :user]
)
end
If I modify the PostsController update action as Pundit suggested:
def update
if #post.update(permitted_attributes(#post))
render jsonapi: #post
else
render jsonapi: #post.errors, status: :unprocessable_entity
end
end
it fails with error:
ActionController::ParameterMissing (param is missing or the value is empty: post):
app/controllers/posts_controller.rb:29:in `update'
I also create the PostPolicy as follows:
class PostPolicy < ApplicationPolicy
def permitted_attributes
if user.admin? || user.national?
[:title, :body]
else
[:body]
end
end
end
but it has no impact on the above error.
Any idea on how can we do that?
The solution I came to (thanks to #max for some tips and tricks) is as follows:
Add the following line to config/application.rb:
config.action_controller.action_on_unpermitted_parameters = :raise
Add the rescue_from either to the AplicationController or the one you are precisely interested:
class ApplicationController < ActionController::API
include ActionController::MimeResponds
include Pundit
rescue_from Pundit::NotAuthorizedError, ActionController::UnpermittedParameters, with: :user_not_authorized
...
private
def user_not_authorized
render jsonapi: errors_response, status: :unathorized
end
def errors_response
{
errors:
[
{ message: 'You are not authorized to perform this action.' }
]
}
end
end
Then add pundit_params_for method to the PostsController and change the update action (in my case I'd like to restrict some attributes in update action only:)
class PostsController < ApplicationController
...
def update
if #post.update(permitted_attributes(#post))
render jsonapi: #post
else
render jsonapi: #post.errors, status: :unprocessable_entity
end
end
private
def post_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params,
only: [:title, :body, :user]
)
end
def pundit_params_for(_record)
params.fetch(:data, {}).fetch(:attributes, {})
end
end
VoilĂ . Now if an unpermitted attribute will be submitted for the update action, the response will have 500 status and contain the error as specified in ApplicationController#errors_response method.
ATTENTION: It still fails if you have some relations posted with the request (for example, you can have an Author as belongs_to relation with Post). Using pundit_params_for as before will fail to extract the corresponding author_id value. To see the way, here my another post where I explained how to use it.
Hope this helps.

handling nested attributes in rails 5 api only application

I have a requirement in rails api application. client can have many orders and each order belongs to a client.
class Client < ApplicationRecord
has_many :orders
end
my order.rb is
class Order < ApplicationRecord
belongs_to :client, dependent: :destroy
accepts_nested_attributes_for :client
validates_presence_of :order_amount, :service_amount, :miners_amount
end
I have a route exposed /place_order and which creates client and orders.
class OrderProcessingController < ApplicationController
def place_order
#order = Order.new(order_processing_params)
if #order.save
render json: #order
else
render json: #order.errors.full_messages
end
end
private
def order_processing_params
params.require(:order).permit(:order_amount, :service_amount, :miners_amount, client_attributes: [:name, :email, :phone_number, :date])
end
end
Everything works fine so far. Now my requirement is, i have to check the client is already present in client table. if yes add the client_id for the orders and create new order. I don't want to create new client and order every time.
how can i achieve the same in before_filter or something like that. get the client from client params and if the client present delete the params key from incoming params ???
the post data for place_order is as follows
{
"order" : {
"order_amount" : "10000",
"service_amount" : "1000",
"miners_amount" : "10000",
"client_attributes": {
"name": "Ajith",
"email": "ajith#gmail.com",
"phone_number": "12231321312",
"date": "12/12/12"
}
}
}
Thanks in advance,
Ajith
The below code is not tested, mostly your approach should be around this
class OrderProcessingController < ApplicationController
before_action :find_client, only: [:place_order]
def place_order
#order = #client.orders.new(order_processing_params)
if #order.save
render json: #order
else
render json: #order.errors.full_messages
end
end
private
def order_processing_params
params.require(:order).permit(:order_amount, :service_amount, :miners_amount, client_attributes: [:name, :email, :phone_number, :date])
end
def find_client
#client = Client.find_or_create_by(email: params[:order][:client_attributes][:email])
#below line can be improved using a method, see the last line if later you want, never update a primary key which is email in bulk params
#client.update_attributes(name: params[:order][:client_attributes][:name], phone_number: params[:order][:client_attributes][:phone_number], date: params[:order][:client_attributes][:date])
end
#def get_client_params
# params.require(:order)
#end
end
I tried below approach to get a solution. not very sure that this is the right way to approach the problem
class OrderProcessingController < ApplicationController
before_action :find_client, only: :place_order
def place_order
if #client.present?
#order = #client.orders.build(order_processing_params)
else
#order = Order.new(order_processing_params)
end
if #order.save
render json: #order
else
render json: #order.errors.full_messages
end
end
private
def order_processing_params
params.require(:order).permit(:order_amount, :service_amount, :miners_amount, client_attributes: [:name, :email, :phone_number, :date])
end
def find_client
begin
#client = Client.find_by_email(params[:order][:client_attributes][:email])
rescue
nil
end
end
end
Thanks,
Ajith

Rails - Dinamically select attributes to be serialized

I'm using ActiveModel Serializers to serialize my models and I'm constantly in need to create a new serializer in order to satisfy the needs of an controller without including unnecessary information into another.
class ContactGroupSerializer < ActiveModel::Serializer
attributes :id, :name, :contacts, :contacts_count,
:company_id, :user_id
def contacts_count
object.contacts.count
end
end
Is there a way to define a single serializer, such as the one above, and them dinamically select which attributes to be included on my controller response?
class ContactsGroupsController < ApplicationController
def index
...
render json: #contact_groups // here I would like to return only id and name, for example
end
end
I know I can achieve that by creating another serializer, but I wouldn't like to.
Well, you can just define a method in your application_controller.rb to which you can pass all your objects to be rendered with array of methods to be returned as response..like for example,
def response_for(object, methods = [:id])
if object.blank?
head :no_content
elsif object.errors.any?
render json: { errors: object.errors.messages }, status: 422
else
render json: build_hash_for(object, methods), status: 200
end
end
private #or in your `application_helper.rb`
def build_hash_for(object, methods)
methods.inject({}) do |hash, method|
hash.merge!(method => object.send(method))
end
end
In your particular case above, you can just
class ContactsGroupsController < ApplicationController
def index
...
response_for #contact_groups, [:id, :name]
end
end

uninitialized constant UController::User_param

I am making a basic account setup and to try to learn how the database stuff works. I have been running into this error constantly, and I have no idea how to make it disappear. I have my stuff named as U, so the URL will be easier to type a username like Reddit has it example.com/u/username
The Error is uninitialized constant UController::User_param
It highlights this code: #user = U.new(User_param)
Controller:
class UController < ApplicationController
def index
#users = U.all
end
def show
end
def create
#user = U.new(User_param)
if #user.save
redirect_to :action => 'list'
else
#user = U.all
render :action => 'new'
end
end
def User_param
params.require(:Us).permit(:id, :email, :password, :created_at, :updated_at)
end
def new
#user = U.new
end
def edit
end
end
Routes:
resources :u
U Model:
class U < ActiveRecord::Base
end
In Rails you don't capitalize methods, only constants and classes. change User_param to user_params along with the method and that should work. I made params plural since it is clearer and easier to understand
Also, change the user_param method to this:
def user_params
params.require(:u).permit(:id, :email, :password, :created_at, :updated_at)
end
The .require(:u) doesn't need to be plural as you had it.

Ruby on Rails "render json:" ignoring alias_attribute

I have the following code in my account.rb model file:
class Account < ActiveRecord::Base
alias_attribute :id, :accountID
alias_attribute :name, :awzAccountName
alias_attribute :description, :awzAccountDescription
end
And the following code in the index method from my accounts_controller.rb file:
def index
#accounts = Account.all
if params["page"]
page = params["page"]
items_per_page = params["per_page"]
render :json => {:total => #accounts.count,:accounts => #accounts.page(page).per(items_per_page) }
else
render json: #accounts
end
end
As expected, render json: #accounts returns a result set that contains the alias_attribute column names defined in the model file. However, the render :json => {:total => #accounts.count,:accounts => #accounts.page(page).per(items_per_page) } code returns a result set that contains the original column names. Is there any way to change this so that the alias_attribute column names are used?
I wouldn't expect render json: #accounts to include the aliased attributes at all. The alias_attribute just gives you the luxury of referring to the attribute with another name - it doesn't replace the original name at all.
If you do want to include the aliases in your json output for a model you can override as_json and add those methods explicitly:
def as_json(options = {})
options[:methods] ||= []
options[:methods] += [:name, :description]
super(options)
end
(I've deliberately omitted :id as that may be a special case - not entirely sure and can't test locally at the moment)
I was able to solve this by overwriting serializable_hash method.
def serializable_hash(options = {})
options[:methods] ||= []
options[:methods] += [:name, :description]
super(options)
end
You can achieve the same result by passing methods argument to as_json without changing your default serialization of your models. like this:
render json: #accounts.as_json(methods: [:name, :description])

Resources