I'm trying to use the Active Model Serializer gem with my API, although I am struggling with something I thought would be pretty simple.
All my JSON responses are in a wrapped format, with every response having a top level message and status property, the data is within the content property. Every JSON response follows this format.
Example
{
'status': statuscode,
'message': message,
'content': { 'object':obj }
}
The contents of the "content" property is where I would like to place the output of the Serializer. My lists of articles, etc.
I cannot figure out how to do this though?
Any help would be greatly appreciated.
IF You dont mind your status and messages hashes being inside a hash you can use a meta key.
(from https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
render :json => #posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
=>
{
"meta": { "total": 10 },
"posts": [
{ "title": "Post 1", "body": "Hello!" },
{ "title": "Post 2", "body": "Goodbye!" }
]
}
Or if you need them to be top level keys you can SubClass ArraySerializer and overwrite as_json to allow it to merge in your keys.
def as_json(*args)
#options[:hash] = hash = {}
#options[:unique_values] = {}
hash.merge!(#options[:top_level_keys]) if #options.key?(:top_level_keys)
root = #options[:root]
if root.present?
hash.merge!(root => serializable_array)
include_meta(hash)
hash
else
serializable_array
end
end
then just
render :json #object, :serializer => YourCustomArraySerializer
Related
I am creating API. Using ActiveRecords. Problem I am getting
Multiple array object of country, all I want one array containing all location
Current Output
{
"id": "180a096",
"country": [
{
"location": "US"
},
{
"location": "CH"
}
]
}
Expected Output
{
"id": "180a096",
"country": [
{"location":["US","CH"]}
]
}
Code
def as_json(options={})
super(:only => [:id ],:include => { :country => { :only => :location } })
end
Can anyone help me to restructured the object as in expected output.
If your hash is called hash you can do:
hash[:country].map {|h| h[:location]}
If you have to access attributes on associated models you can do:
countries.pluck(:location)
Unrelated to the question, but when I have to manage country info in my app I tend to use the countries gem. https://github.com/hexorx/countries
It has all kinds of useful helper methods, and it prevents you from having to maintain standardized country information.
You can simply map all the location and assign it to hash[:country]
2.4.0 :044 > hash[:country].map! { |c| c[:location] }
=> ["US", "CH"]
2.4.0 :045 > hash
=> {:id=>"180a096", :country=>["US", "CH"]}
As mentioned in my comment, you can do in one line like
actual_hash[:country].map! { |country| country[:location]}
actual_hash # => {:id=>"180a096", :country=>["US", "CH"]}
The output is clean but not as expected.
Or, a bit more lines to get the exact output:
location_array = [{location: []}]
actual_hash[:country].each { |country| location_array[0][:location] << country[:location]}
actual_hash[:country] = location_array
actual_hash # => {:id=>"180a096", :country=>[{:location=>["US", "CH"]}]}
def rearrange_json(input)
input_hash = JSON.parse(input)
output_hash = input_hash.clone
output_hash[:country] = {location: []}
input_hash[:country].map {|l| output_hash[:country][:location] << l[:location] }
output_hash.as_json
end
With this method, you can convert your json to a hash, then rearrange its content they way you want by adding the country codes as values for the [:country][:location] key of the output hash, and end up with some properly formatted json. It's not a one-liner, and probably not the most elegant way to do it, but it should work.
I am having trouble trying to render specific fields in my rails app my structure is like so:
Home_controller.rb
def index
taobao_hash = OpenTaobao.get(
:method => "taobao.tbk.item.get",
:fields => "num_iid,title,nick,pict_url,cid,price,type,delist_time,post_fee,score,volume",
:q => "snus",
:page_size => 1,
:sort => "_des",
)
h = JSON.parse(taobao_hash.to_json)
render json: h["tbk_item_get_response"]
end
How this renders my whole request as json like so
{
"results": {
"n_tbk_item": [
{
"nick": "恶毒对于",
"num_iid": 17707525948,
"pict_url": "http://img4.tbcdn.cn/tfscom/i2/TB1umhOHVXXXXa0aXXXXXXXXXXX_!!0-item_pic.jpg",
"title": "瑞典戒烟产品唇烟嚼烟经典款General Classic White Portion Snus",
"volume": 127
}
]
},
"total_results": 99,
"request_id": "1476rjvn5sc2g"
}
How would I go about only rendering title in my view? I can only seem to render everything.
I have created a backend rails server that strictly serves an iOS app I built. Upon initial loading of the iOS app, it needs to immediately pull in about a dozen models and their data. I want to avoid 1) A dozen separate server calls and b) Chaining the dozen calls in completions blocks. I.E. Call A, when A is done, call B, when B is done, call C... etc.
I would like to make a Load resource. Something that will return the data from all dozen models in one call. So the resulting json will be something like...
{
"widgets": [
{
"id": 1,
"desc": "One"
},
{
"id": 2,
"desc": "Two"
}
],
"gadgets": [
{
"id": 1,
"desc": "One"
}
],
"flidgets": [
{
"id": 1,
"desc": "One"
}
]
}
I would also prefer to not include the timestamps.
How can I do this? Suppose I create a new controller, InitialLoadController. Then I get the model data of my dozen objects. How can I render the dozen models to json and format it like this?
Please check the code below:
class InitialLoadsController < ApplicationController
def load
widgets = Widget.select(:id, :desc)
gadgets = Gadget.select(:id, :desc)
flidgets = Flidget.select(:id, :desc)
response = {
widgets: widgets,
gadgets: gadgets,
flidgets: flidgets
}
render json: response
end
end
Even you can use jbuilder to render json response (as an alternative to render json: response).
Assume you have dashboard action you can use below code to return json data from multiple model.
def dashboard
#widgets = Widget.all
#gadgets = Gadget.all
#flidgets = Flidget.all
respond_to do |format|
format.json {
render :json => {
:widgets => #widgets.to_json(:except => [:created_at,:size]),
:gadgets => #gadgets.to_json(:except => [:created_at,:type]),
:flidgets => #flidgets.to_json(:except => [:created_at,:type])
}
}
end
end
Note:- #widgets.to_json(:except =>[:created_at,:type])
this will return json without fields created_at and type
I'm trying to POST some data to my Rails 4 API.
The resource:
App.factory 'House', ['$resource', ($resource) ->
$resource '/api/v1/houses/:id', { id: '#id' }
]
The JSON representation of the resource:
newHouse = {
"owner_id": "30",
"name": "test",
"address_attributes": {
"street": "somewhere",
"city": "on",
"region": "earth"
}
}
On House.save(null, $scope.newHouse), the server logs give me this:
Processing by Api::V1::HouseController#create as JSON
Parameters: {"owner_id"=>"34", "name"=>"test", "address_attributes"=>{"street"=>"somewhere", "city"=>"on", "region"=>"earth"}, "house"=>{"name"=>"test", "owner_id"=>"34"}}
Which is underisable, to say the least.
owner_id and name appear directly bellow root, and below "house" - I would expect only below "house"
address_attributes appears only directly bellow root - I would expect it to be below house
Basically I want this:
Processing by Api::V1::HouseController#create as JSON
Parameters: {"house"=>{"name"=>"test", "owner_id"=>"34", "address_attributes"=>{"street"=>"somewhere", "city"=>"on", "region"=>"earth"}}}
Any help?
EDIT
Rails controller action:
def create
house = House.new house_params
if house.save
head 200
else
render json: {
"error" => "validation errors",
"detail" => house.errors.messages
}, status: 422
end
end
def house_params
fields = [:name, :owner_id, address_attributes: [:street, :city, :region, :country, :postal_code ]]
params.require(:house).permit(fields)
end
The model House has:
has_one :address
accepts_nested_attributes_for :address
I don't want to change the way the server handles the data. Many backends expect parameters to be held in the format I want, and even jQuery does it in its AJAX calls. AngularJS should be the one to change, or at least allow ways to configure.
I am not sure you are using your $resource correctly.
EDIT: I am not sure why your class method behaves like this, the requests made should be equal./EDIT
One example of an alternative way to do it would be this:
Lets say your newHouse model is this:
{
"owner_id": "30",
"name": "test",
"address_attributes": {
"street": "somewhere",
"city": "on",
"region": "earth"
}
}
In your html you would write something like:
<span class="btn btn-danger" ng-click="createNewHouse(newHouse)">Create new house</span>
Your Controller would bind createNewHouse() method to $scope:
$scope.createNewHouse= function(newHouse){
var newHouseInstance = new House();
newHouseInstance.house = newHouse;
newHouseInstance.$save();
}
Above code gave me a POST request with this payload (direct copy from Chrome developer console):
Remote Address:127.0.0.1:8080
Request URL:http://localhost:8080/api/v1/houses
Request Method:POST
Request Payload:
{"house":{"owner_id":"30", "name":"test", "address_attributes":{"street":"somewhere", "city":"on", "region":"earth"}}}
This will nicely translate to what you need back at the server.
Good luck!
So my code is returning json like this:
"offenses": [
{
"name": "Speeding",
"penalties": [
{
"name": "Ticket",
"severity": "Medium"
}
}
]
I have the following fatcory girl:
FactoryGirl.define do
factory :person_offense_penalty do
person_offense
name 'Ticket'
severity 'Medium'
end
end
FactoryGirl.define do
factory :person_offense do
name 'Speeding'
person
end
end
Here my testing for the response and it works fine
person_offenses = person.person_offenses
expect(response_body['offenses'].size).to eq(person_offenses.size)
person_offenses.each_with_index do |offense, offense_index|
expect(response_body['offenses'][offense_index]['name']).to eq(offense.name)
offense.person_offense_penaltys.each_with_index do |penalty, penalty_index|
expect(response_body['offenses'][offense_index]['penaltys'][penalty_index]['name']).to eq(penalty.name)
expect(response_body['offenses'][offense_index]['penaltys'][penalty_index]['severity']).to eq(penalty.severity)
end
end
I need to write another test that makes sure the offense names are sorted in ascending order.
Can someone help me with that
First, let's get your JSON into a valid string:
[1] pry(main)> x = "{\"offenses\": [{ \"name\": \"Speeding\", \"penalties\": [ { \"name\": \"Ticket\", \"severity\": \"Medium\" } ] }] }"
Now we can parse it ActiveSupport::JSON.decode
[2] pry(main)> offenses = ActiveSupport::JSON.decode(x)["offenses"]
=> [{"name"=>"Speeding", "penalties"=>[{"name"=>"Ticket", "severity"=>"Medium"}]}]
You can get a list of the offense names with map
[3] pry(main)> names = offenses.map { |o| o["name"] }
=> ["Speeding"]
Now you can simply sort the names and compare
expect(names).to eq(names.sort)
This will pass if the original JSON has each "offense" in place ordered by it's "name" property.