Validation of uniqueness for an accepts_nested_attributes_for model - ruby-on-rails

class User < ActiveRecord::Base
has_many :friends
accepts_nested_attributes_for :friends
end
class Friend < ActiveRecord::Base
belongs_to :user
end
A user will keep adding friends via a REST API:
{ "user": {
"name": "Peter",
"friends_attributes": [
{ "name": "Paul" },
{ "name": "Mary" }
]
}
}
Later, the user will add more friends and call the same API:
{ "user": {
"name": "Peter",
"friends_attributes": [
{ "name": "Paul" },
{ "name": "Mary" },
{ "name": "John" }
]
}
}
Now, how should I write the validation such that:
only new friends are added (i.e. John)
without duplicating existing ones (i.e. Paul, Mary)
don't failed the API call as a whole

Related

Deserializing relationships JSON API ActiveModelSerializer

I'm using ActiveModelSerializers 0.10.2. - When I Deserializer my params, I'm unable to get my relationships to show up. I have a POST request with the payload:
{
"data": {
"type": "project_toolbox_talks",
"attributes": {
"date": "2017-12-11"
}
},
"relationships": {
"attendees": {
"data": [
{
"type": "atendees",
"id": "559ff2c9-beb6-47cd-9757-66104617403b"
}
]
},
"projects": {
"data": {
"type": "projects",
"id": "d9b28ffd-6f30-4dd0-a227-720caa9b881e"
}
}
}
}
My Serializer for ProjectToolboxTalks is:
module API
module V1
class ProjectToolboxTalkSerializer < ActiveModel::Serializer
attributes :date
has_one :toolbox_talk
has_one :superintendent
has_one :project
has_many :attendees
has_many :project_toolbox_talk_users
end
end
end
In my controller when I call:
ActiveModelSerializers::Deserialization.jsonapi_parse!(
params, only: [:date, :attendees, :projects]
)
the only thing that is returned is {:date=>"2017-12-11"}
Why are my attendees or projects relationships not being returned?
ActiveModelSerializers 0.10.2 + Rails 5.1.2 + Ruby 2.4.2
The key 'relationships' needs to be inside the first 'data'.

jsonapi - Invalid links object on create request for parent and child records

I'm trying to write an API endpoint for creating Redemptions in my app.
In Rails, my model is such that Redemption has many Items (class_name: RedemptionItems).
Here's my POST Body, following what I assume is the correct JSONAPI Specification (since there is no explicit spec for creating parent and child records in one request).
{
"data": {
"type": "redemptions",
"relationships": {
"items": {
"data": [{
"type": "items",
"attributes": { "offer_id": "1", "quantity": "2" }
},
{
"type": "items",
"attributes": { "offer_id": "1", "quantity": "3" }
},
{
"type": "items",
"attributes": { "offer_id": "123", "quantity": "3" }
}
]
}
}
}
}
I'm using JSONAPI::Resources. I have defined my JSONAPI::Resources thus:
class Platform::Api::Members::RedemptionItemResource < JSONAPI::Resource
model_name 'Platform::RedemptionItem'
has_one :redemption
end
class Platform::Api::Members::RedemptionResource < JSONAPI::Resource
model_name 'Platform::Redemption'
has_many :items, class_name: 'RedemptionItem'
end
It's currently giving me an inavlid links object error, which doesnt tell me anything on how I must improve my request body.
{
"errors": [
{
"title": "Invalid Links Object",
"detail": "Data is not a valid Links Object.",
"code": "115",
"status": "400"
}
]
}

Rails Active Model Serializer include in controller not injecting has many relationship in included parameter

I'm seeing a strange behaviour regarding rails 5, active model serializer and the json-api adapter.
Given the following User model with the Rolify gem:
class User < ActiveRecord::Base
#
# Gem Includes
#
rolify
# Include devise modules.
devise :database_authenticatable,
:recoverable, :rememberable, :trackable, :validatable
include DeviseTokenAuth::Concerns::User
#
# Callbacks
#
after_create :assign_default_role
#
# Attributes
#
attr_accessor :remote_image
#
# Validations
#
validates :name, presence: true, length: {in: 1..100}
validates :last_name, presence: true, length: {in: 1..100}
validates :role_ids, presence: true, on: :update
#
# Relations
#
belongs_to :current_scenario, class_name: "Scenario"
#
# Private Instance Methods
#
def assign_default_role
self.add_role(:user) if self.roles.blank?
end
end
and the following controller code:
def show
#user = User.find(params[:id])
authorize #user
render json: #user, include: ['roles'], status: :ok
end
As you can see, I'm including the roles relationship to be rendered as part of the json api response, with json-api adapter format.
FYI, the UserSerializer:
class UserSerializer < ActiveModel::Serializer
#
# Attributes
#
attributes :id, :email, :name, :last_name, :image_url, :image_thumb_url, :created_at, :updated_at, :current_scenario_id, :last_sign_in_at
#
# Relations
#
has_one :current_scenario
has_many :roles
#
# Methods
#
def image_url
object.image_url
end
def image_thumb_url
object.image_url(:thumb)
end
end
When retrieving the json response, I get the following:
{
"data": {
"id":"2",
"type":"users",
"attributes": {
"email":"talvarez#igaltex.com.ar", ...
},
"relationships": {
"current-scenario": {
"data": {
"id":"204",
"type":"scenarios"
}
},
"roles": {
"data": [
{
"id":1,
"name":"user",
"resource-type":null,
"resource-id":null,
"created-at":"2017-01-23T10:27:08.707-03:00",
"updated-at":"2017-01-23T10:27:08.707-03:00"
},
{
"id":2,
"name":"admin",
"resource-type":null,
"resource-id":null,
"created-at":"2017-01-24T09:40:53.020-03:00",
"updated-at":"2017-01-24T09:40:53.020-03:00"
}
]
}
}
}
}
As you can see, the included relationship roles with all its attributes is inside the relationships fragment of the json-api response. Shouldn't the roles data be inside the included fragment, which by the way is missing? Moreover inside the relationship fragment roles should appear only as a reference like: {relationships: {roles: [{id: "1", type: "role"}, {id: "2", type: "role"}]} am I wrong?
To contrast this, look what happens when also including the current_scenario relationship:
{
"data": {
"id":"2",
"type":"users",
"attributes": {
"email":"talvarez#igaltex.com.ar",
"name":"Tomás",
"last-name":"Alvarez",
...
},
"relationships": {
"current-scenario": {
"data": {
"id":"204",
"type":"scenarios"
}
},
"roles": {
"data": [
{
"id":1,
"name":"user",
"resource-type":null,
...
}
]
}
},
"included": [
{
"id":"204",
"type":"scenarios",
"attributes": {
"name":"Scenario reload II",
"description":null,
"created-at":"2017-04-18T11:55:35.242-03:00",
"updated-at":"2017-04-18T11:55:35.242-03:00"
},
"relationships": {
"scenario-stocks": {
"data":[]
}
}
}
]
}
}
See how now the included fragment appears with all the information about current_scenario and only the reference to current_scenario is added to the relationships fragment. Is this because roles is a has_many relationship in the active model serializer while current_scenario is a belongs_to ? Am I understanding wrong the json-api adapter specification?
Many thanks!
Ouch. The inconsistency in the JSON-API response was because i forgot to add a Role model serializer in the backend side (Rails 5). This is the json response now, which is what i was looking for:
{
"data": {
"id": "2",
"type": "users",
"attributes": {
"email": "talvarez#igaltex.com.ar",
"name": "Tomás",
"last-name": "Alvarez",
"image-url": "http://localhost:3001/uploads/user/image/2/05a4dc7.jpg",
"image-thumb-url": "http://localhost:3001/uploads/user/image/2/thumb_05a4dc7.jpg",
"created-at": "2017-01-23T10:39:12.485-03:00",
"updated-at": "2017-04-25T16:32:14.610-03:00",
"current-scenario-id": 204,
"last-sign-in-at": "2017-04-25T16:29:03.559-03:00"
},
"relationships": {
"current-scenario": {
"data": {
"id": "204",
"type": "scenarios"
}
},
"roles": {
"data": [{
"id": "1",
"type": "roles"
}, {
"id": "2",
"type": "roles"
}]
}
}
},
"included": [{
"id": "204",
"type": "scenarios",
"attributes": {
"name": "Scenario reload II",
"description": null,
"created-at": "2017-04-18T11:55:35.242-03:00",
"updated-at": "2017-04-18T11:55:35.242-03:00"
},
"relationships": {
"scenario-stocks": {
"data": []
}
}
}, {
"id": "1",
"type": "roles",
"attributes": {
"name": "user"
}
}, {
"id": "2",
"type": "roles",
"attributes": {
"name": "admin"
}
}]
}
Sorry for this bug. We haven't figured out how to determine the type for a relationship when no serializer is found. Would it have been more helpful if you got an exception?
This is how JSON API works you can not retrieve desired model + relationship in 'one object'. Relationships always are separated. So you need to 'glue' it somehow. You can use gem that can help you with it, or you can do it on front end side (all big frameworks support it).
In general this approach with 'relationships' looks weird, but when you have complex object with tons of dependencies, this is the only one way that works.

AngularJS get related data from user through JSON API

I'm using Ionic framework, but question is about AngularJS. I have written json api on ruby on rails. For authentication I choose ng-token-auth + devise-token-auth.
User json:
{
"data": {
"id": "1",
"type": "users",
"attributes": {
"name": "User1",
"email": "email#email.com",
"current-sign-in-at": "..",
"last-sign-in-at": "..",
"created-at": ".."
},
"relationships": {
"friends": {
"data": [
{
"id": "2",
"type": "users"
}
]
}
}
},
"included": [
{
"id": "2",
"type": "users",
"attributes": {
"name": "User2",
"email": "user#user.com",
"current-sign-in-at": "..",
"last-sign-in-at": "..",
"created-at": ".."
},
"relationships": {
"friends": {
"data": [ ]
}
}
}
]
}
My serializer for user:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :email,:current_sign_in_at,
:last_sign_in_at, :created_at
has_many :friends, through: :friendships
end
Current user object as response:
{"success":true,"data":{"id":1,"provider":"email","uid":"0a56bb6b-dc72-4ef3-906e-1c17cb2fef46","name"
:"User1","nickname":null,"image":null,"email":"email#email.com"}}
There is my problem, I can't reach user relationship information.
I don't know why, but object name in my view is user (not current_user) and don't have access for it in my controllers.
Question is how can I have some additional information from this object and how to provide current user object access for my controllers.
I figured it out, so the answer is really simple. ng-token-auth provide $rootScope.user as the current user and it can be used in controllers.
But I still don't know how to provide related data to user object, fortunately found workaround for my needs.

Creating a deeply nested object in Rails

I've built a small API that, when posted a JSON object, creates the representative model records. The data looks like this:
{
"customer": {
"email": "michael#myemail.com",
"first_name": "Michael T. Smith",
"last_name": "",
"shipping_address_1": "",
"telephone": "5551211212",
"source": "Purchase"
},
"order": {
"system_order_id": "1070",
"shipping_address_1": "",
"billing_address_1": "123 Your Street",
"shipping": "0",
"tax": "0",
"total": "299",
"invoice_date": 1321157461,
"status": "PROCESSING",
"additional_specs": "This is my info!",
"line_items": [
{
"quantity": "1",
"price": "239",
"product": "Thing A",
"comments": "comments"
"specification": {
"width": "12",
"length": "12",
},
},
{
"quantity": "1",
"price": "239",
"product": "Thing A",
"comments": "comments"
"specification": {
"width": "12",
"length": "12",
},
},
]
}
}
The question is how to create the nested objects. My models are setup as such:
class Order < ActiveRecord::Base
has_many :line_items
belongs_to :customer
accepts_nested_attributes_for :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
has_many :specifications
end
class Specification < ActiveRecord::Base
belongs_to :LineItem
end
I'm trying to create the records using this code:
#order = #customer.orders.build(#data[:order])
#order.save
Is there a better way to do this? Currently I'm getting this error: ActiveRecord::AssociationTypeMismatch in ApiController#purchase_request LineItem(#70310607516240) expected, got Hash(#70310854628220)
Thanks!
accepts_nested_attributes_for defines a new setter method for the association: the original name with _attributes appended to it.
In your case, there is a line_items_attributes= method on your Order model, which is what you need to use to take advantage of the nested attributes feature. Something as simple as swapping the key before building the model would probably work, e.g.:
#data[:order][:line_items_attributes] = #data[:order].delete(:line_items)
#order = #customer.orders.build(#data[:order])
#order.save
You can use accepts_nested_attributes_for like this:
(in my example, Products has_many ProductionItineraries and ProductionItineraries belongs_to Products)
model/product.rb
has_many :production_itineraries, dependent: :delete_all
accepts_nested_attributes_for :production_itineraries, allow_destroy: true
model/production_itinerary.rb
belongs_to :product
To instantiate:
products = Product.new
products.production_itineraries.build(other_production_itineraries_fields)
Doing this, after save products, the production_itinerary object will be save automatically, with the respective product_id field

Resources