Can I use factory girl for non persistent models - ruby-on-rails

I have the following factory:
FactoryGirl.define do
factory :poem do
skip_create
title "Poem title"
intro_verse
trait_verse
message_verse
end
end
for the following non active record model class:
class Poem
attr_accessor :title, :intro_verse, :trait_verse, :message_verse
end
Can I create a factory for such a class?
When I run the following test:
it "has a valid factory" do
expect(build(:poem)).to be_valid
end
I get the following error:
Failure/Error: expect(build(:poem)).to be_valid
NoMethodError:
undefined method `valid?'

The error is because the class does not have an instance method valid?. (Active Record models have this defined by default)
You need to come up with some logic for determining whether a Poem instance is valid or not, and write a valid? method accordingly.
IIRC, the syntax expect(something).to be_condition simply calls the method condition? on something and fails if it returns false.

Use the ActiveModel::Validations module to adds the ability to validate class objects like in Active Record:
class Poem
include ActiveModel::Validations
validates :title, presence: true
attr_accessor :title, :intro_verse, :trait_verse, :message_verse
end
poem = Poem.new
poem.valid? #false
poem.title = "title"
poem.valid? #true

Related

How can I pass attributes to an association through a trait in factorygirl?

Consider the following model setup
class Template < ActiveRecord::Base
belongs_to :detail, polymorphic: true, dependent: :destroy
accepts_nested_attributes_for :detail
end
class EbayTitleTemplate < ActiveRecord::Base
has_one :template, as: :detail
end
And here is a working factory
FactoryGirl.define do
factory :template do
merchant
channel
trait :ebay_title do
association :detail, factory: :template_ebay_title
end
factory :ebay_title_template, traits: [:ebay_title]
end
factory :template_ebay_title, class: EbayTitleTemplate do
name "eBay Title Template"
title "Super Cool Hat"
sub_title "Keeps the sun away"
description "The best hat available!"
end
end
The following works for me
create(:ebay_title_template) # creates both records, creating a new Merchant and Channel for me
create(:ebay_title_template, merchant: Merchant.first, channel: Channel.first) # creates both records with explicit channel and merchant
Now what I'd like to also do is pass in custom attributes to override the defaults. Something like this:
create(:ebay_title_template, title: "Overwrite the title", sub_title: "Overwrite the subtitle")
What ends up happening is that I get the error ArgumentError: Trait not registered: title
Somehow FactoryGirl thinks that I'm passing in a trait or the Template factory doesn't recognize title as an attribute.
I've tried using transients to allow custom args to pass through the Template and using a callback in the :template_ebay_title factory to map the attribute to the model column like so:
transient do
custom_args nil
end
after(:create) do |record, evaluator|
evaluator.custom_args.each do |key, value|
record.key = value
end
end
And then I create like this:
create(:ebay_title_template, custom_args: {title: "Overwrite", sub_title: "Overwrite"})
This results in the error NoMethodError: undefined methodcustom_args' for #`
So either there's a way to do this and I'm doing it wrong, or I need a completely new approach. Keep in mind, there will be dozens of associations that will need to be defined as traits (or something else) so I can't possibly assign specific transients to be passed through.
How can I achieve my goal of creating a factory that creates the parent and the polymorphic association, allowing me to pass in arbitrary attributes for the polymorphic association, and return the parent object?
If you are wanting to pass the values within a single hash value, you need to pass them like so:
create(:ebay_title_template, custom_args: {title: "Overwrite the title", sub_title: "Overwrite the subtitle"})
Otherwise you can create new transient values, and pass them the way you are passing them now:
transient do # use ignore with FactoryGirl/FactoryBot < v4.7
title nil
sub_title nil
end
When adding more attributes like above you can then iterate over the evaluator's overrides instance variable:
evaluator.instance_variable_get(:#overrides).each do |key, value|
puts key, value
end
Hope that helps!

Rails: RSpec fails validations of model subclass

I have a Rails 5 setup where RSpec fails to check validations on model subclass. If I manually build the object in console I am able to see the errors which should prevent the record to be valid.
The base model:
class Article < ApplicationRecord
belongs_to :author, class_name: User
validates :author, presence: { message: "L'utente autore dell'articolo è obbligatorio." }
validates :title, presence: { message: "Il titolo dell'articolo è obbligatorio." }
end
The model which inherits from Article:
class LongArticle < Article
mount_uploader :thumbnail, LongArticleThumbnailUploader
validates :excerpt, presence: { message: "L'estratto dell'articolo è obbligatorio." }
validates :thumbnail, presence: { message: "L'immagine di anteprima dell'articolo è obbligatoria." }
end
The factory for these models (FactoryGirl):
FactoryGirl.define do
factory :article do
association :author, factory: :author
title "Giacomo Puccini: Tosca"
factory :long_article do
type "LongArticle"
excerpt "<p>Teatro alla Scala: immenso Franco Corelli.</p>"
thumbnail { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec', 'support', 'images', 'unresized-long-article-thumbnail.jpg')) }
end
end
end
This is the RSpec which doesn't work:
require 'rails_helper'
RSpec.describe LongArticle, type: :model do
describe "is valid with mandatory fields" do
it "should be valid with if all mandatory fields are filled" do
article = FactoryGirl.create(:long_article)
expect(article).to be_valid
end
it "should have an excerpt" do
article = FactoryGirl.create(:long_article)
article.excerpt = nil
expect(article).not_to be_valid
end
it "should have the thumbnail" do
article = FactoryGirl.create(:long_article)
article.thumbnail = nil
expect(article).not_to be_valid
end
end
end
The first spec pass, the other two don't.
I tried to test everything in the console, with the same values, and it works, meaning that the record is invalid as it should be.
Is it possible that with RSpec the validations in the subclass won't work?
I'm sorry for the delay, but I think I have figured out what was breaking my tests.
The problems, actually, were two, and not one as I originally thought.
The first test: should have an excerpt
As suggested by juanitofatas, I have added a byebug line after the one where FactoryGirl builds my model. I have noticed that the model instantiated had class Article and not LongArticle.
I came up noticing that FactoryGirl instantiate a model of the base factory when it first met factory :article do. Then, it adds or overrides the attributes defined into inner factories and treating the type attribute as any one other, ignoring that it drives the STI.
The LongArticle factory should have been defined as a completely different model, at the same level as the Article one is.
The second test: should have the thumbnail
This was a bit silly... I have defined a default_url method in the CarrierWave uploader and, in fact, this is the desired behavior. Test was updated.

ActiveModel::Model error when initializing class with no params

When running a test, if I try to create a new object using User.new I get an error. If instead I use User.new({}), it works fine.
Isn't params supposed to be defaulted to empty if not passed in?
$ rails -v
Rails 5.0.0.1
user.rb
class User
include ActiveModel::Model
attr_accessor :name, :email, :country
end
user_test.rb
require 'test_helper'
class User < ActiveSupport::TestCase
test "should create an empty user when initialized with no params" do
user = User.new
assert_not_nil(user, 'user cannot be empty')
end
end
test result
Error:
User#test_should_create_an_empty_user_when_initialized_with_no_parameters:
ArgumentError: wrong number of arguments (given 0, expected 1)
test/models/user_test.rb:7:in `new'
test/models/user_test.rb:7:in `block in <class:User>'
Generally, attr_accessor is used on a model for columns that are not actual columns in the SQL table.
So if your model has columns :name, :email and :country, then declaring attr_accessor is unnecessary. I think rails is waiting for you to declare them now.
Try commenting out the attr_accessor :name, :email, :country line, then re-run your test.
This may help
Instead of including model class extend it like:
class Crime < ApplicationRecord
//do your stuff here
end
Then you can use #user = User.new
The User you're referencing in the test is the test itself.
The easiest solution is to follow Ruby convention and name the test class UserTest.

Is it bad to create ActiveRecord objects in a constant?

I have some code where I create ActiveRecord objects as constants in my model like so:
class OrderStage < ActiveRecord::Base
validates :name, presence: true, uniqueness: true
DISPATCHED = find_or_create_by(name: 'Dispatched')
end
Each Order has an OrderStage:
class Order < ActiveRecord::Base
belongs_to :order_stage
end
Now this seems to work fine throughout the site, and in my integration tests. However it is breaking in my unit test. The following test
it 'lists all order stages' do
# using FactoryGirl
create(:order, order_stage: OrderStage::DISPATCHED)
expect(Order.all.map(&:order_stage)).to eq [OrderStage::DISPATCHED]
end
passes fine when I run it individually, or when I run just order_spec.rb. But when I run the whole test suite, or even just spec/models I get this error:
Failure/Error: expect(Order.all.map(&:order_stage)).to eq [OrderStage::DISPATCHED]
expected: [#<OrderStage id: 1, name: "Dispatched">]
got: [nil]
That error goes away if I write my test like so:
it 'lists all order stages' do
order_stage = OrderStage.find_or_create_by(name: 'Dispatched')
create(:order, order_stage: order_stage)
expect(Order.all.map(&:order_stage)).to eq [order_stage]
end
So it must be something to do with creating the ActiveRecord object in the constant, it this a bad thing to do?
You should use class method.
attr_accessible :name
def self.dispatched
#dispatched ||= find_or_create_by_name('Dispatched')
end
private
def self.reset!
#dispatched = nil
end

Inclusion in validation and Rspec using FactoryGirl

I'm having troubles testing an ActiveRecord inclusion validation in Rails with Factory Girl and Rspec. The inclusion validation always fails. Here is my code:
class FruitType
has_many :fruits
end
class Fruit
belongs_to :fruit_type
validates :fruit_type_id, numericality: { only_integer: true }
validates :fruit_type_id, inclusion: { in: FruitType.pluck(:id), message: "is invalid" }
end
FactoryGirl.define do
factory :fruit_type_apple, class: FruitType do
name "Apple"
end
end
FactoryGirl.define do
factory :valid_fruit, class: Fruit do
name "Red Apple"
association :fruit_type, factory: :fruit_type_apple
end
end
Rspec test is:
it "should have valid factory" do
f = FactoryGirl.build( :valid_fruit )
puts f.fruit_type_id
puts "\n#{FruitType.all.pluck(:id)}"
expect(f).to be_valid
end
Result is:
1
[1]
F..........
Failures:
1) Fruit when validated should have valid factory
Failure/Error: expect(f).to be_valid
expected # to be valid, but got errors: Fruit type is invalid
As you can see, I've printed out the Fruit Type id list in the test, which includes only 1. And I've printed out the value of fruit_type_id for the fruit, which is 1. Yet, the inclusion validation still fails.
If I do the same thing in the rails console just by creating fruits and types manually, the validation works fine, it's just when I run the test I'm seeing this behavior. Any ideas? I must be missing something about Factory Girl here.
You shouldn't validate the fruit_type_id attribute, rather use presence validation
validates :fruit_type, presence: true

Resources