Grape API in Rails and issues with Strong Parameters - ruby-on-rails

Here is what my API looks like
resource :service_requests do
before do
error!('Unauthorized. Invalid token', 401) unless current_company
end
get do
current_company.service_requests
end
params do
requires :service_request, type: Hash do
optional :prefix, type: String
requires :first_name, type: String
requires :last_name, type: String
requires :contact_email, type: String, regexp: User::EMAIL_REGEX
requires :phone_number, type: String
requires :address, type: String
optional :address2, type: String
requires :city, type: String
requires :state, type: String
requires :zip_code, type: String
requires :country, type: String
requires :address_type, type: String
requires :troubleshooting_reference, type: String
requires :line_items, type: Array do
requires :type, type: String
requires :model_number, type: String
requires :serial_number, type: String
optional :additional_information, type: String
end
end
end
post do
parameters = ActionController::Parameters.new(params).require(:service_request)
sr = ServiceRequest.new(
parameters.permit(
:troubleshooting_reference,
:rma,
:additional_information
)
)
sr.build_customer(
parameters.permit(
:prefix,
:first_name,
:last_name,
:contact_email,
:phone_number
)
)
#
# shipping_info = customer.build_shipping_information(
# parameters.permit(
# :address,
# :address2,
# :company_name,
# :city,
# :state,
# :zip_code,
# :country,
# :address_type
# )
# )
if sr.save
sr
else
sr.errors.full_messages
end
end
end
The problem I am running into is that when the save method is called, I am getting this error Unpermitted parameters: first_name, last_name, contact_email, phone_number, address, city, state, zip_code, country, address_type, line_items
Here is what my JSON post looks like:
{
"service_request": {
"first_name": "Foo",
"last_name": "Bar",
"contact_email": "foo#bar.com",
"phone_number": "111-111-1111",
"address": "102 foo st",
"city": "Nashville",
"state": "TN",
"zip_code": "23233",
"country": "USA",
"address_type": "Business",
"troubleshooting_reference": "dshjf",
"line_items": [
{
"type": "Unit",
"model_number": "123",
"serial_number": "222"
}
]
}
}

Having tried your code locally, I think what you're seeing is normal behaviour. I, too see the "Unpermitted parameters..." messages logged to the console, but the calls to parameters.permit do succeed and return filtered hashes as you expect. So I think if you check you'll find your code is actually working.
You can silence those messages by adding
ActionController::Parameters.action_on_unpermitted_parameters = false
either at the top of your class or in a file under config/initializers.

Related

FactoryGirl.create does not work while upgrading mongoid version from 5 to 6. Below is the issue I run into while running rspec test

This is the place where is create a test table:
factory :reward_scheme, class: RewardsModels::RewardScheme do
uid { ExpectedData::COSTA_UID }
scheme_type { "bricks" }
reward_type { "menu"}
company_address { FactoryGirl.build(:company_address) }
reward_config { FactoryGirl.build(:reward_config) }
brand { FactoryGirl.build(:brand) }
master_offers { [ FactoryGirl.build(:master_offer) ] }
master_specials { [ FactoryGirl.build(:master_special) ] }
url "http://costa.com"
after(:create) do |reward_scheme|
reward_scheme.stores << FactoryGirl.create(:store)
reward_scheme.user_cards << FactoryGirl.create(:user_card)
end
end
The logs are as follows:
CoreModels::Transaction
Failure/Error: reward_scheme.stores << FactoryGirl.create(:store)
Mongoid::Errors::Validations:
message:
Validation of RewardsModels::Store failed.
summary:
The following errors were found: Reward scheme can't be blank
resolution:
Try persisting the document with valid data or remove the validations.
# ./spec/factories/reward_scheme.rb:15:in `block (3 levels) in <top (required)>'
# ./spec/core/transaction_spec.rb:6:in `block (2 levels) in <top (required)>'
This is how the model file looks like:
module UserModels
class Store
include Mongoid::Document
include Mongoid::Timestamps
field :reward_scheme_id, type: String
field :store_id, type: String
field :store_name, type: String, default: "HQ"
field :reward_scheme_name, type:String
field :about, type: String, default: "MyGravity - loyalty begins with trust"
field :logo, type: String, default: 'https://static.mygravity.co/partners/logo.svg'
field :splash_screen_url, type: String, default: "https://static.mygravity.co/assets/SplitShire_Blur_Background_XVI.jpg"
field :awaiting_update, type: Boolean, default:false
embeds_one :location, class_name:'UserModels::Location'
embeds_one :open_hours, class_name:'UserModels::OpenHours'
embeds_one :optional, class_name:'UserModels::Optional'
embeds_many :specials, class_name:'UserModels::Special'
embeds_many :offers, class_name:'UserModels::Offer'
before_create :set_defaults
def set_defaults
self.location = UserModels::Location.new unless self.location
self.optional = UserModels::Optional.new unless self.optional
end
end
class Location
include Mongoid::Document
field :longitude, type: Float, default: -0.131425
field :latitude, type: Float, default: 51.507697
field :address_line_1, type: String, default: 'Impact Hub - Westmister'
field :post_code, type: String, default: 'SW1Y 4TE'
field :city_town, type: String, default: 'London'
embedded_in :store
end
class OpenHours
include Mongoid::Document
field :monday, type: String
field :tuesday, type: String
field :wednesday, type: String
field :thursday, type: String
field :friday, type: String
field :saturday, type: String
field :sunday, type: String
field :sunday_1, type: String
embedded_in :store
end
class Special
include Mongoid::Document
# Need this to search
field :special_id, type: Integer
field :special_uid, type: Integer
field :title, type: String
field :text, type: String
embedded_in :store
before_save :set_special_uid
def set_special_uid
self.special_uid = self.special_id
end
def attributes
# p super
# p Hash[super.map{|k,v| [(alais[k] || k), v]}]
hash = super
alais = {'special_id' => 'id'}
hash.keys.each do |k,v|
hash[ alais[k] ] = hash['special_id'].to_s if alais[k]
# Need this as special_id is mapped in the iOS to a string...
hash['special_id'] = hash['special_id'].to_s if k == 'special_id'
end
hash
end
end
class Offer
include Mongoid::Document
field :name, type: String
field :offer_id, type: Integer
field :value, type: Float, default:0.0 # monetary value
field :points, type: Integer
field :icon_url, type: String
field :icon_name, type: String
embedded_in :store
def attributes
# p super
# p Hash[super.map{|k,v| [(alais[k] || k), v]}]
hash = super
alais = {'offer_id' => 'id'}
hash.keys.each { |k,v| hash[ alais[k] ] = hash['offer_id'] if alais[k] }
hash
end
end
class Optional
include Mongoid::Document
field :email, type: String, default:""
field :twitter, type: String, default:""
field :telephone, type: String, default:""
field :wifi_password, type: String, default:""
embedded_in :store
end
end
Any leads regarding the code changes required for upgrading to mongoid 6 is highly appreciated.
Thanks

Mongoid ignores length validation on embedded document

I have the following model:
class Man < User
field :surname, type: String, default: ""
field :name, type: String, default: ""
NAME_LENGTH = 1..50
validates :surname, length: {in: NAME_LENGTH}, allow_blank: true
validates :name, length: {in: NAME_LENGTH}, allow_blank: true
validates :father, length: {in: NAME_LENGTH}, allow_blank: true
field :birthday, type: Integer #Timestamp
field :about, type: String
ABOUT_LENGTH = 2000
validates :about, length: {maximum: ABOUT_LENGTH}, allow_blank: true
embeds_many :skills
MAX_SKILLS_COUNT = 10
validates :skills, length: {maximum: MAX_SKILLS_COUNT}
#user got unique skills
index({login: 1, 'skills.caption_to_index': 1}, {unique: true, sparse: true})
end
And embedded skills model:
class Skill
include Mongoid::Document
CAPTION_LENGTH = 1..50
CAPTION_FILTER = /[^a-zа-я0-9]/
field :caption, type: String
field :caption_to_index, type: String
field :order, type: Integer
embedded_in :man
validates :caption, length: {in: CAPTION_LENGTH}
validates :order, presence: true
before_save do |document|
document.caption_to_index = document.caption.downcase.gsub(CAPTION_FILTER,'')
end
end
The trouble is, mongoid ignores length validation when I do something like this:
pry(main)> u = Man.find_by(login:'tester')
pry(main)> s3 = Skill.new(caption:'JavaScript',order:1)
pry(main)> u.skills << s3
I can repeat u.skills << s3 infinite times, and driver saves this to database immidiately.
Another problem is that unique index is not working:
index({login: 1, 'skills.caption_to_index': 1}, {unique: true, sparse: true})
All indexes were created successfully via rake:mongoid:create_indexes
Driver allows to save document with the same caption, here is part of the document in mongo shell as the proof:
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
},
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
},
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
},
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
}
Test case for uniqueness also fails:
require 'rails_helper'
include ApplicationHelper
describe Skill do
before :each do
#attrs = attributes_for :tester_man
#tester = create :tester_man
expect(#tester).to be_valid
expect(#tester.persisted?).to be true
end
it "skill caption is unique" do
#debugger
s1_good = Skill.new(caption:'<<cap Tion ..%#',order:2)
s2_good = Skill.new(caption:'other_caption',order:4)
s3_bad = Skill.new(caption:'CAPTION',order:1)
expect(s1_good).to be_valid
expect(s2_good).to be_valid
#tester.skills = [s1_good,s2_good]
#tester.save
expect(#tester).to be_valid
s3_bad = Skill.new(caption:'CAPTION',order:1)
#tester.skills << s3_bad
expect(#tester).not_to be_valid
end
end
How can I get this code work as expected?

Test error message when validating model with rspec

I've inherited a rails api and I'm trying to test controllers. I have an endpoint '/api/v1/vitcords' where I create new vitcords. The video model only has a validation name. So my doubt is how to test that when I create a new video without specify a name, I get the message error I want, that in this case is "Vitcord name has to be specified". Thanks.
This is the Vcord model
class Vcord
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Spacial::Document
include Concerns::Vitcord::M3u8
include Concerns::Vitcord::User
# Validations
validates_presence_of :name
# Atributes
field :name, type: String
field :location, type: Array, spacial: true
field :address, type: String
field :closed, type: Boolean, default: false
field :public, type: Boolean, default: false
field :thumb, type: String
end
This is the controller video_controller.rb
module Api::V1
class VitcordsController < ApiController
def create
user = current_resource_owner
# Validation
param! :name, String, required: true
param! :location, Hash, required: false
param! :address, String, required: false
ap params[:location]
ap params[:location][:latitude]
ap params[:location][:longitude]
# Creating
vcord = Vcord.new()
vcord.name = params[:name] if params[:name]
if params[:location] && params[:location]['latitude'] && params[:location]['longitude']
vcord.location = {
lng: params[:location]['longitude'],
lat: params[:location]['latitude']
}
end
vcord.address = params[:address] if params[:address]
vcord.creator = user
if vcord.save
vcord.members.each do |member|
Notification.generate_notification_for_vitcord_invited(vcord, member)
end
#vitcord = vcord
else
error(:internal_server_error, ["Vitcord name has to be specified"], nil)
end
end
end
And this is the spec
require 'rails_helper'
describe "POST /api/v1/vitcords" do
before(:each) do
db_drop
post "/oauth/token", {
:grant_type => 'assertion',
:assertion => TOKEN
}
#token = response_json['access_token']
end
it "sends an error if a vitcord is created with name nil" do
header 'Authorization', "Bearer #{#token}"
post '/api/v1/vitcords', {
address: "Calle Rue del Percebe",
location: {
latitude: 40.7127837,
longitude: -74.00594130000002
}
}
//This does not work but it would be something like this
expect(error).to eq("Name has to be specified")
end
end
Well, you should refactor your code, but answering your question, you can add an error to you object by doing (look that I used #vcord and not vcord):
#vcord.errors.add(:name, 'Vitcord name has to be specified')
(as you can see here http://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add)
and on your test:
expect(assigns(:vcord).errors.name).to eq('Vitcord name has to be specified').

Ruby JSON API and post request with Postman

I'm using postman to make post request to my rails/grape API
Here is the json object
{
"customer":{
"first_name":"Joe",
"last_name":"Doe",
"email":"test#test.com",
"phone":"999-999-9999",
"addresses_attributes":[{
"address":{
"address1":"123 somewhere st",
"customer_id":"",
"address2":"",
"city":"Moldor",
"state":"CA",
"zip":"99999",
"region_id":"1"
}
}]
}
}
If send this to a api/v1/customers.json
I get the following error message
"error": "first_name is missing, last_name is missing, email is missing, phone is missing",
If I change the JSON format to:
{
"first_name":"Joe",
"last_name":"Doe",
"email":"test#test.com",
"phone":"999-999-9999",
"addresses_attributes":[{
"address":{
"address1":"123 somewhere st",
"customer_id":"",
"address2":"",
"city":"Moldor",
"state":"CA",
"zip":"99999",
"region_id":"1"
}
}]
}
I does creates the customer but it does not creates the address
In my Models I have
#customer.br
has_many :addresses
accepts_nested_attributes_for :addresses, allow_destroy: true
#address.rb
belongs_to :customer
Here is the api/vi/curtomer.rb
desc "Create a Customer"
params do
requires :first_name, type: String, desc: "First Name"
requires :last_name, type: String, desc: "Last Name"
requires :email, type: String, desc: "Email"
requires :phone, type: String, desc: "Phone"
optional :notes, type: String, desc: "Notes"
end
post do
# Tried with new instead of create
# test = Customer.new({
# first_name: params[:first_name],
# last_name: params[:last_name],
# email: params[:email],
# phone: params[:phone],
# notes: params[:notes],
# addresses_attributes: params[:addresses]
# })
Customer.create({
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:email],
phone: params[:phone],
notes: params[:notes]
# addresses_attributes: params[:addresses]
})
end
The address looks pretty much the same as the customers
I'm not using controllers per Grapes Documentation
EDIT: to add customer.rb create code
Think your json data should be:
{ "customer":
{
"first_name":"Joe",
"last_name":"Doe",
"email":"test#test.com",
"phone":"999-999-9999",
"addresses_attributes":[{
"address1":"123 somewhere st",
"customer_id":"",
"address2":"",
"city":"Moldor",
"state":"CA",
"zip":"99999",
"region_id":"1"
}]
}
}
Refer to this example.
If it not works, show your controller code.

Using Grape to build a Rails API but having issues posting JSON

Here is my API post:
resource :service_requests do
get do
authenticate!
current_company.service_requests
end
params do
requires :service_request, type: Hash do
optional :prefix, type: String
requires :first_name, type: String
requires :last_name, type: String
requires :contact_email, type: String, regexp: User::EMAIL_REGEX
requires :telephone, type: String
end
end
post do
authenticate!
{ service_request: params[:service_request] }
end
end
Here is what my json post looks like:
{
'service_request': {
'first_name': 'Foo',
'last_name': 'Bar',
'contact_email': 'foo#bar.com',
'telephone': '111-111-1111'
}
}
The error I am receiving is:
ActionDispatch::ParamsParser::ParseError (795: unexpected token at '{
'service_request': {
'first_name': 'Foo',
'last_name': 'Bar',
'contact_email': 'foo#bar.com',
'telephone': '111-111-1111'
}
}'):
Not sure what I am doing wrong. Anyone see anything that stands out?
Use double quotes in your json:
{
"service_request": {
"first_name": "Foo",
"last_name": "Bar",
"contact_email": "foo#bar.com",
"telephone": "111-111-1111"
}
}

Resources