Rendering json data fields from mysql db in rails response object - ruby-on-rails

I have a rails 4.2 app that uses mysql db 5.7 which supports json fields. So my user model has a field called display_pic which is a json object.
class User < ActiveRecord::Base
serialize :display_pic, JSON
....
In the action get_user I render user as follows
def get_user
#u = User.where(...)
render json: { user: #u }
end
The problem is that the json field display_pic doesn't come out as a nested json object, rather it is rendered as a string. I would like to have a response like the following
{
"user": {
"name": "some name",
"email": "some email",
"display_pic": {
"url": "http://someurl.com",
"width": "400px",
}
}
}

Probably a better way to do this, but you can format it as json in the serializer.
class UserSerializer < ActiveModel::Serializer
attribute :name
attribute :email
attribute :display_pic
def display_pic
JSON.parse(object.display_pic)
end
end

Use the following code it will solve your problem:
def get_user
#u = User.where(...)
render json: { user: JSON.parse(#u)}
end

Have you tried to use the method .as_json ?
def get_user
#u = User.where(...)
render json: #u.as_json
end
You should have not need to set serialize :display_pic, :JSON, but you can overload the method in your user.rb class in order to get on response references or methods results authomatically loaded on your front end:
class PlayerCharacter < ApplicationRecord
[...]
def as_json(options = {})
super(options.merge(include: [ :reference1, :reference2]).merge(methods: [:method_name1, :method_name2])
end
end
EDIT:
you could add display_pic as follow:
class PlayerCharacter < ApplicationRecord
[...]
def as_json(options = {})
super(options.merge(include: [ :display_pic])
end
end

Related

Returning resources with different types for rails Restful API

I am working on implementing a search endpoint with ruby based on a json request sent from the client which should have the form GET /workspace/:id/searches? filter[query]=Old&filter[type]=ct:Tag,User,WokringArea&items=5
The controller looks like this
class SearchesController < ApiV3Controller
load_and_authorize_resource :workspace, class: "Company"
load_and_authorize_resource :user, through: :workspace
load_and_authorize_resource :working_area, through: :workspace
def index
keyword = filtered_params[:query].delete("\000")
keyword = '%' + keyword + '%'
if filtered_params[:type].include?('User')
#users = #workspace.users.where("LOWER(username) LIKE LOWER(?)", keyword)
end
if filtered_params[:type].include?('WorkingArea')
#working_areas = #workspace.working_areas.where("LOWER(name) LIKE LOWER(?)", keyword)
end
#resources = #working_areas
respond_json(#resources)
end
private
def filtered_params
params.require(:filter).permit(:query, :type)
end
def ability_klasses
[WorkspaceAbility, UserWorkspaceAbility, WorkingAreaAbility]
end
end
respond_json returns the resources with a json format and it looks like this
def respond_json(records, status = :ok)
if records.try(:errors).present?
render json: {
errors: records.errors.map do |pointer, error|
{
status: :unprocessable_entity,
source: { pointer: pointer },
title: error
}
end
}, status: :unprocessable_entity
return
elsif records.respond_to?(:to_ary)
#pagy, records = pagy(records)
end
options = {
include: params[:include],
permissions: permissions,
current_ability: current_ability,
meta: meta_infos
}
render json: ApplicationRecord.serialize_fast_apijson(records, options), status: status
end
Now the issue is the response is supposed to look like this:
{
data: [
{
id: 32112,
type: 'WorkingArea'
attributes: {}
},
{
id: 33321,
type: 'User',
attributes: {}
},
{
id: 33221,
type: 'Tag'
attributes: {}
}
How can I make my code support responding with resources that have different types?
You can define a model, not in your database, that is based on the results from the API. Then you include some of the ActiveModel modules for more features.
# app/models/workspace_result.rb
class WorkspaceResult
include ActiveModel::Model
include ActiveModel::Validations
include ActiveModel::Serialization
attr_accessor(
:id,
:type,
:attributes
)
def initialize(attributes={})
filtered_attributes = attributes.select { |k,v| self.class.attribute_method?(k.to_sym) }
super(filtered_attributes)
end
def self.from_json(json)
attrs = JSON.parse(json).deep_transform_keys { |k| k.to_s.underscore }
self.new(attrs)
end
end
Then in your API results you can do something like:
results = []
response.body["data"].each do |result|
results << WorkspaceArea.from_json(result)
end
You can also define instance methods on this model, etc.

In Rails, how can you return model data from multiple different ActiveModelSerializers/Models at once?

I have a couple models. Let's call them Widget and Gadget.
My #index for for Widget and Gadget looks something like this
def index
widgets = Widget.all
if widgets
respond_with widgets, each_serializer: Api::V1::WidgetSerializer
else
render json: { message: [t(:not_found_widget)] }, status: :not_found
end
end
def index
gadgets = Gadget.all
if gadgets
respond_with gadgets, each_serializer: Api::V1::GadgetSerializer
else
render json: { message: [t(:not_found_gadget)] }, status: :not_found
end
end
And my serializers...
class Api::V1::WidgetSerializer < ActiveModel::Serializer
attributes :id, :desc
end
class Api::V1::GadgetSerializer < ActiveModel::Serializer
attributes :id, :desc
end
However, I have the need for a resource that returns both of those in 1. I need both widgets and gadgets returned at once. So the json would look like...
{
"widgets": [
{
"id": 1,
"desc": "One"
},
{
"id": 2,
"desc": "Two"
}
],
"gadgets": [
{
"id": 1,
"desc": "One"
},
{
"id": 2,
"desc": "Two"
}
]
}
How can I achieve this. Something like
widgets = Widget.all
gadgets = Gadget.all
respond_with widgets, each_serializer: Api::V1::WidgetSerializer, gadgets, each_serializer: Api::V1::GadgetSerializer
However, this clearly doesn't work.
Serializer classes don't have to match AR models. Serializer classes should be used as your representation of your JSON you want to produce. In your example, let's assume to call a new serializer DashboardSerializer, then you can put both widgets and gadgets there:
class DashboardSerializer < ActiveModel::Serializer
self.root = false
attributes :widgets, :gadgets
def widgets
Widget.all
end
def gadgets
Gadget.all
end
end
The only way I can see this working is if you had a model that has_many widgets and has_many gadgets with it's own serializer as AMS will apply the correct serializers to associations declared in a serializer.
something like
class Composite < ActiveRecord::Base
has_many :widgets
has_many :gadgets
end
class CompositeSerializer < ActiveModel::Serializer
attributes :id
has_many :widgets
has_many :gadgets
end

Bad formatted json

There is a model Event in my app and I have two problems, the first is the returned json that comes messed with the request:
$ curl http://0.0.0.0:3000/events
it gives me
{
"events":[
{"events":{"name":"First"}},
{"events":{"name":"Second"}}],
"meta":{"total_pages":9,
"next_page":"http://0.0.0.0:3000/events?page=2"}
}
which I think it should gives
{
"events":[
{"name":"First" },
{"name":"Second"}
],
"meta":{
"total_pages":9,
"next_page":"http://0.0.0.0:3000/events?page=2"
}
}
also I am using the gems draper and active_model_serializer, and the second problem is that the json format doesn't respect the Serializer definition:
class EventSerializer < ActiveModel::Serializer
attributes :id, :date_end
end
I would like to know where should I look to fix these problems.
Answering the questions, the code that generate the json:
class EventsController < ApplicationController
def index
#events = Event.order(:name).page(params[:page])
#events = #events.where(id: params[:ids].split(',')) if params[:ids].present?
render json: #events.decorate, meta: { total_pages: #events.total_pages, next_page: next_page_url(#events)}
end
def next_page_url(event)
unless event.last_page?
next_page = event.current_page + 1 unless event.last_page?
events_url(params.merge({:page => next_page}))
end
end
and the decorator:
class EventDecorator < Draper::Decorator
delegate :current_page, :total_pages, :limit_value
def name
object.name
end
end

Define several json formats for model

In my rails app i defined a specific JSON-Format in my model:
def as_json(options={})
{
:id => self.id,
:name => self.name + ", " + self.forname
}
end
And in the controller i simply call:
format.json { render json: #patients}
So now im trying to define another JSON-Format for a different action but i dont know how?
How do i have to define another as_json or how can i pass variables to as_json? Thanks
A very ugly method but you can refactor it for better readability:
def as_json(options={})
if options.empty?
{ :id => self.id, :name => self.name + ", " + self.forname }
else
if options[:include_contact_name].present?
return { id: self.id, contact_name: self.contact.name }
end
end
end
Okay, I should give you a better piece of code, here it is:
def as_json(options = {})
if options.empty?
self.default_json
else
json = self.default_json
json.merge!({ contact: { name: contact.name } }) if options[:include_contact].present?
json.merge!({ admin: self.is_admin? }) if options[:display_if_admin].present?
json.merge!({ name: self.name, forname: self.forname }) if options[:split_name].present?
# etc etc etc.
return json
end
end
def default_json
{ :id => self.id, :name => "#{self.name}, #{self.forname}" }
end
Usage:
format.json { render json: #patients.as_json(include_contact: true) }
By defining hash structure by 'as_json' method, in respective model class i.e User model in (Example 1), it becomes the default hash stucture for active record(i.e., user) in json format. It cannot be overridden by any inline definitions as defined in Example: 2
Example 1:
class User < ActiveRecord::Base
.....
def as_json(options={})
super(only: [:id, :name, :email])
end
end
Example: 2
class UserController < ApplicationController
....
def create
user = User.new(params[:user])
user.save
render json: user.as_json( only: [:id, :name] )
end
end
Therefore, in this example when create action is executed 'user' is returned in ("only: [:id, :name, :email]") format not as ("only: [:id, :name]")
So, options = {} are passed to as_json method to specifiy different format for different methods.
Best Practice, is to define hash structure as constant and call it everwhere it is needed
For Example
Ex: models/user.rb
Here, constant is defined in model class
class User < ActiveRecord::Base
...
...
DEFAULT_USER_FORMAT = { only: [:id, :name, :email] }
CUSTOM_USER_FORMAT = { only: [:id, :name] }
end
Ex: controllers/user.rb
class UserController < ApplicationController
...
def create
...
render json: user.as_json(User::DEFAULT_USER_FORMAT)
end
def edit
...
render json: user.as_json(User::CUSTOM_USER_FORMAT)
end
end
Cool!

Rails ActiveRecord relation to JSON

I'm using Rails to query data and put it into a hash like so...
class AssignmentsController < ApplicationController
respond_to :json
def index
student = Student.find(current_user.student_id)
#assignments = Hash.new
#assignments["individual"] = Assignment.where(:student_id => student.id)
unless student.group_lesson_ids.nil?
student.group_lesson_ids.each do |g|
group_lesson = GroupLesson.find(g)
#assignments[group_lesson.name] = Assignment.where(:group_lesson_id => g)
end
end
end
end
Then I want Rabl to turn this into JSON to be used by a Marionette app.
Here's the Rabl file
object #assignments
attributes :id, :title, :student_id, :assigned
But when I inspect the JSON in the browser, it just shows me the ActiveRecord relation.
{
"#<ActiveRecord::Relation::ActiveRecord_Relation_Assignment:0x007fa2956b43a8>": [
{ },
{ },
{ }
]
}
I understand this is because of the concept of lazy loading, but what should I do in this situation to make the JSON available to Marionette?
How about this, provided you have relationships between models specified (not tested since I'm not currently using RABL):
class AssignmentsController < ApplicationController
respond_to :json
def index
#student = current_user.student
end
end
RABL template:
object false
node :assignments do
child #student.assignments => :individual
#student.group_lessons.each do |gl|
node(gl.name) { gl.assignments }
end
end

Resources