I have the following task model:
class Task < ApplicationRecord
validates :body, presence: true, length: { minimum: 10 }
validates :complete, presence: true
end
and following FactoryGirl object creation code:
FactoryGirl.define do
factory :incomplete_task, class: :Task do
body { Faker::Pokemon.name + Faker::Pokemon.name }
complete false
factory :complete_task do
complete true
end
end
end
In my Tasks controller tests, I have:
describe '#update' do
it 'toggles completion' do
incomplete_task = create :incomplete_task
toggle_completion(incomplete_task)
expect(incomplete_task.complete).to be_true
end
end
However, this fails because the field 'complete' is missing from the task object created by FG:
Failures:
1) TasksController#update toggles completion
Failure/Error: incomplete_task = create :incomplete_task
ActiveRecord::RecordInvalid:
Validation failed: Complete can't be blank
What is going on here? I'm properly setting the complete attribute, and body is checking out fine. Here is the task schema as well:
# Table name: tasks
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# body :text
# complete :boolean
In ruby, false is considered a blank (not present) value (along with nil, empty string/array and other blank values). Therefore, the presence validator rightfully rejects that record.
The documentation has the following comment:
If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, in: [true, false].
This is due to the way Object#blank? handles boolean values: false.blank? # => true.
Related
I'm trying to add validations to my mobility-powered application and i'm confused a little.Earlier I've used code like this
I18n.available_locales.each do |locale|
validates :"name_#{locale}", presence: true, uniqueness: {scope: :animal_type}
end
And it worked fine. But in my last project it doesn't work at all. Any ideas how to perform validations? My config is below:
Mobility.configure do
plugins do
backend :container
active_record
reader
writer
backend_reader
query
cache
presence
locale_accessors
end
end
UPD: I've identified my problem - it is because of , uniqueness: {scope: :animal_type}. Is it possible to use mobility with similar type of validations?
When you use uniqueness validator it uses query to the database to ensure that record haven't already taken and you get this query:
Let's assume you have Animal model
SELECT 1 AS one FROM "animals" WHERE "animals"."name_en" = "Cat" AND "animals"."animal_type" = "some_type" LIMIT 1
And of course there is no name_en field in the animals table that is why you have an error
To archive this you have to write your own validator
class CustomValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if translation_exists?(record, attribute)
record.errors.add(attribute, options[:message] || :taken)
end
end
private
def translation_exists?(record, attribute)
attribute, locale = attribute.to_s.split('_')
record.class.joins(:string_translations).exists?(
mobility_string_translations: { locale: locale, key: attribute },
animals: { animal_type: record.animal_type }
)
end
end
And then in your model do next:
I18n.available_locales.each do |locale|
validates :"name_#{locale}", presence: true, custom: true
end
I have the following join table:
class ContactFormUsership < ApplicationRecord
belongs_to :user
belongs_to :contact_form
validates :user_id, presence: true, uniqueness: { scope: :contact_form_id }
end
It ensures that there are no duplicate user/contact_form pairings when a row is created.
I also have indexes on that table to ensure the uniquess on the db level:
t.index ["user_id", "contact_form_id"], name: "index_contact_form_userships_on_user_id_and_contact_form_id", unique: true
I have a regression test that looks like this:
test 'An error is raised if a user is added to a form more than once' do
contact_form = ContactForm.create
user = users(:user_1)
assert_raises(ActiveRecord::RecordInvalid) do
2.times do
contact_form.users << user
end
end
end
But this does not test that it is not possible to create duplicate rows at the db level. It only tests the validations.
How do I test uniqueness at a db level? Is there any way to << without validations?
Since you are trying to test the behavior of your ContactFormUsership table, you would do something like:
test 'An error is raised if a user is added to a form more than once' do
contact_form = ContactForm.create
user = users(:user_1)
assert_raises(ActiveRecord::RecordInvalid) do
c1 = ContactFormUsership.new(user: user, contact_form: contact_form)
c1.save
c2 = ContactFormUsership.new(user: user, contact_form: contact_form)
c2.save(validate: false)
end
end
You can find out more about validate: false at https://api.rubyonrails.org/classes/ActiveRecord/Validations.html
Consider the following code:
campaign_spec.rb:
describe Campaign do
before :each do
#campaign = Campaign.new
end
it { should validate_presence_of :position }
end
campaign.rb:
class Campaign < ActiveRecord::Base
attr_accessible :position
validates :position, presence: true
default_scope order(:position)
before_validation :next_position
# sets the position to the next id (1 if none exist) before validation
def next_position
if self.position.blank?
self.position = Campaign.select("coalesce(max(position),0) + 1 as position").reorder(nil).first.position
end
end
end
spec output:
Failures:
1) Campaign should require position to be set
Failure/Error: it { should validate_presence_of :position }
Expected errors to include "can't be blank" when position is set to nil, got errors: ["name can't be blank (nil)"]
# ./spec/models/campaign_spec.rb:9:in `block (2 levels) in <top (required)>'
From what my model is saying, I should be able to create a Campaign without giving it a position. It sets the position if none exists before validation. So why is my spec not passing? I'm thinking that maybe it's not calling my before_validation method?
should validate_presence_of sets the value of the attribute to nil and then runs the validations. Since it is nil, nil.blank? returns true, so your callback is executed, setting it back to some value, and hence validation is found not to work.
Fact is that you don't need to validate this field if you are sure it is never empty. Instead, write the test to check whether it is indeed automatically populated when set to nil.
it 'populates position if blank before validations' do
subject.position = nil
subject.valid?
subject.position.should_not be_nil
end
I have been starting to learn testing in my Rails app and am using rSpec and Shoulda.
I have the following test which works:
it { should respond_to(:park_name) }
However, what I don't understand is, what is this being run on? Is this being run on the Model itself or an instance of the model and if it's an instance of the Model then is it automatically using my Factory Girl factory?
Any simple explanations on what is actually occurring here?
UPDATE:
Ok, So I have this:
describe 'validations' do
subject { FactoryGirl.build(:coaster) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:speed) }
it { should validate_presence_of(:height) }
end
But the tests are failing. Any ideas?
Coaster.rb:
class Coaster < ActiveRecord::Base
extend FriendlyId
friendly_id :slug, use: :slugged
belongs_to :park
belongs_to :manufacturer
attr_accessible :name,
:height,
:speed,
:length,
:inversions,
:material,
:lat,
:lng,
:park_id,
:notes,
:manufacturer_id,
:style,
:covering,
:ride_style,
:model,
:layout,
:dates_ridden,
:times_ridden,
:order,
:on_ride_photo
scope :by_name_asc, lambda {
order("name ASC")
}
scope :made_from, lambda { |material|
where("material = ?", material)
}
scope :wooden, lambda {
made_from "wood"
}
scope :steel, lambda {
made_from "steel"
}
delegate :name, :location_1, :location_2, :location_3, :location_4,
to: :park,
allow_nil: true,
prefix: true
delegate :name, :url,
to: :manufacturer,
prefix: true
validates :name,
:presence => true
validates :height,
allow_nil: true,
numericality: {greater_than: 0}
validates :speed,
allow_nil: true,
numericality: {greater_than: 0}
validates :length,
allow_nil: true,
numericality: {greater_than: 0}
Test Results:
1) Coaster validations should require speed to be set
Failure/Error: it { should validate_presence_of(:speed) }
Expected errors to include "can't be blank" when speed is set to nil, got no errors
# ./spec/models/coaster_spec.rb:75:in `block (3 levels) in '
2) Coaster validations should require height to be set
Failure/Error: it { should validate_presence_of(:height) }
Expected errors to include "can't be blank" when height is set to nil, got no errors
# ./spec/models/coaster_spec.rb:76:in `block (3 levels) in '
SIMILAR QUESTION:
I have this test:
describe 'methods' do
subject { FactoryGirl.build(:coaster) }
it "should return a formatted string of coaster name at park name" do
name_and_park.should eq('Nemesis at Alton Towers')
end
end
Coaster.rb:
def name_and_park
[name, park.name].join (' at ')
end
Error when running the test:
2) Coaster methods should return a formatted string of coaster name at
park name
Failure/Error: name_and_park.should eq('Nemesis at Alton Towers')
NameError:
undefined local variable or method name_and_park' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_6:0x007f84f4161798>
# ./spec/models/coaster_spec.rb:111:inblock (3 levels) in '
It says name_and_park cannot be called but surely that method should be being called on the instance of Coaster that is being made in the subject line? No?
It's being run on "subject", which is either explicitly defined through the subject method or implicitly defined by passing in a class as the argument to describe, in which case an instance of that class is instantiated and made the subject of the test. See https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/explicit-subject and https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/subject/implicit-subject
As for the answer to the Update question, in your model validations for :speed and :length, you have allow_nil: true, which is why those two tests are failing. Part of the definition of validates_presence_of is that nil is not a settable value.
As for your latest question, I think you may be confused about the use of implicit subjects. If should is used by itself, it will indeed default to whatever the subject is, but if you include a subject yourself, as you have in this case with name_and_park, it won't treat that as a method of the default subject, it must have a definition within the current namespace. In your case, you would need to say subject.name_and_park.should ....
On a related aside, StackOverflow is best used when you ask a specific question or related set of questions and get an answer. For a variety of reasons, it's not intended for ongoing debugging sessions. One of those reasons is that it becomes tedious tracking substantial, sequential updates of the original question and answer.
I need help with my ActiveRecord model. I have context based validations (mis)using the build-in context options for validations:
validates :foo, :on => :bar, :presence => true
model = Model.new
model.foo = nil
model.valid? # => true
model.save # works as expected
model.valid?(:bar) # => false
model.save(:context => :bar) # fails and returns false
But using my model in a accepts_nested_attributes_for :model and calling parent.save fails (the validation gets called and returns false), any suggestions or solutions?
Still no answer? To explain more about my problem: I have a model called Form which has many Fields. Users should see validation errors on submit, but the form should be saved anyway (with and without errors). There are different types of Fields, each with global validations (to ensure database consistency) and its own specific user-defined validations (to validate user-entered data). So my Fields look someway like that:
# Global validations, to ensure database consistency
# If this validations fail, the record should not be saved!
validates_associated :form, :on => :global
...
# Specific user-defined validations
# If this validations fail, the record should be saved but marked as invalid. (Which is done by a before_save filter btw.)
def validate
validations.each do |validation| # Array of `ActiveModel::Validations`, defined by the user and stored in a hash in the database
validation.new(:on => :specific).validate(self)
end
end
In my controller:
# def create
# ...
form.attributes = params[:form]
form.save!(:global)
form.save(:specific)
Is something similar possible in Rails using the built-in functionality? Btw this not my actual code, which is quite complicated. But I hope, you guys will get the idea.
Try conditional validations
class Customer
attr_accessor :managing
validates_presence_of :first_name
validates_presence_of :last_name
with_options :unless => :managing do |o|
o.validates_inclusion_of :city, :in=> ["San Diego","Rochester"]
o.validates_length_of :biography, :minimum => 100
end
end
#customer.managing = true
#customer.attributes = params[:customer]
#customer.save
"Ability to specify multiple contexts when defining a validation" was introduced in Rails 4.1 - check validate method, :on options description
Only for Rails 5+:
You are looking for
with_options on: :custom_event do
validates :foo, presence: true
validates :baz, inclusion: { in: ['b', 'c'] }
end
To validate or save use
model = YourModel.new
# Either
model.valid?(:custom_event)
# Or
model.save(context: :custom_event)
Change has_nested_attributes_for :model to accepts_nested_attributes_for :models.
Hope this helps.
Good luck.