I have a task to form JSON data for jqGrid. It requires a special format:
{
total: 50,
page:"1",
records: "1500",
rows: [
{ 20, "{2ae39c44-ca9d-4565-9e05-bbd875c1579c}", "Description 1"},
{ 23, "{e1aaf69d-1040-4afa-8995-fd15c3a591b3}", "Description 2"},
{ 25, "{e3df29c7-ef34-46ba-bf66-7838aca7c137}", "Description 3"},
{ 29, "{768ec164-28e5-4614-a259-63257b79e8e0}", "Description 4"}
]
}
So the basic rules for "rows" are: do not generate root object name, list fields without their names, list fields in exact order to bind to corresponding columns.
Can I force to_json method to modify output as I need?
Currently the to_json produces:
myobjs : [
myobj : { id: 20, uuid: "{2ae39c44-ca9d-4565-9e05-bbd875c1579c}", name: "Description 1"},
myobj : { id: 20, uuid: "{e1aaf69d-1040-4afa-8995-fd15c3a591b3}", name: "Description 2"},
myobj : { id: 20, uuid: "{e3df29c7-ef34-46ba-bf66-7838aca7c137}", name: "Description 3"},
myobj : { id: 20, uuid: "{768ec164-28e5-4614-a259-63257b79e8e0}", name: "Description 4"}
]
You can't do it with a model-level to_json call, you'll need to build an intermediary data representation as #Paul said. Something like:
class MyObj
def to_json
[id, uuid, name]
end
end
And then in the controller:
class MyController < ApplicationController
def grid_data
objs = MyObj.all
json_data = {
:total => objs.count,
:page => 1,
:records => 1500,
:rows => objs.collect {|o| o.to_json}
}
... send json as usual ...
end
end
Note that I set your model up to generate an array, not a hash as you specified, as I think you copied that wrong - your JSON example above is not valid. { 20, 'foo', 'bar' } is not valid JSON as "{...}" represents a hash, which must be keyed, and is not ordered.
Related
I am working on a rails 5 api only. I can get all the data that i want, but i would like to customize the output.I tried some solutions, but couldn't find it yet.
This is my portfolio controller:
def show
render json: #portfolio.to_json(
:only => [:title],
:include => {
:cryptos => {
:only => [],
:include => [
{:radarcrypto => { :only => [:ticker, :price] }},
]},
}
)
end
this is the output
{
title: "Portfolio 1",
cryptos: [
{
radarcrypto: {
ticker: "BTC",
price: "190919.85",
}
},
{
radarcrypto: {
ticker: "ETH",
price: "12220.18",
}
},
],
}
But i would like something like that:
{
title: "Portfolio 1",
cryptos:
{
ticker: "BTC",
price: "190919.85",
},
{
ticker: "ETH",
price: "12220.18",
},
}
this are my models
class Portfolio < ApplicationRecord
has_many :cryptos
end
class Radarcrypto < ApplicationRecord
has_many :cryptos
end
class Crypto < ApplicationRecord
belongs_to :portfolio
belongs_to :radarcrypto
end
I already tried to use without success:
class Radarcrypto < ApplicationRecord
ApplicationRecord.include_root_in_json = false
end
I don't know if there is a better solution to this customization, if would be better to use the views, json.jbuilder for example. thanks.
It's not possible to include a collection of objects as a value to a key without wrapping them in an array in JSON. (source (W3Schools))
It is possible, on the other hand, to have a collection of objects, but each one would have to have its own unique key. So the response could be shaped as such:
{
title: "Portfolio 1",
cryptos: {
BTC: {
ticker: "BTC",
price: "190919.85",
},
ETH: {
ticker: "ETH",
price: "12220.18",
},
}
(A different key is going to have to be used if more than one radarcrypto can have the same ticker)
I'm not sure how that would be achieved using the to_json options (e.g. include and only). You could probably do it manually by doing:
def show
portfolio = {};
portfolio['title'] = #portfolio.title
portfolio['cryptos'] = {}
#portfolio.cryptos.each do |crypto|
radarcrypto = crypto.radarcrypto
portfolio['cryptos']["#{radarcrypto.ticker}"] = {
'ticker': radarcrypto.ticker,
'price': radarcrypto.price
}
end
render json: portfolio.to_json
end
Side note
A gem such as Jbuilder may be a good candidate if nested shaping of JSON responses is done in multiple controllers.
What I want
I want to make a form to save "CRUD operation Infomation itself" in my database.
e.g.)
①create new row {"name": "Tom", "price": 200}
②find a row WHERE {"age": 30}
③delete row WHERE {"name": "John"}
④update the row WHERE {"name": "Ted"} SET {"price": 300}
My idea
To realize above, I created two models.
①CrudOperation
t.string :crud_type # this should be one of CRUD ("create", "read", "update", "delete")
t.string :target_database
②CrudOperationParameter
t.integer :crud_operation_id # reference
t.string :key
t.value :value
CrudOperation has many CrudOperationParameter-s.
Problem
My models seem to work well EXCEPT crud_type is "update".
like that
①create new row {"name": "Tom", "price": 200}
**CrudOperation**
id: 1
crud_type: "create"
target_database: "XXXX"
**CrudOperationParameter**
crud_operation_id: 1
key: "name"
value "Tom"
**Another CrudOperationParameter**
crud_operation_id: 1
key: "price"
value "200"
But when it comes to registering the CrudOperation with update type, the problem occurs.
④update the row WHERE {"name": "Ted"} SET {"price": 300}
**CrudOperation**
id: 1
crud_type: "update"
target_database: "XXXX"
**CrudOperationParameter**
crud_operation_id: 1
key: "name"
value "Ted"
**Another CrudOperationParameter**
crud_operation_id: 1
key: "price"
value "300"
Since CrudOperationParameter has only key-value column,
I cannot identify that this CrudOperationParameter is for WHERE clause or SET clause in UPDATE Statement.
Could you teach me better DB schema to save these kinds of data?
What you have is basically a half-baked version of the Entity Attribute Value pattern (or anti-pattern depending on who you are asking).
If I really had to I would set it up as:
class CrudOperation < ApplicationRecord
has_many :crud_operation_parameters
accepts_nested_attributes_for :crud_operation_parameters
enum crud_type: {
create: "create",
read: "read",
update: "update",
delete: "delete"
}
# Convenience setter that maps a hash of attributes
# into a an array of key value attibutes suitible for `accepts_nested_attributes_for`
# and sets the nested attributes
# #return [Array]
def parameters=(hash)
self.crud_operation_parameters_attributes = hash.map do |key, value|
{
key: key,
value: value
}
end
end
end
class CrudOperationParameter < ApplicationRecord
belongs_to :crud_operation
end
# 1. create new row {"name": "Tom", "price": 200}
CrudOperation.create!(
crud_type: :create,
parameters: {
name: "Tom",
price: 200
}
)
# 1. create new row {"name": "Tom", "price": 200}
CrudOperation.create!(
crud_type: :update,
parameters: {
name: "Tom",
price: 200
}
)
I mentioned that its a half baked attempt since you're lacking the Attribute table where you would normalize the definitions of attributes and store stuff like type information. Your solution instead has a string column with tons of duplicates that can become denormalized.
But modern RDBMS systems have column types such as JSON, JSONB and HSTORE which can be used instead of EAV to store data which does not fit a given schema.
Unlike EAV you don't have to store all the attributes in a single column type (usually a string) and typecast or create a bunch of attribute tables to store different types of attributes (such as StringCrudOperationParameter and FloatCrudOperationParameter).
With JSONB on Postgres I would set it up as:
# rails g model crud_operation crud_type:string payload:jsonb conditions:jsonb
class CrudOperation < ApplicationRecord
enum crud_type: {
create: "create",
read: "read",
update: "update",
delete: "delete"
}
end
# 1. create new row {"name": "Tom", "price": 200}
CrudOperation.create!(
crud_type: :create,
payload: {
name: "Tom",
price: 200
}
)
# 2. find a row WHERE {"age": 30}
CrudOperation.create!(
crud_type: :read,
conditions: {
age: 30
}
)
# 3. delete row WHERE {"name": "John"}
CrudOperation.create!(
crud_type: :delete,
conditions: {
name: "John"
}
)
# 4. update the row WHERE {"name": "Ted"} SET {"price": 300}
CrudOperation.create!(
crud_type: :update,
conditions: {
name: "John"
},
payload: {
price: 300
}
)
I need to create multiple "items" to be used within a json request .
Here's a request that works:
customs_info = EasyPost::CustomsInfo.create(
eel_pfc: 'NOEEI 30.37(a)',
customs_certify: true,
customs_signer: "first_name last_name",
contents_type: 'merchandise',
contents_explanation: '',
restriction_type: 'none',
restriction_comments: '',
# non_delivery_option: 'abandon',
customs_items: [
{
description: "asdf",
quantity: 1,
weight: 23,
value: 23,
# hs_tariff_number: '654321',
origin_country: 'US'
},
{
description: "1234568",
quantity: 1,
weight: 23,
value: 23,
# hs_tariff_number: '654321',
origin_country: 'US'
}
]
)
What I need is to not need to manually set the customs_items.
I tried:
customs_info = EasyPost::CustomsInfo.create(
eel_pfc: 'NOEEI 30.37(a)',
customs_certify: true,
customs_signer: "#{shipping_address.shipping_address_final.first_name} #{shipping_address.shipping_address_final.last_name}",
contents_type: 'merchandise',
contents_explanation: '',
restriction_type: 'none',
restriction_comments: '',
# non_delivery_option: 'abandon',
customs_items: [
vendor_line_items.map do |li|
{
description: "#{li.shop_product.product.item.title}",
quantity: li.quantity,
weight: li.shop_product.product.weight,
value: li.shop_product.price,
# hs_tariff_number: '654321',
origin_country: 'US'
}
end
]
)
Error Statement: Parameters to create Custom Item(s) invalid or missing
How can I create the loop to work with the JSON request and work like the first example that works manually?
If you remove the [] that is surrounding the vendor_line_itmes.map code, you will be good to go.
customs_info = EasyPost::CustomsInfo.create(
# ...
customs_items: vendor_line_items.map do |li|
{
description: "#{li.shop_product.product.item.title}",
quantity: li.quantity,
# ...
}
end
)
The map operation returns an array so the json you are currently generating would look like (note the array of arrays in customs_info):
{
"eel_pfc": "NOEEI 30.37(a)",
...
"customs_info": [
[
{
"description": "...",
"quantity": 5,
...
}
]
]
}
I recently came across a condition were i wanted to send two objects in the RABL as a response.
[
{
id: "1",
name: "XYZ"
},
{
id: "1",
name: "XYZ"
},
{
total: "2"
}
]
All I could manage was this , which is not correct.
[
{
id: "1",
name: "XYZ",
total: "2"
},
{
id: "1",
name: "XYZ",
total: "2"
}
]
I Found a solution which was to use a partial to iterate on the object and just add a new
node(:name) {partial("users/names", :object => #users)}
node(:total){ #total}
This is a hack, which i don't want because it wraps all the names in a node .
Is there any other way to do it ?
In your rabl file try this:
child #users, object_root: false do
attributes :id, :name
end
node(:total) { #users.size }
I came across this code in Rails app using mongodb:
"""
Folder format:
{
name: <folder name>,
stocks: [
{
name: <stock name>,
id: <stock id>,
qty: <stock quantity>
}
]
}
"""
def format_with_folders(stocks)
fmap = stock_folder_map
res = stocks.group_by {|s| fmap[s["id"]] }.collect {|fname, ss|
{
"name" => fname,
"stocks" => ss
}
}
new(folders: res)
end
def stock_folder_map
res = {}
folders.each { |ff|
ff.stocks.each { |s|
res[s["id"]] = ff["name"]
}
}
return res
end
end
The doubts are:
1) What does the code inside triple quote signify? Is is a commented code?
2)What would be the right format to use this code inside a ruby script?
First of all, the triple quoted string is often used as a comment, and that is the case here.
To get this to work outside of the class, you would need create a folders method that returns an array of folders in the correct structure. You could do something like this:
Folder = Struct.new(:name, :stocks)
def folders
[
Folder.new(
"Folder 1",
[
{ "name" => "stock name", "id" => "stock id", "qty" => 3 },
{ "name" => "stock name", "id" => "stock id", "qty" => 5 }
]
),
Folder.new(
"Folder 2",
[
{ "name" => "stock name", "id" => "stock id", "qty" => 2 },
{ "name" => "stock name", "id" => "stock id", "qty" => 1 }
]
)
]
end
def format_with_folders(stocks)
# ...
end
def stock_folder_map
# ...
end
The folders method returns an array of Folder objects, which both have a name and stocks attribute. Stocks are an array of hashes.
In Ruby, if you have multiple string literals next to each other, they get concatenated at parse time:
'foo' "bar"
# => 'foobar'
This is a feature inspired by C.
So, what you have there is three string literals next to each other. The first string literal is the empty string:
""
Then comes another string literal:
"
Folder format:
{
name: <folder name>,
stocks: [
{
name: <stock name>,
id: <stock id>,
qty: <stock quantity>
}
]
}
"
And lastly, there is a third string literal which is again empty:
""
At parse time, this will be concatenated into a single string literal:
"
Folder format:
{
name: <folder name>,
stocks: [
{
name: <stock name>,
id: <stock id>,
qty: <stock quantity>
}
]
}
"
And since this string object isn't referenced by anything, isn't assigned to any variable, isn't returned from any method or block, it will just get immediately garbage collected.
In other words: the entire thing is a no-op, it's dead code. A sufficiently smart Ruby compiler (such as JRuby or Rubinius) will probably completely eliminate it, compile it into nothing.