I'm trying to create a shared_example in a rails application. It's working but I'm getting a deprecation error. I can I refactor the code to fix this?
error
Looking up factories by class is deprecated and will be removed in 5.0. Use symbols instead and set FactoryBot.allow_class_lookup = false.
source
shared_examples 'a sanatized_record' do
subject { build(described_class) }
describe 'stripped_attributes' do
described_class::STRIPPED_ATTRIBUTES.each do |attr|
it "strips whitespaces from #{attr}" do
original = subject[attr]
subject[attr] = " #{original} "
subject.validate
expect(subject[attr]).to eq original
end
end
end
end
Related post on the topic: FactoryBot namespaced models without class_name
From the link -- updating the factory to
factory :foo_bar, class: 'foo/bar' do; end should work because of key.to_s.underscore.to_sym in this class
Related
We are migrate rails3 app to Rails4. In FactoryGirl we use this trait:
trait :with_student do
after_create do |resource|
resource.students << FactoryGirl.create(:student)
end
end
In model rspec:
let(:course_with_student) { create(:course, :with_student) }
I raised course_with_student and gives me course object. But when raise course_with_student.students output is:
#<ActiveRecord::Associations::CollectionProxy []>. Raise exception in trait definition resource.students and student object is there, but not in rspec model spec.
The way I usually use the trait is in an after_build block. Have a go at something like this:
trait :with_student do
after_build do |course|
course.students = FactoryGirl.create_list(:student, 1)
end
end
For me that solves some similar problems that I ran into.
I've noticed a couple examples in Rspec and FactoryGirl where some people put the class name in quotes, and some don't.
Example rspec:
describe "User" do
...specs...
end
describe User do
...specs...
end
Example FactoryGirl
factory :high_school_account, class: "Account" do
Name "Test Account Name"
AccountTypeId 1
end
factory :high_school_account, class: Account do
Name "Test Account Name"
AccountTypeId 1
end
I thought I read somewhere it had to do when the class is loaded into the ruby environment, but I might be completely making that up.
Is there a difference between the quoted and non-quoted versions?
From the factory_girl source code:
module FactoryGirl
class Factory
# ...
def build_class
#build_class ||= if class_name.is_a? Class
class_name
else
class_name.to_s.camelize.constantize
end
end
end
end
So no, in this context there is no difference - both are accepted. A String is simply converted to the class. Even a symbol would work.
I have an Active Record based model:- House
It has various attributes, but no formal_name attribute.
However it does have a method for formal_name, i.e.
def formal_name
"Formal #{self.other_model.name}"
end
How can I test that this method exists?
I have:
describe "check the name " do
#report_set = FactoryGirl.create :report_set
subject { #report_set }
its(:formal_name) { should == "this_should_fail" }
end
But I get undefined method 'formal_name' for nil:NilClass
First you probably want to make sure your factory is doing a good job creating report_set -- Maybe put factory_girl under both development and test group in your Gemfile, fire up irb to make sure that FactoryGirl.create :report_set does not return nil.
Then try
describe "#formal_name" do
let(:report_set) { FactoryGirl.create :report_set }
it 'responses to formal_name' do
report_set.should respond_to(:formal_name)
end
it 'checks the name' do
report_set.formal_name.should == 'whatever it should be'
end
end
Personally, I'm not a fan of the shortcut rspec syntax you're using. I would do it like this
describe '#formal_name' do
it 'responds to formal_name' do
report_set = FactoryGirl.create :report_set
report_set.formal_name.should == 'formal_name'
end
end
I think it's much easier to understand this way.
EDIT: Full working example with FactoryGirl 2.5 in a Rails 3.2 project. This is tested code
# model - make sure migration is run so it's in your database
class Video < ActiveRecord::Base
# virtual attribute - no table in db corresponding to this
def embed_url
'embedded'
end
end
# factory
FactoryGirl.define do
factory :video do
end
end
# rspec
require 'spec_helper'
describe Video do
describe '#embed_url' do
it 'responds' do
v = FactoryGirl.create(:video)
v.embed_url.should == 'embedded'
end
end
end
$ rspec spec/models/video_spec.rb # -> passing test
Almost every spec file I come accross I end up writing stuff like:
before :each do
#cimg = Factory.build :cimg_valid
#cimg.stub(:validate_img).and_return true
#cimg.stub(:validate_img_url).and_return true
#cimg.stub(:save_images).and_return true
#cimg.stub(:process_image).and_return true
#cimg.stub(:img).and_return true
end
I mean, the model I get from Factory.build is completely valid. But if I don't stub that stuff it saves things in the filesystem, and validates stuff I'm not testing...
What I mean, I think it would be cleaner to do something like this:
before :each do
#cimg = Factory.build :cimg_for_testing_tags
end
If stubbing within the Factory is even possible.
What is the proper way to stub the model?
#fkreusch's answer works great until you use the new RSpec expect() syntax (3.0+)
Putting this into rails_helper.rb works for me:
FactoryBot::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
In the OP's example, you can now do:
FactoryBot.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after(:build) do |cimg|
allow(cimg).to receive(:validate_img) { true }
end
end
end
Credit: github.com/printercu, see: https://github.com/thoughtbot/factory_bot/issues/703#issuecomment-83960003
In recent versions of factory_girl you have an after_build callback, so I believe you could define your factory like this:
FactoryGirl.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after_build do |cimg|
cimg.stub(:validate_img).and_return true
end
end
end
UPDATE
After factory_girl 3.3.0, the syntax has changed to following:
FactoryGirl.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after(:build) do |cimg|
cimg.stub(:validate_img).and_return true
end
end
end
A factory should produce "real world" objects therefore it's a bad practice (and error prone) to change behaviour (i.e. stub) in a factory.
You can do
let(:user) instance_double(User, FactoryGirl.attributes_for(:user))
before do
allow(user).to receive(:something).and_return('something')
end
and if your before clause gets too big you may want to extract it to a separate method or create a mock child class that overrides methods you want to stub.
You might also consider using FactoryGirl#build_stubbed.
I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.