fixture_factory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9970ff1a9b9427593019d53a7ae239fe9752651b9a8a8d15b684ecac2d041c93
4
+ data.tar.gz: 1842a6092e09ff6d64f25630d0c0a725c49315d2664629b1af0651935ac0970d
5
+ SHA512:
6
+ metadata.gz: a5aa2e004aeabda9c7472c1e159f823b8d97ae8e9d094e82f96fbf4838270ee1f04a69172471f8dd9cc37712d4efe6ce4359e3994e9dd35b534a1982978ec248
7
+ data.tar.gz: c9533a232a9c28dd4d6ecb8356af3f49868b265766780c9d5fb4c29dc7a089907c97b45b7ba06e03d1bb498f0848d6e18245983861bd0d4efa266417939e0e88
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fixture_factory/version'
4
+ require 'fixture_factory/definition'
5
+ require 'fixture_factory/methods'
6
+ require 'fixture_factory/registry'
7
+ require 'fixture_factory/sequence'
8
+ require 'fixture_factory/errors'
9
+
10
+ module FixtureFactory
11
+ class << self
12
+ def attributes_for(name, options) # :nodoc:
13
+ _, attributes = retrieve(name, options)
14
+ attributes
15
+ end
16
+
17
+ def build(name, options) # :nodoc:
18
+ klass, attributes = retrieve(name, options)
19
+ klass.new(attributes)
20
+ end
21
+
22
+ def create(name, options) # :nodoc:
23
+ build(name, options).tap(&:save!)
24
+ end
25
+
26
+ def evaluate(block, args: [], context:) # :nodoc:
27
+ attributes = context.instance_exec(*args, &block)
28
+ extract_attributes(attributes)
29
+ end
30
+
31
+ private
32
+
33
+ def extract_attributes(object)
34
+ raise ArgumentError, "FixtureFactory blocks must return a hash-like object" unless object.respond_to?(:to_h)
35
+ object.to_h.symbolize_keys
36
+ end
37
+
38
+ def retrieve(name, scope:, context: nil, overrides: {})
39
+ definition = scope.all_factory_definitions.fetch(name) do
40
+ raise NotFoundError, name
41
+ end
42
+ attributes = definition.run(context: context)
43
+ attributes.merge!(overrides).delete(:id)
44
+ [definition.klass, attributes]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureFactory
4
+ class Definition # :nodoc:
5
+ EMPTY_BLOCK = proc {}
6
+
7
+ attr_reader :klass
8
+ attr_writer :block
9
+ attr_accessor :fixture_method, :fixture_name, :parent, :sequence
10
+
11
+ def initialize(name, options = {})
12
+ self.parent = options.fetch(:parent) { default_parent_for(name) }
13
+ self.klass = options.fetch(:class) { parent.klass }
14
+ self.fixture_method = options.fetch(:via) { parent.fixture_method }
15
+ self.fixture_name = options.fetch(:like) { parent.fixture_name }
16
+ self.block = options.fetch(:block) { EMPTY_BLOCK }
17
+ self.sequence = Sequence.new
18
+ end
19
+
20
+ def block
21
+ all_blocks = [parent&.block, @block].compact
22
+ -> (*args) do
23
+ all_blocks.reduce({}) do |attributes, block|
24
+ block_attributes = FixtureFactory.evaluate(
25
+ block, args: args, context: self
26
+ )
27
+ attributes.merge(block_attributes)
28
+ end
29
+ end
30
+ end
31
+
32
+ def klass=(new_class)
33
+ @klass = case new_class
34
+ when String
35
+ new_class.to_s.constantize # rubocop:disable Sorbet/ConstantsFromStrings
36
+ else
37
+ new_class
38
+ end
39
+ rescue NameError
40
+ raise WrongClassError, new_class
41
+ end
42
+
43
+ def fixture_args
44
+ [fixture_method, fixture_name]
45
+ end
46
+
47
+ def from_fixture?
48
+ fixture_name.present? && fixture_method.present?
49
+ end
50
+
51
+ def run(context:)
52
+ FixtureFactory.evaluate(runner, context: context)
53
+ end
54
+
55
+ private
56
+
57
+ def runner
58
+ definition = self
59
+ args = sequence.take(1)
60
+ -> do
61
+ attributes = FixtureFactory.evaluate(definition.block, args: args, context: self)
62
+ if definition.from_fixture?
63
+ begin
64
+ fixture = send(*definition.fixture_args)
65
+ attributes.reverse_merge!(fixture.attributes)
66
+ rescue NoMethodError
67
+ raise WrongFixtureMethodError, definition.fixture_args.first
68
+ end
69
+ end
70
+ attributes
71
+ end
72
+ end
73
+
74
+ def default_parent_for(name)
75
+ self.class.new(
76
+ name,
77
+ parent: nil,
78
+ like: nil,
79
+ class: name.to_s.classify.safe_constantize,
80
+ via: name.to_s.pluralize,
81
+ )
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureFactory
4
+ class Error < StandardError # :nodoc:
5
+ end
6
+
7
+ # Raised when a factory is referenced, but not defined.
8
+ class NotFoundError < Error
9
+ def initialize(fixture_name)
10
+ super(
11
+ <<~MSG.squish
12
+ No factory named "#{fixture_name}".
13
+ Did you forget to define it?
14
+ https://github.com/Shopify/fixture_factory/blob/master/README.md#definition
15
+ MSG
16
+ )
17
+ end
18
+ end
19
+
20
+ class WrongFixtureMethodError < Error
21
+ def initialize(method_name)
22
+ super(
23
+ <<~MSG.squish
24
+ No fixture method named "#{method_name}".
25
+ Try using the `via` option in your definition to specify a valid method.
26
+ https://github.com/Shopify/fixture_factory/blob/master/README.md#naming
27
+ MSG
28
+ )
29
+ end
30
+ end
31
+
32
+ class WrongClassError < Error
33
+ def initialize(class_name)
34
+ super(
35
+ <<~MSG.squish
36
+ No class named "#{class_name}".
37
+ Try using the `class` option in your definition to specify a valid class name.
38
+ https://github.com/Shopify/fixture_factory/blob/master/README.md#naming
39
+ MSG
40
+ )
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureFactory
4
+ module Methods
5
+ # Generates a hash of attributes given a factory name and an optional
6
+ # hash of override attributes.
7
+ #
8
+ # === Example
9
+ #
10
+ # attributes_for(:user)
11
+ # attributes_for(:blog, title: 'Riding Rails')
12
+ # attributes_for(:comment, content: 'Hello', approved: false)
13
+ # attributes_for(:post, comments_attributes: [attributes_for(:comment)])
14
+ def attributes_for(name, overrides = {})
15
+ FixtureFactory.attributes_for(
16
+ name, overrides: overrides, context: self, scope: self.class
17
+ )
18
+ end
19
+
20
+ # Generates an array of hash attributes given a factory name, a count,
21
+ # and an optional hash of override attributes.
22
+ #
23
+ # === Example
24
+ #
25
+ # attributes_for_list(:user, 5)
26
+ # attributes_for_list(:blog, 3, title: 'Riding Rails')
27
+ # attributes_for_list(:comment, 50, content: 'Hello', approved: false)
28
+ # attributes_for_list(:post, 3, comments_attributes: attributes_for_list(:comment, 1))
29
+ def attributes_for_list(name, count, overrides = {})
30
+ count.times.map { attributes_for(name, overrides) }
31
+ end
32
+
33
+ # Generates an instance of a model given a factory name and an optional
34
+ # hash of override attributes.
35
+ #
36
+ # === Example
37
+ #
38
+ # build(:user)
39
+ # build(:blog, title: 'Riding Rails')
40
+ # build(:comment, content: 'Hello', approved: false)
41
+ # build(:post, comments: [build(:comment)])
42
+ def build(name, overrides = {})
43
+ FixtureFactory.build(
44
+ name, overrides: overrides, context: self, scope: self.class
45
+ )
46
+ end
47
+
48
+ # Generates an array of model instances given a factory name and an optional
49
+ # hash of override attributes.
50
+ #
51
+ # === Example
52
+ #
53
+ # build_list(:user, 5)
54
+ # build_list(:blog, 3, title: 'Riding Rails')
55
+ # build_list(:comment, 50, content: 'Hello', approved: false)
56
+ # build_list(:post, 3, comments: build_list(:comment, 1))
57
+ def build_list(name, count, overrides = {})
58
+ count.times.map { build(name, overrides) }
59
+ end
60
+
61
+ # Generates a persisted model instance given a factory name and an optional
62
+ # hash of override attributes.
63
+ #
64
+ # === Example
65
+ #
66
+ # create(:user)
67
+ # create(:blog, title: 'Riding Rails')
68
+ # create(:comment, content: 'Hello', approved: false)
69
+ # create(:post, comments: [create(:comment)])
70
+ def create(name, overrides = {})
71
+ FixtureFactory.create(
72
+ name, overrides: overrides, context: self, scope: self.class
73
+ )
74
+ end
75
+
76
+ # Generates an array of persisted model instances given a factory name and
77
+ # an optional hash of override attributes.
78
+ #
79
+ # === Example
80
+ #
81
+ # create_list(:user, 5)
82
+ # create_list(:blog, 3, title: 'Riding Rails')
83
+ # create_list(:comment, 50, content: 'Hello', approved: false)
84
+ # create_list(:post, 3, comments: create_list(:comment, 1))
85
+ def create_list(name, count, overrides = {})
86
+ count.times.map { create(name, overrides) }
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module FixtureFactory
6
+ module Registry
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute(:fixture_factory_definitions) # :nodoc:
11
+ end
12
+
13
+ class_methods do
14
+ # Defines a fixture given a name, options, and a block.
15
+ #
16
+ # === Options
17
+ #
18
+ # [:parent]
19
+ # Specify a parent fixture to inherit options from. If no parent is specified,
20
+ # a default parent will be assumed that guesses class a via options.
21
+ # [:class]
22
+ # Specify a class (typically as a string to prevent autoloading) to instantiate
23
+ # when building or creating models from fixturies. This option is guessed
24
+ # using the classified fixture name.
25
+ # [:via]
26
+ # Specify a fixture method to call when generating data. Only necessary when
27
+ # the `like` option is used to specify a fixture name. This option is guessed
28
+ # by default using the pluralized fixture name.
29
+ # [:like]
30
+ # Specify a fixture to use when generating data. Used in combination with
31
+ # `via` to extract attributes from a fixture.
32
+ # [:block]
33
+ # Specify a block to call when running a fixture. This option will default to
34
+ # a passed block or an empty block if neither are provided.
35
+ #
36
+ # === Example
37
+ #
38
+ # define_factories do
39
+ # fixture(:user, like: :bob)
40
+ # fixture(:active_user, parent: :user) do
41
+ # { active: true }
42
+ # end
43
+ # fixture(:cool_user, class: 'User') do
44
+ # { status: :cool }
45
+ # end
46
+ # fixture(:admin, via: :users, like: :bob_admin, class: 'User')
47
+ # end
48
+ def fixture(name, options = {}, &block)
49
+ parent = all_factory_definitions[options[:parent]]
50
+ options[:parent] = parent if options.key?(:parent)
51
+ options[:block] = block if block
52
+ fixture_factory_definitions[name] = Definition.new(name, options)
53
+ end
54
+
55
+ # Sets up factories definitions for the current class scope. Accepts an
56
+ # optional block to define factories in.
57
+ #
58
+ # === Example
59
+ #
60
+ # class SomeTest < ActiveSupport::TestCase
61
+ # define_factories do
62
+ # fixture(:blog)
63
+ # end
64
+ # end
65
+ #
66
+ # SomeTest.define_factories do
67
+ # fixture(:post)
68
+ # end
69
+ def define_factories(&block)
70
+ self.fixture_factory_definitions = {}.with_indifferent_access
71
+ instance_exec(&block) if block.present?
72
+ end
73
+
74
+ def all_factory_definitions # :nodoc:
75
+ fixtury_ancestors = ancestors.select do |ancestor|
76
+ ancestor.respond_to?(:fixture_factory_definitions)
77
+ end
78
+ [self, *fixtury_ancestors].map(&:fixture_factory_definitions).reduce(:reverse_merge)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureFactory
4
+ class Sequence # :nodoc:
5
+ include Enumerable
6
+
7
+ def initialize
8
+ @count = 0
9
+ end
10
+
11
+ def next
12
+ @count += 1
13
+ end
14
+
15
+ def each
16
+ loop { yield(self.next) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureFactory
4
+ VERSION = "0.1.0"
5
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fixture_factory
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shopify Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activemodel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '5.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '5.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '5.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '5.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - gems@shopify.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - lib/fixture_factory.rb
133
+ - lib/fixture_factory/definition.rb
134
+ - lib/fixture_factory/errors.rb
135
+ - lib/fixture_factory/methods.rb
136
+ - lib/fixture_factory/registry.rb
137
+ - lib/fixture_factory/sequence.rb
138
+ - lib/fixture_factory/version.rb
139
+ homepage:
140
+ licenses: []
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubygems_version: 3.0.3
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Factories via fixtures
161
+ test_files: []