How to fix an API using jsonapi-resources that is responding with mismatch type and not allowing me to create records? - rails-activerecord

I have a problem with Mismatch types in my API everytime I want to create a new item.
It happens in 2 places:
when I try to POST a new item
POST http://localhost:8060/datasets/
{
"data": {
"type": "datasets",
"attributes": {
"doi": "10.5259/2008120816KAKA",
"version": 0,
"is-active": "1",
"datacentre": 201
}
}
Response
"errors": [
{
"title": "Internal Server Error",
"detail": "Internal Server Error",
"code": "500",
"status": "500",
"meta": {
"exception": "Datacentre(#23863440) expected, got 201 which is an instance of Fixnum(#4211740)",
"backtrace": [
"/usr/local/rvm/gems/ruby-2.3.3/gems/activerecord-5.1.1/lib/active_record/associations/association.rb:239:in `raise_on_type_mismatch!'",
"/usr/local/rvm/gems/ruby-2.3.3/gems/activerecord-5.1.1/lib/active_record/associations/belongs_to_association.rb:11:in `replace'",
"/usr/local/rvm/gems/ruby-2.3.3/gems/activerecord-5.1.1/lib/active_record/associations/singular_association.rb:15:in `writer'",
"/usr/local/rvm/gems/ruby-2.3.3/gems/activerecord-5.1.1/lib/active_record/associations/builder/association.rb:119:in `datacentre='"
I can see it happening when accessing the relationships of an item. For example this is a a GET response:
GET http://localhost:8060/datasets/
{
"data": [
{
"id": "5",
"type": "datasets",
"links": {
"self": "http://localhost:8060/datasets/5"
},
"attributes": {
"created": "2011-03-02T16:41:20.000Z",
"doi": "10.5259/20070410230000/HTTP://www.STAFFSPASTTRACK.ORG.UK/EXHIBIT/CRIMEANDPUNISHMENT/IMAGEPAGE/ZERO.HTM",
"version": 0,
"is-active": "",
"updated": "2011-03-02T16:41:20.000Z",
"datacentre": "#<Datacentre:0x000000033389b8>",
"deposited": "2012-07-31T09:12:37.000Z"
},
"relationships": {
"datacentre": {
"links": {
"self": "http://localhost:8060/datasets/5/relationships/datacentre",
"related": "http://localhost:8060/datasets/5/datacentre"
}
}
}
}
You can see how the datacentre (foreign_key) attribute is presented as an object type of the Model Datacentre. If I try to access the relantship to that model http://localhost:8060/datasets/5/datacentre I get the following:
{
* errors: [
* {
* title: "Internal Server Error",
* detail: "Internal Server Error",
* code: "500",
* status: "500",
* meta: {
* exception: "Unknown source type #<Datacentre id: 10015, comments: "", contact_email: "andrew.jackson#bl.uk", contact_name: "Andy Jackson", created: "2010-12-14 22:05:32", doi_quota_allowed: 50000, doi_quota_used: 17, domains: "webarchive.org.uk", is_active: "\x01", name: "Web Archive Programme at BL", password: "98583c1bf114bfe80105f906d800e05325307f06b93ccee6b6...", role_name: "ROLE_DATACENTRE", symbol: "BL.WAP", updated: "2012-02-17 11:49:01", version: 2, allocator: 106, experiments: nil>",
* backtrace: [
* "/usr/local/rvm/gems/ruby-2.3.3/gems/jsonapi-resources-0.9.0/lib/jsonapi/resource_serializer.rb:267:in `top_level_source_key'",
* "/usr/local/rvm/gems/ruby-2.3.3/gems/jsonapi-resources-0.9.0/lib/jsonapi/resource_serializer.rb:47:in `block in serialize_to_hash'",
* "/usr/local/rvm/gems/ruby-2.3.3/gems/jsonapi-resources-0.9.0/lib/jsonapi/resource_serializer.rb:47:in `map'",
* "/usr/local/rvm/gems/ruby-2.3.3/gems/jsonapi-resources-0.9.0/lib/jsonapi/resource_serializer.rb:47:in `serialize_to_hash'",
* "/usr/local/rvm/gems/ruby-2.3.3/gems/jsonapi-resources-0.9.0/lib/jsonapi/response_document.rb:109:in `results_to_hash'",
* "/usr/local/rvm/gems/ruby-2.3.3/gems/jsonapi-resources-0.9.0/lib/jsonapi/response_document.rb:12:in `contents’”,
I think the problem is that the API is expecting the wrong type of object. It’s expecting Datacentre when it should be expecting DatacentreResource.
My setup is as follows:
I have a Legacy database that doesn’t follow the ActiveRecord conventions for naming tables and foreign_keys.
Tables are singular and foreign_keys do not have the _id suffix.
The tables/models in which I have the problem have one_to_many relationship.
The relationship being a datacentre has_many datasets.
I am using jsonapi-resources and rails 5 api-only.
Datacentre Model
class Datacentre < ApplicationRecord
self.table_name = "datacentre"
alias_attribute :allocator_id, :allocator
has_and_belongs_to_many :prefixes, class_name: 'Prefix', join_table: "datacentre_prefixes", foreign_key: :prefixes, association_foreign_key: :datacentre
belongs_to :allocator, class_name: 'Allocator', foreign_key: :allocator
has_many :datasets
end
Dataset Model
class Dataset < ApplicationRecord
self.table_name = "dataset"
alias_attribute :datacentre_id, :datacentre
belongs_to :datacentre, class_name: 'Datacentre', foreign_key: :datacentre
end
Datacentre Resource
class DatacentreResource < JSONAPI::Resource
model_name 'Datacentre'
model_hint model: Datacentre
attributes :comments, :contact_email, :contact_name, :created, :doi_quota_allowed, :doi_quota_used, :domains, :is_active, :name, :password, :role_name, :symbol,
:updated, :version, :experiments, :allocator
has_many :datasets
has_many :prefixes
has_one :allocator, class_name: 'Allocator', foreign_key: :allocator
end
Dataset Resource
class DatasetResource < JSONAPI::Resource
model_name 'Dataset'
model_hint model: Dataset
attributes :created, :doi, :version, :is_active, :updated, :datacentre
attribute :deposited
has_one :datacentre, class_name: "Datacentre", foreign_key: :datacentre
end
So far I have got around the first problem (i.e. accessing the relationship) by modifying the methods for datacentre and its alias datacentre_id in the Dataset Resource
def datacentre(context=nil)
DatacentreResource.find_by_key(#model.datacentre.id)
end
def datacentre_id()
#model.datacentre.id
end
But this doesn’t solve the POST problem.

I think this is the thing that cause the error
in
class Dataset < ApplicationRecord
self.table_name = "dataset"
alias_attribute :datacentre_id, :datacentre
belongs_to :datacentre, class_name: 'Datacentre', foreign_key: :datacentre
end
datacentre_id and datacentre are aliased so it gives back and object datacentre back in response instead of datacentre_id
probably it will wokr if you remove this line

Related

Rails nested attributes validation force save parent firstly

I have 2 models like:
class Father < ActiveRecord::Base
has_many :children
accepts_nested_attributes_for :children
end
class Child < ActiveRecord::Base
belongs_to :father
validate :validate_money
def validate_money
children = Child.where(id: self.father.id)
sum_of_children_pocket_money = my_func # function for getting sum of all pocket money of all children
if sum_of_children_pocket_money > self.father.money
errors.add(:pocket_money, "My error message!!!")
end
end
end
and initially I have:
and when pass passengers_attributes to update customer (id=1) like
{
id: 1,
name: "Father 1",
money: 1000,
children: [
{
name: "Child 1",
id: 1,
pocket_money: 500
}
]
}
and then I am sending for update:
{
id: 1,
name: "Father 1",
money: 2000,
children: [
{
name: "Child 1",
id: 1,
pocket_money: 1500
}
]
}
So I am getting an error in my validation because it starts sum_of_children_pocket_money > self.father.money comparison and sum_of_children_pocket_money is equal to 1500 but self.father.money still 1000. How can I fix this?
Moving the validation logic from Child to Father should solve the problem.
class Father < ActiveRecord::Base
has_many :children
accepts_nested_attributes_for :children
validate :validate_money
def validate_money
children = Child.where(id: id)
sum_of_children_pocket_money = my_func # function for getting sum of all pocket money of all children
if sum_of_children_pocket_money > money
errors.add(:pocket_money, "My error message!!!")
end
end
end
class Child < ActiveRecord::Base
belongs_to :father
end

Get a belongs_to relationship from class Ruby on Rails

Here are my models:
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Order < ApplicationRecord
has_many :order_items
end
Here is my controller:
def index
orders = Order.where(nil)
render json: {'orders': orders}, include:
['order_items'], status: :ok
end
I want to also include the product in the order_items. How can I achieve this to get the following JSON:
{
"id": 2,
"order_items": [
{
"id": 1,
"product": {
"name": "abc"
},
}
]
},
You can reach this with changing
include: ['order_items']
to
include: ['order_items', 'order_items.product'].
More details you can get here.
I have been able to solve this by changing include: ['order_items'] to include: {'order_items': {include: 'product'}}

Rails 5 belongs_to scoped validation

lets say I have this model:
class Post < ApplicationRecord
enum post_type: { post: 0, page: 1 }
belongs_to :user
end
by default rails 5 will make the belongs_to :user association to be required. And If you pass optional: true will make this association to be optional. But what I want is the belongs_to :user association to be optional only when the post_type is page and when it is post to required.
How can I do it at the line belongs_to :user ?
At this moment I am doing this:
class Post < ApplicationRecord
enum post_type: { post: 0, page: 1 }
belongs_to :user, optional: true
validates :user_id, presence: { scope: post? }
end
But this will give me an error like:
NoMethodError: undefined method `post?' for #
Is this the correct way to do it? or there is another way?
The user presence can be validated using if option:
validates :user, presence: true, if: :post?

Rails New Field Not Appear

I just got an issue after I've done a migration for a table, I've added owner field to the project table, but it is not appear when I request it (project).
Expected response after migration:
{
projects: [
{ id: 1, name: "First proj", owner: 1 },
{ id: 2, name: "Second proj", owner: 1 }
]
}
I got:
{
projects: [
{ id: 1, name: "First proj" },
{ id: 2, name: "Second proj" }
]
}
Here is my migration file
class AddOwnerRefToProjects < ActiveRecord::Migration
def change
add_reference :projects, :owner, index: true
end
end
My projects model
class Project < ActiveRecord::Base
belongs_to :owner, :class_name => :User
end
You need to add the new attribute in your json serializer.

Saving nested JSON object to database

Lets say I have a model.
Passengers belongs to Flights.
Flights belongs to Trips
class Trip < ActiveRecord::Base
has_many :flights, :dependent => :destroy, :order => "order_num ASC"
end
class Flight < ActiveRecord::Base
belongs_to :trip, touch: true
has_many :passengers, :dependent => :destroy
end
class Passenger < ActiveRecord::Base
belongs_to :flight, touch: true
end
And I'm getting this sent back to the rails app. (when the user calls save).
*The top level is the trip
{
name: 'Hello Trip',
date: '2013-08-12',
flights: [
{
id: 1
depart_airport: 'RDU',
arrive_airport: 'RDU',
passengers: [
{
user_id: 1,
request: true
}
]
},
{
depart_airport: 'RDU',
arrive_airport: 'RDU',
passengers: [
{
user_id: 1,
request: true
},
{
user_id: 2
request:true
}
]
}
]
}
Right now I'm getting the saved json in and manually looping through the flights to see if there is an id. If there is i'm updating it. If not I'm creating a new one. Then adding the passengers.
I'm wondering if there is an automatic format that Rails takes that can do all the saving for me. I know when you submit a nested form it creates a similar pattern, and adds a _destroy property and the id is a timestamp if it's just created. Would the JSON saving be similar to that?
Thanks for any help!
Yes, you should be able to use accepts_nested_attributes_for here.
You'll need to enable accepts_nested_attributes_for on your models, e.g.,
class Trip < ActiveRecord::Base
has_many :flights, :dependent => :destroy, :order => "order_num ASC"
accepts_nested_attributes_for :flights, :allow_destroy => true
attr_accessible :flights_attributes
end
You'll also need to ensure that your JSON response uses keys that Rails will recognize. You can either modify the JSON response or do something like:
response = JSON.parse(json_string)
response[:flights_attributes] = response.delete(:flights)
# ...
Then you can just do
Trip.create(response)
You'll want to ensure that everything is created/updated as expected. For more on accepts_nested_attributes_for, see the documentation: http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for.
I think accepts_nested_attributes_for is convenient, but note that there are some that think it should be deprecated (e.g., here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/, see here for a response: https://stackoverflow.com/a/17639029/1614607).

Resources