Custom json on rails 5 api only, using to_json - ruby-on-rails

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.

Related

Rails scope conditions

Here are my scopes in the News model:
scope :category, -> (category_names) { joins(:category).where('categories.name IN (?)', category_names)}
scope :tag, -> (tag_name) { joins(:tags).where('tags.name = ?', tag_name)}
Here's the GET request:
localhost:3000/news?category[]=sit&tag_list=ipsum
[
{
"id": 8,
"tag_list": "ipsum",
"category": "sit"
},
{
"id": 9,
"tag_list": "",
"category": "sit"
}
]
My index action:
news = News.filter(params.slice(:tag_list, :category, :days_ago))
I want to get News which satisfy BOTH of the conditions - only those news should be displayed that have category="sit" AND tag_list="ipsum".
What's the best way to achieve this?
You should simply chain the scopes together. In your news controller:
News.category(params['categori']).tag(params['tag_list'])
You can simply link the scopes as so:
News.category(params[:category]).tag(params[:tag_list])

Ember, Ember-data and Rails relations error: "Cannot read property 'typeKey' of undefined"

I connect Ember to a Rails API which delivers some JSON.
I'm trying to get relations working (product hasMany images) but it keeps giving me this error:
Cannot read property 'typeKey' of undefined
My Models:
App.Product = DS.Model.extend({
images: DS.hasMany('image'),
title: DS.attr('string'),
});
App.Image = DS.Model.extend({
product: DS.belongsTo('product')
});
Rails renders json as:
{
"products":[
{
"id": 1,
"title": "product title",
"images":[
{
"id": 1,
"urls":
{
"thumb":"http://domain.com/thumb/image.jpg",
"original":"http://domain.com/original/image.jpg"
}
}
]
}
]
}
Turns out I needed to "sideload" my images in Rails, so the JSON became:
{
"products":[
{
"id": 1,
"title": "product title",
"image_ids": [1]
}
],
"images":[
{
"id": 1,
"urls":
{
"thumb":"http://domain.com/thumb/image.jpg",
"original":"http://domain.com/original/image.jpg"
}
}
]
}
Rails' ProductSerializer:
class ProductSerializer < ActiveModel::Serializer
embed :ids, :include => true
attributes :id, :title
has_many :images
methods :image_urls
end
It seems that you were using embedded JSON in your example. You need to use the EmbeddedRecordsMixin https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/embedded_records_mixin.js and set the appropriate flag to mark images as being embededd

ember-rails not loading hasMany Association

Gems Used:
Using ember-source (1.5.1.1)
Using ember-data-source (1.0.0.beta.7)
Using ember-rails (0.15.0)
Using handlebars-source (1.3.0)
Using active_model_serializers (0.8.1)
This is the ember app code:
window.App = Ember.Application.create
LOG_TRANSITIONS: true
rootElement: '#app-ember-root'
App.store = DS.Store.extend({});
App.ApplicationAdapter = DS.ActiveModelAdapter.extend({});
App.Router.map ()->
#resource 'quotes',
path: '/'
App.QuotesRoute = Ember.Route.extend
model: ()->
this.store.find 'quote'
App.CompaniesQuote = DS.Model.extend
quote: DS.belongsTo 'quote'
App.Quote = DS.Model.extend
message: DS.attr 'string'
createdAt: DS.attr 'date'
companiesQuotes: DS.hasMany('companiesQuote')
companiesQuotesCount: (->
this.get('companiesQuotes.length')
).property('companiesQuotes')
Serializers:
class QuoteSerializer < ActiveModel::Serializer
attributes :id, :message, :created_at
has_many :companies_quotes
end
class CompaniesQuoteSerializer < ActiveModel::Serializer
attributes :id, :company_id
end
Template quotes.handlebars:
<div class="quotes-list">
<div class="quote-container">
{{#each}}
{{ companiesQuotesCount }} companies quote for this quote {{ id }}
{{/each }}
</div>
</div>
/quotes JSON response:
{
"quotes":[
{
"id":10,
"message":"Quote 10!",
"created_at":"2014-06-04T17:00:01.000Z",
"companies_quotes":[
{
"id":27,
"company_id":1
},
{
"id":28,
"company_id":2
},
{
"id":26,
"company_id":3
}
]
},
{
"id":11,
"message":"Quote 11!",
"created_at":"2014-06-11T14:45:02.000Z",
"companies_quotes":[
{
"id":30,
"company_id":1
},
{
"id":31,
"company_id":2
},
{
"id":29,
"company_id":3
}
]
}
]
}
With this env/code, the property companiesQuotesCount is always 0.
What am I missing?
Solved
Using the #kingpin2k's response, I changed the JSON structure modifying QuoteSerializer:
class QuoteSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :message, :created_at
has_many :companies_quotes
end
Ember Data doesn't do embedded records by default.
You'll either need to fix the json using a client side serializer, or fix it server side.
It should be in this format:
{
"quotes":[
{
"id":10,
"message":"Quote 10!",
"created_at":"2014-06-04T17:00:01.000Z",
"companies_quotes":[27, 28, 29]
},
{
"id":11,
"message":"Quote 11!",
"created_at":"2014-06-11T14:45:02.000Z",
"companies_quotes":[30, 31, 32]
]
}
],
companies_quotes:[
{
"id":27,
"company_id":1
},
{
"id":28,
"company_id":2
},
{
"id":26,
"company_id":3
},
....
]
}
Here's an example for using extractArray http://emberjs.com/api/data/classes/DS.RESTSerializer.html#method_extractArray
Additionally your computed property is just watching the reference, which won't change as the length changes.
companiesQuotesCount: (->
this.get('companiesQuotes.length')
).property('companiesQuotes.length')

Rails to_json - exact order of existing columns

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.

MongoDB Mongoid delete value from array

I have mongodb document like this and want to remove one element form unpublished array
which is in resource_well
{
"_id": ObjectId("4fa77c808d0a6c287d00000a"),
"production_status": "Unscheduled",
"projected_air_date": null,
"published": false,
"resource_well": {
"published": [
],
"unpublished": {
"0": ObjectId("4fa795038d0a6c327e00000e"),
"1": ObjectId("4fa795318d0a6c327e00000f"),
"2": ObjectId("4fa795508d0a6c327e000011"),
"3": ObjectId("4fa796f48d0a6c327e000013")
}
},
"segment_status": "Draft",
}
Code in controller
segment = Segment.find(params[:segment_id])
# segment.resource_well[params['resource_well_type']].class => Array
# segment.resource_well[params['resource_well_type']].inspect => [BSON::ObjectId('4fa795038d0a6c327e00000e'), BSON::ObjectId('4fa795318d0a6c327e00000f'), BSON::ObjectId('4fa795508d0a6c327e000011'), BSON::ObjectId('4fa796f48d0a6c327e000013')]
segment.resource_well[params['resource_well_type']].delete(params[:asset_ids])
segment.save
Not able remove an element form array
Your ODM (or ORM) provides associations for you so that you can take advantage of having the ODM manage the links for you. So you should use the associations, otherwise you risk hanging yourself. Just make sure to specify the relationship correctly, and use the generated method named by the association and its methods, e.g. resource_well.unpublished <<, resource_well.unpublished.delete. The following models and tests work for me. The inconvenience is that the delete method on the association takes an object, e.g., other.delete(object) and not a string or conditions, so if you start with a string, you have to supply an object in order to delete it. Note that other.delete_all(conditions) or other.where(conditions).delete_all remove both actual documents as well as the association, which is not what you were looking for. Anyway, hope that this helps.
Models
class Segment
include Mongoid::Document
field :production_status, type: String
field :projected_air_date, type: Date
field :published, type: Boolean
field :segment_status, type: String
embeds_one :resource_well
end
class ResourceWell
include Mongoid::Document
embedded_in :segment
has_and_belongs_to_many :published, :class_name => 'Resource'
has_and_belongs_to_many :unpublished, :class_name => 'Resource'
end
class Resource
include Mongoid::Document
field :name, type: String
end
Test
require 'test_helper'
class Object
def to_pretty_json
JSON.pretty_generate(JSON.parse(self.to_json))
end
end
class SegmentTest < ActiveSupport::TestCase
def setup
Segment.delete_all
end
test 'resource_well unpublished delete' do
res = (0..3).collect{|i| Resource.create(name: "resource #{i}")}
seg = Segment.create(
production_status: 'Unscheduled',
projected_air_date: nil,
published: false,
resource_well: ResourceWell.new(unpublished: res[0..2]),
segment_status: 'Draft')
seg.resource_well.unpublished << res[3] #just an append example
puts seg.to_pretty_json
id = res[0]['_id'].to_s
puts "id: #{id}"
resource_obj = Resource.find(id)
puts "resource: #{resource_obj.inspect}"
Rails.logger.debug("delete: #{resource_obj.inspect}")
seg.resource_well.unpublished.delete(resource_obj)
puts Segment.find(:all).to_pretty_json
end
end
Result
# Running tests:
{
"_id": "4fa839197f11ba80a9000006",
"production_status": "Unscheduled",
"projected_air_date": null,
"published": false,
"resource_well": {
"_id": "4fa839197f11ba80a9000005",
"published_ids": [
],
"unpublished_ids": [
"4fa839197f11ba80a9000001",
"4fa839197f11ba80a9000002",
"4fa839197f11ba80a9000003",
"4fa839197f11ba80a9000004"
]
},
"segment_status": "Draft"
}
id: 4fa839197f11ba80a9000001
resource: #<Resource _id: 4fa839197f11ba80a9000001, _type: nil, name: "resource 0">
[
{
"_id": "4fa839197f11ba80a9000006",
"production_status": "Unscheduled",
"projected_air_date": null,
"published": false,
"resource_well": {
"_id": "4fa839197f11ba80a9000005",
"published_ids": [
],
"unpublished_ids": [
"4fa839197f11ba80a9000002",
"4fa839197f11ba80a9000003",
"4fa839197f11ba80a9000004"
]
},
"segment_status": "Draft"
}
]
.
Finished tests in 0.016026s, 62.3986 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips
db.collection.update(
{_id : ObjectId("4fa77c808d0a6c287d00000a")},
{ $unset : { "resource_well.unpublished.1" : 1 }}
);
This will remove element [1] of the array.
Also you can read how to implement it using mongoid: http://mongoid.org/docs/upgrading.html

Resources