dirty_seed 0.1.7 → 0.1.8

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/dirty_seed.rb +15 -11
  4. data/lib/dirty_seed/assigners/assigner.rb +73 -0
  5. data/lib/dirty_seed/assigners/base.rb +86 -0
  6. data/lib/dirty_seed/assigners/{dirty_boolean.rb → boolean.rb} +2 -2
  7. data/lib/dirty_seed/assigners/{dirty_date.rb → date.rb} +2 -2
  8. data/lib/dirty_seed/assigners/fakers.yml +64 -1
  9. data/lib/dirty_seed/assigners/float.rb +21 -0
  10. data/lib/dirty_seed/assigners/integer.rb +20 -0
  11. data/lib/dirty_seed/assigners/min_max_helper.rb +115 -0
  12. data/lib/dirty_seed/assigners/{dirty_number.rb → number.rb} +2 -2
  13. data/lib/dirty_seed/assigners/string.rb +39 -0
  14. data/lib/dirty_seed/assigners/{dirty_time.rb → time.rb} +2 -2
  15. data/lib/dirty_seed/{dirty_association.rb → association.rb} +6 -17
  16. data/lib/dirty_seed/attribute.rb +62 -0
  17. data/lib/dirty_seed/data_model.rb +17 -18
  18. data/lib/dirty_seed/method_missing_helper.rb +1 -1
  19. data/lib/dirty_seed/{dirty_model.rb → model.rb} +81 -33
  20. data/lib/dirty_seed/version.rb +1 -1
  21. data/spec/dummy/app/models/bravo.rb +2 -1
  22. data/spec/dummy/db/test.sqlite3 +0 -0
  23. data/spec/dummy/log/development.log +2 -0
  24. data/spec/dummy/log/test.log +62192 -0
  25. data/spec/lib/dirty_seed/assigners/assigner_spec.rb +19 -0
  26. data/spec/lib/dirty_seed/assigners/base_spec.rb +81 -0
  27. data/spec/lib/dirty_seed/assigners/boolean_spec.rb +13 -0
  28. data/spec/lib/dirty_seed/assigners/date_spec.rb +15 -0
  29. data/spec/lib/dirty_seed/assigners/float_spec.rb +43 -0
  30. data/spec/lib/dirty_seed/assigners/integer_spec.rb +43 -0
  31. data/spec/lib/dirty_seed/assigners/string_spec.rb +47 -0
  32. data/spec/lib/dirty_seed/assigners/time_spec.rb +15 -0
  33. data/spec/lib/dirty_seed/association_spec.rb +64 -0
  34. data/spec/lib/dirty_seed/attribute_spec.rb +49 -0
  35. data/spec/lib/dirty_seed/data_model_spec.rb +8 -36
  36. data/spec/lib/dirty_seed/{dirty_model_spec.rb → model_spec.rb} +13 -13
  37. data/spec/support/helpers.rb +5 -5
  38. metadata +37 -33
  39. data/lib/dirty_seed/assigners/dirty_assigner.rb +0 -95
  40. data/lib/dirty_seed/assigners/dirty_float.rb +0 -35
  41. data/lib/dirty_seed/assigners/dirty_integer.rb +0 -16
  42. data/lib/dirty_seed/assigners/dirty_string.rb +0 -74
  43. data/lib/dirty_seed/dirty_attribute.rb +0 -75
  44. data/spec/lib/dirty_seed/assigners/dirty_assigner_spec.rb +0 -68
  45. data/spec/lib/dirty_seed/assigners/dirty_boolean_spec.rb +0 -13
  46. data/spec/lib/dirty_seed/assigners/dirty_date_spec.rb +0 -15
  47. data/spec/lib/dirty_seed/assigners/dirty_float_spec.rb +0 -43
  48. data/spec/lib/dirty_seed/assigners/dirty_integer_spec.rb +0 -43
  49. data/spec/lib/dirty_seed/assigners/dirty_string_spec.rb +0 -53
  50. data/spec/lib/dirty_seed/assigners/dirty_time_spec.rb +0 -15
  51. data/spec/lib/dirty_seed/dirty_association_spec.rb +0 -64
  52. data/spec/lib/dirty_seed/dirty_attribute_spec.rb +0 -49
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DirtySeed
4
+ module Assigners
5
+ # Helps with min and max validations
6
+ module MinMaxHelper
7
+ # Returns the minimal value
8
+ # @return [Integer, Float]
9
+ def min
10
+ @min ||= define_min_and_max && @min
11
+ end
12
+
13
+ # Returns the maximal value
14
+ # @return [Integer, Float]
15
+ def max
16
+ @max ||= define_min_and_max && @max
17
+ end
18
+
19
+ # Defines min and max depending on validator
20
+ # @return [void]
21
+ def define_min_and_max
22
+ @min = acceptable_min
23
+ @max = acceptable_max
24
+
25
+ # If necessary, adjust a value depending on the other
26
+ @min ||= floor
27
+ @max ||= @min + ceiling || ceiling # rubocop:disable Naming/MemoizedInstanceVariableName
28
+ end
29
+
30
+ # Returns default min depending on type
31
+ # @return [Integer, Float]
32
+ def floor # rubocop:disable Metrics/CyclomaticComplexity
33
+ case type
34
+ when :string then 1
35
+ when :integer then @max&.negative? ? @max * 2 : 0
36
+ when :float then @max&.negative? ? @max * 2 : 0.0
37
+ end
38
+ end
39
+
40
+ # Returns default max depending on type
41
+ # @return [Integer, Float]
42
+ def ceiling
43
+ case type
44
+ when :string then 50
45
+ when :integer then 42
46
+ when :float then 42.0
47
+ end
48
+ end
49
+
50
+ # Returns the validator that validate min and/or max
51
+ def min_max_validator
52
+ validators.find { |validator| validator.is_a? validator_class }
53
+ end
54
+
55
+ # Returns the validator class depending on the type
56
+ def validator_class
57
+ case type
58
+ when :string then ActiveRecord::Validations::LengthValidator
59
+ when :integer, :float then ActiveModel::Validations::NumericalityValidator
60
+ end
61
+ end
62
+
63
+ # Returns a value representing the minimal acceptable value
64
+ # @return [Integer, Float]
65
+ def acceptable_min
66
+ return unless min_max_validator
67
+
68
+ min_max_validator.options[:in]&.min || type_related_acceptable_min
69
+ end
70
+
71
+ # Returns a value representing the minimal acceptable value depending on type
72
+ # @return [Integer, Float]
73
+ def type_related_acceptable_min
74
+ case type
75
+ when :string
76
+ min_max_validator.options[:minimum] || min_max_validator.options[:is]
77
+ when :integer, :float
78
+ min_max_validator.options[:greater_than]&.+(gap) ||
79
+ min_max_validator.options[:greater_than_or_equal_to]
80
+ end
81
+ end
82
+
83
+ # Returns a value representing the maximal acceptable value
84
+ # @return [Integer, Float]
85
+ def acceptable_max
86
+ return unless min_max_validator
87
+
88
+ min_max_validator.options[:in]&.max || type_related_acceptable_max
89
+ end
90
+
91
+ # Returns a value representing the maximal acceptable value depending on type
92
+ # @return [Integer, Float]
93
+ def type_related_acceptable_max
94
+ case type
95
+ when :string
96
+ min_max_validator.options[:maximum] || min_max_validator.options[:is]
97
+ when :integer, :float
98
+ min_max_validator.options[:less_than]&.-(gap) ||
99
+ min_max_validator.options[:less_than_or_equal_to]
100
+ end
101
+ end
102
+
103
+ # Defines the gap to add to min when "greater_than" or to substract to max when "less_than"
104
+ # For example if type is :float and value should be greater_than 0, then the min is 0.01
105
+ # and if the value should be lesser_than 1, then the max is 0.99
106
+ # @return [Integer, Float]
107
+ def gap
108
+ case type
109
+ when :integer then 1
110
+ when :float then 0.01
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -3,12 +3,12 @@
3
3
  module DirtySeed
4
4
  module Assigners
5
5
  # Draws an integer matching validators
6
- class DirtyNumber < DirtyAssigner
6
+ class Number < Assigner
7
7
  attr_reader :min, :max
8
8
 
9
9
  # Returns an value matching all validators
10
10
  # @return [Integer, Float]
11
- def define_value
11
+ def value
12
12
  unless min && max
13
13
  define_min_and_max
14
14
  adjust_values
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DirtySeed
4
+ module Assigners
5
+ # Draws a String matching validators
6
+ class String < Assigner
7
+ include MinMaxHelper
8
+
9
+ # Returns a value matching all validators
10
+ # @return [String]
11
+ def value
12
+ regex_value || default
13
+ end
14
+
15
+ private
16
+
17
+ # Returns a standard string
18
+ # @return [String]
19
+ def default
20
+ faker_value(
21
+ category: :Lorem,
22
+ method: :paragraph_by_chars,
23
+ options: { number: rand(min..max), supplemental: false }
24
+ )
25
+ end
26
+
27
+ # Returns a value matching the pattern
28
+ # @note Rescue from unreadable regex
29
+ # @return [String]
30
+ def regex_value
31
+ return unless regex
32
+
33
+ regex.random_example
34
+ rescue RegexpExamples::IllegalSyntaxError
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -3,10 +3,10 @@
3
3
  module DirtySeed
4
4
  module Assigners
5
5
  # Draws a value matching validators
6
- class DirtyTime < DirtyAssigner
6
+ class Time < Assigner
7
7
  # Returns a value matching all validators
8
8
  # @return [Time]
9
- def define_value
9
+ def value
10
10
  ::Faker::Time.between(from: DateTime.now - 42, to: DateTime.now + 42)
11
11
  end
12
12
  end
@@ -2,32 +2,21 @@
2
2
 
3
3
  module DirtySeed
4
4
  # Represents an Active Record association
5
- class DirtyAssociation
5
+ class Association
6
6
  extend ::DirtySeed::MethodMissingHelper
7
7
  forward_missing_methods_to :reflections
8
8
 
9
- attr_reader :dirty_model, :reflection
9
+ attr_reader :model, :reflection
10
10
 
11
11
  # Initializes an instance
12
- # @param dirty_model [DirtySeed::DirtyModel]
12
+ # @param model [DirtySeed::Model]
13
13
  # @param reflection [ActiveRecord::Reflection::BelongsToReflection]
14
- # @return [DirtySeed::DirtyAssociation]
15
- def initialize(dirty_model, reflection)
16
- @dirty_model = dirty_model
14
+ # @return [DirtySeed::Association]
15
+ def initialize(model, reflection)
16
+ @model = model
17
17
  @reflection = reflection
18
18
  end
19
19
 
20
- # Assigns a random value to the association
21
- # @param instance [Object] an instance of a class inheriting from ApplicationRecord
22
- # @return [void]
23
- def assign_value(instance)
24
- return if associated_models.empty?
25
-
26
- instance.public_send(:"#{name}=", value)
27
- rescue ArgumentError => e
28
- @errors << e
29
- end
30
-
31
20
  # Returns a random instance matching the reflection
32
21
  # @return [Object] an instance of a class inheriting from ApplicationRecord
33
22
  def value
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DirtySeed
4
+ # Represents an Active Record attribute
5
+ class Attribute
6
+ extend ::DirtySeed::MethodMissingHelper
7
+ forward_missing_methods_to :column
8
+
9
+ attr_reader :model, :column
10
+
11
+ TYPE_SYMMETRIES = {
12
+ binary: :integer,
13
+ datetime: :time,
14
+ decimal: :float,
15
+ text: :string
16
+ }.freeze
17
+ private_constant :TYPE_SYMMETRIES
18
+
19
+ # Initializes an instance
20
+ # @param model [DirtySeed::Model]
21
+ # @param column [ActiveRecord::ConnectionAdapters::Column]
22
+ # @return [DirtySeed::Attribute]
23
+ def initialize(model, column)
24
+ @model = model
25
+ @column = column
26
+ end
27
+
28
+ # Returns a value matching type and validators
29
+ # @return [Object, nil]
30
+ def value
31
+ assigner.value
32
+ end
33
+
34
+ # Returns the attribute assigner
35
+ # @return [DirtySeed::Assigners::Assigner]
36
+ def assigner
37
+ @assigner ||= DirtySeed::Assigners::Base.new(self)
38
+ end
39
+
40
+ # Returns attribute name
41
+ # @return [Symbol]
42
+ def name
43
+ @name ||= column.name.to_sym
44
+ end
45
+
46
+ # Returns attribute type
47
+ # @return [Symbol]
48
+ def type
49
+ @type ||=
50
+ TYPE_SYMMETRIES[column.sql_type_metadata.type] || column.sql_type_metadata.type
51
+ end
52
+
53
+ # Returns an validators related to the current attribute
54
+ # @return [Array<ActiveModel::Validations::EachValidators>]
55
+ def validators
56
+ @validators ||=
57
+ model.validators.select do |validator|
58
+ validator.attributes.include? name
59
+ end
60
+ end
61
+ end
62
+ end
@@ -4,11 +4,10 @@ module DirtySeed
4
4
  # Represents the data model
5
5
  class DataModel
6
6
  include Singleton
7
- attr_accessor :logs
8
7
 
9
8
  class << self
10
9
  # Defines class methods forwarding to instance methods
11
- %i[dirty_models active_record_models print_logs reset].each do |method_name|
10
+ %i[active_record_models models print_logs reset].each do |method_name|
12
11
  define_method(method_name) { instance.public_send(method_name) }
13
12
  end
14
13
 
@@ -20,19 +19,19 @@ module DirtySeed
20
19
  end
21
20
 
22
21
  # Returns dirty model if method_name matches its name
23
- # @return [DirtySeed::DirtyModel]
22
+ # @return [DirtySeed::Model]
24
23
  # @raise [NoMethodError] if method_name does not match any dirty model
25
24
  def method_missing(method_name, *args, &block)
26
- dirty_models.find do |dirty_model|
27
- dirty_model.name.underscore.to_sym == method_name
25
+ models.find do |model|
26
+ model.name.underscore.to_sym == method_name
28
27
  end || super
29
28
  end
30
29
 
31
30
  # Returns true if method_name matches a dirty model name
32
31
  # @return [Boolean]
33
32
  def respond_to_missing?(method_name, include_private = false)
34
- dirty_models.any? do |dirty_model|
35
- dirty_model.name.underscore.to_sym == method_name
33
+ models.any? do |model|
34
+ model.name.underscore.to_sym == method_name
36
35
  end || super
37
36
  end
38
37
  end
@@ -41,7 +40,6 @@ module DirtySeed
41
40
  # @return [DirtySeed::DataModel]
42
41
  def initialize
43
42
  Rails.application.eager_load!
44
- @logs = {}
45
43
  end
46
44
 
47
45
  # Seeds the database with dirty instances
@@ -50,16 +48,16 @@ module DirtySeed
50
48
  def seed(count)
51
49
  # check if ApplicationRecord is defined first (or raise error)
52
50
  ::ApplicationRecord &&
53
- dirty_models.each { |dirty_model| dirty_model.seed(count) }
51
+ models.each { |model| model.seed(count) }
54
52
  print_logs
55
53
  end
56
54
 
57
55
  # Returns dirty models
58
- # @return [Array<DirtySeed::DirtyModel>]
59
- def dirty_models
60
- @dirty_models ||=
56
+ # @return [Array<DirtySeed::Model>]
57
+ def models
58
+ @models ||=
61
59
  active_record_models.map do |active_record_model|
62
- DirtySeed::DirtyModel.new(active_record_model)
60
+ DirtySeed::Model.new(active_record_model)
63
61
  end
64
62
  end
65
63
 
@@ -79,10 +77,11 @@ module DirtySeed
79
77
  # Prints logs in the console
80
78
  # @return [void]
81
79
  def print_logs
82
- dirty_models.sort_by(&:name).each do |dirty_model|
83
- puts dirty_model.name
84
- puts " created: #{dirty_model.seeded}"
85
- puts " errors: #{dirty_model.errors.join(', ')}" if dirty_model.errors.any?
80
+ puts ''
81
+ models.sort_by(&:name).each do |model|
82
+ puts model.name
83
+ puts " seeded: #{model.instances.count}"
84
+ puts " errors: #{model.errors.join(', ')}" if model.errors.any?
86
85
  end
87
86
  end
88
87
 
@@ -90,7 +89,7 @@ module DirtySeed
90
89
  # @return [void]
91
90
  def reset
92
91
  @logs = nil
93
- @dirty_models = nil
92
+ @models = nil
94
93
  @active_record_models = nil
95
94
  end
96
95
  end
@@ -24,7 +24,7 @@ module DirtySeed
24
24
 
25
25
  # Defines missing_method method so it returns the adressee or calls super
26
26
  # @example
27
- # calling #name on a DirtyModel instance will call name on its @model object
27
+ # calling #name on a Model instance will call name on its @model object
28
28
  # @return [void]
29
29
  def define_method_missing
30
30
  define_method :method_missing do |method_name, *args, &block|
@@ -2,12 +2,11 @@
2
2
 
3
3
  module DirtySeed
4
4
  # Represents an Active Record model
5
- class DirtyModel
5
+ class Model
6
6
  extend ::DirtySeed::MethodMissingHelper
7
7
  forward_missing_methods_to :model
8
8
 
9
- attr_reader :model, :instances, :seeded
10
- attr_writer :errors
9
+ attr_reader :model, :instances
11
10
 
12
11
  PROTECTED_COLUMNS = %w[
13
12
  id
@@ -23,41 +22,105 @@ module DirtySeed
23
22
 
24
23
  # Initializes an instance
25
24
  # @param model [Class] a class inheriting from ApplicationRecord
26
- # @return [DirtySeed::DirtyModel]
25
+ # @return [DirtySeed::Model]
27
26
  def initialize(model)
28
27
  @model = model
29
28
  @instances = []
30
- @seeded = 0
31
29
  @errors = []
32
30
  end
33
31
 
34
- # Creates instances for each model
32
+ # Creates records
35
33
  # @param count [Integer]
36
- # @return [void]
34
+ # @return [Array<Object>]
37
35
  def seed(count)
38
- count.times { create_instance }
39
- @instances
36
+ puts "Seeding #{name.underscore.pluralize}"
37
+ @count = count
38
+ create_records
39
+ rescue ActiveRecord::ActiveRecordError => e
40
+ @errors << e
41
+ ensure
42
+ instances
43
+ end
44
+
45
+ # Creates records
46
+ # @return [void]
47
+ def create_records
48
+ data = params_collection
49
+ @count.times do |i|
50
+ instance = model.new(data[i])
51
+ save(instance)
52
+ end
53
+ puts ''
54
+ end
55
+
56
+ # Tries to save instance in database
57
+ # Populates instances and errors and log message
58
+ # @return [void]
59
+ def save(instance)
60
+ if instance.save
61
+ print '.'
62
+ @instances << instance
63
+ else
64
+ print '!'
65
+ @errors << instance.errors.full_messages
66
+ end
67
+ end
68
+
69
+ # Generate records params
70
+ # @return [Array<Hash>] where Hash is params for one record
71
+ def params_collection
72
+ data = Hash[attributes_collection + associations_collection]
73
+ data.values.transpose.map { |vs| data.keys.zip(vs).to_h }
74
+ end
75
+
76
+ # Generate attributes params
77
+ # Each sub-array contains the attribute name and a collection of values
78
+ # @return [Array<Array>]
79
+ # @example
80
+ # [
81
+ # ["a_string", ["foo", "bar"]],
82
+ # [an_integer, [1, 2]]
83
+ # ]
84
+ def attributes_collection
85
+ attributes.map do |attribute|
86
+ Faker::UniqueGenerator.clear
87
+ [attribute.name, @count.times.map { attribute.value }]
88
+ end
89
+ end
90
+
91
+ # Generate associations params
92
+ # Each sub-array contains the association name and a collection of values
93
+ # @return [Array<Array>]
94
+ # @example
95
+ # [
96
+ # ["alfa", [#<Alfa>, #<Alfa>]],
97
+ # ["bravo", [#<Bravo>, #<Bravo>]]
98
+ # ]
99
+ def associations_collection
100
+ associations.map do |association|
101
+ [association.name, @count.times.map { association.value }]
102
+ end
40
103
  end
41
104
 
42
105
  # Returns models where models are associated to the current model through a has_many or has_one associations
43
106
  # @return [Array<Class>] ActiveRecord models
44
107
  def associated_models
45
- dirty_associations.map(&:associated_models).flatten
108
+ associations.map(&:associated_models).flatten
46
109
  end
47
110
 
48
111
  # Returns an dirty associations representing the self.model belongs_to associations
49
- # @return [Array<DirtySeed::DirtyAssociation>]
50
- def dirty_associations
112
+ # @return [Array<DirtySeed::Association>]
113
+ def associations
51
114
  included_reflections.map do |reflection|
52
- DirtySeed::DirtyAssociation.new(self, reflection)
115
+ DirtySeed::Association.new(self, reflection)
53
116
  end
54
117
  end
55
118
 
56
119
  # Returns model attributes
57
- # @return [Array<DirtySeed::DirtyAttribute>]
58
- def dirty_attributes
120
+ # @return [Array<DirtySeed::Attribute>]
121
+ def attributes
59
122
  included_columns.map do |column|
60
- DirtySeed::DirtyAttribute.new(self, column)
123
+ DirtySeed::Attribute.new(self, column)
61
124
  end
62
125
  end
63
126
 
@@ -69,21 +132,6 @@ module DirtySeed
69
132
 
70
133
  private
71
134
 
72
- # Creates an instance
73
- # @return [void]
74
- def create_instance
75
- @instances << instance = model.new
76
- dirty_associations.each { |dirty_association| dirty_association.assign_value(instance) }
77
- dirty_attributes.each { |dirty_attribute| dirty_attribute.assign_value(instance) }
78
- if instance.save
79
- @seeded += 1
80
- else
81
- @errors << instance.errors.full_messages
82
- end
83
- rescue ActiveRecord::ActiveRecordError => e
84
- errors << e
85
- end
86
-
87
135
  # Returns columns that should be treated as regular attributes
88
136
  # @return [Array<ActiveRecord::ConnectionAdapters::Column>]
89
137
  def included_columns
@@ -99,8 +147,8 @@ module DirtySeed
99
147
  # @return [Array<String>]
100
148
  def reflection_related_attributes
101
149
  all_reflection_related_attributes =
102
- dirty_associations.map do |dirty_association|
103
- [dirty_association.attribute, dirty_association.type_key].compact
150
+ associations.map do |association|
151
+ [association.attribute, association.type_key].compact
104
152
  end
105
153
  all_reflection_related_attributes.flatten.map(&:to_s)
106
154
  end