Mongoid ignores length validation on embedded document - ruby-on-rails

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?

Related

Use form object for the new/create and edit/update?

I'm starting my adventure with form objects.
I have a problem with create form object for new/create and edit/update.
Maybe exist a trick, which I do not know?
I have Branch, branch has one BranchAddress.
BranchAddress always is create after create Branch.
This is my code. This is well written?
class BranchesController < ApplicationController
def new
#branch_form = BranchForm.new
end
def edit
#branch = Branch.find(params[:id])
#branch_form = BranchForm.new(branch: #branch)
end
def update
#branch = Branch.find(params[:id])
#branch_form = BranchForm.new(branch: #branch, attributes: branch_params)
if #branch_form.update
render root_path
else
render :edit
end
end
def create
#branch_form = BranchForm.new(user: current_user, attributes: branch_params)
if #branch_form.save
redirect_to root_path
else
render :new
end
end
private
def branch_params
params.require(:branch).permit( :branch_name,
:branch_description,
:branch_url,
:branch_publish,
:branch_main,
:branch_date_published,
:address_country,
:address_street,
:address_zip_code,
:address_city,
:address_real_estate,
:address_apartment,
:address_lat,
:address_lng )
end
I have form object
class BranchForm < BaseForm
attr_reader :branch
attr_reader :address
#
## branch address
#
attribute :branch_name, String
attribute :branch_description, String
attribute :branch_url, String
attribute :branch_publish, Boolean, default: false
attribute :branch_main, Boolean
attribute :branch_date_published, Date
# branch address
attribute :address_country, String
attribute :address_street, String
attribute :address_zip_code, String
attribute :address_city, String
attribute :address_real_estate, String
attribute :address_apartment, String
attribute :address_lat, String
attribute :address_lng, String
#
## branch validator
#
validates :branch_name,
presence: true,
length: { maximum: 255 }
validates :branch_description,
presence: true,
length: { maximum: 65535 }
validates :branch_url, length: { maximum: 255 }
#
## branch address validator
#
validates :address_country, length: { maximum: 255 }
validates :address_street, length: { maximum: 255 }
validates :address_zip_code, length: { maximum: 255 }
validates :address_city, length: { maximum: 255 }
validates :address_real_estate, length: { maximum: 255 }
validates :address_apartment, length: { maximum: 255 }
validates :address_lat, length: { maximum: 255 }
validates :address_lng, length: { maximum: 255 }
def initialize(user: nil, branch: nil, attributes: {})
if !branch.nil?
#branch = branch
#branch_address = #branch.branch_address
#user = user
super(branch_attributes.merge(attributes))
super(branch_address_attributes.merge(attributes))
end
super(attributes)
end
def save
if valid?
create_branch
update_branch_address
true
else
false
end
end
def update
if valid?
update_branch
update_branch_address
true
else
false
end
end
def update_branch
#branch.update_attributes(branch_permitted_parameters)
end
def create_branch
#branch = #user.branches.create(branch_permitted_parameters)
end
def update_branch_address
#branch.branch_address.update_attributes(branch_address_permitted_parameters)
end
def branch_permitted_parameters
{
name: branch_name,
description: branch_description,
url: branch_url,
publish: branch_publish,
main: branch_main,
date_published: branch_date_published,
}
end
def branch_address_permitted_parameters
{
country: address_country,
street: address_street,
zip_code: address_zip_code,
city: address_city,
real_estate: address_real_estate,
apartment: address_apartment,
lat: address_lat,
lng: address_lng,
}
end
def branch_attributes
{
branch_name: branch.name,
branch_description: branch.description,
branch_url: branch.url,
branch_publish: branch.publish,
branch_main: branch.main,
branch_date_published: branch.date_published
}
end
def branch_address_attributes
{
address_country: #branch_address.country,
address_street: #branch_address.street,
address_zip_code: #branch_address.zip_code,
address_city: #branch_address.city,
address_real_estate: #branch_address.real_estate,
address_apartment: #branch_address.apartment,
address_lat: #branch_address.lat,
address_lng: #branch_address.lng,
}
end
end
It's my simple_form
= simple_form_for(#branch_form, as: :branch, url: generate_url, method: generate_method) do |f|
= f.input :branch_name
= f.input :branch_description
= f.input :branch_url
= f.input :branch_main, as: :boolean, wrapper: :icheck_form_left_text
= f.input :branch_publish, as: :boolean, wrapper: :icheck_form_left_text
= f.input :branch_date_published, wrapper: :datepicker, icon: "fa fa-calendar", :input_html => {minlength: 8, required: true, data: { :mask => "9999-99-99"}}
= f.input :address_lat
= f.input :address_lng
= f.input :address_country
= f.input :address_city
= f.input :address_zip_code
.form-group
= f.input :address_street, wrapper: :address, maxlength: 255
= f.input :address_real_estate, wrapper: :address_nr, input_html: {:min => 1}
#slash_adress
= f.input :address_apartment,wrapper: :address_nr, input_html: {:min => 1}
.hr-line-dashed
.form-group
.col-sm-4.col-sm-offset-2
= f.submit "Save", class: "btn btn-primary"
Url and method for simple_form I generate in helper.
In class BranchForm, can someone wrote better or have a better idea?

Rails - Test validation of enum fields

I'm using Rails 4 enums and I want to properly test them, so I set these tests up for my enum fields:
it { should validate_inclusion_of(:category).in_array(%w[sale sale_with_tax fees lease tax_free other payroll]) }
it { should validate_inclusion_of(:type).in_array(%w[receivable payable]) }
And this is the model they're validating:
class Invoice < ActiveRecord::Base
belongs_to :user
enum category: [:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll]
enum type: [:receivable, :payable]
validates :user, presence: true
validates :issue_date, presence: true
validates :series, presence: true
validates :folio, presence: true
validates :issuing_location, presence: true
validates :payment_method, presence: true
validates :last_digits, presence: true
validates :credit_note, presence: true
validates :total, presence: true
validates :subtotal, presence: true
validates :category, presence: true
validates_inclusion_of :category, in: Invoice.categories.keys
validates :type, presence: true
validates_inclusion_of :type, in: Invoice.types.keys
end
But when I run the tests I get:
1) Invoice should ensure inclusion of type in [0, 1]
Failure/Error: it { should validate_inclusion_of(:type).in_array([0,1]) }
ArgumentError:
'123456789' is not a valid type
# ./spec/models/invoice_spec.rb:20:in `block (2 levels) in <top (required)>'
2) Invoice should ensure inclusion of category in [0, 1, 2, 3, 4, 5, 6]
Failure/Error: it { should validate_inclusion_of(:category).in_array([0,1,2,3,4,5,6]) }
ArgumentError:
'123456789' is not a valid category
# ./spec/models/invoice_spec.rb:19:in `block (2 levels) in <top (required)>'
I've also tried with string values in the test arrays, but I get the same error and I really don't understand it.
Using Shoulda matchers we can use the following to test the enum
it { should define_enum_for(:type).with([:receivable, :payable]) }
it { should define_enum_for(:category).
with(:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll) }
Try this:
it { should validate_inclusion_of(:category).in_array(%w[sale sale_with_tax fees lease tax_free other payroll].map(&:to_sym)) }
Additionally, for code-cleanup, try putting the valid categories/types in a corresponding constant. Example:
class Invoice < ActiveRecord::Base
INVOICE_CATEGORIES = [:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll]
enum category: INVOICE_CATEGORIES
end
Your migration could be the issue, it should look something like:
t.integer :type, default: 1
You may also consider testing this another way.
Maybe more like:
it "validates the category" do
expect(invoice with category fee).to be_valid
end
Use shoulda matchers along with check for column_type.
it do
should define_enum_for(:type).
with_values(:receivable, :payable).
backed_by_column_of_type(:integer)
end
it do
should define_enum_for(:category).
with_values(:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll).
backed_by_column_of_type(:integer)
end
Just use shoulda matchers:
it { should define_enum_for(:type).with_values([:receivable, :payable]) }
it { should define_enum_for(:category).with_values(:sale, :sale_with_tax, :fees, :lease, :tax_free, :other, :payroll)}
You have this string in your validations:
validates_inclusion_of :category, in: Invoice.categories.keys
In case of enum
Invoice.categories.keys #=> ["sale", "sale_with_tax", "fees", "lease", "tax_free", "other", "payroll"]
You should update your object data with one of names of your enum.

Grape API in Rails and issues with Strong Parameters

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.

How to set a virtual attribute in model with Rails?

I have a model User with usual attributes email, email_confirmation, password, password_confirmation. I use "has_secure_password" so the real attributes in the database are email and password_digest. However I would like to limit password length without spaces to 6 characters.
Here is my model :
class User < ActiveRecord::Base
before_validation :auto_strip_confirmation
validates :email, presence: true,
length: { maximum: MAX_SIZE_DEFAULT_INPUT_TEXT },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false },
confirmation: true
validates :email_confirmation, presence: true
has_secure_password
validates :password, length: { minimum: MIN_SIZE_USER_PASSWORD,
maximum: MAX_SIZE_USER_PASSWORD }
private
def auto_strip_confirmation
self.password.strip!
self.password_confirmation.strip!
end
end
But I get this in console :
> user.password = user.password_confirmation = "a "
=> "a "
> user.valid?
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('user#example.com') LIMIT 1
=> true
Thanks for your help.
After reload, my code actually works in console : user.valid? => false (thanks surendran). But my initial problem was in my tests : I thought I could not set virtual attributes because of the error message "undefined method `strip!' for nil:NilClass". But I forgot I test if my user is valid when password is nil, nearly like this :
before { user.password = nil) }
it { should_not be_valid }
before_validation comes before this test so he tries to strip a nil object.

Rails Model Validations

I have a model and adding some validations
This is what I originally had:
validates :speed,
allow_blank: true,
numericality: { only_integer: true, greater_than: 0 }
But I keep getting errors when importing items from my CSV file stating that
Speed must be an integer
I then changed it to:
validates :speed,
numericality: { only_integer: true, greater_than: 0 }, unless: "speed.nil?"
But I get the same errors here too.
Basically I want it to validate that speed is numeric and greater than 1 unless no speed is passed in and to allow that blank value.
Any ideas?
CSV Importer:
def self.import_from_csv(file)
Coaster.destroy_all
csv_file = CSV.parse(
File.read(
file.tempfile,
{encoding: 'UTF-8'}
),
headers: true,
header_converters: :symbol
)
csv_file.each do |row|
coaster_name = row[:name]
# No need to keep track of coasters already in the database as the CSV only lists each coaster once unlike parks
# create the new coaster
park = Park.find_by_name_and_location_1(row[:park], row[:location_1])
manufacturer = Manufacturer.find_by_name(row[:manufacturer])
coaster = Coaster.create!({
name: row[:name],
height: row[:height],
speed: row[:speed],
length: row[:length],
inversions: row[:inversions] == nil ? 0 : row[:inversions],
material: (row[:material].downcase if row[:material]),
lat: row[:coaster_lat],
lng: row[:coaster_lng],
park_id: park.id,
notes: row[:notes],
powered: row[:powered],
manufacturer_id: (manufacturer.id if manufacturer),
covering: row[:covering],
ride_style: row[:ride_style],
model: row[:model],
layout: row[:layout],
dates_ridden: row[:dates_ridden],
times_ridden: row[:times_ridden],
order: row[:order],
on_ride_photo: row[:on_ride_photo] == 1 ? true : false,
powered: row[:powered] == 1 ? true : false
})
ap "Created #{row[:name]} at #{row[:park]}"
end
end
I think the value for speed from csv is interpreted as string. You may use .to_i with that specific value that you are using for speed. Change your code like this:
park = Park.find_by_name_and_location_1(row[:park], row[:location_1])
manufacturer = Manufacturer.find_by_name(row[:manufacturer])
row_speed = row[:speed].blank? ? nil : row[:speed].to_i
coaster = Coaster.create!({
.....
speed: row_speed,
.....
})
And then in validation:
validates :speed, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
Validations accept an :allow_nil argument, as noted here in the Rails guides: http://edgeguides.rubyonrails.org/active_record_validations.html#allow-nil
If the attribute is nil when :allow_nil is true, that particular validation will only run if the attribute in question is present.
I think, validates numericality accepts allow_nil attribute. Try this:
validates :speed, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true

Resources