faker_maker 1.3.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6355e061188d88ab10d97dae93595abccbf2a73346b6220c265c9241198f93ba
4
- data.tar.gz: 487700e0d8e88f4fb69d00796bb4d644263e7c91af91eea188caa7eaf82861c5
3
+ metadata.gz: a077bc198db9d38ed9784aa30231b51955197b1ae28c0fc790aad28bd5c07f9f
4
+ data.tar.gz: f8eea37df0773cf0abb16f57e104a45941e24577c3ca0eb9ff19fa7922cad93a
5
5
  SHA512:
6
- metadata.gz: e2e50d6ffa61e8164163d2157a65d8a3b93b24e442028e0e04951b25f8e298693bd3a8c6ff3ff948442c38c972f427ec05017020dd120b0cfeefdbb02ebd82b8
7
- data.tar.gz: 4c1ae45972c019f0929b1c0337b20ebe4f92ecaa040811f515934d50ce38e826956c994a3043d1f8a27928921930a32980d4bff29fceffbb0c8a7a82f1d4ebd6
6
+ metadata.gz: d2423079545d812ca8434016d2e34399ac4c72be3cabdb237a36fe4f645de606c87f308cbcb082ad8dda0638f49fbd84a409421ee447fa0b67297475c1724f87
7
+ data.tar.gz: f6ead79d10a9f82bffc4a031629ce3c1c3b7885563db2c9ff00a6fc671e5d207bc4405cde40af11cee0261e95d5dbbd64b7cb5e7087749898d7d42587e626125
@@ -0,0 +1,37 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ pull_request:
13
+ branches: [ "master" ]
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ test:
20
+
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ matrix:
24
+ ruby-version: ["3.0", "3.1", "3.2"]
25
+
26
+ steps:
27
+ - uses: actions/checkout@v3
28
+ - name: Set up Ruby
29
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
30
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
31
+ # uses: ruby/setup-ruby@v1
32
+ uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
33
+ with:
34
+ ruby-version: ${{ matrix.ruby-version }}
35
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
36
+ - name: Run tests
37
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -14,4 +14,5 @@ Gemfile.lock
14
14
  .DS_Store
15
15
 
16
16
  faker_maker-*.gem
17
- test.rb
17
+ test.rb
18
+ .idea
data/.rubocop.yml CHANGED
@@ -45,5 +45,11 @@ Metrics/MethodLength:
45
45
  Lint/MissingSuper:
46
46
  Enabled: false
47
47
 
48
+ Metrics/CyclomaticComplexity:
49
+ Max: 10
50
+
51
+ Metrics/PerceivedComplexity:
52
+ Max: 10
53
+
48
54
  AllCops:
49
55
  NewCops: enable
data/bin/console CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'bundler/setup'
5
5
  require 'faker_maker'
6
+ require 'faker'
6
7
 
7
8
  # You can add fixtures and/or initialization code here to make experimenting
8
9
  # with your gem easier. You can also use a different console, if you like.
@@ -0,0 +1,45 @@
1
+ ---
2
+ layout: default
3
+ title: Chaos
4
+ parent: Usage
5
+ nav_order: 11
6
+ ---
7
+
8
+ # Chaos
9
+
10
+ Chaos mode introduces extra spice to your generated factories.
11
+
12
+ Attributes can be marked as either `required` or `optional`, which Chaos will use to determine what attributes are included when instantiating your factory.
13
+
14
+ Required attributes will always be present, however, optional attributes are not guaranteed to be present when Chaos is enabled.
15
+
16
+ *All attributes are optional by default.*
17
+
18
+ To explicitly mark attributes as either required or optional:
19
+
20
+ ```ruby
21
+ FM.factory :item, naming: :json do
22
+ name { 'Blanket' }
23
+ price(required: true) { 100 }
24
+ description(optional: true) { 'Keeps you warm and cozy' }
25
+ manufacturer(optional: 0.7) { 'A large fruit company' }
26
+ end
27
+ ```
28
+
29
+ You can state an attribute is optional using the `optional` option set to either be a `Boolean`, `Integer` or a `Float`.
30
+
31
+ When optional is set to either an `Integer` or a `Float`, this overrides the weighting which Chaos uses to determine the likelihood that attribute will be removed.
32
+
33
+ Higher the value, the more likely that attribute will be present. By default there's a 50/50 chance an optional attribute will be present.
34
+
35
+ To unleash Chaos over a factory, you need to enable it when instantiating your object:
36
+
37
+ ```ruby
38
+ result = FakerMaker[:item].build( chaos: true )
39
+ ```
40
+
41
+ You can also specify which attributes Chaos can use when instantiating your object:
42
+
43
+ ```ruby
44
+ result = FakerMaker[:item].build( chaos: %i[name manufacturer] )
45
+ ```
@@ -15,9 +15,59 @@ FakerMaker.factory :item do
15
15
  price { Faker::Commerce.price }
16
16
  end
17
17
 
18
+ FakerMaker.factory :basket do
19
+ items( has: 10, factory: :item )
20
+ end
21
+ ```
22
+
23
+ In this example, FakerMaker will build an `item` (well, 10 `item`s in this case) using item factory as it is building a `basket`. The advantage of this method is that `item` factory can be declared *after* the `basket` factory.
24
+
25
+ If you want to select randomly from one or more factories, provide an array of factory names:
26
+
27
+ ```ruby
28
+ FakerMaker.factory :coupon do
29
+ discount { Faker::Commerce.price }
30
+ end
31
+
32
+ FakerMaker.factory :item do
33
+ name { Faker::Commerce.product_name }
34
+ price { Faker::Commerce.price }
35
+ end
36
+
37
+ FakerMaker.factory :basket do
38
+ items( has: 10, factory: [:item, :discount] )
39
+ end
40
+ ```
41
+
42
+ In this example, through 10 iterations, one of `item` and `discount` factories will be called to build their objects.
43
+
44
+ Blocks can still be provided and the referenced factory built object will be passed to the block:
45
+
46
+ ```ruby
47
+ FakerMaker.factory :item do
48
+ name { Faker::Commerce.product_name }
49
+ price { Faker::Commerce.price }
50
+ end
51
+
52
+ FakerMaker.factory :basket do
53
+ items( has: 10, factory: :item ) { |item| item.price = 10.99 ; item}
54
+ end
55
+ ```
56
+ **Important:** the value for the attribute will be the value returned from the block. If you want to modify the contents of the referenced factory's object, don't forget to return it at the end of the block (as above).
57
+
58
+ ## Alternative method
59
+
60
+ There is an alternative style which might be of use:
61
+
62
+ ```ruby
63
+ FakerMaker.factory :item do
64
+ name { Faker::Commerce.product_name }
65
+ price { Faker::Commerce.price }
66
+ end
67
+
18
68
  FakerMaker.factory :basket do
19
69
  items( has: 10 ) { FakerMaker[:item].build }
20
70
  end
21
71
  ```
22
72
 
23
- You might have to [manage your dependencies]({% link usage/dependencies.md %}) and `require` your referenced factory.
73
+ With this pattern, you might have to [manage your dependencies]({% link usage/dependencies.md %}) and `require` your referenced factory.
data/docs/usage/index.md CHANGED
@@ -49,4 +49,15 @@ FakerMaker.factory :user, class: 'EmailUser' do
49
49
  email {'patsy@fabulous.co.uk'}
50
50
  admin {false}
51
51
  end
52
+ ```
53
+
54
+ Fields with no block (or reference to another factory) will be nil.
55
+
56
+ ```ruby
57
+ FakerMaker.factory :request do
58
+ body
59
+ end
60
+
61
+ FakerMaker[:request].build.body
62
+ # => nil
52
63
  ```
data/faker_maker.gemspec CHANGED
@@ -44,7 +44,7 @@ Gem::Specification.new do |spec|
44
44
  spec.add_dependency 'activesupport', '>= 5.2', '< 8'
45
45
 
46
46
  spec.add_development_dependency 'bundler', '~> 2.0'
47
- spec.add_development_dependency 'faker', '~> 2.1'
47
+ spec.add_development_dependency 'faker', '~> 3.2'
48
48
  spec.add_development_dependency 'guard', '~> 2.16'
49
49
  spec.add_development_dependency 'guard-bundler', '~> 3.0'
50
50
  spec.add_development_dependency 'guard-rubocop', '~> 1.3'
@@ -53,6 +53,7 @@ Gem::Specification.new do |spec|
53
53
  spec.add_development_dependency 'rake', '~> 13.0'
54
54
  spec.add_development_dependency 'rspec', '~> 3.8'
55
55
  spec.add_development_dependency 'rubocop', '~> 1.0'
56
+ spec.add_development_dependency 'racc', '~> 1.0'
56
57
 
57
58
  spec.required_ruby_version = '>= 3.0'
58
59
  end
@@ -3,16 +3,26 @@
3
3
  module FakerMaker
4
4
  # Attributes describe the fields of classes
5
5
  class Attribute
6
- attr_reader :name, :block, :translation
6
+ attr_reader :name, :block, :translation, :required, :optional, :optional_weighting, :embedded_factories
7
+
8
+ DEFAULT_OPTIONAL_WEIGHTING = 0.5
7
9
 
8
10
  def initialize( name, block = nil, options = {} )
9
11
  assert_valid_options options
10
12
  @name = name
11
- @block = block || proc {}
13
+ @block = block || nil
12
14
  @cardinality = options[:has] || 1
13
15
  @translation = options[:json]
14
16
  @omit = *options[:omit]
15
17
  @array = options[:array] == true
18
+ @embedded_factories = *options[:factory]
19
+
20
+ if options[:required].to_s.downcase.eql?('true') || options[:optional].to_s.downcase.eql?('false')
21
+ @required = true
22
+ else
23
+ @optional = true
24
+ @optional_weighting = determine_optional_weighting(options[:optional])
25
+ end
16
26
  end
17
27
 
18
28
  def array?
@@ -49,7 +59,18 @@ module FakerMaker
49
59
  end
50
60
 
51
61
  def assert_valid_options( options )
52
- options.assert_valid_keys :has, :array, :json, :omit
62
+ options.assert_valid_keys :has, :array, :json, :omit, :required, :optional, :factory
63
+ end
64
+
65
+ def determine_optional_weighting( value )
66
+ case value
67
+ when Float
68
+ value.between?(0, 1) ? value : (value / 100)
69
+ when Integer
70
+ value.ceil.between?(0, 100) ? (value.to_f / 100) : DEFAULT_OPTIONAL_WEIGHTING
71
+ else
72
+ DEFAULT_OPTIONAL_WEIGHTING
73
+ end
53
74
  end
54
75
  end
55
76
  end
@@ -5,7 +5,7 @@ module FakerMaker
5
5
  # Factories construct instances of a fake
6
6
  class Factory
7
7
  include Auditable
8
- attr_reader :name, :class_name, :parent
8
+ attr_reader :name, :class_name, :parent, :chaos_selected_attributes
9
9
 
10
10
  def initialize( name, options = {} )
11
11
  assert_valid_options options
@@ -19,7 +19,7 @@ module FakerMaker
19
19
  when nil
20
20
  nil
21
21
  else
22
- raise FakerMaker::NoSuchAttributeNamingStrategy, opttions[:naming]
22
+ raise FakerMaker::NoSuchAttributeNamingStrategy, options[:naming]
23
23
  end
24
24
  @attributes = []
25
25
  @klass = nil
@@ -42,11 +42,22 @@ module FakerMaker
42
42
  @instance ||= instantiate
43
43
  end
44
44
 
45
- def build( attributes = {} )
45
+ def build( attributes: {}, chaos: false, **kwargs )
46
+ if kwargs.present?
47
+ validate_deprecated_build(kwargs)
48
+ attributes = kwargs
49
+ end
50
+
46
51
  @instance = nil
47
52
  before_build if respond_to? :before_build
48
53
  assert_only_known_attributes_for_override( attributes )
49
- populate_instance instance, attributes
54
+
55
+ assert_chaos_options chaos if chaos
56
+
57
+ optional_attributes
58
+ required_attributes
59
+
60
+ populate_instance instance, attributes, chaos
50
61
  yield instance if block_given?
51
62
  after_build if respond_to? :after_build
52
63
  audit(@instance) if FakerMaker.configuration.audit?
@@ -70,6 +81,7 @@ module FakerMaker
70
81
  def as_json(*_args)
71
82
  build.as_json
72
83
  end
84
+ alias to_h as_json
73
85
 
74
86
  def parent?
75
87
  !@parent.nil?
@@ -110,11 +122,14 @@ module FakerMaker
110
122
 
111
123
  protected
112
124
 
113
- def populate_instance( instance, attr_override_values )
114
- FakerMaker[parent].populate_instance instance, attr_override_values if parent?
115
- @attributes.each do |attr|
116
- value = value_for_attribute( instance, attr, attr_override_values )
117
- instance.send "#{attr.name}=", value
125
+ def populate_instance( instance, attr_override_values, chaos )
126
+ FakerMaker[parent].populate_instance instance, attr_override_values, chaos if parent?
127
+
128
+ attributes = chaos ? chaos_select(chaos) : @attributes
129
+
130
+ attributes.each do |attribute|
131
+ value = value_for_attribute( instance, attribute, attr_override_values )
132
+ instance.send "#{attribute.name}=", value
118
133
  end
119
134
  instance.instance_variable_set( :@fm_factory, self )
120
135
  end
@@ -128,6 +143,19 @@ module FakerMaker
128
143
  raise FakerMaker::NoSuchAttributeError, issue unless unknown_attrs.empty?
129
144
  end
130
145
 
146
+ def assert_only_known_and_optional_attributes_for_chaos( chaos_attr_values )
147
+ chaos_attr_values = chaos_attr_values.map(&:to_sym)
148
+ unknown_attrs = chaos_attr_values - attribute_names
149
+ issue = "Can't build an instance of '#{class_name}' " \
150
+ "setting '#{unknown_attrs.join( ', ' )}', no such attribute(s)"
151
+ raise FakerMaker::NoSuchAttributeError, issue unless unknown_attrs.empty?
152
+
153
+ # Are any chaos attributes marked as required?
154
+ conflicting_attributes = chaos_attr_values.select { |attr| required_attributes.map(&:name).include? attr }
155
+ issue = "Can't use chaos on a required attribute: '#{conflicting_attributes}'"
156
+ raise FakerMaker::ChaosConflictingAttributeError, issue unless conflicting_attributes.empty?
157
+ end
158
+
131
159
  def attribute_hash_overridden_value?( attr, attr_override_values )
132
160
  attr_override_values.keys.include?( attr.name )
133
161
  end
@@ -136,12 +164,28 @@ module FakerMaker
136
164
  if attribute_hash_overridden_value?( attr, attr_override_values )
137
165
  attr_override_values[attr.name]
138
166
  elsif attr.array?
139
- [].tap { |a| attr.cardinality.times { a << instance.instance_eval(&attr.block) } }
167
+ [].tap do |a|
168
+ attr.cardinality.times do
169
+ manufacture = manufacture_from_embedded_factory( attr )
170
+ # if manufacture has been build and there is a block, instance_exec the block
171
+ # otherwise just add the manufacture to the array
172
+ a << (attr.block ? instance.instance_exec(manufacture, &attr.block) : manufacture)
173
+ end
174
+ end
140
175
  else
141
- instance.instance_eval(&attr.block)
176
+ manufacture = manufacture_from_embedded_factory( attr )
177
+ attr.block ? instance.instance_exec(manufacture, &attr.block) : manufacture
142
178
  end
143
179
  end
144
180
 
181
+ def manufacture_from_embedded_factory( attr )
182
+ # The name of the embedded factory randomly selected from the list of embedded factories.
183
+ embedded_factory_name = attr.embedded_factories.sample
184
+ # The object that is being manufactured by the factory.
185
+ # If an embedded factory name is provided, it builds the object using FakerMaker.
186
+ embedded_factory_name ? FakerMaker[embedded_factory_name].build : nil
187
+ end
188
+
145
189
  def instantiate
146
190
  assemble.new
147
191
  end
@@ -159,11 +203,74 @@ module FakerMaker
159
203
  .transform_keys { |key| @fm_factory.json_key_map[key] || key }
160
204
  .filter { |key, value| !@fm_factory.find_attribute(key)&.omit?( value ) }
161
205
  end
206
+ @klass.alias_method :to_h, :as_json
162
207
  end
163
208
 
164
209
  def assert_valid_options( options )
165
210
  options.assert_valid_keys :class, :parent, :naming
166
211
  end
212
+
213
+ # Asserts attributes passed in for chaos mode are valid
214
+ def assert_chaos_options( chaos )
215
+ eval = -> { [Array, String, TrueClass, FalseClass, Symbol].include? chaos.class }
216
+ msg = "chaos: arg does not support object of type: '#{chaos.class}'"
217
+ raise NoSuchAttributeError, msg unless eval.call
218
+
219
+ case chaos
220
+ when Array
221
+ assert_only_known_and_optional_attributes_for_chaos(chaos)
222
+ when String, Symbol
223
+ assert_only_known_and_optional_attributes_for_chaos([chaos])
224
+ end
225
+ end
226
+
227
+ # Selects required @attributes
228
+ def required_attributes
229
+ @required_attributes ||= @attributes.select { |attr| attr.required.eql? true }
230
+ end
231
+
232
+ # Selects optional @attributes
233
+ def optional_attributes
234
+ @optional_attributes ||= @attributes.select(&:optional)
235
+ end
236
+
237
+ # Randomly selects optional attributes
238
+ # Attributes selected from parent will also be selected for the child
239
+ # @param [Array || TrueClass] chaos_attrs
240
+ # @return [Array]
241
+ def chaos_select( chaos_attrs = [] )
242
+ selected_attrs = []
243
+ optional_attrs = optional_attributes.dup
244
+
245
+ # Filter specific optional attributes if present
246
+ if chaos_attrs.is_a?(Array) && chaos_attrs.size.positive?
247
+ optional_attrs, selected_attrs = optional_attrs.partition { |attr| chaos_attrs.include?(attr.name) }
248
+ end
249
+
250
+ # Grab parent selected attributes
251
+ @chaos_selected_attributes = parent? ? FakerMaker[parent].chaos_selected_attributes : []
252
+ selected_inherited_attr = optional_attrs.select do |attr|
253
+ @chaos_selected_attributes.map(&:name).include? attr.name
254
+ end
255
+
256
+ # Select optional attributes based on weighting
257
+ optional_attrs.each do |optional_attr|
258
+ selected_attrs.push(optional_attr) if Random.rand < optional_attr.optional_weighting
259
+ end
260
+
261
+ # Concat required, selected and parent attributes
262
+ @chaos_selected_attributes.concat(required_attributes)
263
+ .concat(selected_inherited_attr)
264
+ .concat(selected_attrs).uniq!
265
+ @chaos_selected_attributes
266
+ end
267
+
268
+ def validate_deprecated_build(kwargs)
269
+ usage = kwargs.each_with_object([]) { |kwarg, result| result << "#{kwarg.first}: #{kwarg.last}" }.join(', ')
270
+
271
+ warn "[DEPRECATION] `FM[:#{name}].build(#{usage})` is deprecated. " \
272
+ "Please use `FM[:#{name}].build(attributes: { #{usage} })` instead."
273
+ end
167
274
  end
168
275
  end
169
276
  # rubocop:enable Metrics/ClassLength
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FakerMaker
4
- VERSION = '1.3.0'
4
+ VERSION = '2.1.0'
5
5
  end
data/lib/faker_maker.rb CHANGED
@@ -27,6 +27,7 @@ module FakerMaker
27
27
  class Error < StandardError; end
28
28
  class NoSuchFactoryError < StandardError; end
29
29
  class NoSuchAttributeError < StandardError; end
30
+ class ChaosConflictingAttributeError < StandardError; end
30
31
  class NoSuchAttributeNamingStrategy < StandardError; end
31
32
  # Your code goes here...
32
33
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faker_maker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nigel Brookes-Thomas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-15 00:00:00.000000000 Z
11
+ date: 2024-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.1'
53
+ version: '3.2'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.1'
60
+ version: '3.2'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: guard
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -170,6 +170,20 @@ dependencies:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
172
  version: '1.0'
173
+ - !ruby/object:Gem::Dependency
174
+ name: racc
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '1.0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '1.0'
173
187
  description: FakerMaker is a simple factory builder so you can throw away your fixtures
174
188
  and generate test data instead.
175
189
  email:
@@ -179,6 +193,7 @@ extensions: []
179
193
  extra_rdoc_files: []
180
194
  files:
181
195
  - ".github/dependabot.yml"
196
+ - ".github/workflows/ruby.yml"
182
197
  - ".gitignore"
183
198
  - ".rspec"
184
199
  - ".rubocop.yml"
@@ -202,6 +217,7 @@ files:
202
217
  - docs/usage/arrays.md
203
218
  - docs/usage/audit_logs.md
204
219
  - docs/usage/building_instances.md
220
+ - docs/usage/chaos.md
205
221
  - docs/usage/dependencies.md
206
222
  - docs/usage/destroying_factories.md
207
223
  - docs/usage/embedding_factories.md
@@ -246,7 +262,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
262
  - !ruby/object:Gem::Version
247
263
  version: '0'
248
264
  requirements: []
249
- rubygems_version: 3.4.10
265
+ rubygems_version: 3.5.9
250
266
  signing_key:
251
267
  specification_version: 4
252
268
  summary: FakerMaker bakes fakes.