ActiveModelSerializers (0.10.0.rc3) an object's relation's relation is not generated with default FlattenJson adapter - active-model-serializers

Rails 5 Alpha version / Ruby 2.2.3 / active_model_serializers (0.10.0.rc3) (henceforth referred as AMS)
GIT
remote: https://github.com/rails/rails.git
revision: 5217db2a7acb80b18475709018088535bdec6d30
GEM
remote: https://rubygems.org/
specs:
active_model_serializers (0.10.0.rc3)
I already have a working API-only app utilizing Rabl-Rails to generate the JSON responses.
I am working on making it ready to work with Rails 5 and as part of this evaluating Rails 5 inbuilt API features esp the reusability and flexibility ActiveModel::Serializers can provide.
I created few serializers
- app
- serializers
- client
- base_serializer.rb
- device
- base_serializer.rb
- provider
- base_serializer.rb
The JSON responses in the app is created in composite manner reusing any existing templates and a base_serializer contains basic data about a resource which can be serialized.
Below shown are the 3 serializers I have initially created:
For ActiveRecord model Device
class Device::BaseSerializer < ActiveModel::Serializer
attributes :id, :name
belongs_to :client, serializer: Client::BaseSerializer
def client
unless exclude_client?
object.client
end
end
private
def device_opts?
options.key?(:device) # options inherited from by ActiveModel::Serializer
end
def device_opts
options[:device]
end
def exclude_client?
device_opts? && device_opts.key?(:exclude_client) # options inherited from by ActiveModel::Serializer
end
end
For ActiveRecord model Client
class Client::BaseSerializer < ActiveModel::Serializer
attributes :id, :name, :time_zone
belongs_to :provider, serializer: Provider::BaseSerializer
def provider
unless exclude_provider?
object.provider
end
end
private
def client_opts?
options.key?(:client) # options inherited from by ActiveModel::Serializer
end
def client_opts
options[:client]
end
def exclude_provider?
client_opts? && client_opts.key?(:exclude_provider)
end
end
For ActiveRecord model Provider
class Provider::BaseSerializer < ActiveModel::Serializer
attributes :id, :name
end
Provider model
class Provider < ActiveRecord::Base
has_many :clients, dependent: :destroy
end
Client model
class Client < ActiveRecord::Base
belongs_to :provider
has_many :devices
end
Device model
class Device < ActiveRecord::Base
belongs_to :client
end
Problem
When serializing a Device object its client node doesn't contain the client's provider node as defined by belongs_to :provider, serializer: Provider::BaseSerializer in Client::BaseSerializer
device = Device.find(1)
options = { serializer: Device::BaseSerializer }
serializable_resource = ActiveModel::SerializableResource.new(device, options)
puts JSON.pretty_generate(serializable_resource.as_json)
{
"id": 1,
"name": "Test Device",
"client": {
"id": 2,
"name": "Test Client",
"time_zone": "Eastern Time (US & Canada)"
}
}
However when serializing a Client object it contains the provider node:
client = Client.find(2)
options = { serializer: Client::BaseSerializer }
serializable_resource = ActiveModel::SerializableResource.new(client, options)
puts JSON.pretty_generate(serializable_resource.as_json)
{
"id": 2,
"name": "Test Client",
"time_zone": "Eastern Time (US & Canada)",
"provider": {
"id": 1,
"name": "Test Provider"
}
}
As can be seen above when we generate a Device's json in its "client" property, client's provider relation is not getting generated.
However when we generate a Client's json it contains "provider" property. Passing any include option like following
options = { serializer: Client::BaseSerializer, include: "client.provider.**" }
as mentioned here doesn't have any effect.
I dig into the AMS source code and found that include option is only considered if the adapter is JSON API. However, the default adapter is base.config.adapter = :flatten_json.
As can be seen in the adapter's implementation and ActiveModel::Serializer#attributes(options = {}) method (shown below) only following data is taken into account during serialization:
Object's attributes
Object's relationships and their attributes. Relationship's own relations are not taken into account.
ActiveModel::Serializer::Adapter::FlattenJson < ActiveModel::Serializer::Adapter::Json
def serializable_hash(options = nil)
options ||= {}
if serializer.respond_to?(:each)
result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
else
hash = {}
core = cache_check(serializer) do
serializer.attributes(options)
end
serializer.associations.each do |association|
serializer = association.serializer
opts = association.options
if serializer.respond_to?(:each)
array_serializer = serializer
hash[association.key] = array_serializer.map do |item|
cache_check(item) do
item.attributes(opts)
end
end
else
hash[association.key] =
if serializer && serializer.object
cache_check(serializer) do
# As can be seen here ASSOCIATION's serializer's attributes only gets serialize and not its own relations.
serializer.attributes(options)
end
elsif opts[:virtual_value]
opts[:virtual_value]
end
end
end
result = core.merge hash
end
ActiveModel::Serializer
def attributes(options = {})
attributes =
if options[:fields]
self.class._attributes & options[:fields]
else
self.class._attributes.dup # <<<<<<<<<< here
end
attributes.each_with_object({}) do |name, hash|
unless self.class._fragmented
hash[name] = send(name)
else
hash[name] = self.class._fragmented.public_send(name)
end
end
end
As my JSON structures are custom and pre-defined, I cannot switch to JSON API adapter. That leaves the option to use only attributes like
class Client::BaseSerializer < ActiveModel::Serializer
attributes :id, :name, :time_zone
attributes :provider
def provider
unless exclude_provider?
object.provider
end
end
end
But that way I do not get flexibility to use a custom serializer for :provider attribute.
Questions:
Is there any way to get around the problem mentioned above and achieve the desired results?
Is there any provision to ignore an attribute from getting included in the serialized hash? For e.g. using Rabl-Rails in my JSON template I can do following:
node(:client, if: ->(device_decorator) { !device_decorator.exclude_client? } ) do |device_decorator|
partial('../clients/base', object: device_decorator.client_decorator)
end
With that in place if DeviceDecorator#exclude_client? returns false, :client node doesn't get generated in the JSON.
Thanks.

Related

Setting the Group Bypass property of a Node

I can't seem to find an answer from the documentation http://origen-sdk.org/origen/guides/program/flowapi/
From SmartTest 7.1.3 and later of Advantest, we have the option to set “Group Bypass” property of the Group node testflow component.
{
run_and_branch(TestA)
then
{
}
else
{
if #Alarm then
{
binout;
}
else
{
}
}
run_and_branch(TestB)
then
{
}
else
{
if #Alarm then
{
binout;
}
else
{
}
}
}, groupbypass, open,"DataCollectionTests", ""
I tried using if_flag:, continue: and if_enable properties in my group definition but I’m getting an
if #GROUPBYPASS == 1 then
{
.
.
.
}, open,"DataCollectionTests", ""
in the flow instead.
What is the correct way of hooking up into this property?
This property is not currently supported, if you want it added please open a ticket describing it here: https://github.com/Origen-SDK/origen_testers/issues
In the meantime, you could generate it by using the render method which allows you to explicitly define code to be injected into the flow.
For example:
render '{'
# Your existing code to be wrapped in the group here, e.g.
test :testA
render '}, groupbypass, open,"DataCollectionTests", ""'
You could create your own helper method for that within your interface:
def group_bypass(name)
render '{'
yield
render "}, groupbypass, open,\"#{name}\", \"\""
end
Then in your flow:
group_bypass "DataCollectionTests" do
# Your existing code to be wrapped in the group here, e.g.
test :testA
end

setting a value on object member in fabric.js

Following advice from this question I keep custom attributes in a central place using a _data object.
Setting attributes using set doesn't work like expected.
var opts = {
'width': 200,
'height': 200,
_data:{'key':'oldvalue'}
};
var rect = new fabric.Rect(opts);
rect.set('_data.key', 'newvalue')
//rect.get('_data.key') will still return 'oldvalue'
It seems obvious that set uses _data.key as string - but is there any other way?
Of course i can do rec._data.key = 'newvalue'...
using this approach for now:
var _data = rect.get('_data')
_data.key = 'newvalue'
rect.set('_data', _data)
You can use any key name in a fabric object set method. But in a typescript project the editor will complain because the interfaces for the Object class don't have these properties.
The interface "Object" extends the interface "IObjectOptions", which includes the properties that belong to the fabric base Object. There are two properties (name and data) that can be used to add custom properties. name must be a string, and data can be anything.
Here's the snippet within the IObjectOptions interface:
/**
* Not used by fabric, just for convenience
*/
name?: string;
/**
* Not used by fabric, just for convenience
*/
data?: any;
Example:
rect.set('name', 'myname');
rect.set('data', {
'key1':'value1',
'key2':'value2',
'key3':'value3'
});

Slick timestamp calculation

I am using the pg-slick extension for postgres. I try to do a calculation in the where clause but i don't get it working. It always says:
value - is not a member of java.sql.Timestamp
Filter clause:
.filter(r => Timestamp.from(Instant.now()) - r.lastActivity < Duration.ofMinutes(30))
where lastActivity is:
def lastActivity = column[Timestamp]("last_activity")
and my postgres driver is:
trait MyPostgresDriver extends ExPostgresProfile
with PgPostGISSupport
with PgDate2Support
with PgEnumSupport {
override val api: API = new API {}
///
trait API extends super.API
with PostGISImplicits
with DateTimeImplicits
with PostGISAssistants {
}
}
object MyPostgresDriver extends MyPostgresDriver
Adding:
val plainAPI = new API
with Date2DateTimePlainImplicits {}
seems to solve the issue. Also don't forget to add with DateTimeImplicits and with PgDateSupport

Deserializing Json-Api with Rails Strong Parameters

I am using Active Model Serializers 0.10.x with EmberCLI and Rails while trying to have the Json-Api as the Adapter. GET requests are working, but deserialization for the Active Model is not even though I tried to implement the rails strong_parameters solution described by jimbeaudoin here.
My latest attempt in saving a comment:
Payload:
{"data":{
"attributes": {"soft_delete":false,"soft_delete_date":null,"text":"hjfgfhjghjg","hidden":false,"empathy_level":0},
"relationships":{
"user":{"data":{"type":"users","id":"1"}},
"post":{"data":{"type":"posts","id":"1"}}},"type":"comments"}}
Console Output:
Completed 400 Bad Request in 13ms (ActiveRecord: 8.6ms)
ActionController::ParameterMissing (param is missing or the value is empty: data):
Comments Controller:
class Api::V1::CommentsController < MasterApiController
respond_to :json
...
def create
render json: Comment.create(comment_params)
end
...
def comment_params
#Deserialization issues... Waiting for #950 https://github.com/rails-api/active_model_serializers/pull/950
params.require(:data).require(:attributes).permit(:text, :user_id, :post_id, :empathy_level, :soft_delete_date, :soft_delete, :hidden)
end
end
Noting that if I set the parameters to only params.permit(...), the server saves it with everything null (I did not set any constraints on the comments model for now):
data: {id: "9", type: "comments",…}
attributes: {soft_delete: null, soft_delete_date: null, text: null, hidden: null, empathy_level: null}
id: "9"
relationships: {post: {data: null}, user: {data: null}}
type: "comments"
You can access the full code here.
Update #2: For AMS >= 0.10.2, please check other answers.
Update #1: Answer is still valid for AMS 0.10.1.
If you use 0.10.0.rc4, you can now use the Deserialization implementation described on Active Model Serializers #1248.
def post_params
ActiveModel::Serializer::Adapter::JsonApi::Deserialization.parse(params.to_h)
// or (params.to_unsafe_h) in some cases like in my example below...
end
Bonus: If you use Ember Data, then you can see an example implementation on my Fakktion Github repo.
For AMS >= 0.10.2
In 0.10.2 there was a cleanup so after 0.10.2 use:
def post_params
ActiveModelSerializers::Deserialization.jsonapi_parse(params)
end
Reference:
https://github.com/rails-api/active_model_serializers/commit/252f9c4ae932e6280dfe68605d495b208fe22ba7
With AMS 0.10.2+
Use only hash to create a parameter whitelist,
def post_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params, only: [:title, :author, :tags]
)
end
For googlers:
If you have empty data payload, you need to add Mime support
https://stackoverflow.com/a/32013294/2664779
When you want to access json-api formatted json, you should do it like this
(in your controller)
def create
user = User.new(user_params)
...
end
private
def user_params
params.require(:data).require(:attributes).permit(:email, :password)
end
When previously I would do it like this
private
def user_params
params.require(:user).permit(:email, :password)
end

Puppet cannot find parent resource type

I'm trying to set up Puppet to use templates in order to create configuration files for our servers. The current way I am doing this is by using inheritance to pass default values to a class. This allows me to properly separate the data from the rest of the code. An example of this structure is as follows:
class grading_properties(
$home='tomcat-deploy',
$financialScoreHigh = $grading_properties_defaults::financialScoreHigh,
$financialScoreLow = $grading_properties_defaults::financialScoreLow,
$qualityScoreHigh = $grading_properties_defaults::qualityScoreHigh,
$qualityScoreLow = $grading_properties_defaults::qualityScoreLow,
)inherits grading_properties_defaults {
file{"${base}/${home}/company-conf/grading.properties" :
ensure => present,
mode => '0755',
owner => 'root',
group => 'root',
content => template("company/company-conf_pr/grading.properties.erb")
}
}
This class is responsible for generating the "grading.properties" config file based on the "grading.properties.erb" template. The "grading_properties_defaults" class which it inherits looks like this:
class grading_properties_defaults{
$financialScoreHigh = 4
$financialScoreLow = 7
$qualityScoreHigh = 6000
$qualityScoreLow = 4000
}
The problem I am having is when I try to create the class using
class{ "grading_properties" :
financialScoreHigh => 10,
}
from a class in the same module, I get the following error from puppet apply:
Error: Could not find parent resource type 'grading_properties_defaults' of type hostclass in production at /home/git/puppet/modules/company/manifests/grading_properties.pp:1 on node sv1.company.qa0
What is the proper way to reference a class in the same module?
It turns out I simply need to fully qualify my class names in order for puppet to find them. E.g. class grading_properties_defaults should be modulename::grading_properties_defaults
If you aren't in the same puppetfile we must write modulename::classname as example:
class modulename::role {
### CODE HERE ####
}
class role::www inherits modulename::role {
### CODE HERE ####
}
In the same puppetfile you don't need declare modulename
class role {
### CODE HERE ####
}
class role::www inherits role {
### CODE HERE ####
}
Great material about profile and roles https://www.craigdunn.org/2012/05/239/

Resources