RSpec validation fails with little explaination - ruby

I've created an RSpec test to simply test if my model is valid with the given info, it should be, yet my test is still failing. I'm hoping someone can see why since I've stared at this all day yesterday.
I'm also using MongoDB (not sure if that matters).
models/stock.rb
class Stock
include Mongoid::Document
field :symbol, type: String
field :last_trade_price, type: Integer
field :ask, type: Integer
field :change, type: Integer
field :change_percent, type: String
field :market_cap, type: String
field :avg_volume, type: Integer
field :change_from_year_high, type: Integer
field :change_from_year_low, type: Integer
field :change_from_year_high_percent, type: Integer
field :change_from_year_low_percent, type: Integer
field :year_high, type: Integer
field :year_low, type: Integer
field :day_high, type: Integer
field :day_low, type: Integer
field :day_range, type: String
field :ebitda, type: String
field :eps_estimate_current_year, type: Integer
field :eps_estimate_next_year, type: Integer
field :eps_estimate_next_quarter, type: Integer
validates :symbol, :last_trade_price, :ask, :change, :change_percent, :market_cap,
:avg_volume, :change_from_year_high, :change_from_year_low, :change_from_year_high_percent,
:change_from_year_low_percent, :year_high, :year_low, :day_high, :day_low, :day_range,
:ebitda, :eps_estimate_current_year, :eps_estimate_next_year, :eps_estimate_next_quarter, presence: true
validates :last_trade_price, :ask, :change, :avg_volume,
:change_from_year_high, :change_from_year_low, :change_from_year_high_percent,
:change_from_year_low_percent, :year_high, :year_low, :day_high, :day_low,
:eps_estimate_current_year, :eps_estimate_next_year, :eps_estimate_next_quarter, numericality: true
validates_uniqueness_of :symbol
end
spec/factories.rb
FactoryGirl.define do
factory :stock do
symbol "AAPL"
last_trade_price 92.51
ask 92.78
change -0.91
change_percent "-0.91 - -0.97"
market_cap "512.93B"
avg_volume 37776500
change_from_year_high -40.46
change_from_year_low 0.66
change_from_year_high_percent -30.43
change_from_year_low_percent 0.72
year_high 132.97
year_low 91.85
day_high 93.57
day_low 92.46
day_range "92.46 - 93.57"
ebitda "82.79B"
eps_estimate_current_year 8.29
eps_estimate_next_year 9.15
eps_estimate_next_quarter 1.67
end
end
spec/models/stock_spec.rb
describe Stock do
let(:stock) { build(:stock) }
it "should be valid if all information is provided" do
expect(stock).to be_valid
end
end
My output from running the rspec test is:
Failures:
1) Stock should be valid if all information is provided
Failure/Error: expect(stock).to be_valid
expected `#<Stock _id: 5734dd60b8066872f6000000, symbol: "AAPL", last_trade_price: 92, ask: 92, change: 0, change_percent: "-0.91 - -0.97", market_cap: "512.93B", avg_volume: 37776500, change_from_year_high: -40, change_from_year_low: 0, change_from_year_high_percent: -30, change_from_year_low_percent: 0, year_high: 132, year_low: 91, day_high: 93, day_low: 92, day_range: "92.46 - 93.57", ebitda: "82.79B", eps_estimate_current_year: 8, eps_estimate_next_year: 9, eps_estimate_next_quarter: 1>.valid?` to return true, got false
# ./spec/models/stock_spec.rb:5:in `block (2 levels) in <top (required)>'
Finished in 0.02311 seconds (files took 1.72 seconds to load)
1 examples, 1 failure
Failed examples:
rspec ./spec/models/stock_spec.rb:4 # Stock should be valid if all information is provided
Randomized with seed 36574
From looking at the error, it seems that all of the information was built into the factory test object, so I'm unsure why the test is getting false instead of the true it's expecting.
Thanks for any help!

You can test what fields are giving an error by modifiying the spec:
describe Stock do
let(:stock) { build(:stock) }
it "should be valid if all information is provided" do
#expect(stock).to be_valid
stock.valid?
expect(stock.errors.full_messages).to eq []
end
end
However even as such the spec has very little actual value - you're just testing that your factory has all the required fields. If it didn't you would get failures in other specs anyways.
Also if you are grouping a bunch of similar validations by type you might want to use the longhand methods instead as it is much easier to read:
validates_presence_of :symbol, :last_trade_price, :ask, :change, :change_percent, :market_cap,
:avg_volume, :change_from_year_high, :change_from_year_low, :change_from_year_high_percent,
:change_from_year_low_percent, :year_high, :year_low, :day_high, :day_low, :day_range,
:ebitda, :eps_estimate_current_year, :eps_estimate_next_year, :eps_estimate_next_quarter
Added
When defining factories you should use sequences or computed properties to ensure that unique fields are unique - otherwise your validations will fail if you create more than one record from your factory!
FactoryGirl.define do
factory :stock do
sequence :symbol do |n|
"TEST-#{n}"
end
last_trade_price 92.51
ask 92.78
change -0.91
change_percent "-0.91 - -0.97"
market_cap "512.93B"
avg_volume 37776500
change_from_year_high -40.46
change_from_year_low 0.66
change_from_year_high_percent -30.43
change_from_year_low_percent 0.72
year_high 132.97
year_low 91.85
day_high 93.57
day_low 92.46
day_range "92.46 - 93.57"
ebitda "82.79B"
eps_estimate_current_year 8.29
eps_estimate_next_year 9.15
eps_estimate_next_quarter 1.67
end
end
Gems like FFaker are really helpful here. See the FactoryGirl docs for more info.
Also you should use a gem like database_cleaner (Yes it works for mongoid) to clean out your database between specs - the reason your validation is currently failing is that you have residual test state from some other test which is effecting the result.

Related

Error while passing argument list to Superclass

Sorry if the question is unclear as I'm new to Ruby.
I have a Parent class (not implemented by me) Reports that has a method query defined as:
class Reports
def self.query(id:, is_active: false, is_timestamp: false, version: DEFAULT_VERSION, limit: DEFAULT_LIMIT)
<<code>>
end
I have defined a child class Report that inherits Reports and defined the same method query as below:
class Report < Reports
def self.q_version(id)
<<some logic to decide version>>
end
def self.query(id:, is_active: false, is_timestamp: false, limit: DEFAULT_LIMIT)
version = q_version(id)
super(id, is_active, is_timestamp, version, limit)
end
Now when I run the code, I'm getting an argument error as such:
ArgumentError: wrong number of arguments (given 5, expected 0; required keyword: id)
I suspect I'm not doing the super call correctly but couldn't figure out which part.
so any help would be appreciated.
The method's signature tells that it expects keyword arguments like this:
def self.query(id:, is_active: false, is_timestamp: false, limit: DEFAULT_LIMIT)
version = q_version(id)
super(id: id, is_active: is_active, is_timestamp: is_timestamp, version: version, limit: limit)
end
Or you could use the new shorthand hash syntax when you are on Ruby 3.1+:
def self.query(id:, is_active: false, is_timestamp: false, limit: DEFAULT_LIMIT)
version = q_version(id)
super(id:, is_active:, is_timestamp:, version:, limit:)
end

Mongoid Capped Collection

I'm trying to create a capped collection with Mongoid. I have a definition as follows:
class Customer
include Mongoid::Document
store_in(collection: 'customers')
field: n, type: String, as: :name
field: a, type: String, as: :address
field: z, type: String, as: :zip
end
I've been referencing the documentation but can't figure out how to make a capped collection in this portion of the code. I've tried removing the store_in line and replacing it with session.command(create: "customers", capped: true, size: 10000000, max: 1000) to no avail. Is session supposed to be replaced with something? Or am I going about this incorrectly?
Mongoid does not provide a mechanism for creating capped collections on the fly - you will need to create these yourself via the Mongo console.

Rails 4 validation message: removes "_id" from message

# model.rb
validates :employee_id, presence: true, uniqueness: true
When left empty, the error message says "Employee can't be blank" when I want it to say "Employee ID can't be blank".
I resolved this by:
# model.rb
validates :employee_id, presence: { message: " ID can't be blank" }, uniqueness: true
which outputs "Employee ID can' be blank".
However, this isn't a really good solution IMO. I would like some means of customizing the entire message, including the attribute prefix.
Is there a simple way to do this?
There are several "correct" ways to go about this, but you definitely shouldn't do it via the validation itself, or by defining your own validation method.
On a model-by-model level, this is controlled by the class-level human_attribute_name method.
If you want your model's employee_id field to be a special case where the _id postfix isn't truncated, define that special case by overridding human_attribute_name:
class MyModel
validates :employee_id, presence: true
def self.human_attribute_name(attr, options = {})
attr == :employee_id ? 'Employee ID' : super
end
end
In broader terms, you can redefine human_attribute_name on ActiveRecord::Base to override handling of all _id attributes, but I doubt you want to do this. Generally, it's a good thing that Rails drops the _id postfix.
The second (and probably better) mechanism is to simply rely on localization. ActiveRecord ties into your locale YAML files for just about everything. If you want your employee_id field to humanize to Employee ID regardless of language, you'll need to edit your YAML files.
# config/locales/en.yml
en:
activerecord:
attributes:
employee_id: "Employee ID"
You can override human_attribute_name and always send default value with id
class MyModel
def self.human_attribute_name(attribute, options = {})
super(attribute, { default: attribute.to_s.humanize(keep_id_suffix: true) } )
end
end
You can write a custom validation. By doing it that way, you get to define the error message inside the validation method.
The relevant section from Rails Guides is here: Performing Custom Validations
Something like:
Class Paystub
validate :employee_id_is_not_blank
def employee_id_is_not_blank
errors[:base] << "Employee must be a part of the record.") if id.blank?
end
end
p = Paystub.create
p.errors.full_messages #=> ["Employee must be a part of the record."]
Section 7.4 in the Rails Guides specified using errors[:base]. Error messages shoveled into :base don't require an attribute to be tied to them.
Update: This is not the right answer. See #meagars answer above.

Rails validation of mistyped numbers with allow_blank

When I, for example, enter 11q instead of 11 I get no error and the form is being saved as if nothing happened. Unfortunately Rails thinks that 11q is actually a nil.
this is my current validation line:
validates :order_retain_days, numericality: {in: 1..999, only_integer: true, message: 'retain value must be an integer between 1 and 999'}, allow_blank: true
fragment of db/schema.rb
create_table "order_details", force: true do |t|
t.integer "order_retain_days"
end
How can I validate it if Javascript is turned off? I need to allow blank field or have the value within numeric range.
You can create your own custom validation:
validate :presence_and_numericality_of_order_retain_days
def presence_and_numericality_of_order_retain_day
if self.order_retain_days && !self.order_retain_days=~ /insert your regex here/
errors.add(order_retain_dats, 'retain value must be an integer between 1 and 999')
end
end

Using an argument value in argument declaration

I wish to specify the default value of an argument based on the value of a previously declared argument. In this example, the handlerSendPort default value should be handlerRecvPort + 1
require "thor/group"
module Noov
class App < Thor::Group
include Thor::Actions
argument :name, type: :string, desc: "The name of the project to create"
argument :handlerRecvPort, type: :numeric, desc: "The 0MQ recv port number on the handler side (random if not specified)", optional: true, default: rand(2**16-1024)+1024+1
argument :handlerSendPort, type: :numeric, desc: "The 0MQ send port number on the handler side (recv port + 1 if not specified)", optional: true, default: handlerRecvPort+1
argument :handlerUUID, type: :string, desc: "The mongrel2 server UUID (project name if not specified)", optional: true, default: name
desc "Creates a project directory for a new website. Fully equipped to support BDD with Cucumber"
end
end
This fails with:
undefined local variable or methodhandlerRecvPort' for Noov::App:Class (NameError)`
I realize that the argument value is not available in the context of the class evaluation (although I had hoped for some metamagic to make it work), so how can I access the argument value at this point?

Resources