Testing ActiveRecord:Observer using RSpec - ruby-on-rails

I have a model class as follows:
class DataInfo < ActiveRecord::Base
STATUS = {:UNAPPROVED => 1, :APPROVED => 2, :PROCESSED => 3 }
attr_accessible :id, :owner, :status
validates :status, :inclusion => {:in => STATUS.values}
end
I have written a observer as follows:
class StatusObserver < ActiveRecord::Observer
def after_save(datainfo)
if datainfo.status.eql? "APPROVED"
DataInfoWorker.perform_async datainfo.id
end
end
end
How do I test the observer using RSpec? Do I necessarily have to use "No Peeping Toms" plugin? I mainly want test after_save part of it?

You should be able to do something like this using rspec-3 syntax:
describe DataInfo do
describe 'after save when status is APPROVED' do
let(:data_info) { DataInfo.new(status: :APPROVED) }
before do
data_info.save!
end
it 'queues DataInfoWorker' do
expect(DataInfoWorker.jobs.size).to eq 1
end
end
end
Updated to test that a Sidekiq job is queued. Make sure that the rspec-sidekiq gem is added to your Gemfile.

Related

Skip all validations on state machine method

I am trying to figure out how to skip validations on a specific instance of an ActiveRecord object when my reservation model transitions on state machine via a rake task. I would like to skip all validations whenever reservation.close! is called. Would hope to call something like reservation.close!(:validate => false). FYI we are using https://github.com/pluginaweek/state_machine for state machine.
Here is a sample of my reservation model.
class Reservation < ActiveRecord::Base
validates :ride_id, :start_at, :end_at, presence: true
validate :proper_custom_price?, allow_nil: true, on: :update
validate :dates_valid?
validate :dates_make_sense?
scope :needs_close_transition, lambda { where("end_at < ?", Time.now).where("state" => ["requested", "negotiating", "approved"]) }
state_machine :initial => 'requested' do
all_prebooked = ["requested", "negotiating", "approved"]
event :close do
transition :from => all_prebooked,
:to => "precanceled"
end
before_transition :on => [:close] do |reservation|
reservation.cancel_reason = :admin
end
end
end
Here is a sample of my rake task.
namespace :reservation do
task :close => :environment do
puts "== close"
Reservation.needs_close_transition.each do |reservation|
puts "===> #{reservation.id}"
begin
reservation.close!(:validate => false)
rescue Exception => e
Airbrake.notify(e, error_message: e.message) if defined?(Airbrake)
end
end
end
I had the same problem, however I did not want to alter my current validations, so I checked the state machine code (version 1.2.0) and I found another solution, for your specific case it would be something like:
reservation.close!(false)
reservation.save(validate: false)
That will trigger all callbacks that you have defined in your state machine, and well this solution is working well for me.
When using the state_machine gem, the state attribute is updated before the validations are run, so you can add an unless condition to the validation that tests the current state:
validates :start_at, :end_at, presence: true, unless: Proc.new {state == 'closed'}
If you want more complex logic, pass a method name symbox to unless instead of a proc:
validates :start_at, :end_at, presence: true, unless: :requires_validation?
def requires_validation?
# complex logic to determine if the record should be validated
end

Using Roles for Validations in Rails

Is it possible to use the roles used for attr_accessible and attr_protected? I'm trying to setup a validation that only executes when not an admin (like this sort of http://launchware.com/articles/whats-new-in-edge-scoped-mass-assignment-in-rails-3-1). For example:
class User < ActiveRecord::Base
def validate(record)
unless # role.admin?
record.errors[:name] << 'Wrong length' if ...
end
end
end
user = User.create({ ... }, role: "admin")
After looking into this and digging through the source code, it appears that the role passed in when creating an Active Record object is exposed through a protected method mass_assignment_role. Thus, the code in question can be re-written as:
class User < ActiveRecord::Base
def validate(record)
unless mass_assignment_role.eql? :admin
record.errors[:name] << 'Wrong length' if ...
end
end
end
user = User.create({ ... }, role: "admin")
Sure can would be something like this:
class User < ActiveRecord::Base
attr_accessible :role
validates :record_validation
def record_validation
unless self.role == "admin"
errors.add(:name, "error message") if ..
end
end
You could do this
class User < ActiveRecord::Base
with_options :if => :is_admin? do |admin|
admin.validates :password, :length => { :minimum => 10 } #sample validations
admin.validates :email, :presence => true #sample validations
end
end
5.4 Grouping conditional validations

has_many with at least two entries

I need a has_many association that has at least two entries, how do I write the validation and how could this be tested using RSpec + factory-girl? This is what I got till now, but it fails with ActiveRecord::RecordInvalid: Validation failed: Bars can't be blank and I'm completely stuck on the RSpec test.
/example_app/app/models/foo.rb
class Foo < ActiveRecord::Base
has_many :bars
validates :bars, :presence => true, :length => { :minimum => 2}
end
/example_app/app/models/bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo
validates :bar, :presence => true
end
/example-app/spec/factories/foo.rb
FactoryGirl.define do
factory :foo do
after(:create) do |foo|
FactoryGirl.create_list(:bar, 2, foo: foo)
end
end
end
/example-app/spec/factories/bar.rb
FactoryGirl.define do
factory :bar do
foo
end
end
class Foo < ActiveRecord::Base
validate :must_have_two_bars
private
def must_have_two_bars
# if you allow bars to be destroyed through the association you may need to do extra validation here of the count
errors.add(:bars, :too_short, :count => 2) if bars.size < 2
end
end
it "should validate the presence of bars" do
FactoryGirl.build(:foo, :bars => []).should have_at_least(1).error_on(:bars)
end
it "should validate that there are at least two bars" do
foo = FactoryGirl.build(:foo)
foo.bars.push FactoryGirl.build(:bar, :foo => nil)
foo.should have_at_least(1).error_on(:bar)
end
You want to use a custom validator
class Foo < ActiveRecord::Base
has_many :bars
validate :validates_number_of_bars
private
def validates_number_of_bars
if bars.size < 2
errors[:base] << "Need at least 2 bars"
end
end
end

Simple Rspec test failure

I have the following model:
module CgCart
class ShippingInfo < ActiveRecord::Base
set_table_name 'cg_cart.shipping_infos'
has_many :order, :class_name => "CgCart::Order"
accept_nested_attributes_for :orders, :allow_destroy => true
validates_presence_of :name, :street, :city, :country, :zip, :state
def to_s
retval = <<-formatted_addr
#{self.name}
#{self.street}
#{self.city} #{self.state} #{self.zip}
#{self.country}
formatted_addr
retval.upcase
end
end
end # module
I am writing a spec test. It goes like this:
require File.dirname(__FILE__) + '/../../spec_helper'
describe CgCart::ShippingInfo do
context "when first created" do
it "should be empty" do
#shipping_info = CgCart::ShippingInfo.new
#shipping_info.should be_empty
end
end
end
When I run the spec test I get the following error:
1)
NoMethodError in 'CgCart::ShippingInfo when first created should be empty'
undefined method `be_empty' for #<Spec::Matchers::PositiveOperatorMatcher:0x105f165c0>
./spec/models/cg_cart/shipping_info_spec.rb:6:
Finished in 0.015 seconds
1 example, 1 failure
This seems like it should very straight forward. Any ideas on why this test fails?
I was trying to use empty where I should have been using nil.
#shipping_info.should be_nil

validates_associated not honoring :if

I'm totally blocked on this.
See this code:
# user.rb
class User < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
validates_associated :address, :if => Proc.new {|u| u.addressable? }
end
# address.rb
class Address < ActiveRecord::Base
belongs_to :user
validates_presence_of :address_text
end
# user_test.rb
require File.dirname(__FILE__) + '/../test_helper'
class UserTest < ActiveSupport::TestCase
setup { }
test "address validation is not ran w update attributes and anaf" do
#user = User.create!
#user.build_address
assert_nothing_raised do
#user.update_attributes!(:addressable => false, :address_attributes => {:address => "test"})
end
end
test "address validation w update_attributes and anaf" do
#user = User.create!
#user.build_address
#user.save
assert_raise ActiveRecord::RecordInvalid do
#user.update_attributes!(:addressable => true, :address_attributes => {:address => "test"})
end
end
end
The first test will fail.
The user model validates an associated address model, but is only supposed to do it if a flag is true. In practice it does it all the time.
What is going on?
Actually I ran into further problems with my (more complicated) real world scenario that were only solved by doing the equivalent of:
def validate_associated_records_for_address
self.addressable? ? validate_single_association(User.reflect_on_association(:address)) : nil
end
This adapts anaf's compulsory validations to only run under the condition we want (addressable? is true).
The validates_associated...:if is not necessary now.

Resources