Rails-Grape Nested PUT routes for Ember - ruby-on-rails

I create Grape-create action which work perfectly
desc "Create a project."
params do
group :project, type: Hash do
requires :name, type: String, desc: "Name of project."
requires :user_id, type: String, desc: "user id"
requires :description, type: String, desc: "Description of project"
requires :project_type_id, type: String, desc: "Type of project"
end
end
post do
Project.create!(
name: params[:project][:name],
user_id: params[:project][:user_id],
description: params[:project][:description],
project_type_id: params[:project][:project_type_id]
)
end
But, when I want to create a PUT action to edit some values, I have error from response in JSON: error: "project[id] is missing"
My PUT code:
desc "Update a project."
params do
group :project, type: Hash do
requires :id, type: String, desc: "project ID."
requires :name, type: String, desc: "Name of project."
requires :user_id, type: String, desc: "user id"
requires :description, type: String, desc: "Description of project"
requires :project_type_id, type: String, desc: "Type of project"
end
end
put ':id' do
Project.find(params[:project][:id]).update!(
name: params[:project][:name],
user_id: params[:project][:user_id],
description: params[:project][:description],
project_type_id: params[:project][:project_type_id]
)
end
What I do wrong with this?

OK. I repair this by this code and it work
desc "Update a project."
params do
group :project, type: Hash do
requires :name, type: String, desc: "Name of project."
requires :user_id, type: String, desc: "user id"
requires :description, type: String, desc: "Description of project"
requires :project_type_id, type: String, desc: "Type of project"
end
end
put ':id' do
Project.find(params[:id]).update!(
name: params[:project][:name],
user_id: params[:project][:user_id],
description: params[:project][:description],
project_type_id: params[:project][:project_type_id]
)
end

I'm not sure of your structure without seeing what you are sending in your PUT request, but I would think the Grape code should be like - but again, I'm totally guessing without seeing your JSON payload.
put ':id' do
Project.find(params[:id]).update!(
name: params[:name],
user_id: params[:user_id],
description: params[:description],
project_type_id: params[:project_type_id]
)
end

Related

Re-instancing a class after autoloading with Rails and RSpec

I have this Class where I created some attributes depending on a database table:
class Form
include ActiveAttr::Model
attribute :type, type: String
attribute :default_name, type: String
Languages.all.each do |lang|
attribute :"name_#{lang}", type: String
end
end
This works fine, but then I made two tests with RSpec:
Unit Test:
require 'rails_helper'
RSpec.describe Form, type: :class do
...
end
E2E Test:
require 'rails_helper'
RSpec.describe 'forms', type: :system do
let!(:languages) do
create(:language, name: 'es')
create(:language, name: 'en')
end
scenario 'accessing the page where I can see all the attributes of the Form' do
#form = create(:form, :with_languages)
visit form_page(#form)
end
...
When I run rspec Rails autoloads everything and the Form class is created without any Language in the database yet, so it hasn't any name_ attribute. The first test works fine, but the second one fails because the Form Class was loaded without the Languages mocked:
undefined method `name_en' for #<Form:0x000000014766c4f0>
This is because in order to load the view, we do #view = Form.new(#form) in the controller. And apparently it doesn't create a new object.
If I only run the second test it works like a charm, I tried with DatabaseCleaner but it's the same.
Is there a way to do this without disabling autoloading? I tried disabling it with config.autoload_paths but it gives me thousands of errors and it's a huge app.
I tried several solutions but none of them work, what I need is to re-create that class.
#view = Form.new(#form) does create a new object but it doesn't reload the class, so attributes do not get defined again. attribute is a class method it runs when Form class is loaded, it doesn't run when Form.new is called.
I'm not sure what you're doing with just Form class, usually you'd have a specific class for each form. You could add attributes later, but it is a permanent change to Form class:
>> Form
# Form class is autoloaded and all attributes are defined
Language Load (0.9ms) SELECT "languages".* FROM "languages"
=> Form(default_name: String, type: String)
>> Language.create(name: :en)
>> Form
# will not add language attributes
=> Form(default_name: String, type: String)
>> Form.attribute :name_en, type: String
=> attribute :name_en, :type => String
>> Form
=> Form(default_name: String, name_en: String, type: String)
You could change Form class when initializing a new object:
class Form
include ActiveAttr::Model
attribute :type, type: String
attribute :default_name, type: String
def self.lang_attributes languages = Language.pluck(:name)
Array.wrap(languages).each do |lang|
attribute :"name_#{lang}", type: String
end
end
lang_attributes
def initialize(...)
self.class.lang_attributes
super
end
end
>> Language.create(name: :en)
>> Form # first call to Form autoloads it
Language Pluck (0.8ms) SELECT "languages"."name" FROM "languages"
=> Form(default_name: String, name_en: String, type: String)
>> Language.create(name: :es)
>> Form.lang_attributes
>> Form
=> Form(default_name: String, name_en: String, name_es: String, type: String)
>> Language.create(name: :fr)
>> my_form = Form.new
Language Pluck (0.6ms) SELECT "languages"."name" FROM "languages"
=> #<Form default_name: nil, name_en: nil, name_es: nil, name_fr: nil, type: nil>
# NOTE: but this changes the class itself
If you want the actual class to be dynamic, then the class definition has to be dynamic:
class FormClass
def self.new langs: Language.pluck(:name)
Class.new do
include ActiveAttr::Model
attribute :type, type: String
attribute :default_name, type: String
Array.wrap(langs).each do |lang|
attribute :"name_#{lang}", type: String
end
end
end
end
>> form_class = FormClass.new # aka Form
Language Load (0.5ms) SELECT "languages".* FROM "languages"
=> (default_name: String, name_en: String, name_es: String, name_fr: String, type: String)
>> form_object = form_class.new # aka Form.new
=> #< default_name: nil, name_en: nil, name_es: nil, name_fr: nil, type: nil>
>> Language.destroy_all
>> form_object = FormClass.new.new
Language Load (0.7ms) SELECT "languages".* FROM "languages"
=> #< default_name: nil, type: nil>
# no more language attributes ^
# and you can override language attribute(s)
>> FormClass.new(langs: :en).new
=> #< default_name: nil, name_en: nil, type: nil>

Is there a way to include an external Params block in a Rails Grape resource?

I'm using Ruby on Rails 4 and Grape.
I'd like my Grape Resources to take up a little space so that they are more readable by other developers.
In the last few days we have been integrating the Stripe API (as an example)
and in the params do section of the Resources there are code blocks like this:
desc 'Add bank account' do
headers API::V1::Defaults.xxxxxxx
success API::V1::Entities::xxxxxxx
end
params do
requires :external_account, type: Hash, allow_blank: false, desc: 'Bank account nested data' do
requires :bank_account, type: Hash, allow_blank: false, desc: 'Bank account nested data' do
requires :id, type: String, desc: 'Stripe token for bank account'
requires :account_holder_name, type: String, desc: 'Bank account holder name'
requires :account_holder_type, type: String, desc: 'Bank account holder type [individual or company]'
optional :bank_name, type: String, desc: 'Bank name'
requires :country, type: String, desc: 'Bank account country'
optional :currency, type: String, desc: 'Bank account currency'
requires :routing_number, type: String, desc: 'Bank account routing number'
requires :name, type: String, desc: 'Bank account holders name'
requires :status, type: String, desc: 'Bank account status'
requires :last4, type: Integer,
desc: 'Account holder ID number.'
end
requires :client_ip, type: String, desc: 'IP address of user for Stripe service agreement'
end
requires :email, type: String, desc: 'Users email'
requires :business_type, type: String, desc: 'Individual or Company'
requires :tos_acceptance, type: Hash, allow_blank: false, desc: 'Type of Service' do
requires :date, type: Integer, desc: 'ToS [date]'
requires :ip, type: String, desc: 'ToS [ip]'
end
optional :individual, type: Hash do
requires :first_name, type: String, desc: 'Individuals [first name]'
requires :last_name, type: String, desc: 'Individuals [last name]'
requires :ssn_last_4, type: String, desc: 'Individuals SSN number'
optional :dob, type: Hash do
requires :day, type: String, desc: 'Individuals date of birth [day]'
requires :month, type: String, desc: 'Individuals date of birth [month]'
requires :year, type: String, desc: 'Individuals date of birth [year]'
end
end
optional :company, type: Hash do
requires :name, type: String, desc: 'Company [first name]'
requires :email, type: String, desc: 'Company [email]'
requires :phone, type: String, desc: 'Company [phone]'
end
end
# ...
oauth2
post '/' do
# ...
end
How can I make that params block go to another file (for example inside a file in the helpers folder) and the params block can be included?
I tried to do this with include API::V1::Helpers::my_helper but I don't know how to insert the params block. Can someone help me, please?
You might use shared params
module SharedParams
extend Grape::API::Helpers
params :pagination do
optional :page, type: Integer
optional :per_page, type: Integer
end
end
class API < Grape::API
helpers SharedParams
desc 'Get collection.'
params do
use :pagination
end
get do
# your logic here
end
end

Rails - Array of hashes in Grape API

I'm really a new guy to Rails, in my project I want to submit a JSON string to Grape API. As you can see, my JSON has an user array that contains many objects. How can I define it in my Grape ?
Thank you
{
"users":[
{
"first_name":"Brittany",
"last_name":"Chen",
"email":"comstock#mailinator.com",
"phone_number":"+29-46-957-15423"
},
{
"first_name":"Lynn",
"last_name":"Brooks",
"email":"jensen#mailinator.com",
"phone_number":"+84-95-185-00137"
},
{
"first_name":"Claire",
"last_name":"Paul",
"email":"mei#mailinator.com",
"phone_number":"+66-64-893-53401"
},
{
"first_name":"Gemma",
"last_name":"Carter",
"email":"malik#mailinator.com",
"phone_number":"+83-46-325-54538"
}
],
"service_ids":["1", "2", "3"],
"auth_token":"xxxxxxxxxxxxxxxxxxxxxx"
}
this is my Grape params
params do
optional :user, type: Hash do
optional :email, type: String, desc: "user email"
optional :first_name, type: String, desc: "user first name"
optional :last_name, type: String, desc: "user last name"
optional :phone_number, type: String, desc: "user phone number"
end
optional :service_ids, type: Array[Integer], desc: "list of service ids selected"
requires :auth_token, type: String, desc: "authentication_token"
end
This is called "Validation of nested parameters" in Grape. In your code, you were actually asking for a user hash containing optional parameters email, first_name, last_name and phone_number, so not exactly what you were looking for.
With a block, group, requires and optional accept an additional option type which can be either Array or Hash, and defaults to Array. Depending on the value, the nested parameters will be treated either as values of a hash or as values of hashes in an array.
Source: https://github.com/ruby-grape/grape#validation-of-nested-parameters
So in your case, you would have to describe your params like this :
params do
optional :users, type: Array do
optional :email, type: String, desc: "user email"
optional :first_name, type: String, desc: "user first name"
optional :last_name, type: String, desc: "user last name"
optional :phone_number, type: String, desc: "user phone number"
end
# ...
# any other params
# ...
end
And so each item in the array will be expected to match the fields in the given block.

How to conditionally require a parameter based on a previous parameter

I am working with apipie for my rails app API and have a parameter that needs to be conditionally required. If a user is hired, they need to have a hired_at date. For other reasons those must be 2 separate columns. I cannot just check for the presence of a hired_at date...
What I currently have is essentially the following:
param :person, Hash, desc: "person information", required: true do
param :name, String, desc: "person name", required: true
param :hired, [true, false], desc: "person has been hired", required: true
param :dates, Array, of: Hash, desc: "important dates", required: true do
param :hired_at, String, desc: "date of fire", required: <if hired == true >
end
end
the <if hired==true> is pseudocode. That is the logic I need there but I don't know how to implement it.

Rails: documenting post endpoint which consumes json params using swagger-blocks?

Using swagger-blocks in rails, would i document a post endpoint which consumes a single json body such as:
{
"id":"1",
"name": "bill",
"age":"22"
}
No matter what I do, my tests keep saying that my setup is not valid Swagger 2.0 JSON schema.
Below is the code I am using to generate my documentation:
swagger_path '/list/add' do
operation :post do
key :summary, 'Add person to list'
parameter name: :id, in: :body, required: true, type: :string
parameter name: :name, in: :body, required: true, type: :string
parameter name: :age, in: :body, required: true, type: :string
response 200 do
key :description, 'Successfully added to list'
end
end
end
The JSON seems correct syntactically except that "id" should be generated automatically by default if you are trying to create. You may check this specification for violations.

Resources