I have two objects, an #article and a #profile. Article is a model and #profile is a Struct. I'd like to end up with some JSON that looks like this:
{
"article": {
"title": "this is a title",
"author": "Author McAuthor",
"profile": {
"first_name": "Bobby",
"last_name": "Fisher"
}
}
}
As of now, I can just manually create this by doing something like:
#json = { article: { title: #article.title, author: #article.author, profile: { first_name: #profile.first_name, last_name: #profile.last_name } }}
I feel like building the json object this way is sorta crude, also, every time I change the author model, I might have to change this code. It would be great if I could find an easier way to build these json objects without having to do so manually... Any help? Thanks!
In addition to shioyama's correct answer, you can use rabl to craft your JSON objects, similar to how ERB works for views.
For example, you would create a 'view', say, index.rabl. It would look like:
collection #articles
attributes :author, :title
child(:profile) { attributes :first_name, :last_name }
Rails serializes objects in two steps, first by calling as_json to create the object to be serialized, then by calling to_json to actually create the JSON string.
Generally, if you want to customize how your models are represented in JSON, it's best to override as_json. Assuming your profile struct is a virtual attribute (i.e. defined with attr_accessor, not saved in the db), you could do this in your Article model:
def as_json(options = {})
super((options || {}).merge({
:methods => :profile
}))
end
Hope that helps. See also:
as_json documentation
Add virtual attribute to json output
Related
I upgraded activemodel serializer and now the output is different than what it once was. I'm trying to get the JSON output to match what it previously was. Specifically, I'd like an objects nested attributes to be on the same level as the main object.
For example, let's say my serializer is as follows:
class DishesSerializer < ActiveModel::Serializer
has_many :ingredients
end
This would be the current output:
{
"dish": {
"dish_stuff_1": "dish_stuff_1",
"dish_stuff_2": "dish_stuff_2",
"dish_stuff_3": "dish_stuff_3",
"ingredients": {
"ingredients_stuff_1": "ingredients_stuff_1"
}
}
}
And what I'd like is something like this:
{
"dish": {
"dish_stuff_1": "dish_stuff_1",
"dish_stuff_2": "dish_stuff_2",
"dish_stuff_3": "dish_stuff_3"
}
"ingredients": {
"ingredients_stuff_1": "ingredients_stuff_1"
}
}
I am currently doing this in the controller using multiple serializers, but it takes some additional querying and feels wrong. I feel like there should be some hacky way to do it in AMS.
I tried something like this:
def attributes
hash = super
hash.merge!(:dishes => dishes)
end
but that ends up in the same layer.
Any help would be greatly appreciated.
In this case, can't you just create a new serializer that has all the data you need - say a menu serializer:
class MenuSerializer < ActiveModel::Serializer
has_one :dish
has_many :ingredients
end
Not sure having a serializer that returns 2 root elements is a good idea, as that would make it hard to reuse in the future.
Consider the follow:
#posts = Post.all
render json: { success: true, data: #posts }
Now, each post will have parameters that are not required to be sent in this particular scenario (although it'll be in others), so I'd like to restrict the parameters sent and I thought maybe I could use map like so:
#posts = Post.all.map { |user| [user.email, user.first_name, user.last_name] }
render json: { success: true, data: #posts }
As you can imagine that doesn't work. Its very likely I'm using - or intending to use - map in a completely incorrect way and I'd appreciate your comments on how to achieve the above.
Thanks in advance!
You can make use of Hash#slice method to do something like:
#posts = Post.all.map { |user| user.as_json.slice("email", "first_name", "last_name") }
The #posts will be an array of Hash in this case, with each hash containing three key-value pairs.
It may be more appropriate to use a serializer.
You'll have to install the active_model_serializers gem first.
An example serializer for your Post controller would be:
class PostSerializer < ActiveModel::Serializer
attributes :email,
:first_name,
:last_name,
end
You'll use the serializer in this manner (I'm assuming you'd call it in the index method):
def index
posts = Mentor.all
render(
json: ActiveModel::ArraySerializer.new(
posts,
each_serializer: PostSerializer,
root: 'posts'
)
)
end
I have the following embedded structure
class CbAuthor
include Mongoid::Document
embeds_many: cb_bylines
field :name, type: String
end
class CbByline
include Mongoid::Document
embedded_in :cb_author
has_many :cb_articles
field :byline, type: String
end
class CbArticle
include Mongoid::Document
belongs_to :cb_byline
end
This is because there are many bylines or pseudonyms the authors publish under and that is will be attached to their analytics reports. So when I have a byline, how do I find the author? This will be necessary because They will have dashboards that should list all the articles they wrote under all their respective bylines.
I tried CbAuthor.cb_bylines but that gives me a no method error. or CbAuthor.where(cb_bylines["byline"]: bylineInQuestion) but that also gives errors.
Essentially the goal is to have one author name to find all his bylines and the articles associated with those bylines
embeds_many :cb_bylines is just a fancy way of saying "add an array of hashes called cb_bylines" (at least as far as storage is concerned). That means that your CbAuthors look like this inside MongoDB:
{
_id: '...',
name: '...',
cb_bylines: [
{ _id: '...', byline: '...' },
...
]
}
MongoDB will unroll the array for simple queries for you so you can simply look for 'cb_bylines.byline' as though you were querying a hash inside the collection:
authors_by_lined_as_pancakes = CbAuthor.where('cb_bylines.byline' => 'Pancakes McGee')
or if you know there is just one:
pancakes_mcgee = CbAuthor.find_by('cb_bylines.byline' => 'Pancakes McGee')
Don't be afraid to bypass Rails and Mongoid to look at what your data really looks like inside MongoDB.
I'm fairly familiar with Mongoid and Ruby and have used Mongoid for several large applications in a production system. However, I find myself a bit stumped at this issue.
The title is probably a bit head scratching so let me give an example JSON:
{
"could_be_anything": {
"key": "something",
"value": "something else"
},
"some_other_runtime_value": {
"key": "another",
"value": "another something"
},
// ... ect
}
So the key for the object could be anything, however the data inside is structured and needs validations like presence of key and value.
The only way I can think to do it is to take a block of code which represents the class definition of the embedded object, and dynamically create a new class and class_eval it with the block of code. I guess I could use after_initialize and after_find to look at all attributes and hook things together that way.
Is there no simpler way?
Suppose I have an Article with n Comments. How would I go about grabbing all the comments with the article in one query with DataMapper?
Something like the following false code:
Article.get(:id).include(:comments).to_json
I want the associated comments to be returned in the json like so:
{
article object
comments: [
{ comment object },
{ comment object }
]
}
Seems like there must be a better way than grabbing the comments, and manually adding them to an attributes hash before calling to_json.
Found it on https://github.com/datamapper/dm-serializer in lib/to_json.rb
There are two options it seems, relationships and methods as options to the to_json method. Default inclusions are not yet possible, but requested:
#article.to_json(methods: [ :comments ])
To go deeper, there is an undocumented (so subject to change) example in a comment in the code is:
comments.to_json(:relationships=>{:user=>{:include=>[:first_name],:methods=>[:age]}})
So something like:
#article.to_json(relationships: { comments: { methods: [ :likes ] } }