Skip to content

Commit

Permalink
[Fix #80] Add ability to preserve traits (#83)
Browse files Browse the repository at this point in the history
* [Fix #80] Ability to revert to default behavior when have traits on association

* Use kwargs in `set_factory_default` and store options along objects

* Enable non-default Style/ReturnNil cop

* Remove unused methods
  • Loading branch information
Vasfed authored and palkan committed Jun 28, 2018
1 parent bfc7ee6 commit 5d9c543
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 16 deletions.
4 changes: 4 additions & 0 deletions .rubocop.yml
Expand Up @@ -71,6 +71,10 @@ Style/GlobalVars:
- 'spec/integrations/fixtures/**/*'
- 'spec/bugs/fixtures/**/*'

Style/ReturnNil:
Enabled: true
EnforcedStyle: return

Layout/SpaceInsideStringInterpolation:
EnforcedStyle: no_space

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -58,6 +58,12 @@ end
Do not take into account `xit`, `pending`, `its`, etc. examples,
only consider regular `it`, `specify`, `scenario`, `example`.

- [Fix [#80](https://github.com/palkan/test-prof/issues/80)] Added ability to preserve traits. ([@Vasfed][])

Disabled by default for compatibility. Enable globally by `FactoryDefault.preserve_traits = true` or for single `create_default`: `create_default(:user, preserve_traits: true)`

When enabled - default object will be used only when there's no [traits](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#traits) in association.

## 0.5.0 (2018-04-25)

### Features
Expand Down Expand Up @@ -314,3 +320,4 @@ Ensure output dir exists in `#artifact_path` method.
[@IDolgirev]: https://github.com/IDolgirev
[@desoleary]: https://github.com/desoleary
[@rabotyaga]: https://github.com/rabotyaga
[@Vasfed]: https://github.com/Vasfed
19 changes: 19 additions & 0 deletions docs/factory_default.md
Expand Up @@ -109,3 +109,22 @@ before { FactoryBot.set_factory_default(:user, user) }
- `FactoryBot#create_default(factory, *args)` – is a shortcut for `create` + `set_factory_default`.

**NOTE**. Defaults are cleaned up after each example.

### Working with traits

When you have traits in your associations like:

```ruby
factory :post do
association :user, factory: %i[user able_to_post]
end

factory :view do
association :user, factory: %i[user unable_to_post_only_view]
end
```

and set a default for `user` factory - you will find the same object used in all of the above factories. Sometimes this may break your logic.

To prevent this - set `FactoryDefault.preserve_traits = true` or use per-factory override
`create_default(:user, preserve_traits: true)`. This reverts back to original FactoryBot behavior for associations that have explicit traits defined.
35 changes: 22 additions & 13 deletions lib/test_prof/factory_default.rb
Expand Up @@ -9,18 +9,21 @@ module TestProf
module FactoryDefault
module DefaultSyntax # :nodoc:
def create_default(name, *args, &block)
set_factory_default(
name,
TestProf::FactoryBot.create(name, *args, &block)
)
options = args.extract_options!
preserve = options.delete(:preserve_traits)

obj = TestProf::FactoryBot.create(name, *args, options, &block)
set_factory_default(name, obj, preserve_traits: preserve)
end

def set_factory_default(name, obj)
FactoryDefault.register(name, obj)
def set_factory_default(name, obj, preserve_traits: nil)
FactoryDefault.register(name, obj, preserve_traits: preserve_traits)
end
end

class << self
attr_accessor :preserve_traits

def init
TestProf::FactoryBot::Syntax::Methods.include DefaultSyntax
TestProf::FactoryBot.extend DefaultSyntax
Expand All @@ -29,18 +32,24 @@ def init
TestProf::FactoryBot::Strategy::Stub.prepend StrategyExt

@store = {}
# default is false to retain backward compatibility
@preserve_traits = false
end

def register(name, obj)
store[name] = obj
def register(name, obj, **options)
options[:preserve_traits] = true if FactoryDefault.preserve_traits
store[name] = { object: obj, **options }
obj
end

def get(name)
store[name]
end
def get(name, traits = nil)
record = store[name]
return unless record

def exists?(name)
store.key?(name)
if traits && !traits.empty?
return if FactoryDefault.preserve_traits || record[:preserve_traits]
end
record[:object]
end

def remove(name)
Expand Down
7 changes: 5 additions & 2 deletions lib/test_prof/factory_default/factory_bot_patch.rb
Expand Up @@ -7,15 +7,18 @@ module RunnerExt
def name
@name
end

def traits
@traits
end
end
end

using RunnerExt

module StrategyExt
def association(runner)
return super unless FactoryDefault.exists?(runner.name)
FactoryDefault.get(runner.name)
FactoryDefault.get(runner.name, runner.traits) || super
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/integrations/factory_default_spec.rb
Expand Up @@ -6,6 +6,6 @@
specify "RSpec integration", :aggregate_failures do
output = run_rspec('factory_default')

expect(output).to include("5 examples, 0 failures")
expect(output).to include("8 examples, 0 failures")
end
end
43 changes: 43 additions & 0 deletions spec/integrations/fixtures/rspec/factory_default_fixture.rb
Expand Up @@ -41,4 +41,47 @@
expect(post.user).to eq user2
end
end

context "with preserved traits" do
let(:traited_post) { TestProf::FactoryBot.create(:post, :with_traited_user) }
let(:traited_user) { TestProf::FactoryBot.create_default(:user, :traited, tag: 'foo') }

context "global setting" do
before { TestProf::FactoryDefault.preserve_traits = true }

it "can still be set default" do
expect(traited_user.tag).to eq 'foo'
expect(post.user).to eq traited_user
end

it "uses different objects for default and for traits" do
expect {
user
post
expect(post.user).to eq user
expect(traited_post.user).not_to eq user
expect(TestProf::FactoryBot.create(:post, :with_traited_user).user).not_to eq traited_post.user
}.to change(User, :count).by(3)
end
end

context "local override" do
before { TestProf::FactoryDefault.preserve_traits = false }
let(:override_user) { TestProf::FactoryBot.create_default(:user, preserve_traits: true) }
let(:other_traited_post) { TestProf::FactoryBot.create(:post, :with_traited_user) }

it "uses different objects for default and for traits" do
expect {
user
post
expect(post.user).to eq user
expect(traited_post.user).to eq user

override_user
expect(other_traited_post.user).not_to eq override_user
expect(other_traited_post.user).not_to eq traited_post.user
}.to change(User, :count).by(3)
end
end
end
end
17 changes: 17 additions & 0 deletions spec/support/ar_models.rb
Expand Up @@ -13,6 +13,7 @@
ActiveRecord::Schema.define do
create_table :users do |t|
t.string :name
t.string :tag
end

create_table :posts do |t|
Expand Down Expand Up @@ -48,6 +49,14 @@ class Post < ActiveRecord::Base
TestProf::FactoryBot.create_pair(:post)
end
end

trait :traited do
tag 'traited'
end

trait :other_trait do
tag 'other_trait'
end
end

factory :post do
Expand All @@ -57,6 +66,14 @@ class Post < ActiveRecord::Base
trait :with_bad_user do
user { create(:user) }
end

trait :with_traited_user do
association :user, factory: %i[user traited]
end

trait :with_other_traited_user do
association :user, factory: %i[user other_trait]
end
end
end

Expand Down

0 comments on commit 5d9c543

Please sign in to comment.