action_factory 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +81 -0
- data/Rakefile +3 -0
- data/lib/action_factory/active_record.rb +54 -0
- data/lib/action_factory/assignment_compiler.rb +23 -0
- data/lib/action_factory/assignments/attribute.rb +15 -0
- data/lib/action_factory/assignments/sequence.rb +17 -0
- data/lib/action_factory/assignments/trait.rb +15 -0
- data/lib/action_factory/assignments.rb +3 -0
- data/lib/action_factory/attribute_assigner.rb +17 -0
- data/lib/action_factory/base.rb +144 -0
- data/lib/action_factory/factory_finder.rb +26 -0
- data/lib/action_factory/helpers.rb +16 -0
- data/lib/action_factory/registry.rb +25 -0
- data/lib/action_factory/runner.rb +29 -0
- data/lib/action_factory/version.rb +3 -0
- data/lib/action_factory.rb +28 -0
- data/spec/action_factory/assignment_compiler_spec.rb +66 -0
- data/spec/action_factory/attribute_assigner_spec.rb +16 -0
- data/spec/action_factory/factory_finder_spec.rb +38 -0
- data/spec/action_factory/registry_spec.rb +45 -0
- data/spec/action_factory/runner_spec.rb +36 -0
- data/spec/action_factory_spec.rb +37 -0
- data/spec/spec_helper.rb +97 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 45042f5597609edaa5fbf3a24b85f970f67718a99072f32fbb4b2ec3a331eb42
|
4
|
+
data.tar.gz: 9f09d6f30142805d35c36940811c46a0c1bfe4c392edd4cffef3e2fddd372511
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '008726cf59ddd69261213220f38887d9baca503e14c9db2aac61cf96fdadaa185e04ce2c34602c4365ed49f1c306ad3c7fbeb98d58acf88ad47a45805f8cf980'
|
7
|
+
data.tar.gz: 7db5bfe669686ce793af26aab8927f0b3ee7e168f4ae2411fa10adf7335c228dc91a0b2dd4a303423539e64e90cff5903cdfc12dd8023c41b0e6e39ed118de2e
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# ActionFactory
|
2
|
+
A Simple OOO Factory lib for Ruby (and Rails)
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
|
6
|
+
### Setup
|
7
|
+
Create a base factory class
|
8
|
+
```ruby
|
9
|
+
class ApplicationFactory < ActionFactory::Base
|
10
|
+
end
|
11
|
+
```
|
12
|
+
|
13
|
+
If you're using `ActiveRecord`, add the `association` helpers
|
14
|
+
```ruby
|
15
|
+
class ApplicationFactory < ActionFactory::Base
|
16
|
+
include ActionFactory::ActiveRecord
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
### Creating your first factory
|
21
|
+
```ruby
|
22
|
+
class UserFactory < ApplicationFactory
|
23
|
+
attribute(:name) { "Hank Green" }
|
24
|
+
sequence(:email) { |i| "hgreen#{i}@example.com" }
|
25
|
+
|
26
|
+
trait(:activated) do
|
27
|
+
instance.activate!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
### Callbacks
|
33
|
+
`ActionFactory::Base` uses `ActiveModel::Callbacks` to add the following lifecycle events:
|
34
|
+
```
|
35
|
+
after_initialize
|
36
|
+
before_assign_attributes
|
37
|
+
around_assign_attributes
|
38
|
+
after_assign_attributes
|
39
|
+
before_create
|
40
|
+
around_create
|
41
|
+
after_create
|
42
|
+
```
|
43
|
+
|
44
|
+
For example
|
45
|
+
```ruby
|
46
|
+
class MyModelFactory < ApplicationFactory
|
47
|
+
after_initialize :do_the_thing
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def do_the_thing
|
52
|
+
instance.some_attribute = true if attributes[:some_attribute].blank?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
### Including helpers
|
58
|
+
You can use `ActionFactory::Helpers` to call `create(:my_factory_name)` and `build(:my_factory_name)`.
|
59
|
+
Here's an example for setting it up with RSpec:
|
60
|
+
```ruby
|
61
|
+
RSpec.configure do |config|
|
62
|
+
config.include ActionFactory::Helpers
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
Via bundler:
|
68
|
+
```bash
|
69
|
+
bundle add action_factory
|
70
|
+
```
|
71
|
+
|
72
|
+
Or install it yourself as:
|
73
|
+
```bash
|
74
|
+
$ gem install action_factory
|
75
|
+
```
|
76
|
+
|
77
|
+
## Contributing
|
78
|
+
Contributions welcome
|
79
|
+
|
80
|
+
## License
|
81
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
require "action_factory/runner"
|
6
|
+
|
7
|
+
module ActionFactory
|
8
|
+
module ActiveRecord
|
9
|
+
|
10
|
+
class Association
|
11
|
+
def initialize(strategy:, factory_name:, traits:, block:)
|
12
|
+
@strategy = strategy
|
13
|
+
@factory_name = factory_name
|
14
|
+
@traits = traits
|
15
|
+
@block = block
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate(strategy)
|
19
|
+
@block ? @block.call(runner) : runner.run(strategy)
|
20
|
+
end
|
21
|
+
|
22
|
+
def runner
|
23
|
+
@runner ||= ActionFactory::Runner.new(@factory_name, *@traits, strategy: @strategy)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.extend ClassMethods
|
29
|
+
base.class_exec do
|
30
|
+
after_assign_attributes :build_associations
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
def associations
|
36
|
+
@associations ||= {}.with_indifferent_access
|
37
|
+
end
|
38
|
+
|
39
|
+
def association(name, strategy: nil, factory: name, traits: [], &block)
|
40
|
+
associations[name] = Association.new(strategy:, factory_name: factory, traits:, block:)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def build_associations
|
47
|
+
self.class.associations.except(*@attributes.keys).each do |name, association|
|
48
|
+
associated_record = association.generate(@strategy)
|
49
|
+
@instance.association(name).writer(associated_record)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionFactory
|
4
|
+
class AssignmentCompiler
|
5
|
+
|
6
|
+
def initialize(factory)
|
7
|
+
@factory = factory
|
8
|
+
end
|
9
|
+
|
10
|
+
def compile(assignments, only: nil, except: [])
|
11
|
+
if only.present? && except.present?
|
12
|
+
raise ArgumentError, "Cannot use both 'only' and 'except' options"
|
13
|
+
end
|
14
|
+
|
15
|
+
assignments = assignments.slice(*only) unless only.nil?
|
16
|
+
|
17
|
+
assignments.except(*except).transform_values do |assignment|
|
18
|
+
assignment.compile(@factory)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionFactory
|
4
|
+
module Assignments
|
5
|
+
class Sequence
|
6
|
+
def initialize(block)
|
7
|
+
@count = 0
|
8
|
+
@block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile(factory)
|
12
|
+
# @count += 1
|
13
|
+
factory.instance_exec(@count += 1, &@block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionFactory
|
4
|
+
class AttributeAssigner
|
5
|
+
|
6
|
+
def initialize(instance)
|
7
|
+
@instance = instance
|
8
|
+
end
|
9
|
+
|
10
|
+
def assign(attributes)
|
11
|
+
attributes.each do |name, value|
|
12
|
+
@instance.public_send("#{name}=", value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
require "active_support/core_ext/object/deep_dup"
|
6
|
+
|
7
|
+
require "active_model/callbacks"
|
8
|
+
|
9
|
+
require "action_factory/assignment_compiler"
|
10
|
+
require "action_factory/attribute_assigner"
|
11
|
+
require "action_factory/assignments"
|
12
|
+
|
13
|
+
module ActionFactory
|
14
|
+
class Base
|
15
|
+
extend ActiveModel::Callbacks
|
16
|
+
|
17
|
+
define_model_callbacks :initialize, only: [:after]
|
18
|
+
define_model_callbacks :assign_attributes, :create
|
19
|
+
|
20
|
+
ClassNotFound = Class.new(StandardError)
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_reader :initializer, :creator
|
24
|
+
|
25
|
+
def factory(name)
|
26
|
+
Registry.register(name, self.name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def class_name(name)
|
30
|
+
@klass_name = name
|
31
|
+
end
|
32
|
+
|
33
|
+
def klass_name
|
34
|
+
@klass_name ||= name.delete_suffix('Factory')
|
35
|
+
end
|
36
|
+
|
37
|
+
def klass
|
38
|
+
@klass ||= klass_name.constantize
|
39
|
+
rescue NameError
|
40
|
+
raise ClassNotFound, "Class with name #{class_name.inspect} not found"
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_initialize(&block)
|
44
|
+
raise ArgumentError, 'Block required' unless block_given?
|
45
|
+
@initializer = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_create(&block)
|
49
|
+
raise ArgumentError, 'Block required' unless block_given?
|
50
|
+
@creator = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def attribute(name, &block)
|
54
|
+
assignments[:attributes][name] = ActionFactory::Assignments::Attribute.new(block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def sequence(name, &block)
|
58
|
+
assignments[:attributes][name] = ActionFactory::Assignments::Sequence.new(block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def trait(name, &block)
|
62
|
+
assignments[:traits][name] = ActionFactory::Assignments::Trait.new(block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def assignments
|
66
|
+
@assignments ||= begin
|
67
|
+
if self.superclass.respond_to?(:assignments)
|
68
|
+
self.superclass.assignments.deep_dup
|
69
|
+
else
|
70
|
+
ActiveSupport::HashWithIndifferentAccess.new { |hash, key| hash[key] = {}.with_indifferent_access }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
delegate :initializer, :creator, :klass, :assignments, to: :class
|
77
|
+
|
78
|
+
attr_reader :traits, :attributes, :instance
|
79
|
+
|
80
|
+
def initialize(*traits, **attributes)
|
81
|
+
@traits = traits
|
82
|
+
@attributes = attributes
|
83
|
+
@instance = build_instance_with_callbacks
|
84
|
+
end
|
85
|
+
|
86
|
+
def run(strategy)
|
87
|
+
@strategy = strategy
|
88
|
+
public_send(@strategy)
|
89
|
+
end
|
90
|
+
|
91
|
+
def build
|
92
|
+
run_callbacks :assign_attributes do
|
93
|
+
assign_attributes
|
94
|
+
end
|
95
|
+
instance
|
96
|
+
end
|
97
|
+
|
98
|
+
def create
|
99
|
+
build
|
100
|
+
run_callbacks :create do
|
101
|
+
persist_instance
|
102
|
+
end
|
103
|
+
instance
|
104
|
+
end
|
105
|
+
|
106
|
+
def factory_attributes
|
107
|
+
@factory_attributes ||= assignment_compiler.compile(assignments[:attributes], except: @attributes.keys)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def build_instance_with_callbacks
|
113
|
+
run_callbacks :initialize do
|
114
|
+
build_instance
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def build_instance
|
119
|
+
return klass.class_exec(self, &initializer) if initializer
|
120
|
+
|
121
|
+
klass.public_send(ActionFactory.configuration.initialize_method)
|
122
|
+
end
|
123
|
+
|
124
|
+
def persist_instance
|
125
|
+
return @instance.instance_exec(self, &creator) if creator
|
126
|
+
|
127
|
+
@instance.public_send(ActionFactory.configuration.persist_method)
|
128
|
+
end
|
129
|
+
|
130
|
+
def assign_attributes
|
131
|
+
attribute_assigner.assign(@attributes)
|
132
|
+
attribute_assigner.assign(factory_attributes)
|
133
|
+
assignment_compiler.compile(assignments[:traits], only: @traits)
|
134
|
+
end
|
135
|
+
|
136
|
+
def assignment_compiler
|
137
|
+
@assignment_compiler ||= AssignmentCompiler.new(self)
|
138
|
+
end
|
139
|
+
|
140
|
+
def attribute_assigner
|
141
|
+
@attribute_assigner ||= AttributeAssigner.new(instance)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_factory/registry"
|
4
|
+
|
5
|
+
module ActionFactory
|
6
|
+
class FactoryFinder
|
7
|
+
FactoryNotFound = Class.new(StandardError)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def factory_class_for(name)
|
11
|
+
new(name).factory_class
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(name)
|
16
|
+
@name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def factory_class
|
20
|
+
factory_class_name = Registry.factory_class_name(@name)
|
21
|
+
factory_class_name.constantize
|
22
|
+
rescue NameError
|
23
|
+
raise FactoryNotFound, "Factory with class name #{factory_class_name.inspect} not found"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require "action_factory/factory_finder"
|
4
|
+
require "action_factory/runner"
|
5
|
+
|
6
|
+
module ActionFactory
|
7
|
+
module Helpers
|
8
|
+
def build(factory_name, *traits, **attributes)
|
9
|
+
ActionFactory::Runner.run(factory_name, *traits, strategy: :build, **attributes)
|
10
|
+
end
|
11
|
+
|
12
|
+
def create(factory_name, *traits, **attributes)
|
13
|
+
ActionFactory::Runner.run(factory_name, *traits, strategy: :create, **attributes)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/inflections"
|
4
|
+
|
5
|
+
module ActionFactory
|
6
|
+
module Registry
|
7
|
+
class << self
|
8
|
+
def register(factory_name, factory_class_name)
|
9
|
+
factories[factory_name.to_sym] = factory_class_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def factories
|
13
|
+
@factories ||= Hash.new { |factories, name| factories[name.to_sym] = name.to_s.classify }
|
14
|
+
end
|
15
|
+
|
16
|
+
def factory_class_name(factory_name)
|
17
|
+
"#{class_name(factory_name)}Factory"
|
18
|
+
end
|
19
|
+
|
20
|
+
def class_name(factory_name)
|
21
|
+
factories[factory_name.to_sym]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
|
5
|
+
require "action_factory/factory_finder"
|
6
|
+
|
7
|
+
module ActionFactory
|
8
|
+
class Runner
|
9
|
+
class << self
|
10
|
+
def run(...)
|
11
|
+
new(...).run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :factory, :factory_name, :strategy
|
16
|
+
delegate :traits, :attributes, to: :factory
|
17
|
+
|
18
|
+
def initialize(factory_name, *traits, strategy: nil, **attributes)
|
19
|
+
@factory_name = factory_name
|
20
|
+
@strategy = strategy
|
21
|
+
factory_class = FactoryFinder.factory_class_for(@factory_name)
|
22
|
+
@factory = factory_class.new(*traits, **attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(strategy = nil)
|
26
|
+
@factory.run(strategy || @strategy)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
|
5
|
+
require "action_factory/version"
|
6
|
+
require "action_factory/helpers"
|
7
|
+
require "action_factory/base"
|
8
|
+
|
9
|
+
module ActionFactory
|
10
|
+
class Configuration
|
11
|
+
attr_accessor :persist_method, :initialize_method
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@persist_method = :save!
|
15
|
+
@initialize_method = :new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def configuration
|
21
|
+
@configuration ||= Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def configure
|
25
|
+
yield(configuration)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActionFactory::AssignmentCompiler do
|
4
|
+
let(:factory) { double }
|
5
|
+
|
6
|
+
subject(:compiler) { described_class.new(factory) }
|
7
|
+
|
8
|
+
describe "#compile" do
|
9
|
+
let(:name_attribute_assignment) { instance_double(ActionFactory::Assignments::Attribute) }
|
10
|
+
let(:uid_sequence_assignment) { instance_double(ActionFactory::Assignments::Sequence) }
|
11
|
+
let(:unique_trait_assignment) { instance_double(ActionFactory::Assignments::Sequence) }
|
12
|
+
let(:assignments) do
|
13
|
+
{
|
14
|
+
name: name_attribute_assignment,
|
15
|
+
uid: uid_sequence_assignment,
|
16
|
+
unique: unique_trait_assignment
|
17
|
+
}
|
18
|
+
end
|
19
|
+
let(:name) { "John" }
|
20
|
+
let(:uid) { "123" }
|
21
|
+
let(:unique) { "unique" }
|
22
|
+
|
23
|
+
let(:expected_attributes) do
|
24
|
+
{
|
25
|
+
name: name,
|
26
|
+
uid: uid,
|
27
|
+
unique: unique
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
it "compiles the assignments" do
|
32
|
+
assignments.each do |key, assignment|
|
33
|
+
expect(assignment).to receive(:compile).with(factory).and_return(send(key))
|
34
|
+
end
|
35
|
+
expect(compiler.compile(assignments)).to eq(expected_attributes)
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when 'only' is given" do
|
39
|
+
let(:only) { [:name] }
|
40
|
+
|
41
|
+
it "compiles only the given assignments" do
|
42
|
+
expect(name_attribute_assignment).to receive(:compile).with(factory).and_return(name)
|
43
|
+
expect(compiler.compile(assignments, only: only)).to eq(name: name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when 'except' is given" do
|
48
|
+
let(:except) { [:name] }
|
49
|
+
|
50
|
+
it "compiles all except the given assignments" do
|
51
|
+
expect(uid_sequence_assignment).to receive(:compile).with(factory).and_return(uid)
|
52
|
+
expect(unique_trait_assignment).to receive(:compile).with(factory).and_return(unique)
|
53
|
+
expect(compiler.compile(assignments, except: except)).to eq(uid: uid, unique: unique)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when both 'only' and 'except' are given" do
|
58
|
+
let(:only) { [:name] }
|
59
|
+
let(:except) { [:uid] }
|
60
|
+
|
61
|
+
it "raises an ArgumentError" do
|
62
|
+
expect { compiler.compile(assignments, only: only, except: except) }.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActionFactory::AttributeAssigner do
|
4
|
+
let(:instance) { double }
|
5
|
+
|
6
|
+
subject(:assigner) { described_class.new(instance) }
|
7
|
+
|
8
|
+
describe "#assign" do
|
9
|
+
let(:attributes) { { name: "John" } }
|
10
|
+
|
11
|
+
it "assigns the attributes to the instance" do
|
12
|
+
expect(instance).to receive(:name=).with("John")
|
13
|
+
assigner.assign(attributes)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActionFactory::FactoryFinder do
|
4
|
+
describe ".factory_class_for" do
|
5
|
+
let(:factory_class) { Class.new(ActionFactory::Base) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
stub_const("UserFactory", factory_class)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns the factory class for the given name" do
|
12
|
+
expect(described_class.factory_class_for(:user)).to eq(factory_class)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#factory_class" do
|
17
|
+
let(:factory_class) { Class.new(ActionFactory::Base) }
|
18
|
+
let(:factory_name) { :user }
|
19
|
+
|
20
|
+
before do
|
21
|
+
stub_const("UserFactory", factory_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
subject(:factory_finder) { described_class.new(factory_name) }
|
25
|
+
|
26
|
+
it "returns the factory class for the given name" do
|
27
|
+
expect(factory_finder.factory_class).to eq(factory_class)
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when the factory class is not found" do
|
31
|
+
let(:factory_name) { :foo }
|
32
|
+
|
33
|
+
it "raises an error" do
|
34
|
+
expect { factory_finder.factory_class }.to raise_error(ActionFactory::FactoryFinder::FactoryNotFound)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActionFactory::Registry do
|
4
|
+
let(:factory_name) { :user }
|
5
|
+
let(:model_name) { "CustomUser" }
|
6
|
+
let(:default_factory_class_name) { "UserFactory" }
|
7
|
+
let(:expected_factory_class_name) { "CustomUserFactory" }
|
8
|
+
|
9
|
+
describe ".register" do
|
10
|
+
subject(:register) { described_class.register(factory_name, model_name) }
|
11
|
+
|
12
|
+
it "registers the factory class name for the given factory name" do
|
13
|
+
expect { register }.to(
|
14
|
+
change { described_class.factory_class_name(factory_name) }.from(
|
15
|
+
default_factory_class_name
|
16
|
+
).to(
|
17
|
+
expected_factory_class_name
|
18
|
+
)
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".factories" do
|
24
|
+
subject { described_class.factories }
|
25
|
+
|
26
|
+
it { is_expected.to be_a(Hash) }
|
27
|
+
|
28
|
+
it "returns a hash with default values" do
|
29
|
+
expect(subject.default_proc).to be_a(Proc)
|
30
|
+
expect(subject[:foo]).to eq("Foo")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".factory_class_name" do
|
35
|
+
before do
|
36
|
+
described_class.register(factory_name, model_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
subject(:factory_class_name) { described_class.factory_class_name(factory_name) }
|
40
|
+
|
41
|
+
it "returns the factory class name for the given factory name" do
|
42
|
+
expect(factory_class_name).to eq(expected_factory_class_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActionFactory::Runner do
|
4
|
+
describe "#run" do
|
5
|
+
let(:factory_class) { Class.new(ActionFactory::Base) }
|
6
|
+
let(:factory_name) { :user }
|
7
|
+
let(:attributes) { { name: "John" } }
|
8
|
+
let(:traits) { [:admin] }
|
9
|
+
let(:factory) { instance_double(ActionFactory::Base) }
|
10
|
+
let(:instance) { double }
|
11
|
+
|
12
|
+
before do
|
13
|
+
stub_const("UserFactory", factory_class)
|
14
|
+
allow(ActionFactory::FactoryFinder).to receive(:factory_class_for).with(factory_name).and_return(factory_class)
|
15
|
+
allow(factory_class).to receive(:new).with(*traits, **attributes).and_return(factory)
|
16
|
+
end
|
17
|
+
|
18
|
+
subject(:runner) { described_class.new(factory_name, *traits, **attributes) }
|
19
|
+
|
20
|
+
it "runs the factory" do
|
21
|
+
allow(factory).to receive(:run).with(nil).and_return(instance)
|
22
|
+
expect(runner.run).to eq(instance)
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when a strategy is given" do
|
26
|
+
subject(:runner) { described_class.new(factory_name, *traits, strategy: strategy, **attributes) }
|
27
|
+
|
28
|
+
let(:strategy) { :create }
|
29
|
+
|
30
|
+
it "runs the factory with the given strategy" do
|
31
|
+
allow(factory).to receive(:run).with(strategy).and_return(instance)
|
32
|
+
expect(runner.run(strategy)).to eq(instance)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActionFactory do
|
4
|
+
it "has a version number" do
|
5
|
+
expect(ActionFactory::VERSION).to eq "0.1.1"
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".configuration" do
|
9
|
+
subject { described_class.configuration }
|
10
|
+
|
11
|
+
it { is_expected.to be_a(ActionFactory::Configuration) }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".configure" do
|
15
|
+
subject(:configure) { described_class.configure { |config| config.persist_method = :save } }
|
16
|
+
|
17
|
+
it "configures the gem" do
|
18
|
+
expect { configure }.to change { described_class.configuration.persist_method }.from(:save!).to(:save)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ActionFactory::Configuration do
|
23
|
+
subject(:configuration) { described_class.new }
|
24
|
+
|
25
|
+
describe "#persist_method" do
|
26
|
+
subject { configuration.persist_method }
|
27
|
+
|
28
|
+
it { is_expected.to eq :save! }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#initialize_method" do
|
32
|
+
subject { configuration.initialize_method }
|
33
|
+
|
34
|
+
it { is_expected.to eq :new }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
ENV["RAILS_ENV"] ||= "test"
|
2
|
+
|
3
|
+
require "action_factory"
|
4
|
+
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
5
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
7
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
8
|
+
# files.
|
9
|
+
#
|
10
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
11
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
12
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
13
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
14
|
+
# a separate helper file that requires the additional dependencies and performs
|
15
|
+
# the additional setup, and require it from the spec files that actually need
|
16
|
+
# it.
|
17
|
+
#
|
18
|
+
# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
|
+
RSpec.configure do |config|
|
20
|
+
# rspec-expectations config goes here. You can use an alternate
|
21
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
22
|
+
# assertions if you prefer.
|
23
|
+
config.expect_with :rspec do |expectations|
|
24
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
+
# and `failure_message` of custom matchers include text for helper methods
|
26
|
+
# defined using `chain`, e.g.:
|
27
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
+
# # => "be bigger than 2 and smaller than 4"
|
29
|
+
# ...rather than:
|
30
|
+
# # => "be bigger than 2"
|
31
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
35
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
36
|
+
config.mock_with :rspec do |mocks|
|
37
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
38
|
+
# a real object. This is generally recommended, and will default to
|
39
|
+
# `true` in RSpec 4.
|
40
|
+
mocks.verify_partial_doubles = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
44
|
+
# have no way to turn it off -- the option exists only for backwards
|
45
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
46
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
47
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
48
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
49
|
+
|
50
|
+
# The settings below are suggested to provide a good initial experience
|
51
|
+
# with RSpec, but feel free to customize to your heart's content.
|
52
|
+
=begin
|
53
|
+
# This allows you to limit a spec run to individual examples or groups
|
54
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
55
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
56
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
57
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
58
|
+
config.filter_run_when_matching :focus
|
59
|
+
|
60
|
+
# Allows RSpec to persist some state between runs in order to support
|
61
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
62
|
+
# you configure your source control system to ignore this file.
|
63
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
64
|
+
|
65
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
66
|
+
# recommended. For more details, see:
|
67
|
+
# https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/
|
68
|
+
config.disable_monkey_patching!
|
69
|
+
|
70
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
71
|
+
# file, and it's useful to allow more verbose output when running an
|
72
|
+
# individual spec file.
|
73
|
+
if config.files_to_run.one?
|
74
|
+
# Use the documentation formatter for detailed output,
|
75
|
+
# unless a formatter has already been configured
|
76
|
+
# (e.g. via a command-line flag).
|
77
|
+
config.default_formatter = "doc"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Print the 10 slowest examples and example groups at the
|
81
|
+
# end of the spec run, to help surface which specs are running
|
82
|
+
# particularly slow.
|
83
|
+
config.profile_examples = 10
|
84
|
+
|
85
|
+
# Run specs in random order to surface order dependencies. If you find an
|
86
|
+
# order dependency and want to debug it, you can fix the order by providing
|
87
|
+
# the seed, which is printed after each run.
|
88
|
+
# --seed 1234
|
89
|
+
config.order = :random
|
90
|
+
|
91
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
92
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
93
|
+
# test failures related to randomization by passing the same `--seed` value
|
94
|
+
# as the one that triggered the failure.
|
95
|
+
Kernel.srand config.seed
|
96
|
+
=end
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Loyola
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-07-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.0.6
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '7.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.0.6
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activemodel
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '7.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 7.0.6
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '7.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 7.0.6
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rspec
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '3.12'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - "~>"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '3.12'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: debug
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '1.8'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '1.8'
|
81
|
+
description: Your factories are now classes.
|
82
|
+
email:
|
83
|
+
- berna.loyola@gmail.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- README.md
|
89
|
+
- Rakefile
|
90
|
+
- lib/action_factory.rb
|
91
|
+
- lib/action_factory/active_record.rb
|
92
|
+
- lib/action_factory/assignment_compiler.rb
|
93
|
+
- lib/action_factory/assignments.rb
|
94
|
+
- lib/action_factory/assignments/attribute.rb
|
95
|
+
- lib/action_factory/assignments/sequence.rb
|
96
|
+
- lib/action_factory/assignments/trait.rb
|
97
|
+
- lib/action_factory/attribute_assigner.rb
|
98
|
+
- lib/action_factory/base.rb
|
99
|
+
- lib/action_factory/factory_finder.rb
|
100
|
+
- lib/action_factory/helpers.rb
|
101
|
+
- lib/action_factory/registry.rb
|
102
|
+
- lib/action_factory/runner.rb
|
103
|
+
- lib/action_factory/version.rb
|
104
|
+
- spec/action_factory/assignment_compiler_spec.rb
|
105
|
+
- spec/action_factory/attribute_assigner_spec.rb
|
106
|
+
- spec/action_factory/factory_finder_spec.rb
|
107
|
+
- spec/action_factory/registry_spec.rb
|
108
|
+
- spec/action_factory/runner_spec.rb
|
109
|
+
- spec/action_factory_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
homepage: https://github.com/b-loyola/action_factory
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata:
|
115
|
+
allowed_push_host: https://rubygems.org
|
116
|
+
homepage_uri: https://github.com/b-loyola/action_factory
|
117
|
+
source_code_uri: https://github.com/b-loyola/action_factory
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 3.2.2
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubygems_version: 3.4.10
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: A Simple OOO Factory lib for Ruby (and Rails)
|
137
|
+
test_files:
|
138
|
+
- spec/action_factory/assignment_compiler_spec.rb
|
139
|
+
- spec/action_factory/attribute_assigner_spec.rb
|
140
|
+
- spec/action_factory/factory_finder_spec.rb
|
141
|
+
- spec/action_factory/registry_spec.rb
|
142
|
+
- spec/action_factory/runner_spec.rb
|
143
|
+
- spec/action_factory_spec.rb
|
144
|
+
- spec/spec_helper.rb
|