dirty_seed 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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