seedie 0.3.0 → 0.4.0

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