faker_maker 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6355e061188d88ab10d97dae93595abccbf2a73346b6220c265c9241198f93ba
4
- data.tar.gz: 487700e0d8e88f4fb69d00796bb4d644263e7c91af91eea188caa7eaf82861c5
3
+ metadata.gz: cc9201db222f65f8df79f92edc882d9d76e5ca21c6220b377f5e86ce9414d287
4
+ data.tar.gz: d4601f347ce1231fd287d33addde2bb83c00a1a770783eec8007dd7417c6c2f3
5
5
  SHA512:
6
- metadata.gz: e2e50d6ffa61e8164163d2157a65d8a3b93b24e442028e0e04951b25f8e298693bd3a8c6ff3ff948442c38c972f427ec05017020dd120b0cfeefdbb02ebd82b8
7
- data.tar.gz: 4c1ae45972c019f0929b1c0337b20ebe4f92ecaa040811f515934d50ce38e826956c994a3043d1f8a27928921930a32980d4bff29fceffbb0c8a7a82f1d4ebd6
6
+ metadata.gz: ce1be4538d82898bb1accd89585485e6072974350088261dece5f0481a3d1950cbf1462dba6363231e2b25019632d8f1aa649e04cab5c4a0a752c6ca9e428c7f
7
+ data.tar.gz: 2d442d69c483b0aa5ba5f7bc9fd26396376edbc2ff700c9d47305d27f2fc809d42d4971bf8ef8b09c67f7f4912395e6eb0127c0d105e6763708f342916e2ae85
@@ -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
+ ```
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'
@@ -3,7 +3,9 @@
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
7
+
8
+ DEFAULT_OPTIONAL_WEIGHTING = 0.5
7
9
 
8
10
  def initialize( name, block = nil, options = {} )
9
11
  assert_valid_options options
@@ -13,6 +15,13 @@ module FakerMaker
13
15
  @translation = options[:json]
14
16
  @omit = *options[:omit]
15
17
  @array = options[:array] == true
18
+
19
+ if options[:required].to_s.downcase.eql?('true') || options[:optional].to_s.downcase.eql?('false')
20
+ @required = true
21
+ else
22
+ @optional = true
23
+ @optional_weighting = determine_optional_weighting(options[:optional])
24
+ end
16
25
  end
17
26
 
18
27
  def array?
@@ -49,7 +58,18 @@ module FakerMaker
49
58
  end
50
59
 
51
60
  def assert_valid_options( options )
52
- options.assert_valid_keys :has, :array, :json, :omit
61
+ options.assert_valid_keys :has, :array, :json, :omit, :required, :optional
62
+ end
63
+
64
+ def determine_optional_weighting( value )
65
+ case value
66
+ when Float
67
+ value.between?(0, 1) ? value : (value / 100)
68
+ when Integer
69
+ value.ceil.between?(0, 100) ? (value.to_f / 100) : DEFAULT_OPTIONAL_WEIGHTING
70
+ else
71
+ DEFAULT_OPTIONAL_WEIGHTING
72
+ end
53
73
  end
54
74
  end
55
75
  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?
@@ -110,11 +121,14 @@ module FakerMaker
110
121
 
111
122
  protected
112
123
 
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
124
+ def populate_instance( instance, attr_override_values, chaos )
125
+ FakerMaker[parent].populate_instance instance, attr_override_values, chaos if parent?
126
+
127
+ attributes = chaos ? chaos_select(chaos) : @attributes
128
+
129
+ attributes.each do |attribute|
130
+ value = value_for_attribute( instance, attribute, attr_override_values )
131
+ instance.send "#{attribute.name}=", value
118
132
  end
119
133
  instance.instance_variable_set( :@fm_factory, self )
120
134
  end
@@ -128,6 +142,19 @@ module FakerMaker
128
142
  raise FakerMaker::NoSuchAttributeError, issue unless unknown_attrs.empty?
129
143
  end
130
144
 
145
+ def assert_only_known_and_optional_attributes_for_chaos( chaos_attr_values )
146
+ chaos_attr_values = chaos_attr_values.map(&:to_sym)
147
+ unknown_attrs = chaos_attr_values - attribute_names
148
+ issue = "Can't build an instance of '#{class_name}' " \
149
+ "setting '#{unknown_attrs.join( ', ' )}', no such attribute(s)"
150
+ raise FakerMaker::NoSuchAttributeError, issue unless unknown_attrs.empty?
151
+
152
+ # Are any chaos attributes marked as required?
153
+ conflicting_attributes = chaos_attr_values.select { |attr| required_attributes.map(&:name).include? attr }
154
+ issue = "Can't use chaos on a required attribute: '#{conflicting_attributes}'"
155
+ raise FakerMaker::ChaosConflictingAttributeError, issue unless conflicting_attributes.empty?
156
+ end
157
+
131
158
  def attribute_hash_overridden_value?( attr, attr_override_values )
132
159
  attr_override_values.keys.include?( attr.name )
133
160
  end
@@ -164,6 +191,68 @@ module FakerMaker
164
191
  def assert_valid_options( options )
165
192
  options.assert_valid_keys :class, :parent, :naming
166
193
  end
194
+
195
+ # Asserts attributes passed in for chaos mode are valid
196
+ def assert_chaos_options( chaos )
197
+ eval = -> { [Array, String, TrueClass, FalseClass, Symbol].include? chaos.class }
198
+ msg = "chaos: arg does not support object of type: '#{chaos.class}'"
199
+ raise NoSuchAttributeError, msg unless eval.call
200
+
201
+ case chaos
202
+ when Array
203
+ assert_only_known_and_optional_attributes_for_chaos(chaos)
204
+ when String, Symbol
205
+ assert_only_known_and_optional_attributes_for_chaos([chaos])
206
+ end
207
+ end
208
+
209
+ # Selects required @attributes
210
+ def required_attributes
211
+ @required_attributes ||= @attributes.select { |attr| attr.required.eql? true }
212
+ end
213
+
214
+ # Selects optional @attributes
215
+ def optional_attributes
216
+ @optional_attributes ||= @attributes.select(&:optional)
217
+ end
218
+
219
+ # Randomly selects optional attributes
220
+ # Attributes selected from parent will also be selected for the child
221
+ # @param [Array || TrueClass] chaos_attrs
222
+ # @return [Array]
223
+ def chaos_select( chaos_attrs = [] )
224
+ selected_attrs = []
225
+ optional_attrs = optional_attributes.dup
226
+
227
+ # Filter specific optional attributes if present
228
+ if chaos_attrs.is_a?(Array) && chaos_attrs.size.positive?
229
+ optional_attrs, selected_attrs = optional_attrs.partition { |attr| chaos_attrs.include?(attr.name) }
230
+ end
231
+
232
+ # Grab parent selected attributes
233
+ @chaos_selected_attributes = parent? ? FakerMaker[parent].chaos_selected_attributes : []
234
+ selected_inherited_attr = optional_attrs.select do |attr|
235
+ @chaos_selected_attributes.map(&:name).include? attr.name
236
+ end
237
+
238
+ # Select optional attributes based on weighting
239
+ optional_attrs.each do |optional_attr|
240
+ selected_attrs.push(optional_attr) if Random.rand < optional_attr.optional_weighting
241
+ end
242
+
243
+ # Concat required, selected and parent attributes
244
+ @chaos_selected_attributes.concat(required_attributes)
245
+ .concat(selected_inherited_attr)
246
+ .concat(selected_attrs).uniq!
247
+ @chaos_selected_attributes
248
+ end
249
+
250
+ def validate_deprecated_build(kwargs)
251
+ usage = kwargs.each_with_object([]) { |kwarg, result| result << "#{kwarg.first}: #{kwarg.last}" }.join(', ')
252
+
253
+ warn "[DEPRECATION] `FM[:#{name}].build(#{usage})` is deprecated. " \
254
+ "Please use `FM[:#{name}].build(attributes: { #{usage} })` instead."
255
+ end
167
256
  end
168
257
  end
169
258
  # 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.0.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.0.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: 2023-06-21 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
@@ -179,6 +179,7 @@ extensions: []
179
179
  extra_rdoc_files: []
180
180
  files:
181
181
  - ".github/dependabot.yml"
182
+ - ".github/workflows/ruby.yml"
182
183
  - ".gitignore"
183
184
  - ".rspec"
184
185
  - ".rubocop.yml"
@@ -202,6 +203,7 @@ files:
202
203
  - docs/usage/arrays.md
203
204
  - docs/usage/audit_logs.md
204
205
  - docs/usage/building_instances.md
206
+ - docs/usage/chaos.md
205
207
  - docs/usage/dependencies.md
206
208
  - docs/usage/destroying_factories.md
207
209
  - docs/usage/embedding_factories.md