I'm returning a resource in API (I'm, using Grape) but I would like to return also the relationship objects (as included objects just as an ember application expects). How can I achieve that ? My serializer is as follow:
class JudgeSerializer < ActiveModel::Serializer
attributes :ID, :FIRST_NAME, :LAST_NAME, :FULL_NAME
end
My model:
class Judge < ApplicationRecord
self.primary_key = 'id'
self.table_name = 'judge'
has_many :judgecourts, class_name: 'Judgecourt', primary_key: 'ID',foreign_key: 'JUDGE_ID'
end
I'm returning this resource this way:
desc 'Return a specific judge'
route_param :id do
get do
judge = Judge.find(params[:id])
present judge
end
end
It would be good to generate something like this:
data
:
{type: "judges", id: "1", attributes: {…}, relationships: {…}}
included
:
Array(1)
0
:
{type: "judgecourts", id: "1", attributes: {…}}
Well it looks like there are two topics in your question:
1. How to include a relationship in a ActiveModelSerializer
This can be done pretty easily by adjusting your active model serializer and adding the relation, so that ActiveModelSerializer knows that it must include the relation in the serialized objects:
class JudgeSerializer < ActiveModel::Serializer
attributes :ID, :FIRST_NAME, :LAST_NAME, :FULL_NAME
has_many :judgecourts
end
This would automatically provide the judgecourts relation inside the serialized json. An example with the classic Post/Comment objects:
2. Use specific format...
The format you specified looks a lot like JSON:API format. If that's what you're really wanting to achieve, then you might be better off using the JSON:API adapter built-in ActiveModelSerializer. For this, you need to tell AMS to use the proper adapter, maybe through an initializer file :
# ./initializers/active_model_serializer.rb
ActiveModelSerializers.config.adapter = :json_api
After this, your json should be formatted the way you seem to expect. I'm no expert in the json api specification, so there might be some more things to tweak. You'll be able to find more information about this adapter in ActiveModelSerializers wiki page on adapters, section JSON API.
Here's what I get with my Post/Comment example:
Note that there are other gems out there that were built to specifically address the JSON API specification, such as jsonapi-rb and Netflix Fast JSON API. They might be of interest for you.
Related
ruby 2.50
rails 5.1.4
gem 'mongoid'
gem 'mongoid_paranoia'
gem 'mongoid-tree'
gem 'mongoid-autoinc'
gem 'mongoid-userstamps'
I am having trouble using an embedded resource.
I have an Account model.
class Account
store_in collection: 'accounts'
has_many :email_routes, dependent: :destroy
embeds_many :labels, as: :labelable
etc..
end
As you can see, each account has many labels.
class Label
embedded_in :labelable, polymorphic: true
belongs_to :email_routes
field :text, type: String
field :color, type: String
end
The available labels are specified at the account level...
and I have an EmailRoute, which I want to apply some labels to..
class EmailRoute
belongs_to :account
field :email_address, type: String
has_many :labels, as: :labelable, dependent: :nullify
# field :labels, type: Array, default: []
end
In the email_routes_controller, I have a simple update action..
def update
selected_labels = params[:labels]
#email_route.update_attributes!(labels: selected_labels)
render json: #email_route
end
params[:labels] contains an array of Label objects. When I patch to the controller, I get the following error/message.
Referencing a(n) Label document from the EmailRoute document via a relational association is not allowed since the Label is embedded.
summary:
In order to properly access a(n) Label from EmailRoute the reference would need to go through the root document of Label. In a simple case this would require Mongoid to store an extra foreign key for the root, in more complex cases where Label is multiple levels deep a key would need to be stored for each parent up the hierarchy.
resolution:
Consider not embedding Label, or do the key storage and access in a custom manner in the application code.):"
Is there any way I can work around this? I know there are many articles regarding embedded but none seemed to quite fit this particular case. Any help is greatly appreciated, as I don't want to lose label data and need them to be specified at the account level.
Thanks!
edit Is something like this going to be the only solution?
https://www.cookieshq.co.uk/posts/switching-relationships-with-mongoid
edit 2 ... I have managed to get to this point when calling #account.email_routes[0].labels from within the rails console. The strings in the nested array within selector are the labels I meant to add. However, when I render :json #account.email_route, the labels array is returned empty. How do I use the information within the selector?
#<Mongoid::Criteria
selector: {"_id"=>{"$in"=>["Critical Test updates", "Chill", "New", >"Make", "Cool", "1", "2", "3"]}}
options: {}
class: Label
embedded: true>
This getter in the EmailRoute model does the trick
def labels
account.labels.where(:_id.in => self[:labels])
end
So, I have this system Im working on using rails 4 and Grape Api. Basically it aggregates information about maintenance services executed on vehicles. My models are defined like this:
# service.rb
class Service < ActiveRecord::Base
has_many :service_items
#service_item.rb
class ServiceItem < ActiveRecord::Base
belongs_to :service
Im implementing a API so external applications could post services on my system. Each service has a list of 1 or more service items associated. I have a route like: example.com/api/v1/services for POST. My question is how can I make this to accept a post with the service attributes, and service_items attributes nested with it?
I read Grape docs and started something like this:
#service_providers_api.rb
resource :services do
desc "Post a Service"
params do
#requires :category_id, type: Integer
requires :description, type: String
requires :plate, type: String
requires :mileage, type: Integer
requires :date, type: Date
optional :cost, type: BigDecimal
requires :service_items do
requires :description, type: Integer
end
end
post do
.
.
.
end
end
But Im not sure how I can mount the post data for this to work.
Is it possible to make all this in a single request like so, or do I have to make each request separated? e.g. one POST to receive the service, then a series of POSTs for every service_item associated. What is the best approach recommended in this scenario?
params do
requires :service_items, type: Hash do
requires :description, type: Integer
end
end
requires takes two param, you must provide a type. in your case a Hash
I am using ActiveModel Serializers in a Rails project.
The default serializer for the object is fairly large, and nesting an object in API responses result in rather large JSON objects.
Sometimes, I want to embed an object, but only need a small subset of the object's attributes to be present in the JSON.
Obviously, I could do something like this:
render json: #user, serializer: SmallerUserSerializer
but that would lead to a lot of duplication.
Is there an option that I can pass to the serializer so that it will only include a subset of the serializers attributes? Eg:
class BlogSerializer
# This is pseudocode. Does not actually work.
has_one :user, only_show: [:user_id, :profile_url]
end
Create a method and call to_json on the user object. Then add that method name to your list of attributes. The method can be called user also.
class BlogSerializer
attributes :id, :user
def user
object.user.to_json( only: [ :id, :profile_url ] )
end
end
Use the active model serialzers gem.
Your pseudo code will become the following simple modularized code:
class BlogSerializer < ActiveModel::Serializer
attributes :user_id, :profile_url
end
Guide: http://railscasts.com/episodes/409-active-model-serializers
Create a method and call to_json on the user object. Then add that method name to your list of attributes. The method can be called user also.
class BlogSerializer
require 'json'
attributes :id, :user
def user
JSON.parse "#{object.user.to_json( only: [ :id, :profile_url ] )}"
end
end
I'm using ember-data with rails and MongoDB and am having problem with the way IDs are stored in MongoDB - in a _id field.
Ember-data will use id as the default field for ID so I tried to override it like this:
App.User = DS.Model.extend
primaryKey: "_id"
name: DS.attr "string"
image: DS.attr "string"
This seems to work most of the time but in some instances I get exceptions from ember saying:
Uncaught Error: assertion failed: Your server returned a hash with the
key _id but you have no mappings
I suspect this might be a bug in ember-data because it's still heavily under development, but I was trying to find a way to get to map _id to id on the server side in rails? I'm using mongoid to do the mongo mapping.
If you are using Mongoid here is a solution that makes it so you don't have to add a method def id; object._id.to_s; end to every serializer
Add the following Rails initializer
Mongoid 3.x
module Moped
module BSON
class ObjectId
alias :to_json :to_s
alias :as_json :to_s
end
end
end
Mongoid 4
module BSON
class ObjectId
alias :to_json :to_s
alias :as_json :to_s
end
end
Active Model Serializer for Building
class BuildingSerializer < ActiveModel::Serializer
attributes :id, :name
end
Resulting JSON
{
"buildings": [
{"id":"5338f70741727450f8000000","name":"City Hall"},
{"id":"5338f70741727450f8010000","name":"Firestation"}
]
}
This is a monkey patch suggested by brentkirby and updated for Mongoid 4 by arthurnn
An other way could be to use (if possible for you) the ActiveModel::Serializer. (I think it should be close to rabl (?))
From the ember-data gihtub: https://github.com/emberjs/data:
Out of the box support for Rails apps that follow the active_model_serializers gem's conventions
When we began with ember-data we were crafting as_json(), but using the gem is definitely better :)
Ahh, instead of including _id in your JSON, you could craft the JSON to instead use the id method rather than the _id attribute. Ways:
You could use rabl, and the JSON could be like:
object #user
attributes :id, :email
node(:full_name) {|user| "#{user.first_name} #{user.last_name}"}
You could also craft the as_json method
class User
def as_json(args={})
super args.merge(:only => [:email], :methods => [:id, :full_name])
end
end
I had a similar problem using ember.js with ember-resource and couchdb, which also stores it's IDs as _id.
As solution to this problem I defined a superclass for all my model classes containing a computed property to duplicate _id into id like this:
// get over the fact that couchdb uses _id, ember-resource uses id
id: function(key, value) {
// map _id (couchdb) to id (ember)
if (arguments.length === 1) {
return this.get('_id');
}
else {
this.set('_id', value);
return value;
}
}.property('_id').cacheable()
Maybe this could solve your problem too?
The best way is to use ActiveModel::Serializers. Since we are using Mongoid, you will need to add an include statement like that (see this gist from benedikt):
# config/initializers/active_model_serializers.rb
Mongoid::Document.send(:include, ActiveModel::SerializerSupport)
Mongoid::Criteria.delegate(:active_model_serializer, :to => :to_a)
And then include your serializer. Something like that:
# app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email
def id
object._id
end
end
This fixes the _id problem
The second part of joscas's answer fixed the id issue for me with Rails4/Ruby2 except I had to .to_s the _id.
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email
def id
object._id.to_s
end
end
If you use Mongoid3, here is the monkey patch may work for you.
https://gist.github.com/4700909
I don't know exactly when this was added, but you can just tell Ember-Data that the primaryKey is _id:
DS.RESTAdapter.extend({
serializer: DS.RESTSerializer.extend({
primaryKey: '_id'
})
});
Although the question is quite old but i still think my answer could help others:
If you are using ActiveModelSerializer then you just need to do this :
class UserSerializer < ActiveModel::Serializer
attributes :id , :name
end
It works all fine. I am using emberjs on the front end btw.
Is there a way to have a custom serialization for fields in rails, a method that runs when a field is saved and loaded to convert from/to a string which is what ultimately is saved on the database.
Specifically what I want to do is have a field of type symbol like gender, with possible values :male and :female storing "male" and "female" on the database. There are some workarounds, like:
def gender
read_attribute(:gender).try(:to_sym)
end
but that leaves obj.attributes unchanged, so it's a leaky abstraction.
You can do it in Rails 3.1. The object you want to serialize has to reply to load and dump methods.
Here is an example of serializing a string in Base64.
class User < ActiveRecord::Base
class Base64
def load(text)
return unless text
text.unpack('m').first
end
def dump(text)
[text].pack 'm'
end
end
serialize :bank_account_number, Base64.new
end
For more details see: http://archives.edgerails.info/articles/what-s-new-in-edge-rails/2011/03/09/custom-activerecord-attribute-serialization/index.html
def whachamacallit
read_attribute("whachamacallit").to_sym
end
def whachamacallit=(name)
write_attribute("whachamacallit", name.to_s)
end
store them as stings in the database, but extract them as symbols when you pull them out then convert back before you save.
would work with any number or combination of strings / symbols.
to limit it to only a select few
validates_inclusion_of :whachamacallit, :in => [ :male, :female, :unknown, :hidden ]
From http://blog.quov.is/2012/05/01/custom-activerecord-attribute-serializers/
class Recipe < ActiveRecord::Base
serialize :ingredients, IngredientsList
end
class IngredientsList < Array
def self.dump(ingredients)
ingredients ? ingredients.join("\n") : nil
end
def self.load(ingredients)
ingredients ? new(ingredients.split("\n")) : nil
end
end
you can define the models to_xml for a model and it will do that
http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html
its possible to define Marshall.dump and put in that way i think, but its something to look into
You could use serialize method inside the model. Please reference to this link:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
(ps. search keyword "serialize" in that page ;D)
In short, you could do this:
class YourModel < ActiveRecord::Base
serialize :db_field
end
Rails would automatically serialize the field before saving to database, and deserialize it after fetched from the database.
well for just male/female you could just do a Boolean column like male and if it was false assume that meant female, add wrapper methods for it
def female?
return !self.male?
end
We just released a gem (AttributeHelpers) that does exactly this. Disclaimer: I am a maintainer for the gem.
It allows you to call attr_symbol :gender in your class definition and the serialization happens automagically.