active_model_serializers for different classes in array - ruby-on-rails

FeedController returns an array with objects of these classes: Product, Kit and Article.
Is it possible and how with active_model_serializers apply ProductSerializer for Product, KitSerializer for Kit and ArticleSerializer for Article?
This should render something like this:
[
{ "type": "product", other fiels of Product },
{ "type": "kit", other fiels of Kit },
{ "type": "article", other fiels of Article }
]

This should work with version 0.9.0, and version 0.10.0 should probably support this out of the box when it is finally ready, but at the time of this answer, it was suggested that you not use that version (master/edge)
class MyArraySerializer < ActiveModel::ArraySerializer
def initialize(object, options={})
options[:each_serializer] = get_serializer_for(object)
super(object, options)
end
private
def get_serializer_for(klass)
serializer_class_name = "#{klass.name}Serializer"
serializer_class = serializer_class_name.safe_constantize
if serializer_class
serializer_class
elsif klass.superclass
get_serializer_for(klass.superclass)
end
end
end
You can modify the get_serializer_for method to better suit your needs. I used this for handling STI subclasses where my parent class had all of the attributes defined. You will however need individual serializers for each of your individual objects since the attributes will most likely vary.
Then in your controller:
render json: #my_array_of_mixed_classes, serializer: MyArraySerializer

If this is no possible with active_model_serializers out of the box, maybe the reason is that you have a somewhat non-standard design of your API response. e.g. if I had to consume your API, would it be easy for me to deserialize your JSON response?
One way to get around this issue would therefore be to redesign your API response:
...
"products" : [ ARRAY of Product ],
"kits" : [ ARRAY of Kit ],
"articles" : [ ARRAY of Article ]
....
That would be easier to deserialize by the API consumer as well.

Related

Rails transform variable name and value from database

I want to use a transformed name/value in the property of a class than we get from the database. The reason I need this is I have two versions of an app that points to the same database, but the newer version recently updated the column name. So, for all apps that use the older version it has to be made compatible.
Environment: Rails version: 6.0.3.3 & Ruby version: ruby 2.7.1p83
Suppose there is class called Sprinter.
Sprinter table looks like:
id
name
m
s
1
Mr. Bolt
100
9.58
A sprinter instance would look like:
sprinter = {
"id" => 1,
"name" => "Mr. Bolt",
"m" => 100,
"s" => 9.58
}
The older version expects a different format. Now I want to have something like:
sprinter = {
"id" => 1,
"name" => "Mr. Bolt",
"cm" => 10000, # transformed from `m`
"ms" => 9580 # transformed from `s`
}
The m column name has been transformed to cm and the value has been transformed too. Same case for s to ms. The value transformation is not a big deal, but the variable name seems to be.
I'd prefer an internal update when I fetch the object. However if that's not supported, as a base-level solution, I'd prefer to change the json representation that I sent to the client. For reference, I use it as part of nested include like:
render status: :ok, json: {races: races}.as_json(
{:include => [:sprinters] }
)
Thanks.
Instead of mucking about with the internals of .as_json or how your model does serialization you can use a serialization layer instead to handle different representations of your model in JSON.
For example you can use ActiveModel::Serializers or JBuilder or even roll your own if thats your deal.
module API
module V2
class SprinterAdapter
def intialize(sprinter)
#sprinter = sprinter
end
def to_json
#sprinter.to_json.except("m", "s").merge(
"cm" => #sprinter.m * 100,
"ms" => #sprinter.s * 1000
)
end
end
end
end
And in your controller you would use the serializer to render the resource:
module API
module V2
class SprintersController < ApplicationController
def show
#sprinter = Sprinter.find(params[:id])
render json: SprinterAdapter.new(#sprinter)
end
end
end
end
See:
Thoughtbot: Better serialization, less as_json
Ruby Toolbox
You could transform keys/values by select from Sprinter:
Sprinter.select(:id, :name, 'm * 100 AS cm', 's * 1000 AS ms')
But not sure how to combine it with as_json

How can I add an element/field/column when rendering JSON in Rails?

Let's say you have a model called Widget which includes :id and :name.
In your Widget#index endpoint, you want to render all widgets in json, however... you also want to include another value for each record that isn't apart of the model called foobar. So you want the end result to look like this...
{
"widgets": [
{
"id":, 1,
"name": "widgy",
"foobar": true
},
{
"id":, 2,
"name": "gadgy",
"foobar": false
}
]
}
How can you edit the following code to allow for something like this?
widgets = Widget.all
render json: widgets
Depending on the version of Rails you're running, you could choose to use active_model_serializers or a simple jbuilder view.
Rails 4 already includes the jbuilder gem, so you don't need to include anything special in your Gemfile.
Cheers!

Operation to create a collection

I want to create an operation which accepts a json array and creates several objects. Something like Books::CreateCollection.
I believe I need to somehow reuse Books::Create - just call it multiple times and wrap the whole loop in transaction.
json:
{
"books": [
{
title: "A Tale Of Two Cities"
},
{
title: "Don Quixote"
}
]
}
But how the contract of Books::CreateCollection should look like?
Trailblazer 0.3.0
Your contract can handle this.
contract do
model Book # since you don't call this on the operation.
collection :songs, populate_if_empty: Book do
property :title
end
end
This is basic Reform wizardry.
The contract will now create one Book instance per incoming hash fragment in the songs array.

Constructing a nested JSON request with Jbuilder

Currently my JSON request is returning the below, where each person/lender has many inventories.
#output of /test.json
[
{"id":13, "email":"johndoe#example.com", "inventories":
[
{"id":10,"name":"2-Person Tent","category":"Camping"},
{"id":11,"name":"Sleeping bag","category":"Camping"},
{"id":27,"name":"6-Person Tent","category":"Camping"}
]
},
{"id":14, "email":"janedoe#example.com", "inventories":
[
{"id":30,"name":"Electric drill","category":"Tools"},
{"id":1,"name":"Hammer","category":"Tools"},
{"id":37,"name":"Plane","category":"Tools"}
]
}
]
I need to nest in one more thing and am having trouble doing so. For context, each inventory item is referenced via it's id as a foreign key in a borrow record. Each borrow record belongs to a request parent that stores returndate and pickupdate. What I need now, is for each inventory item, to nest an array of all the request records, with information on pickupdate and returndate. In other words, desired output:
[
{"id":13, "email":"johndoe#example.com", "inventories":
[
{"id":10,"name":"2-Person Tent","category":"Camping", "requests":
[
{"id":1, "pickupdate":"2014-07-07","returndate":"2014-07-10"},
{"id":2, "pickupdate":"2014-06-02","returndate":"2014-06-05"},
{"id":3, "pickupdate":"2014-08-14","returndate":"2014-08-20"}
]
},
{"id":11,"name":"Sleeping bag","category":"Camping", "requests":
[
{"id":4, "pickupdate":"2014-05-27","returndate":"2014-05-30"},
{"id":5, "pickupdate":"2014-04-22","returndate":"2014-04-25"}
]
},
{"id":27,"name":"6-Person Tent","category":"Camping", "requests":
[
{"id":6, "pickupdate":"2014-07-10","returndate":"2014-07-12"}
]
}
]
},
{"id":14, "email":"janedoe#example.com", "inventories":
...
I have written the following code:
json.array!(#lenders) do |json, lender|
json.(lender, :id, :email)
json.inventories lender.inventories do |json, inventory|
json.id inventory.id
json.name Itemlist.find_by_id(inventory.itemlist_id).name
#code below says, json.requests should equal all the Requests where there is a Borrows within that Request that is using the Inventory in question
json.requests Request.select { |r| r.borrows.select { |b| b.inventory_id == inventory.id }.present? } do |json, request|
json.pickupdate request.pickupdate
json.returndate request.returndate
end
end
end
When I refresh the page, I get wrong number of arguments (0 for 2..5)
I feel like the issue is that the Request.select... is returning an Array which isn't what needs to go here... but in the earlier nested function lender.inventories is an Inventory::ActiveRecord_Associations_CollectionProxy though I'm not sure how to correct for this.
NOTE: Someone said the problem could be that unlike with the nesting between inventories and lender, there's not an explicit association between inventory and request, but then again the line json.name Itemlist.find_by_id(inventory.itemlist_id).name worked so I'm not sure this is right. (Also if this is the case, I'm not sure how to bypass this limitation... I currently don't want to create a relationship between the two.)
Thanks!
ARG. Ok so this code is perfectly right. The issue was that I"m using the Gon gem in conjunction with Jbuilder, and Request is a predefined class in Gon.
So just changed code to
#requestrecords.select....
And in the controller:
#requestrecords = Request.all
-__-

Ruby on Rails: reverse lookup of array list of values

i have a model with a user selectable option that is set up in an array on the model.
def Pie < ActiveRecored::Base
def self.sel_options
[ [ "Apple Blueberry", "AB" ],
[ "Cranberry Date", "CD" ] ]
end
end
while the short string is retrieved from elsewhere and stored in the database, i would like to display the longer string when showing the object. e.g. in the view use:
Pie.display_customeor_choice[#pie_flavor]
i don't want to hard code the reverse hash, but if i create a display_options method that converts the array to a hash with reverse mapping will it run the conversion every time display_options is called? this could be resource intensive with large arrays that are converted a lot, is there a way to create the reverse hash once when the app is started and never again? (using rails 3 and ruby 1.9.2)
You are looking for Array#rassoc
Pie.display_customeor_choice.rassoc("#pie_flavor")
Here's how you could do it:
def Pie < ActiveRecored::Base
def self.sel_options
[ [ "Apple Blueberry", "AB" ],
[ "Cranberry Date", "CD" ] ]
end
def self.display_customeor_choice
unless #options
#options = {}
sel_options.each { |items| #options[items.last] = items.first }
end
#options
end
end
This guarantees it's going to be loaded only once on production (or other environments where cache_classes is set to true) but always reloads on development mode, making it simpler for you to change and see the changes.

Resources