Grape api - How to POST nested resources? - ruby-on-rails

So, I have this system Im working on using rails 4 and Grape Api. Basically it aggregates information about maintenance services executed on vehicles. My models are defined like this:
# service.rb
class Service < ActiveRecord::Base
has_many :service_items
#service_item.rb
class ServiceItem < ActiveRecord::Base
belongs_to :service
Im implementing a API so external applications could post services on my system. Each service has a list of 1 or more service items associated. I have a route like: example.com/api/v1/services for POST. My question is how can I make this to accept a post with the service attributes, and service_items attributes nested with it?
I read Grape docs and started something like this:
#service_providers_api.rb
resource :services do
desc "Post a Service"
params do
#requires :category_id, type: Integer
requires :description, type: String
requires :plate, type: String
requires :mileage, type: Integer
requires :date, type: Date
optional :cost, type: BigDecimal
requires :service_items do
requires :description, type: Integer
end
end
post do
.
.
.
end
end
But Im not sure how I can mount the post data for this to work.
Is it possible to make all this in a single request like so, or do I have to make each request separated? e.g. one POST to receive the service, then a series of POSTs for every service_item associated. What is the best approach recommended in this scenario?

params do
requires :service_items, type: Hash do
requires :description, type: Integer
end
end
requires takes two param, you must provide a type. in your case a Hash

Related

Include Relationship in Rails application with Grape

I'm returning a resource in API (I'm, using Grape) but I would like to return also the relationship objects (as included objects just as an ember application expects). How can I achieve that ? My serializer is as follow:
class JudgeSerializer < ActiveModel::Serializer
attributes :ID, :FIRST_NAME, :LAST_NAME, :FULL_NAME
end
My model:
class Judge < ApplicationRecord
self.primary_key = 'id'
self.table_name = 'judge'
has_many :judgecourts, class_name: 'Judgecourt', primary_key: 'ID',foreign_key: 'JUDGE_ID'
end
I'm returning this resource this way:
desc 'Return a specific judge'
route_param :id do
get do
judge = Judge.find(params[:id])
present judge
end
end
It would be good to generate something like this:
data
:
{type: "judges", id: "1", attributes: {…}, relationships: {…}}
included
:
Array(1)
0
:
{type: "judgecourts", id: "1", attributes: {…}}
Well it looks like there are two topics in your question:
1. How to include a relationship in a ActiveModelSerializer
This can be done pretty easily by adjusting your active model serializer and adding the relation, so that ActiveModelSerializer knows that it must include the relation in the serialized objects:
class JudgeSerializer < ActiveModel::Serializer
attributes :ID, :FIRST_NAME, :LAST_NAME, :FULL_NAME
has_many :judgecourts
end
This would automatically provide the judgecourts relation inside the serialized json. An example with the classic Post/Comment objects:
2. Use specific format...
The format you specified looks a lot like JSON:API format. If that's what you're really wanting to achieve, then you might be better off using the JSON:API adapter built-in ActiveModelSerializer. For this, you need to tell AMS to use the proper adapter, maybe through an initializer file :
# ./initializers/active_model_serializer.rb
ActiveModelSerializers.config.adapter = :json_api
After this, your json should be formatted the way you seem to expect. I'm no expert in the json api specification, so there might be some more things to tweak. You'll be able to find more information about this adapter in ActiveModelSerializers wiki page on adapters, section JSON API.
Here's what I get with my Post/Comment example:
Note that there are other gems out there that were built to specifically address the JSON API specification, such as jsonapi-rb and Netflix Fast JSON API. They might be of interest for you.

Rails and mongo embed relations

I'm trying to create some relations on Mongoid but when I try to save the inner object or add it to the user.personal_accounts collection I get the following error
NoMethodError: undefined method `bson_type' for #<Bank:0x71c01a8>
My Object in rails console is correct
#<PersonalAccount _id: 56e87f669c27691be0d3041b, number: "55", active: true, bank: #<Bank _id: 56d74cdb9c27692fb4bd4c6d, code: 123, name: "Bradesco", country: "USA">>
My mappings
class PersonalAccount
include Mongoid::Document
field :number, type: String
field :active, type: Boolean
field :bank, type: Bank
embedded_in :user
end
class User
include Mongoid::Document
field :first_name, type: String
field :last_name, type: String
embeds_many :personal_accounts
end
class Bank
include Mongoid::Document
field :code, type: Integer
field :name, type: String
field :country, type: String
end
The mapping that I was expecting is:
User
PersonalAccounts
Bank
Bank
As I have read that I need to copy the outer bank to each PersonalAccount.
I have already tried the following Link
Versions installed:
bson (4.0.2)
bson_ext (1.5.1)
mongoid (5.0.2)
mongo (2.2.4)
The root of your problem is right here:
field :bank, type: Bank
MongoDB doesn't know how to store a Bank so Mongoid will try to convert it to something that MongoDB will understand while Mongoid is preparing the data for the database, hence the NoMethodError.
Presumably you want Bank to exist as its own collection and then each PersonalAccount would refer to a Bank. That would be a standard belongs_to setup:
class PersonalAccount
#... but no `field :bank`
belongs_to :bank
end
That will add a field :bank_id, :type => BSON::ObjectId to PersonalAccount behind the scenes and hook up accessor (bank) and mutator (bank=) methods for you.
Normally you'd want the other half of the relation in Bank:
class Bank
#...
has_many :personal_accounts
end
but that won't work (as you found out) because PersonalAccount is embedded inside User so Bank can't get at it directly. Keep in mind that embeds_one is just a fancy of wrapping the Mongoid machinery around a Hash field in a document and embeds_many is just a fancy way of wrapping the Mongoid machinery around an array of hashes inside another document; embedded documents don't have an independent existence, they're just a part of their parent.

Generate a ruby class in memory

I have a requirement to convert an ActiveRecord model class into a MongoDB Document class automatically. I am able to do so using a rails generator which will read the attributes of a model and generate the new document.rb.
If a ActiveRecord model class looks like below:
class Project < ActiveRecord::Base
attr_accessible :completed, :end_date, :name, :start_date
end
Then, a generated class confirming to Mongoid's structure will be as below:
class ProjectDocument
field :name, type: String
field :start_date, type: Date
field :end_date, type: Date
field :completed, type: Boolean
field :created_at, type: Time
field :updated_at, type: Time
end
But I don't want to store a different document files, one for each model. I want to be able to generate this document class on the fly, whenever the rails application is started.
Is this possible? Is this approach of generating and using classes from memory advised? I don't have constraints on changes to AR model structure; the document is flexible w.r.t data structure and changed columns will get added automatically.
My first attempt would look something like this:
klass = Project
new_class = Object.const_set(klass.name + "Document", Class.new)
klass.columns.each do |c|
new_class.class_eval do
field c.name.to_sym, type: c.type
end
end
You'll almost certainly have to do something more complicated to set the field type correctly, but this should give you a good starting point.

Mongoid relation with a pre-defined object model?

Here's my use case:
I've got a collection full of sales tax rates that have been imported from CSV files. I created the Mongoid model to mirror the field names (these are not changeable):
class SalesTaxRate
include Mongoid::Document
field :state, type: String
field :zip_code, type: String
field :tax_region_name, type: String
field :tax_region_code, type: String
field :combined_rate, type: Float
end
Next, I'm building a model for use in my app. Let's say I want to create something called a Location:
class Location
include Mongoid::Document
field :name, type: String
field :street, type: String
field :city, type: String
field :state, type: String
field :zip_code, type: String
end
I'd like to be able to get a location's sales tax rate simply by calling something like this:
home = new Location(...)
home.sales_tax_rate
I'll never be setting the rate via home, just looking it up.
What's the "right" way to do this? I can think of two approaches -- the simple way seems to be just to define a method that does the lookup, as so:
class Location
...
def sales_tax_rate
SalesTaxRate.where(zip_code: self.zip_code).first.combined_rate
end
And this works. But I'm wondering whether I should be using a belongs_to association and, if so, why and how best to do that.
Still learning the ropes here, so apologies if this is a novice/silly question. Many thanks in advance!
If you have an index on zip_code in model SalesTaxRate what you are doing is essentially the same as what belongs_to will do. Just have a nil check in your code to ensure that it doesn't fail:
SalesTaxRate.where(zip_code: self.zip_code).first.try(:combined_rate)
# or
rate = SalesTaxRate.where(zip_code: self.zip_code).first
rate.nil? ? nil : rate.combined_rate
If you still want to go belongs_to route, you can define zip_code to be the identity in your SalesTaxRate. But you should take care of few things if you do that: First, all the zip codes in imported data need to be unique. Second, your location model can not have any zip code which is not available in SalesTaxRate otherwise you will face issues.

Custom fields with mongoid

I started to use the mongoid gem in my project, and I'm a little confused about how it store and get the information on the database. I have fields of specifics types in my models, but when I get it from the DB it returns a Hash.
Here is my models:
service.rb
class Service
include Mongoid::Document
field :username, type: String
field :strategy, type: Strategy
field :design, type: Design
end
strategy.rb
class Strategy
include Mongoid::Document
field :name, type: String
field :description, type: String
field :resources, type: Resources
field :scalability, type: Scalability
field :localization, type: Localization
field :contact, type: Contact
end
If I initialize a new service #service, and do #service.class it returns Service, the right one, but if I try do #service.strategy.class, it returns Hash, and not Strategy, like I was expecting. I read on the mongoid manual there are the "Custom field serialization", what I think allows me to do what I want. But I was wondering if there are not any other way to do that easily, because I have lots of models to change.

Resources