seedie 0.3.0 → 0.4.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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  module Associations
3
5
  class HasOne < BaseAssociation
@@ -5,25 +7,23 @@ module Seedie
5
7
  return if association_config["has_one"].nil?
6
8
 
7
9
  report(:has_one_start)
8
-
10
+
9
11
  association_config["has_one"].each do |association_name, association_config|
10
12
  reflection = model.reflect_on_association(association_name)
11
13
  association_class = reflection.klass
12
14
  count = get_association_count(association_config)
13
-
15
+
14
16
  report(:associated_records, count: count, name: association_name, parent_name: model.to_s)
15
- if count > 1
16
- raise InvalidAssociationConfigError, "has_one association cannot be more than 1"
17
- else
18
- config = only_count_given?(association_config) ? {} : association_config
19
- field_values_set = FieldValuesSet.new(association_class, config, INDEX).generate_field_values
20
- parent_field_set = generate_associated_field(record.id, reflection.foreign_key)
21
-
22
- record_creator = Model::Creator.new(association_class, reporters)
23
- record_creator.create!(field_values_set.merge!(parent_field_set))
24
- end
17
+ raise InvalidAssociationConfigError, "has_one association cannot be more than 1" if count > 1
18
+
19
+ config = only_count_given?(association_config) ? {} : association_config
20
+ field_values_set = FieldValuesSet.new(association_class, config, INDEX).generate_field_values
21
+ parent_field_set = generate_associated_field(record.id, reflection.foreign_key)
22
+
23
+ record_creator = Model::Creator.new(association_class, reporters)
24
+ record_creator.create!(field_values_set.merge!(parent_field_set))
25
25
  end
26
26
  end
27
27
  end
28
28
  end
29
- end
29
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  class Configuration
3
5
  attr_accessor :default_count, :custom_attributes
@@ -7,4 +9,4 @@ module Seedie
7
9
  @custom_attributes = {}
8
10
  end
9
11
  end
10
- end
12
+ end
@@ -1,9 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  module FieldValues
3
5
  class CustomValue
4
- VALID_KEYS = ["values", "value", "options"].freeze
5
- PICK_STRATEGIES = ["random", "sequential"].freeze
6
-
7
6
  attr_reader :name, :parsed_value
8
7
 
9
8
  def initialize(name, value_template, index)
@@ -12,7 +11,7 @@ module Seedie
12
11
  @index = index
13
12
  @parsed_value = ""
14
13
 
15
- validate_value_template
14
+ ValueTemplateValidator.new(@value_template, @index, @name).validate
16
15
  end
17
16
 
18
17
  def generate_custom_field_value
@@ -27,89 +26,21 @@ module Seedie
27
26
 
28
27
  private
29
28
 
30
- def validate_value_template
31
- return unless @value_template.is_a?(Hash)
32
-
33
- validate_keys
34
- validate_values if @value_template.key?("values")
35
- validate_options if @value_template.key?("options")
36
- end
37
-
38
- def validate_values
39
- values = @value_template["values"]
40
- options = @value_template["options"]
41
-
42
- if values.is_a?(Array) || values.is_a?(Hash)
43
- validate_sequential_values_length
44
- else
45
- raise InvalidCustomFieldValuesError, "The values key for #{@name} must be an array or a hash with start and end keys."
46
- end
47
- end
48
-
49
- def validate_options
50
- options = @value_template["options"]
51
- pick_strategy = options["pick_strategy"]
52
-
53
- if pick_strategy.present? && !PICK_STRATEGIES.include?(pick_strategy)
54
- raise InvalidCustomFieldOptionsError,
55
- "The pick_strategy for #{@name} must be either 'sequential' or 'random'."
56
- end
57
- end
58
-
59
- ## If pick strategy is sequential, we need to ensure there is a value for each index
60
- # If there isn't sufficient values, we raise an error
61
- def validate_sequential_values_length
62
- return unless @value_template.key?("options")
63
- return unless @value_template["options"]["pick_strategy"] == "sequential"
64
-
65
- values = @value_template["values"]
66
-
67
- if values.is_a?(Hash) && values.keys.sort == ["end", "start"]
68
- # Assuming the values are an inclusive range
69
- values_length = values["end"] - values["start"] + 1
70
- else
71
- values_length = values.length
72
- end
73
-
74
- if values_length < @index + 1
75
- raise CustomFieldNotEnoughValuesError,
76
- "There are not enough values for #{@name}. Please add more values."
77
- end
78
- end
79
-
80
- def validate_keys
81
- invalid_keys = @value_template.keys - VALID_KEYS
82
-
83
- if invalid_keys.present?
84
- raise InvalidCustomFieldKeysError,
85
- "Invalid keys for #{@name}: #{invalid_keys.join(", ")}. Only #{VALID_KEYS} are allowed."
86
- end
87
-
88
- if @value_template.key?("values")
89
- if @value_template.key?("value")
90
- raise InvalidCustomFieldKeysError,
91
- "Invalid keys for #{@name}: values and value cannot be used together."
92
- end
93
-
94
- if @value_template["values"].is_a?(Hash)
95
- if !@value_template["values"].key?("start") || !@value_template["values"].key?("end")
96
- raise InvalidCustomFieldValuesError,
97
- "The values key for #{@name} must be an array or a hash with start and end keys."
98
- end
99
- end
100
- end
101
- end
102
-
103
29
  def generate_custom_value_from_string
104
30
  @parsed_value = @value_template.gsub("{{index}}", @index.to_s)
105
31
 
106
32
  @parsed_value.gsub!(/\{\{(.+?)\}\}/) do
107
- method_string = $1
33
+ method_string = ::Regexp.last_match(1)
108
34
 
109
- if method_string.start_with?("Faker::")
110
- eval($1)
111
- else
112
- raise InvalidFakerMethodError, "Invalid method: #{method_string}"
35
+ raise InvalidFakerMethodError, "Invalid method: #{method_string}" unless method_string.start_with?("Faker::")
36
+
37
+ method_chain = method_string.split(".")
38
+ # Faker::Name will be shifted off the array
39
+ faker_class = method_chain.shift.constantize
40
+
41
+ # For Faker::Internet.unique.email, there will be two methods in the array
42
+ method_chain.reduce(faker_class) do |current_class_or_value, method|
43
+ current_class_or_value.public_send(method)
113
44
  end
114
45
  end
115
46
  end
@@ -123,7 +54,7 @@ module Seedie
123
54
  generate_custom_values_from_range(@value_template["values"]["start"], @value_template["values"]["end"])
124
55
  end
125
56
  options = @value_template["options"]
126
-
57
+
127
58
  if options.present? && options["pick_strategy"] == "sequential"
128
59
  @parsed_value = values[@index]
129
60
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  module FieldValues
3
5
  class FakeValue
@@ -9,41 +11,107 @@ module Seedie
9
11
  def generate_fake_value
10
12
  case @column.type
11
13
  when :string, :text, :citext
12
- Faker::Lorem.word
14
+ generate_string
13
15
  when :uuid
14
- SecureRandom.uuid
16
+ generate_uuid
15
17
  when :integer, :bigint, :smallint
16
- Faker::Number.number(digits: 5)
18
+ generate_integer
17
19
  when :decimal, :float, :real
18
- Faker::Number.decimal(l_digits: 2, r_digits: 2)
20
+ generate_decimal
19
21
  when :datetime, :timestamp, :timestamptz
20
- Faker::Time.between(from: DateTime.now - 1, to: DateTime.now)
22
+ generate_datetime
21
23
  when :date
22
- Faker::Date.between(from: Date.today - 2, to: Date.today)
24
+ generate_date
23
25
  when :time, :timetz
24
- Faker::Time.forward(days: 23, period: :morning)
26
+ generate_time
25
27
  when :boolean
26
- Faker::Boolean.boolean
28
+ generate_boolean
27
29
  when :json, :jsonb
28
- { "value" => { "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) } }
30
+ generate_json
29
31
  when :inet
30
- Faker::Internet.ip_v4_address
32
+ generate_inet
31
33
  when :cidr, :macaddr
32
- Faker::Internet.mac_address
34
+ generate_macaddr
33
35
  when :bytea
34
- Faker::Internet.password
36
+ generate_bytea
35
37
  when :bit, :bit_varying
36
- ["0","1"].sample
38
+ generate_bit
37
39
  when :money
38
- Faker::Commerce.price.to_s
40
+ generate_money
39
41
  when :hstore
40
- { "value" => { "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) } }
42
+ generate_hstore
41
43
  when :year
42
- rand(1901..2155)
44
+ generate_year
43
45
  else
44
46
  raise UnknownColumnTypeError, "Unknown column type: #{@column.type}"
45
47
  end
46
48
  end
49
+
50
+ private
51
+
52
+ def generate_string
53
+ Faker::Lorem.word
54
+ end
55
+
56
+ def generate_uuid
57
+ SecureRandom.uuid
58
+ end
59
+
60
+ def generate_integer
61
+ Faker::Number.number(digits: 5)
62
+ end
63
+
64
+ def generate_decimal
65
+ Faker::Number.decimal(l_digits: 2, r_digits: 2)
66
+ end
67
+
68
+ def generate_datetime
69
+ Faker::Time.between(from: DateTime.now - 1, to: DateTime.now)
70
+ end
71
+
72
+ def generate_date
73
+ Faker::Date.between(from: Date.today - 2, to: Date.today)
74
+ end
75
+
76
+ def generate_time
77
+ Faker::Time.forward(days: 23, period: :morning)
78
+ end
79
+
80
+ def generate_boolean
81
+ Faker::Boolean.boolean
82
+ end
83
+
84
+ def generate_json
85
+ { "value" => { "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) } }
86
+ end
87
+
88
+ def generate_inet
89
+ Faker::Internet.ip_v4_address
90
+ end
91
+
92
+ def generate_macaddr
93
+ Faker::Internet.mac_address
94
+ end
95
+
96
+ def generate_bytea
97
+ Faker::Internet.password
98
+ end
99
+
100
+ def generate_bit
101
+ %w[0 1].sample
102
+ end
103
+
104
+ def generate_money
105
+ Faker::Commerce.price.to_s
106
+ end
107
+
108
+ def generate_hstore
109
+ { "value" => { "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) } }
110
+ end
111
+
112
+ def generate_year
113
+ rand(1901..2155)
114
+ end
47
115
  end
48
116
  end
49
- end
117
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  module FieldValues
3
5
  class FakerBuilder
@@ -17,11 +19,11 @@ module Seedie
17
19
  return @seedie_config_custom_attributes[@name.to_sym] if @seedie_config_custom_attributes.key?(@name.to_sym)
18
20
 
19
21
  @unique_prefix = "unique." if has_validation?(:uniqueness)
20
-
22
+
21
23
  add_faker_class_and_method(@column.type)
22
-
24
+
23
25
  if has_validation?(:inclusion)
24
- handle_inclusion_validation
26
+ handle_inclusion_validation
25
27
  else
26
28
  @options += handle_numericality_validation if has_validation?(:numericality)
27
29
  @options += handle_length_validation if has_validation?(:length)
@@ -40,58 +42,46 @@ module Seedie
40
42
  def add_faker_class_and_method(type)
41
43
  case type
42
44
  when :string, :text, :citext
43
- @class_prefix = "Lorem."
44
- @method_prefix = "word"
45
+ set_faker("Lorem.", "word")
45
46
  when :uuid
46
- @class_prefix = "Internet."
47
- @method_prefix = "uuid"
47
+ set_faker("Internet.", "uuid")
48
48
  when :integer, :bigint, :smallint
49
- @class_prefix = "Number."
50
- @method_prefix = "number"
51
- @options = "(digits: 5)"
49
+ set_faker("Number.", "number", "(digits: 5)")
52
50
  when :decimal, :float, :real
53
- @class_prefix = "Number."
54
- @method_prefix = "decimal"
55
- @options = "(l_digits: 2, r_digits: 2)"
51
+ set_faker("Number.", "decimal", "(l_digits: 2, r_digits: 2)")
56
52
  when :datetime, :timestamp, :timestamptz, :time, :timetz
57
- @class_prefix = "Time."
58
- @method_prefix = "between"
59
- @options = "(from: DateTime.now - 1, to: DateTime.now)"
53
+ set_faker("Time.", "between", "(from: DateTime.now - 1, to: DateTime.now)")
60
54
  when :date
61
- @class_prefix = "Date."
62
- @method_prefix = "between"
63
- @options = "(from: Date.today - 1, to: Date.today)"
55
+ set_faker("Date.", "between", "(from: Date.today - 1, to: Date.today)")
64
56
  when :boolean
65
- @class_prefix = "Boolean."
66
- @method_prefix = "boolean"
57
+ set_faker("Boolean.", "boolean")
67
58
  when :json, :jsonb
68
59
  @faker_expression = { "value" => "Json.shallow_json(width: 3, options: { key: 'Name.first_name', value: 'Number.number(digits: 2)' })" }
69
60
  when :inet
70
- @class_prefix = "Internet."
71
- @method_prefix = "ip_v4_address"
61
+ set_faker("Internet.", "ip_v4_address")
72
62
  when :cidr, :macaddr
73
- @class_prefix = "Internet."
74
- @method_prefix = "mac_address"
63
+ set_faker("Internet.", "mac_address")
75
64
  when :bytea
76
- @class_prefix = "Internet."
77
- @method_prefix = "password"
65
+ set_faker("Internet.", "password")
78
66
  when :bit, :bit_varying
79
- @class_prefix = "Internet."
80
- @method_prefix = "password"
67
+ set_faker("Internet.", "password")
81
68
  when :money
82
- @class_prefix = "Commerce."
83
- @method_prefix = "price.to_s"
69
+ set_faker("Commerce.", "price.to_s")
84
70
  when :hstore
85
71
  @faker_expression = { "value" => "Json.shallow_json(width: 3, options: { key: 'Name.first_name', value: 'Number.number(digits: 2)' })" }
86
72
  when :year
87
- @class_prefix = "Number."
88
- @method_prefix = "number"
89
- @options = "(digits: 4)"
73
+ set_faker("Number.", "number", "(digits: 4)")
90
74
  else
91
75
  raise UnknownColumnTypeError, "Unknown column type: #{type}"
92
76
  end
93
77
  end
94
78
 
79
+ def set_faker(class_prefix, method_prefix, options = "")
80
+ @class_prefix = class_prefix
81
+ @method_prefix = method_prefix
82
+ @options = options
83
+ end
84
+
95
85
  def has_validation?(kind)
96
86
  @validations.any? { |validation| validation.kind == kind }
97
87
  end
@@ -132,7 +122,8 @@ module Seedie
132
122
  @method_prefix = ""
133
123
  @options = ""
134
124
  if options[:in].is_a?(Range)
135
- @faker_expression = { "values" => { "start" => options[:in].first, "end" => options[:in].last }, "options" => { "pick_strategy" => "random" } }
125
+ @faker_expression = { "values" => { "start" => options[:in].first, "end" => options[:in].last },
126
+ "options" => { "pick_strategy" => "random" } }
136
127
  else
137
128
  @faker_expression = { "values" => options[:in], "options" => { "pick_strategy" => "random" } }
138
129
  end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seedie
4
+ module FieldValues
5
+ class ValueTemplateValidator
6
+ VALID_KEYS = %w[values value options].freeze
7
+ PICK_STRATEGIES = %w[random sequential].freeze
8
+
9
+ def initialize(value_template, index, name)
10
+ @value_template = value_template
11
+ @index = index
12
+ @name = name
13
+ end
14
+
15
+ def validate
16
+ return unless @value_template.is_a?(Hash)
17
+
18
+ validate_keys
19
+ validate_values if @value_template.key?("values")
20
+ validate_options if @value_template.key?("options")
21
+ end
22
+
23
+ private
24
+
25
+ def validate_keys
26
+ invalid_keys = @value_template.keys - VALID_KEYS
27
+
28
+ if invalid_keys.present?
29
+ raise InvalidCustomFieldKeysError,
30
+ "Invalid keys for #{@name}: #{invalid_keys.join(', ')}. Only #{VALID_KEYS} are allowed."
31
+ end
32
+
33
+ return unless @value_template.key?("values")
34
+
35
+ if @value_template.key?("value")
36
+ raise InvalidCustomFieldKeysError,
37
+ "Invalid keys for #{@name}: values and value cannot be used together."
38
+ end
39
+
40
+ return unless @value_template["values"].is_a?(Hash)
41
+
42
+ return unless !@value_template["values"].key?("start") || !@value_template["values"].key?("end")
43
+
44
+ raise InvalidCustomFieldValuesError,
45
+ "The values key for #{@name} must be an array or a hash with start and end keys."
46
+ end
47
+
48
+ def validate_values
49
+ values = @value_template["values"]
50
+
51
+ unless values.is_a?(Array) || values.is_a?(Hash)
52
+ raise InvalidCustomFieldValuesError,
53
+ "The values key for #{@name} must be an array or a hash with start and end keys."
54
+ end
55
+
56
+ validate_sequential_values_length
57
+ end
58
+
59
+ def validate_options
60
+ options = @value_template["options"]
61
+ pick_strategy = options["pick_strategy"]
62
+
63
+ return unless pick_strategy.present? && !PICK_STRATEGIES.include?(pick_strategy)
64
+
65
+ raise InvalidCustomFieldOptionsError,
66
+ "The pick_strategy for #{@name} must be either 'sequential' or 'random'."
67
+ end
68
+
69
+ ## If pick strategy is sequential, we need to ensure there is a value for each index
70
+ # If there isn't sufficient values, we raise an error
71
+ def validate_sequential_values_length
72
+ return unless @value_template.key?("options")
73
+ return unless @value_template["options"]["pick_strategy"] == "sequential"
74
+
75
+ values = @value_template["values"]
76
+
77
+ values_length = if values.is_a?(Hash) && values.keys.sort == %w[end start]
78
+ # Assuming the values are an inclusive range
79
+ values["end"] - values["start"] + 1
80
+ else
81
+ values.length
82
+ end
83
+
84
+ return unless values_length < @index + 1
85
+
86
+ raise CustomFieldNotEnoughValuesError,
87
+ "There are not enough values for #{@name}. Please add more values."
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  class FieldValuesSet
3
- attr_reader :attributes_config, :index
5
+ attr_reader :model, :model_config, :attributes_config, :index
4
6
 
5
7
  def initialize(model, model_config, index)
6
8
  @model = model
@@ -18,6 +20,12 @@ module Seedie
18
20
  @field_values
19
21
  end
20
22
 
23
+ def generate_field_values_with_associations
24
+ associated_field_values_set = generate_belongs_to_associations
25
+ model_field_values_set = generate_field_values
26
+ model_field_values_set.merge!(associated_field_values_set)
27
+ end
28
+
21
29
  def generate_field_value(name, column)
22
30
  return generate_custom_field_value(name) if @attributes_config&.key?(name)
23
31
 
@@ -26,18 +34,27 @@ module Seedie
26
34
 
27
35
  private
28
36
 
37
+ def generate_belongs_to_associations
38
+ associations_config = model_config["associations"]
39
+ return {} unless associations_config.present?
40
+
41
+ belongs_to_associations = Associations::BelongsTo.new(model, associations_config)
42
+ belongs_to_associations.generate_associations
43
+ belongs_to_associations.associated_field_set
44
+ end
45
+
29
46
  def populate_values_for_model_fields
30
47
  @field_values = @model.columns_hash.map do |name, column|
31
48
  next if @model_fields.disabled_fields.include?(name)
32
49
  next if @model_fields.foreign_fields.include?(name)
33
-
50
+
34
51
  [name, generate_field_value(name, column)]
35
52
  end.compact.to_h
36
53
  end
37
54
 
38
55
  def populate_values_for_virtual_fields
39
56
  virtual_fields = @attributes_config.keys - @model.columns_hash.keys
40
-
57
+
41
58
  virtual_fields.each do |name|
42
59
  @field_values[name] = generate_custom_field_value(name) if @attributes_config[name]
43
60
  end
@@ -47,4 +64,4 @@ module Seedie
47
64
  FieldValues::CustomValue.new(name, @attributes_config[name], @index).generate_custom_field_value
48
65
  end
49
66
  end
50
- end
67
+ end
@@ -1,18 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  module Model
3
5
  class Creator
4
6
  include Reporters::Reportable
5
-
7
+
6
8
  def initialize(model, reporters = [])
7
9
  @model = model
8
10
  @reporters = reporters
9
11
 
10
12
  add_observers(@reporters)
11
13
  end
12
-
14
+
13
15
  def create!(field_values_set)
14
16
  record = @model.create!(field_values_set)
15
- report(:record_created, name: "#{record.class}", id: "#{record.id}")
17
+ report(:record_created, name: record.class.to_s, id: record.id.to_s)
16
18
 
17
19
  record
18
20
  end
@@ -22,9 +24,9 @@ module Seedie
22
24
  create!(field_values_set)
23
25
  rescue ActiveRecord::RecordInvalid => e
24
26
  report(:record_invalid, record: e.record)
25
- return nil
27
+ nil
26
28
  end
27
29
  end
28
30
  end
29
31
  end
30
- end
32
+ end
@@ -1,30 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seedie
2
4
  module Model
3
5
  class IdGenerator
4
6
  def initialize(model)
5
7
  @model = model
6
8
  end
7
-
9
+
8
10
  def random_id
9
11
  id = @model.pluck(:id).sample
10
12
  raise InvalidAssociationConfigError, "#{@model} has no records" unless id
11
13
 
12
- return id
14
+ id
13
15
  end
14
-
16
+
15
17
  def unique_id_for(association_klass, model_id_column)
16
18
  unless association_klass.column_names.include?(model_id_column)
17
- raise InvalidAssociationConfigError, "#{model_id_column} does not exist in #{association_klass}"
19
+ raise InvalidAssociationConfigError, "#{model_id_column} does not exist in #{association_klass}"
18
20
  end
19
-
21
+
20
22
  unique_ids = @model.ids - association_klass.pluck(model_id_column)
21
-
23
+
22
24
  if unique_ids.empty?
23
25
  raise InvalidAssociationConfigError, "No unique ids for #{@model}"
24
26
  end
25
-
27
+
26
28
  unique_ids.first
27
29
  end
28
30
  end
29
31
  end
30
- end
32
+ end