I am building an Rails 5 app and in this app I got a User model. Each user can have exactly one manager (using the attribute manager_id).
I want to print a JSON-structure (using Rabl) that shows how the User models are related. Meaning I want to print out how is manager to each user.
User 1 (manager_id is null)
|
User 2 (manager_id is 1)
User 3 (manager_id is 1)
|
User 4 (manager_id is 3)
This is what I want the UI to look like (this is already working I just need the JSON-structure to support it).
These is how the finished structure must look like.
datasource =
'name': 'Peter Fettingview'
'title': 'CEO'
'children': [
{
'name': 'Mike Palmer'
'title': 'CIO'
}
{
'name': 'Maria Persson'
'title': 'CTO'
'children': [
{
'name': 'James Hatton'
'title': 'Customer success'
}
{
'name': 'Lars Andersson'
'title': 'Customer success'
}
]
}
{
'name': 'Jan Roslund'
'title': 'Economy'
}
{
'name': 'Annika Holm'
'title': 'Sales'
}
]
This is what I got right now
attributes :id, :fullname
node :children do |n|
n.children.map { |c| partial("admin/users/index", :object => c) }
end
This is the output
[{
"id": 1,
"fullname": "Peter Fettingview",
"children": [{
"id": 2,
"fullname": "Richard Pooler"
},
{
"id": 1,
"fullname": "Mike Palmer"
}
]
},
{
"id": 2,
"fullname": "Richard Pooler",
"children": [{
"id": 3,
"fullname": "Mike Palmer"
}]
},
{
"id": 3,
"fullname": "Mike Palmer",
"children": []
}
]
How can I print out such a JSON-tree using the User models?
I have had to use tree data structures (i.e. self-referential models) before. I would consider using the Ancestry gem. It allows you to do things like enumerate descendants and paths bath to the root, making traversing your data structure significantly easier.
Related
I have a Hash like this, which should be "merged" to its uniq nested values
[
{
"slug": "color",
"values": [{ "slug": "amethyst" },
{ "slug": "coral" }],
},
{
"slug": "color",
"values": [{ "slug": "amethyst" }],
},
{
"slug": "power-source",
"values": [{ "slug": "110V"}],
}
]
at the same time it should count the duplicate values but made uniq in an items array:
{ "slug": "color",
"items": [
{
"slug": "amethyst",
"count": 2
},
{
"slug": "coral",
"count": 1
}]
},
{
"slug": "power-source",
"items": [
{
"slug": "110V",
"count": 1
}]
}
]
is there a "Rails method" to achieve this?
Thank you
I think there's nothing built-in in Rails that allows you to get such a custom requirement, but you can achieve it by playing around with different methods and their return values:
data
.group_by { |hash| hash[:slug] }
.transform_values do |values|
values
.flat_map { |vals| vals[:values] }
.group_by { |value| value[:slug] }
.transform_values(&:count)
end.map do |slug, items|
[slug, items.map { |item, count| {slug: item, count: count} }]
end.map { |slug, items| {slug: slug, items: items} }
# [{:slug=>"color",
# :items=>[{:slug=>"amethyst", :count=>2}, {:slug=>"coral", :count=>1}]},
# {:slug=>"power-source", :items=>[{:slug=>"110V", :count=>1}]}]
As you see, you can first group every hash in the array by their slug value, then transform the values that hash contains, mapping and flattening every array by their values key and then grouping to get their total.
After that you can just create the hash with its keys/values you need.
It might simplify the things a bit if you end up with a single hash, whose keys are the "slugs" and contains the items as its values.
I have a simple "rss" (ApplicationRecord) table indexed by an id. I would like to have a structured JSON that group each user from a family in an array structure. And then each family in a global array. How can I do that ?
my current plain code to put my data in a json file is :
json.rss #rss do |rs|
json.id rs.id
json.name rs.name
json.family rs.family
json.lastdate rs.lastdate
json.last rs.last
json.s1w rs.s1w
json.s2w rs.s2w
end
But the target file that I want is this one :
{
"rss": [
{
"familyname": "Smith",
"children": [
{
"id": "1",
"name": "bob",
"lastdate": "2010-09-23",
"last": "0.88",
"s1w": "0.83",
"s2w": "0.88"
},
{
"id": 2,
"name": "Mary",
"lastdate": "2011-09-23",
"last": "0.89",
"s1w": "0.83",
"s2w": "0.87"
}
]
},
{
"familyname": "Wesson",
"children": [
{
"id": "1",
"name": "john",
"lastdate": "2001-09-23",
"last": "0.88",
"s1w": "0.83",
"s2w": "0.88"
},
{
"id": 2,
"name": "Bruce",
"lastdate": "2000-09-23",
"last": "0.89",
"s1w": "0.83",
"s2w": "0.87"
}
]
}
]
}
The grouping you are trying to achieve can be done in Ruby with:
#rss.group_by(&:family).values
This is assuming #rss is an array-like collection of objects that have a .family method. The result: is an array of arrays of objects grouped by family.
Now it will be up to use to use Jbuilder's array! method to build the desired JSON output.
I'm trying to build a JSON API style API using AM::Serializer. I'm running into an issue with sideloading.
I want to be able to build JSON that looks like:
{
"primaries": [{
"id": 123,
"data": "Hello world.",
"links": {
"secondaries": [ 1, 2, 3 ]
}
}],
"linked" : {
"secondaries": [
{
"id": 1,
"data": "test1"
},
{
"id": 2,
"data": "test2"
},
{
"id": 3,
"data": "test3"
}
]
}
}
The code I've been able to come up with looks like:
class PrimarySerializer < ActiveModel::Serializer
attributes :id, :data
has_many :secondaries, key: :secondaries, root: :secondaries
embed :ids, include: true
end
Which generates JSON that looks like:
{
"primaries": [{
"id": 123,
"data": "Hello world.",
"secondaries": [ 1, 2, 3 ]
}],
"secondaries": [
{
"id": 1,
"data": "test1"
},
{
"id": 2,
"data": "test2"
},
{
"id": 3,
"data": "test3"
}
]
}
Is there a way to override the location of the in-element secondaries and sideloaded secondaries such that they live in child nodes link and linked?
The above code is an abstraction of the actual code and may not work. Hopefully it illustrates the point sufficiently.
Thanks!
ActiveModel Serializers can do this. The problem is that the built-in association methods are to restrictive. Instead you must build up the links & linked parts manually.
(This answer refers to the stable 0.8.1 version of ActiveModel Serializers)
Here's a Gist with a complete JSON-API solution https://gist.github.com/mars/97a637560109b8ddfb27
Example:
class ExampleSerializer < JsonApiSerializer # see Gist for superclass
attributes :id, :name, :links
def links
{
things: object.things.map(&:id),
whatzits: object.whatzits.map(&:id)
}
end
def as_json(*args)
hash = super(*args)
hash[:linked] = {
things: ActiveModel::ArraySerializer.new(
object.things,
each_serializer: ThingsSerializer
).as_json,
whatzits: ActiveModel::ArraySerializer.new(
object.whatzits,
each_serializer: WhatzitsSerializer
).as_json
}
hash
end
end
I've been really struggling getting a simple scenario running with EmberJS and Rails.
Here's what I have (combined JS):
App = Ember.Application.create
LOG_TRANSITIONS: true
App.Post = DS.Model.extend
title: DS.attr 'string'
description: DS.attr 'string'
App.StreamRoute = Ember.Route.extend
setupController: (controller, model) ->
controller.set 'posts', model
model: -> #store.find('post')
App.Router.map ->
#.route 'stream', path: '/'
Here's the template content:
{{#each posts}}
{{title}}
{{/each}}
Here's the /posts JSON for Post.all (perhaps this is wrong?):
{
"posts": [
{
"posts": {
"created_at": "2013-08-15T23:48:54+01:00",
"description": "A few months ago I helped develop these posters for research that our UX team had gathered to create personas for our customers to show who they are and who actually uses our product.",
"id": 7,
"likes_count": 1,
"slug": "16ErQ",
"thumb": {
"url": "\/posts\/1\/16ErQ\/man.png",
"medium": {
"url": "\/posts\/1\/16ErQ\/medium_man.png"
}
},
"title": "Persona Project",
"updated_at": "2013-08-15T23:48:54+01:00",
"user_id": 1,
"views_count": 0
}
},
{
"posts": {
"created_at": "2013-08-16T15:47:03+01:00",
"description": "Just a little something.",
"id": 8,
"likes_count": 0,
"slug": "VYIvn",
"thumb": {
"url": "\/posts\/2\/VYIvn\/face.jpg",
"medium": {
"url": "\/posts\/2\/VYIvn\/medium_face.jpg"
}
},
"title": "Face",
"updated_at": "2013-08-16T15:47:03+01:00",
"user_id": 2,
"views_count": 0
}
},
{
"posts": {
"created_at": "2013-08-16T17:03:10+01:00",
"description": "Some people say, he's still running.",
"id": 9,
"likes_count": 2,
"slug": "hQBnt",
"thumb": {
"url": "\/posts\/1\/hQBnt\/run.jpg",
"medium": {
"url": "\/posts\/1\/hQBnt\/medium_run.jpg"
}
},
"title": "Run, Forest, run.",
"updated_at": "2013-08-23T23:44:19+01:00",
"user_id": 1,
"views_count": 0
}
}
]
}
I thought this would be fine, but it doesn't quite work, when I run it, I get 3 post results (which is how many there are) but the columns all contain null values: http://c.daryl.im/RTzO
As you can see, I also have that error. Any ideas?
Your JSON looks wrong. It should be something like this:
{
"posts":[
{
"created_at":"2013-08-15T23:48:54+01:00",
"description":"A few months ago I helped develop these posters for research that our UX team had gathered to create personas for our customers to show who they are and who actually uses our product.",
"id":7,
"likes_count":1,
"slug":"16ErQ",
"thumb":{
"url":"/posts/1/16ErQ/man.png",
"medium":{
"url":"/posts/1/16ErQ/medium_man.png"
}
},
"title":"Persona Project",
"updated_at":"2013-08-15T23:48:54+01:00",
"user_id":1,
"views_count":0
},
...
]}
Basically you have nested posts and need to remove one layer.
i am working with Ext JS & Rails as backend..i am having myysql database that has parent-child relationship i.e. 1-to-many relationship...I want a "single" JSON store that can be used to load "GRID" data with parent & child records..
Also is there any way to bind both - the form as well as the grid to the same store but having different jsonreaders? i.e. form's datareader would read the string with root: 'customers' and grid will read the string with root :'products'?
The JSON looks like this :
{
"customers": [{
"amt": 8000,
"custnm": "rashmi",
"id": 2,
"purdt": "2011-04-27",
"products": [{
"amt": 40,
"customer_id": 2,
"id": 3,
"prodnm": "oil",
"qty": 1,
"rate": 40
}]
}, {
"amt": 300000,
"custnm": "bhumika",
"id": 3,
"purdt": "2011-04-14",
"products": [{
"amt": 40,
"customer_id": 3,
"id": 1,
"prodnm": "soap",
"qty": 20000,
"rate": 20
}, {
"amt": 150,
"customer_id": 3,
"id": 2,
"prodnm": "shampoo",
"qty": 3000,
"rate": 50
}, {
"amt": null,
"customer_id": 3,
"id": 14,
"prodnm": "aa",
"qty": 2,
"rate": null
}]
}, {
"amt": 15000,
"custnm": "Shruti",
"id": 13,
"purdt": "2011-04-08",
"products": []
}, {
"amt": 200000,
"custnm": "Jayesh",
"id": 14,
"purdt": "2011-03-31",
"products": []
}, {
"amt": 220000,
"custnm": "SHRUTI",
"id": 15,
"purdt": "2011-04-06",
"products": []
}, {
"amt": 10000,
"custnm": "RASHMI",
"id": 24,
"purdt": "2011-04-06",
"products": []
}],
"results": 6
}
The new Ext JS 4 has Model associations which will solve the problem (http://docs.sencha.com/ext-js/4-0/guide/data)
// each User hasMany Orders
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'email'],
proxy : {
type: 'rest',
url : '/users',
reader: 'json'
},
hasMany: 'Orders'
});
// each Order belongsTo a User, and hasMany OrderItems
Ext.define('Order', {
extend: 'Ext.data.Model',
fields: ['id', 'user_id', 'status'],
belongsTo: 'User',
hasMany: 'OrderItems'
});
// each OrderItem belongsTo an Order
Ext.define('OrderItem', {
extend: 'Ext.data.Model',
fields: ['id', 'order_id', 'name', 'description', 'price', 'quantity'],
belongsTo: 'Order'
});
Calling user.orders() returns a Store configured with the Orders model, because the User model defined an association of hasMany: 'Orders'.
User.load(123, {
success: function(user) {
//we can iterate over the orders easily using the Associations API
user.orders().each(function(order) {
console.log(order.get('status'));
//we can even iterate over each Order's OrderItems:
order.orderItems().each(function(orderItem) {
console.log(orderItem.get('title'));
});
});
}
});
You can use override the default rendering method. Define a renderer method for the column.
renderer: function(value, metaData, record, rowIndex, colIndex, store) {
// you can use record.get method to access the data you require
return the_retrived_data;
}
You need to use mapping property to map the fields of your json store to the nested json. For example, if you need to access product name you can have:
{name: 'productname', mapping: 'products.prodnm'}
You can also use the array notation instead of the dot operator. ex: "products['prodnm']".