JSONAPI Active Model Serializer not separating model relationships to it's own object as spec describes - active-model-serializers

I'm building a rails app off a tutorial I found and am trying to use a JSONAPI Active Model Serializer to generate a response of that format.
In an initializer, I've put:
ActiveModelSerializers.config.adapter = :json_api
In my gemfile:
gem 'active_model_serializers', '~> 0.10.0.rc3'
I'm expecting two resource level keys, data and relationships as per the json-api specs. However, is not separating out the relationship to its own object. This is my request for /contacts.
{
"data": [
{
"id": "1",
"type": "contacts",
"attributes": {
"family-name": "La",
"given-names": "ch",
"company": {
"id": 1,
"name": "Lorem Inc",
"phone": "+1 (415) 555-1234",
"email": "office#lorem.inc",
"website": "www.lorem.inc",
"address": "213 Main St. 94063 San Francisco, CA",
"customer_id": "10001",
"additional_info": "",
"created_at": "2017-01-31T05:47:02.024Z",
"updated_at": "2017-01-31T05:47:02.024Z"
},
"title": null,
"phone": null,
"email": null,
"website": null,
"address": null,
"customer-id": null,
"additional-info": null
}
}
]
}
Company is a belong_to for contacts. Here are my serializers.
class CompanySerializer < ActiveModel::Serializer
attributes :id, :name, :phone, :email, :website, :address, :customer_id, :additional_info
end
class ContactSerializer < ActiveModel::Serializer
attributes :id, :family_name, :given_names, :company, :title, :phone, :email, :website, :address, :customer_id, :additional_info
end
These are my models:
class Contact < ApplicationRecord
belongs_to :company
validates :family_name, presence: true
validates :given_names, presence: true
end
class Company < ApplicationRecord
validates :name, presence: true
end
Everything else is just default generated code from rails cli. I'm not sure what else I need to add here because it is my understanding the default rails behavior is to generate a response that will show everything in the serializer. I'm assuming that jsonapi adapter should separate that out for me.
What else do I need to do to get the jsonapi adapter working properly?

I was missing relationships in my serializer!
Also answered in x post
https://github.com/rails-api/active_model_serializers/issues/2044

Related

Can't get data for entity

I use jhipster to generate 2 entity Employee and Department
and relationship:
Employee.java
#ManyToOne
#JsonIgnoreProperties(value = "employees", allowSetters = true)
private Department department;
Department.java
#OneToMany(mappedBy = "department")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Employee> employees = new HashSet<>();
and when I called api/employees , I had:
{
"id": 1,
"code": "FU_EMP_DTP726",
"name": "Concrete system",
"birthDate": "2020-11-30",
"address": "Granite frame",
"phone": "0152104977",
"salary": 65309.0,
"department": {
"id": 1,
"code": "FU_DE_787778",
"name": "Tools"
}
}
but with api/departments , I had:
{
"id": 1,
"code": "FU_DE_787778",
"name": "Tools",
"employees": null
}
I didn't know why department's employees is null
In bidirectional one-to-many relationship case jhipster do not get all ‘child’ form “One” side,because “N+1” problem .
For your purpose you need to be modify the generated code。use EntityGraph
modify repository、add a “employees” Set into ” DepartmentDTO“ and Mapper
How to see both sides in one-to-many relationship generated by JHipsterHow to see both sides in one-to-many relationship generated by JHipster

How to find common struct for all documents in collection?

I have an array of documents, that have more or less same structure. But I need find fields that present in all documents. Somethink like:
{
"name": "Jow",
"salary": 7000,
"age": 25,
"city": "Mumbai"
},
{
"name": "Mike",
"backname": "Brown",
"sex": "male",
"city": "Minks",
"age": 30
},
{
"name": "Piter",
"hobby": "footbol",
"age": 25,
"location": "USA"
},
{
"name": "Maria",
"age": 22,
"city": "Paris"
},
All docs have name and age. How to find them with ArangoDB?
You could do the following:
Retrieve the attribute names of each document
Get the intersection of those attributes
i.e.
LET attrs = (FOR item IN test RETURN ATTRIBUTES(item, true))
RETURN APPLY("INTERSECTION", attrs)
APPLY is necessary so each list of attributes in attrs can be passed as a separate parameter to INTERSECTION.
Documentation:
ATTRIBUTES: https://www.arangodb.com/docs/stable/aql/functions-document.html#attributes
INTERSECTION: https://www.arangodb.com/docs/stable/aql/functions-array.html#intersection
APPLY: https://www.arangodb.com/docs/stable/aql/functions-miscellaneous.html#apply

What is the difference between the TransactionRegistry and the Historian?

Say I just wanted to get a list of all transactions that involved a specific asset (assuming I would need a query for this?). Should I use the TransactionRegistry or the Historian? What's the difference?
we have a current issue open for Historian to show history of changes/ deltas for a particular asset - https://github.com/hyperledger/composer/issues/991 As a workaround you can do the following - so for sample network trade-network with an asset Commodity (and a transaction class 'Trade') you could create a query eg:
query selectTransaction {description: "choose specific commodity asset"
statement: SELECT org.acme.biznet.Trade
WHERE (commodity == _$commodity ) }
On the difference:
Historian records all transaction activities (eg. create Asset, create participant, create identity etc etc - and also business network specific custom transactions like 'TransferAsset' or 'PlaceOrder') including (where assets / participants are concerned) what changed.
For the TransactionRegistry itself (ie a particular class - say 'TransferAsset' or 'PlaceOrder') this is stored in the Transaction registry for that class - you may have many Transaction classes in your business network. But nothing in here would be related to other activities, such as system activities, that also recorded in Historian records.
to query - you would do something like this (in a query file for example):
query myTransactions{
description: "return all transactions made (ie system transactions)"
statement: SELECT org.acme.sample.PlaceOrder
}
ie SELECT org.acme.sample.NAME_OF_TRANSACTION_CLASS
For Historian queries - eg
SELECT org.hyperledger.composer.system.HistorianRecord WHERE (transactionType == 'myTranType'
see more examples here -> https://www.ibm.com/developerworks/cloud/library/cl-create-powerful-blockchain-queries-with-hyperledger-composer/index.html
furthermore, to see the transaction data (deltas) for the asset id you're zoning in on - ie available through the transactionInvoked field of the transaction class (eg org.acme.trading.Trade transaction class). you could use REST APIs with loopback filters -eg either (both return a promise below):
return this.httpClient.get('http://localhost:3000/api/Trade?filter=%7B%22include%22%3A%22resolve%22%7D', {withCredentials: true}).toPromise();`
or
return this.httpClient.get('http://localhost:3000/api/Trade?filter=%7B%22include%22%3A%22resolve%22%7D').toPromise();
which has the {"include":"resolve"} filter to resolve relationships in the transaction class - each resolved transaction has the transaction deltas. Then you could look for the asset id in question.
Sample unresolved transaction class (below, followed by resolved txn class):
Not resolved:
[
{
"$class": "org.acme.mynetwork.Trade",
"commodity": "resource:org.acme.mynetwork.Commodity#1",
"newOwner": "resource:org.acme.mynetwork.Trader#2",
"transactionId": "354dca97fc6ac00aabbd923883e3ec2a3d09b8c75a54a8f536a88b6df31e8a0f",
"timestamp": "2018-03-23T12:02:11.228Z"
},
{
"$class": "org.acme.mynetwork.Trade",
"commodity": "resource:org.acme.mynetwork.Commodity#2",
"newOwner": "resource:org.acme.mynetwork.Trader#1",
"transactionId": "9da43acca718633ac8870e6ea34c3c9f481194e48bcdba42673570177091809f",
"timestamp": "2018-03-23T12:02:31.294Z"
}
]
Resolved with {"include":"resolve"} as a filter:
[
{
"$class": "org.acme.mynetwork.Trade",
"commodity": {
"$class": "org.acme.mynetwork.Commodity",
"tradingSymbol": "1",
"description": "werwer",
"mainExchange": "wrrewer",
"quantity": 10,
"owner": {
"$class": "org.acme.mynetwork.Trader",
"tradeId": "2",
"firstName": "tes2t",
"lastName": "test"
}
},
"newOwner": {
"$class": "org.acme.mynetwork.Trader",
"tradeId": "2",
"firstName": "tes2t",
"lastName": "test"
},
"transactionId": "354dca97fc6ac00aabbd923883e3ec2a3d09b8c75a54a8f536a88b6df31e8a0f",
"timestamp": "2018-03-23T12:02:11.228Z"
},
{
"$class": "org.acme.mynetwork.Trade",
"commodity": {
"$class": "org.acme.mynetwork.Commodity",
"tradingSymbol": "2",
"description": "Ut fugiat.",
"mainExchange": "ACE2",
"quantity": 10,
"owner": {
"$class": "org.acme.mynetwork.Trader",
"tradeId": "1",
"firstName": "test",
"lastName": "test"
}
},
"newOwner": {
"$class": "org.acme.mynetwork.Trader",
"tradeId": "1",
"firstName": "test",
"lastName": "test"
},
"transactionId": "9da43acca718633ac8870e6ea34c3c9f481194e48bcdba42673570177091809f",
"timestamp": "2018-03-23T12:02:31.294Z"
}
]

Active Model Serializer remove relationship member when using JSON API

I'm using the latest AMS v0.10.0.rc3 with the JSON API adapter.
So far is working great and is adding some useful conventions that i would like to change.
For example, lets suppose that i have a Post serializer and a Comment serializer like this:
class Post < ActiveModel::Serializer
attributes :id, :title
has_many :comments
end
class Comment < ActiveModel::Serializer
attributes :id, :comment
belongs_to :post
end
Then if i request /posts/1 for example i get the following
{
"data": {
"id": "1",
"type": "posts",
"attributes": {
"title": "My awesome title",
},
"relationships": {
"comments": {
"data": [
{
"id": "1",
"type": "comments"
},
{
"id": "2",
"type": "comments"
}
]
}
}
}
}
Notice how the relationships member appears and according with the spec is marked as an optional member with MAY.
It is a nice convention that i need to override some times.
So my question is:
How i remove the relationship member at the serializer or controller level ?
(If i miss some detail please comment and i will update the question.)
Currently, your best option is to define two serializers, one with the associations, one without, and specify in your render call which to use (render json: posts, each_serializer: PostWithoutAssociationsSerializer).
There is an ongoing discussion about adding an include_if option to associations and attributes, which you could potentially leverage if it gets merged.

how to prevent to customer to add same credit card

I am using stripe as my payment provider and storing encrypted credit card id in my db returned from stripe.
My question is that from GUI customer can add same card again. I see stripe do not prevent to add same card multiple time for same customer. Since stripe always generates different encrypted card id for same card so I can't use it to validate if same card is being added again.
How can stop customer to add same card again again.
Looks like I got that . I can use fingerprint returned in json response. I saw stripe dashboard and found that fingerprint is always same for same card which I was adding again again.
Here is json request and response to confirm
Request
{
"source": {
"number": "378282246310005",
"cvc": "123",
"address_line2": "4th Floor",
"address_line1": "140 2nd Street",
"address_country": "USA",
"name": "VIAY KUMAR",
"address_state": "CA",
"exp_month": 12,
"exp_year": 2015,
"address_zip": "94105",
"address_city": "San Francisco",
"object": "card"
}
}
Response
{
"id": "card_166H9rC8Y8JrMFgBh9GVsmNG",
"object": "card",
"status": null,
"exp_month": 12,
"exp_year": 2015,
"last4": "0005",
"country": "US",
"type": null,
"name": "VIAY KUMAR",
"customer": "cus_6IrxhfwXNyD1Uw",
"recipient": null,
"address_line1": "140 2nd Street",
"address_line2": "4th Floor",
"address_zip": "94105",
"address_city": "San Francisco",
"address_state": "CA",
"address_country": "USA",
"address_zip_check": "pass",
"address_line1_check": "pass",
"cvc_check": "pass",
"fingerprint": "TwjSA2KqPDhSMUvQ",
"brand": "American Express",
"funding": "credit"
}
added same card again and got different card id but same finger print :-)
Request
{
"source": {
"number": "378282246310005",
"cvc": "123",
"address_line2": "4th Floor",
"address_line1": "140 2nd Street",
"address_country": "USA",
"name": "VIAY KUMAR",
"address_state": "CA",
"exp_month": 12,
"exp_year": 2015,
"address_zip": "94105",
"address_city": "San Francisco",
"object": "card"
}
}
Response
{
"id": "card_166HKVC8Y8JrMFgBfvbHPgk2",
"object": "card",
"status": null,
"exp_month": 12,
"exp_year": 2015,
"last4": "0005",
"country": "US",
"type": null,
"name": "VIAY KUMAR",
"customer": "cus_6IrxhfwXNyD1Uw",
"recipient": null,
"address_line1": "140 2nd Street",
"address_line2": "4th Floor",
"address_zip": "94105",
"address_city": "San Francisco",
"address_state": "CA",
"address_country": "USA",
"address_zip_check": "pass",
"address_line1_check": "pass",
"cvc_check": "pass",
"fingerprint": "TwjSA2KqPDhSMUvQ",
"brand": "American Express",
"funding": "credit"
}
Thanks to Vijay for the above answer.
I've written the following Ruby code in my Rails app to check for this.
Replace the relevant #user.stripe_customer_id variable with your own.
# Retrieve the customer we're adding this token to
customer = Stripe::Customer.retrieve(#user.stripe_customer_id)
# Retrieve the token
token = Stripe::Token.retrieve(params[:stripeToken])
# The fingerprint of the card is stored in `card.fingerprint`
card_fingerprint = token.card.fingerprint
# Check if the card fingerprint submitted matches one of the customer's current sources
fingerprint_already_exists = customer.sources.any? {|source| source[:fingerprint] == card_fingerprint}
if fingerprint_already_exists
# You can do whatever you want here. I've personally set a flash message to let the user know this card is already on their account
flash[:warning] = "That card already exists on your account."
redirect_to user_path(#user) and return
end
# Continue adding the source as normal
customer.sources.create(source: params[:stripeToken])
# Anything else you want to do...
flash[:success] = "Your account has been updated."
redirect_to user_path(#user) and return
The fingerprint generated by Stripe is unique to each card. As taken from the docs:
fingerprint string
Uniquely identifies this particular card number. You can use this attribute to check whether two customers who’ve signed up with you are using the same card number, for example.
Assuming you already have a customer object at this point (either a new one or one to update), this will let you add a card and make it default, without duplicates:
// $customer = \Stripe\Customer::retrieve( $clicker->customer );
/**
* Add the card, but only if it does not exist. Make it default in any case.
**/
$token = \Stripe\Token::retrieve( $_POST['stripeToken'] );
$card = null;
/** Does this customer already have this card? If so, find it. **/
foreach ( $customer->sources['data'] as $source ) {
if ( $source['fingerprint'] == $token['card']->fingerprint ) {
$card = $source;
}
}
/** If not, add the new card to the customer **/
if ( is_null( $card ) ) {
$card = $customer->sources->create( array( 'source' => $_POST['stripeToken'] ) );
}
$customer->default_source = $card->id;
$customer->save();
You'd enclose this in the usual try/catch as exemplified by the docs.
That being said, from the point of view of the user, it will be better, if possible, to show them a list of their cards (last4 + expire date) and let them pick the one they'd like to charge – instead of asking for their info needlessly.

Resources