I'm performing the simplest test on the following class (inside model's folder):
class Offer
attr_accessor :title, :payout, :thumbnail
def initialize(title, payout, thumbnail)
#title = title
#payout = payout
#thumbnail = thumbnail
end
end
The thing is there's no 'offers' db table. The objects created out of this class are never saved in a database.
Then i perform the tests using rspec:
describe Offer do
it "has a valid factory" do
expect(FactoryGirl.create(:offer)).to be_valid
end
...
end
and FactoryGirl:
FactoryGirl.define do
factory :offer do
skip_create
title { Faker::Name.name }
payout { Faker::Number.number(2) }
thumbnail { Faker::Internet.url }
initialize_with { new(title, payout, thumbnail)}
end
end
And i get the following error:
> undefined method `valid?' for #<Offer:0x00000002b78958>
Because your Offer class is not inheriting from ActiveRecord::Base, you're not getting any of the stuff that comes along with it (such as validations). valid? is a method provided through ActiveRecord's modules, not by Ruby directly, so it won't be available on a basic Ruby class.
If all you care about is validations, then you can include the ActiveModel::Validations module in your class and it will give you valid? as well as validates_presence_of, etc.:
class Offer
include ActiveModel::Validations
...
end
You can also just include ActiveModel to get a couple other things such as ActiveRecord's naming and conversion benefits (as well as validation).
Related
I would like to create an Active Record model for Rspec test.
However, this model has callbacks, namely: before_create and after_create method (I think these methods are called callbacks not validation if I am not wrong).
Is there a way to create an object without triggering the callbacks?
Some previous solutions I have tried / does not work for my case:
Update Method:
update_column and other update methods will not work because I would like to create an object and I can't use update methods when the object does not exist.
Factory Girl and After Build:
FactoryGirl.define do
factory :withdrawal_request, class: 'WithdrawalRequest' do
...
after(:build) { WithdrawalRequest.class.skip_callback(:before_create) }
end
end
Failure/Error: after(:build) { WithdrawalRequest.class.skip_callback(:before_create) }
NoMethodError:
undefined method `skip_callback' for Class:Class
Skip callbacks on Factory Girl and Rspec
Skip Callback
WithdrawalRequest.skip_callback(:before_create)
withdrawal_request = WithdrawalRequest.create(withdrawal_params)
WithdrawalRequest.set_callback(:before_create)
Failure/Error: WithdrawalRequest.skip_callback(:before_create)
NoMethodError:undefined method `_before_create_callbacks' for #
How to save a model without running callbacks in Rails
I have also tried
WithdrawalRequest.skip_callbacks = true
Which does not work too.
---------- EDIT -----------
My factory function is edited to:
after(:build) { WithdrawalRequest.skip_callback(:create, :before, :before_create) }
My before_create function looks like this:
class WithdrawalRequest < ActiveRecord::Base
...
before_create do
...
end
end
---------- EDIT 2 -----------
I changed the before_create to a function so that I can reference it. Is either of these a better practice?
class WithdrawalRequest < ActiveRecord::Base
before_create :before_create
...
def before_create
...
end
end
Based on the referenced answer:
FactoryGirl.define do
factory :withdrawal_request, class: 'WithdrawalRequest' do
...
after(:build) { WithdrawalRequest.skip_callback(:create, :before, :callback_to_be_skipped) }
#you were getting the errors here initially because you were calling the method on Class, the superclass of WithdrawalRequest
#OR
after(:build) {|withdrawal_request| withdrawal_request.class.skip_callback(:create, :before, :callback_to_be_skipped)}
end
end
Any links to documentation proving or disproving my thoughts here would be very appreciated; I can't seem to find any.
AFAIK, if you had a Rails application with a Product model, you could define a FactoryGirl factory as
FactoryGirl.define do
factory :product do
# stuffs
end
end
and then call your factory in tests with (RSpec example)
let(:product) { FactoryGirl.create(:product) }
but you may also call it with
let(:product) { FactoryGirl.create(Product) }
This is helpful if you're wanting to keep your model tests a bit more dynamic and free to change with RSpec's described_class helper.
My problem:
I've got a model that happens to be namespaced
class Namespace::MyModel < ActiveRecord::Base
# model stuffs
end
with a factory
FactoryGirl.define do
factory :my_model, class: Namespace::MyModel do
# factory stuffs
end
end
and when attempting to use RSpec's helpers...
RSpec.describe Namespace::MyModel do
let(:my_object) { FactoryGirl.create(described_class) }
# testing stuffs
end
FactoryGirl complains of a missing factory
Factory not registered: Namespace::MyModel
Am I missing this feature of FactoryGirl, without understanding its true purpose? Or is there another way I can define my factory to resolve correctly?
Why don't you try
RSpec.describe Namespace::MyModel do
let(:my_object) { FactoryGirl.create(:my_factory) }
# testing stuffs
end
FactoryGirl is usually used by factory name, but not class name, that is defines.
You can have a multiple factories, that define instances of the same class. The difference between them can be in fields values, for example.
Or you can dynamicly get factory name, from described_class name.
It is already answered at How do you find the namespace/module name programmatically in Ruby on Rails?
So I'm writing code in which we need to issue refunds. I wrote a refund validator that checks to ensure that the refund is not for more than the original charge. However, in my specs I came to the realization that the associated charge isnt' present yet. Using FactorGirl. How can I make something like this work?
Validator
class RefundValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value <= record.charge.amount
record.errors[:attribute] << "is greater than original charge"
end
end
end
Validation
validates :amount, refund: true
Factory
FactoryGirl.define do
factory :refund do
association :client
association :charge
amount 99
end
end
Spec
context 'validations' do
%i(client_id therapist_id appointment_id booking_id value).each do |attr|
it { is_expected.to validate_presence_of attr }
end
it { is_expected.to validate_numericality_of(:value).is_greater_than_or_equal_to(0).is_less_than_or_equal_to(5) }
end
Error (i get this for all 4 attributes in the presence spec, not just client_id):
1) Refund validations should require client_id to be set
Failure/Error: it { is_expected.to validate_presence_of attr }
NoMethodError:
undefined method `amount' for nil:NilClass
# ./app/validators/refund_validator.rb:3:in `validate_each'
# ./spec/models/refund_spec.rb:20:in `block (4 levels) in <top (required)>'
I have tried explicitly creating the Factory model with a charge factory explicitly specified, but to no avail. No clue what's going on. Any help would be appreciated.
I would change unless x <= y to if x > y for the sake of readability
In any case, you should use record.try(:charge).try(:amount) instead of record.charge.amount to prevent NoMethodErrors in your validator.
Let me know if that fixes the problem already.
In my model, I dynamically create some methods based on database records:
class Job < ActiveRecord::Base
belongs_to :job_status
# Adds #requisition?, #open?, #paused?, #closed?
class_eval do
JobStatus.all.each do |status|
unless method_defined? "#{status.name.downcase}?"
define_method("#{status.name.downcase}?") do
job_status_id == status.id
end
end
end
end
end
class JobStatus < ActiveRecord::Base
has_many :jobs
end
The job_statuses table contains some seed data, so is not going to be frequently changing, but in case I ever need to add new statuses, I don't have to add more code to get a boolean method for the new status.
However, I am not sure how to test these methods, because when rspec starts the job_statuses table is obviously empty, and when the JobStatus objects are created, Job gets initialized, but since no objects exist yet, it doesn't create any methods, and my tests fail because the methods don't exist.
Note that I am using rspec with spork & guard, and using database-cleaner with the truncation strategy (as per Railscast #257, since I'm using Selenium), so that probably complicates matters.
The solution I came up with was to abstract the creation of runtime methods out into a library file, and then in my test file, remove and redeclare my class before each test, and reload the actual class (and blueprints) at the end of the suite:
describe AssociationPredicate do
before(:all) do
["Continuous", "Standard"].each { |type| JobType.create!(:job_type => type) }
["Requisition", "Open", "Paused", "Closed"].each { |status| JobStatus.create!(:job_status => status) }
end
after(:all) do
DatabaseCleaner.clean_with :truncation, :only => %w( job_types job_statuses )
# Reload Job model to remove changes
Object.send(:remove_const, 'Job')
load 'job.rb'
load 'support/blueprints.rb'
end
before(:each) do
Object.send(:remove_const, 'Job')
# Redefine Job model for testing purposes
class Job < ActiveRecord::Base
belongs_to :job_type
belongs_to :job_status
has_many :job_applications
end
end
it "should add methods when included" do
Job.send(:association_predicate, :job_type)
job.should respond_to(:continuous?)
job.should respond_to(:standard?)
end
end
This way, I create a basic class for each test, add the runtime methods as necessarily, and return to the actual class when I'm done.
Try with enumerize gem. This make your status field like enumerator and build the "#{status.name.downcase}?" for your models. This gem came with it's own rspec-matchers making easiest your unit test.
I'm having this issue with factory girl where it gives me a undefined method 'each' for #<String:0x0000012915bc18> error with a serialized field coming from the factory.
within ActiveRecord, it runs the each with no problem, as the object returned is an array.
My question is: how should I format the serialized object in my factory? The way that active record returns it? or the way it's actually stored in the database? (i.e. serialized or not?) will rspec do the same serialize magic on saving and retrieving that active record does?
this is a simplified version of what I'm doing:
Tvdb.rb-- Model
class Tvdb < ActiveRecord::Base
set_table_name 'tvdb'
serialize :cache
def self.episodes(id)
cached = self.find_by_term('episodes_' + id.to_s)
return cached.cache unless cached.nil?
info = self.series_info(id)
request = info.episodes
Tvdb.create(:term=>'episodes_' + info.id.to_s, :cache=>request)
return request
end
end
Then in my Series.rb model I can do this:
class Series < ActiveRecord::Base
def episodes
episodes = Tvdb.episodes(self.tvdb_id)
episodes.each do |episode|
puts episode.name
end
end
end
Tvdb.rb -- Factory
FactoryGirl.define do
factory :series1_episodes, :class=>Tvdb do
term 'episodes_79488'
cache %q([#<AnObject::Module:0x000001290a4568 #value="dsada"]>,#<AnObject::Module:0x0002321290a4568 #value="dsadsada"]> )
end
end
note: The syntax of the cache value might be invalid here, I tried to shorten what was a very long serialized object. The point is that it works in my model, but not in rspec
and in my *series_spec.rb* calling this:
series.episodes.count.should_not == 0
gives that error
undefined method 'each' for #<String:0x0000012915bc18>
In your factory, you shouldn't set cache to the serialized value, but to the actual value.
FactoryGirl.define do
factory :series1_episodes, :class => Tvdb do
term 'episodes_79488'
cache ["foo", "bar"]
end
end
You can change it to JSON this way:
FactoryGirl.define do
factory :series1_episodes, class: Tvdb do
term { 'episodes_79488' }
cache { %i[dsada dsadsada].to_json }
end
end