I have the following controller code in a simple Rails API:
class Api::V1::AccountsController < ApplicationController
def index
render json: Account.all
end
def show
begin
render json: Account.includes(:cash_flows).find(params[:id]), include: :cash_flows
rescue ActiveRecord::RecordNotFound => e
head :not_found
end
end
end
The problem with this is that, the generated json have the format:
{
id:2,
name: 'Simple account',
cash_flows: [
{
id: 1,
amount: 34.3,
description: 'simple description'
},
{
id: 2,
amount: 1.12,
description: 'other description'
}
]
}
I need that my generated json is camelCase('cashFlows' instead of 'cash_flows')
Thanks in advance!!!
Following the recommended by #TomHert, I used JBuilder and the available config:
Keys can be auto formatted using key_format!, this can be used to convert keynames from the standard ruby_format to camelCase:
json.key_format! camelize: :lower
json.first_name 'David'
# => { "firstName": "David" }
You can set this globally with the class method key_format (from inside your environment.rb for example):
Jbuilder.key_format camelize: :lower
Thanks!!!
Related
My application sends data to a metadata repository through a REST API. I choosed Faraday to handle the HTTP requests. I basically setup some headers, a json dataset, and POST to the webservice. The following code takes place in the skills_controller, and is triggered when the user decides to publish the definition of a variable:
### Create the variable for the BusinessArea, get the location header in return
connection = Faraday.new("https://sis-sms-r.application.opendataquality.ch", :ssl => {:verify => false})
request_body = {
definedVariableType: #skill.skill_type.property,
description: {
en: #skill.description_translations.where(language: :en).take!,
de: #skill.description_translations.where(language: :de_OFS).take!,
fr: #skill.description_translations.where(language: :fr_OFS).take!
},
identifier: "Variable TEST 10",
name: {
en: #skill.name_translations.where(language: :en).take!,
de: #skill.name_translations.where(language: :de_OFS).take!,
fr: #skill.name_translations.where(language: :fr_OFS).take!
},
pattern: nil,
pseudonymized: true,
validFrom: Time.now,
validTo: Time.now + 1.year,
version: "1",
responsibleDeputy: {
identifier: #skill.deputy.email,
name: #skill.deputy.external_directory_id
},
responsibleOrgUnit: {
identifier: #skill.organisation.code,
name: #skill.organisation.external_reference
},
responsiblePerson: {
identifier: #skill.responsible.email,
name: #skill.responsible.external_directory_id
}
}
puts "--- body"
puts request_body
response = connection.post("/api/InformationFields/#{business_area.uuid}/definedVariables") do |req|
req.body = request_body.to_json
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
req.headers['Authorization'] = "Bearer #{token}"
end
puts "--- response"
puts response.status # Status 201 => successful request
puts response.body # Message
puts response.headers["location"] # uuid of new object
The method then renders an updated partial of the Show view of the skill, with its updated status.
This works fine as long as the request body is quite simple. But I'd like to handle a variable number of translations, and in some cases also send child records to the web service: i.e. implement loops, nested json objects, and probably partials.
I read about Jbuilder features to create complex json for views. Is there something similar I could use in a controller? Or is there a way to create a json view (and partials) and render it into Faraday' request body? Which would be a good architecture to build this feature? Do you know any article that would describe this?
Thanks a lot for showing me the way.
Start by creating an object that touches your application boundry:
class JSONClient
attr_reader :connection
def initialize(base_uri, **opts, &block)
#connection = Faraday.new(
base_uri,
**opts
) do |f|
f.request :json # encode req bodies as JSON
f.response :json # decode response bodies as JSON
yield f if block_given?
end
end
end
class BusinessAreaClient < JSONClient
def initialize(**opts)
super(
"https://sis-sms-r.application.opendataquality.ch",
ssl: { verify: false},
**opts
)
end
def defined_variables(skill:, uiid: token:)
response = connection.post(
"/api/InformationFields/#{uuid}/definedVariables"
SkillSerializer.serialize(skill),
{
'Authorization' => "Bearer #{token}"
}
)
if response.success?
response
else
# handle errors
end
end
end
response = BusinessAreaClient.new
.defined_variables(
skill: skill,
uuid: business_area.uuid,
token: token
)
This gives you object that can be tested in isolation and stubbed out. Its also the only object that should know about the quirks and particularities of the API thus limiting the impact on your application if it should change.
While using a view sounds like a good idea intially you're basically using a very awkward DSL to generate basic data structures like arrays and hashes that map 1-1 to JSON. jBuilder is also very slow.
As a first step to refactoring you could just extract turning a Skill into JSON into its own PORO:
class SkillSerializer < SimpleDelegator
LANG_MAPPING = {
en: :en,
de: :de_OFS,
fr: :fr_OFS
}.freeze
def serialize
{
definedVariableType: skill_type.property,
description: translate(description_translations),
identifier: "Variable TEST 10",
name: translate(name_translations),
pattern: nil,
pseudonymized: true,
validFrom: Time.now,
validTo: Time.now + 1.year,
version: "1",
responsibleDeputy: {
identifier: deputy.email,
name: deputy.external_directory_id
},
responsibleOrgUnit: {
identifier: organisation.code,
name: organisation.external_reference
},
responsiblePerson: {
identifier: responsible.email,
name: responsible.external_directory_id
}
}
end
def self.serialize(object)
new(object).serialize
end
private
# should probally be refactored to not cause an excessive amount of queries
def translate(relation)
LANG_MAPPING.dup.transform_values do |lang|
relation.where(language: lang).take!
end
end
end
ActiveModel::Serializers is also an option.
I'm new to rails,
Please check my code and tell me whats wrong with my use params, because this is how it made sense to me.
Controller:
def create
user = User.find(user_params)
order = user.purchases.new
render json: order.errors if !order.save
basket = params.require(:basket)
basket.each do |b|
i = Item.find(b[:item_id])
render json: i.errors, status: 422 if !i
order.purchases_items.create(item_id: i, quantity: b[:quantity])
end
render nothing: true, status: 201 # location: show action
end
and my test file is sending
test "making order" do
post "/api/users/#{#tuser.id}/orders",
{ basket: [ { item_id: '2', quantity: '5' },
{ item_id: '1', quantity: '4'} ] }.to_json,
{ 'Accept' => Mime::JSON, 'Content-Type' => Mime::JSON.to_s }
assert_response 201
assert_equal Mime::JSON, response.content_type
end
Thanks,
What I basically want to do is store each array element in the array basket from params[:basket], and iterate over it.
Sometime params keys are not get converted into symbols automatically. Can u try passing string "basket" instead of symbol :basket?
This:
def index
render json: Slide.all.to_json(include: :user)
end
Is rendering this:
[
{
id: 1,
title: 'Hello',
user: {
first_name: 'Guilherme',
last_name: 'Oderdenge',
email: 'guilhermeoderdenge#gmail.com'
}
}
]
Ok. But I just want the first_name from user. There's a way to do this?
Yes, you can do this:
render json: Slide.all.to_json(include: { user: { only: :first_name} )
See the rails api for more information.
Change it like
render json: Slide.all.to_json(:include=>{:user=>{:only=>[:first_name]}})
I am trying Rabl, however, I seem to receive a practically empty json block.
require_dependency "api/application_controller"
module Api
class RentablePropertiesController < ApplicationController
respond_to :json
def index
#r = Core::RentableProperty.all
# render :text => #r.to_json --> note: this renders the json correctly
render "api/rentable_properties/index" #note: rabl here does not
end
end
end
index.json.rabl
collection #r
Output
[{"rentable_property":{}}]
Note: with a simply #r.to_json, it renders correctly:
[{"id":1,"description":"description","property_type_id":1,"created_at":"2013-08-22T19:04:35.000Z","updated_at":"2013-08-22T19:04:35.000Z","title":"Some Title","rooms":null,"amount":2000.0,"tenure":null}]
Any idea why rabl doesn't work?
The documentation of RABL (https://github.com/nesquena/rabl#overview) says that you need to precise what attributes you want to show in your JSON.
Their example:
# app/views/posts/index.rabl
collection #posts
attributes :id, :title, :subject
child(:user) { attributes :full_name }
node(:read) { |post| post.read_by?(#user) }
Which would output the following JSON or XML when visiting /posts.json:
[{ "post" :
{
"id" : 5, title: "...", subject: "...",
"user" : { full_name : "..." },
"read" : true
}
}]
want to get all db entries by a specific hash and return it as json. I use the following code:
#tasks = Task.find_all_by_hash(params[:hash])
respond_to do |format|
format.json { render :json => #tasks }
end
now i have the problem that my json file isn't correct. it has the following output:
[
{
task: {
hash: "9dfca619f00f5488785f6b74ad1b590beefaee7a88c04884bf197e7679f3"
id: 4
created_at: "2010-12-16T09:09:51Z"
updated_at: "2010-12-16T09:14:10Z"
done: true
name: "Hallo"
}
},
{
task: {
hash: "9dfca619f00f5488785f6b74ad1b590beefaee7a88c04884bf197e7679f3"
id: 5
created_at: "2010-12-16T09:12:37Z"
updated_at: "2010-12-16T09:12:37Z"
done: true
name: "Test"
}
},
...
]
but actually i want it like this:
{ tasks: [
{"id":"1","date_added":"0001-02-22 00:00:00","post_content":"Check out my content, this is loaded via ajax and parsed with JSON","author":"Ryan Coughlin"},
{"id":"2","date_added":"0000-00-00 00:00:00","post_content":"More content, loaded. Lets try to add a form in and post via ajax and append the new data","author":"Billy Bob"}
]}
any advice? thank you!
Try collecting the task alone to an array and create a json object using that. Some thing like
#tasks = Task.find_all_by_hash(params[:hash])
a = []
#tasks.each do |t|
a << t[:task]
end
b = {:tasks => a}
respond_to do |format|
format.json { render :json => b }
end