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])
Related
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
I am currently doing this in my controller action:
render json: order.to_json(:include => [items: {include: [products: {include: [:inventory]}]}])
Now I am moving this over to a richer object like this:
class OrderResponse
attr_accessor :success, :errors, :order, :users
def initialize(success, errors)
#success = success
#errors = errors
end
end
The problem now is that it doesn't render all the inner data of the order model.
The json now has only the order attributes since I am not calling the :includes now.
response = OrderResponse.new(true, [])
response.order = order
response.user = user
render json: response
How can I override how the order is being converted to json now?
Just like you can create .html templates using the ERB templating language (or others), you can use the jbuilder DSL to render complex json responses.
OrdersController:
#response = OrderResponse.new(true, [])
render :response, formats: [:json]
views/orders/response.json.jbuilder
json.call #response, :id
json.user do
json.extract! #response.user, :id, :created_at # ...
end
json.order do
json.extract! #response.order, :id, :created_at # ...
end
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
AlbumsController:
respond_to :json
def index
respond_with albums.ordered
end
Now how can I make it so that the underlying call to_json always executes with this options: except => [:created_at, :updated_at]? And I don't mean just for this action, but for all others actions defined in this controller.
The as_json method is what is used to serialize into json
class Album
def as_json(params={})
params.merge! {:except=>[:created_at, :updated_at]}
super(params)
end
end
You could define serializable_hash on your model which defines the keys and values to return. Rails will then return JSON / XML based on this hash:
def serializable_hash
{ :name => "Stack Overflow",
:created_at => created_at",
:posts_count => posts.count
}
end
First, the desired result
I have User and Item models. I'd like to build a JSON response that looks like this:
{
"user":
{"username":"Bob!","foo":"whatever","bar":"hello!"},
"items": [
{"id":1, "name":"one", "zim":"planet", "gir":"earth"},
{"id":2, "name":"two", "zim":"planet", "gir":"mars"}
]
}
However, my User and Item model have more attributes than just those. I found a way to get this to work, but beware, it's not pretty... Please help...
Update
The next section contains the original question. The last section shows the new solution.
My hacks
home_controller.rb
class HomeController < ApplicationController
def observe
respond_to do |format|
format.js { render :json => Observation.new(current_user, #items).to_json }
end
end
end
observation.rb
# NOTE: this is not a subclass of ActiveRecord::Base
# this class just serves as a container to aggregate all "observable" objects
class Observation
attr_accessor :user, :items
def initialize(user, items)
self.user = user
self.items = items
end
# The JSON needs to be decoded before it's sent to the `to_json` method in the home_controller otherwise the JSON will be escaped...
# What a mess!
def to_json
{
:user => ActiveSupport::JSON.decode(user.to_json(:only => :username, :methods => [:foo, :bar])),
:items => ActiveSupport::JSON.decode(auctions.to_json(:only => [:id, :name], :methods => [:zim, :gir]))
}
end
end
Look Ma! No more hacks!
Override as_json instead
The ActiveRecord::Serialization#as_json docs are pretty sparse. Here's the brief:
as_json(options = nil)
[show source]
For more information on to_json vs as_json, see the accepted answer for Overriding to_json in Rails 2.3.5
The code sans hacks
user.rb
class User < ActiveRecord::Base
def as_json(options)
options = { :only => [:username], :methods => [:foo, :bar] }.merge(options)
super(options)
end
end
item.rb
class Item < ActiveRecord::Base
def as_json(options)
options = { :only => [:id, name], :methods => [:zim, :gir] }.merge(options)
super(options)
end
end
home_controller.rb
class HomeController < ApplicationController
def observe
#items = Items.find(...)
respond_to do |format|
format.js do
render :json => {
:user => current_user || {},
:items => #items
}
end
end
end
end
EDITED to use as_json instead of to_json. See How to override to_json in Rails? for a detailed explanation. I think this is the best answer.
You can render the JSON you want in the controller without the need for the helper model.
def observe
respond_to do |format|
format.js do
render :json => {
:user => current_user.as_json(:only => [:username], :methods => [:foo, :bar]),
:items => #items.collect{ |i| i.as_json(:only => [:id, :name], :methods => [:zim, :gir]) }
}
end
end
end
Make sure ActiveRecord::Base.include_root_in_json is set to false or else you'll get a 'user' attribute inside of 'user'. Unfortunately, it looks like Arrays do not pass options down to each element, so the collect is necessary.
Incase anyone is looking for an alternative solution for this, this is how I solved this in Rails 4.2:
def observe
#item = some_item
#user = some_user
respond_to do |format|
format.js do
serialized_item = ItemSerializer.new(#item).attributes
serialized_user = UserSerializer.new(#user).attributes
render :json => {
:item => serialized_item,
:user => serialized_user
}
end
end
end
This returns the serialized version of both objects as JSON, accessible via response.user and response.item.
There are a lot of new Gems for building JSON now, for this case the most suitable I have found is Jsonify:
https://github.com/bsiggelkow/jsonify
https://github.com/bsiggelkow/jsonify-rails
This allows you to build up the mix of attributes and arrays from your models.
Working answer #2 To avoid the issue of your json being "escaped", build up the data structure by hand, then call to_json on it once. It can get a little wordy, but you can do it all in the controller, or abstract it out to the individual models as to_hash or something.
def observe
respond_to do |format|
format.js do
render :json => {
:user => {:username => current_user.username, :foo => current_user.foo, :bar => current_user.bar},
:items => #items.collect{ |i| {:id => i.id, :name => i.name, :zim => i.zim, :gir => i.gir} }
}
end
end
end