seedie 0.1.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 +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +16 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +16 -0
- data/lib/generators/seedie/USAGE +7 -0
- data/lib/generators/seedie/install_generator.rb +136 -0
- data/lib/generators/seedie/templates/seedie.yml +26 -0
- data/lib/seedie/associations/base_association.rb +44 -0
- data/lib/seedie/associations/belongs_to.rb +89 -0
- data/lib/seedie/associations/has_many.rb +24 -0
- data/lib/seedie/associations/has_one.rb +29 -0
- data/lib/seedie/field_values/custom_value.rb +97 -0
- data/lib/seedie/field_values/fake_value.rb +49 -0
- data/lib/seedie/field_values/faker_builder.rb +138 -0
- data/lib/seedie/field_values_set.rb +50 -0
- data/lib/seedie/model/creator.rb +30 -0
- data/lib/seedie/model/id_generator.rb +32 -0
- data/lib/seedie/model/model_sorter.rb +87 -0
- data/lib/seedie/model_fields.rb +16 -0
- data/lib/seedie/model_seeder.rb +60 -0
- data/lib/seedie/railtie.rb +10 -0
- data/lib/seedie/reporters/base_reporter.rb +81 -0
- data/lib/seedie/reporters/console_reporter.rb +16 -0
- data/lib/seedie/reporters/reportable.rb +16 -0
- data/lib/seedie/seeder.rb +39 -0
- data/lib/seedie/version.rb +5 -0
- data/lib/seedie.rb +42 -0
- data/lib/tasks/seedie.rake +8 -0
- metadata +175 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Seedie
|
2
|
+
module FieldValues
|
3
|
+
class FakeValue
|
4
|
+
def initialize(name, column)
|
5
|
+
@name = name
|
6
|
+
@column = column
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_fake_value
|
10
|
+
case @column.type
|
11
|
+
when :string, :text, :citext
|
12
|
+
Faker::Lorem.word
|
13
|
+
when :uuid
|
14
|
+
SecureRandom.uuid
|
15
|
+
when :integer, :bigint, :smallint
|
16
|
+
Faker::Number.number(digits: 5)
|
17
|
+
when :decimal, :float, :real
|
18
|
+
Faker::Number.decimal(l_digits: 2, r_digits: 2)
|
19
|
+
when :datetime, :timestamp, :timestamptz
|
20
|
+
Faker::Time.between(from: DateTime.now - 1, to: DateTime.now)
|
21
|
+
when :date
|
22
|
+
Faker::Date.between(from: Date.today - 2, to: Date.today)
|
23
|
+
when :time, :timetz
|
24
|
+
Faker::Time.forward(days: 23, period: :morning)
|
25
|
+
when :boolean
|
26
|
+
Faker::Boolean.boolean
|
27
|
+
when :json, :jsonb
|
28
|
+
{ "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) }
|
29
|
+
when :inet
|
30
|
+
Faker::Internet.ip_v4_address
|
31
|
+
when :cidr, :macaddr
|
32
|
+
Faker::Internet.mac_address
|
33
|
+
when :bytea
|
34
|
+
Faker::Internet.password
|
35
|
+
when :bit, :bit_varying
|
36
|
+
["0","1"].sample
|
37
|
+
when :money
|
38
|
+
Faker::Commerce.price.to_s
|
39
|
+
when :hstore
|
40
|
+
{ "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) }
|
41
|
+
when :year
|
42
|
+
rand(1901..2155)
|
43
|
+
else
|
44
|
+
raise UnknownColumnTypeError, "Unknown column type: #{@column.type}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Seedie
|
2
|
+
module FieldValues
|
3
|
+
class FakerBuilder
|
4
|
+
def initialize(name, column, validations)
|
5
|
+
@name = name
|
6
|
+
@column = column
|
7
|
+
@validations = validations
|
8
|
+
@faker_expression = "{{Faker::"
|
9
|
+
@unique_prefix = ""
|
10
|
+
@class_prefix = ""
|
11
|
+
@method_prefix = ""
|
12
|
+
@options = ""
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_faker_constant
|
16
|
+
@unique_prefix = "unique." if has_validation?(:uniqueness)
|
17
|
+
|
18
|
+
add_faker_class_and_method(@column.type)
|
19
|
+
|
20
|
+
if has_validation?(:inclusion)
|
21
|
+
handle_inclusion_validation
|
22
|
+
else
|
23
|
+
@options += handle_numericality_validation if has_validation?(:numericality)
|
24
|
+
@options += handle_length_validation if has_validation?(:length)
|
25
|
+
end
|
26
|
+
|
27
|
+
if @faker_expression.is_a?(String)
|
28
|
+
@faker_expression += "#{@class_prefix}#{@unique_prefix}#{@method_prefix}#{@options}"
|
29
|
+
@faker_expression += "}}" if @faker_expression.start_with?("{{") # We may not need }} when random attributes
|
30
|
+
end
|
31
|
+
|
32
|
+
@faker_expression
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def add_faker_class_and_method(type)
|
38
|
+
case type
|
39
|
+
when :string, :text, :citext
|
40
|
+
@class_prefix = "Lorem."
|
41
|
+
@method_prefix = "word"
|
42
|
+
when :uuid
|
43
|
+
@class_prefix = "Internet."
|
44
|
+
@method_prefix = "uuid"
|
45
|
+
when :integer, :bigint, :smallint
|
46
|
+
@class_prefix = "Number."
|
47
|
+
@method_prefix = "number"
|
48
|
+
@options = "(digits: 5)"
|
49
|
+
when :decimal, :float, :real
|
50
|
+
@class_prefix = "Number."
|
51
|
+
@method_prefix = "decimal"
|
52
|
+
@options = "(l_digits: 2, r_digits: 2)"
|
53
|
+
when :datetime, :timestamp, :timestamptz, :time, :timetz
|
54
|
+
@class_prefix = "Time."
|
55
|
+
@method_prefix = "between"
|
56
|
+
@options = "(from: DateTime.now - 1, to: DateTime.now)"
|
57
|
+
when :date
|
58
|
+
@class_prefix = "Date."
|
59
|
+
@method_prefix = "between"
|
60
|
+
@options = "(from: Date.today - 1, to: Date.today)"
|
61
|
+
when :boolean
|
62
|
+
@class_prefix = "Boolean."
|
63
|
+
@method_prefix = "boolean"
|
64
|
+
when :json, :jsonb
|
65
|
+
@class_prefix = "Json."
|
66
|
+
@method_prefix = "shallow_json(width: 3, options: { key: \"Name.first_name\", value: \"Number.number(digits: 2)\" })"
|
67
|
+
when :inet
|
68
|
+
@class_prefix = "Internet."
|
69
|
+
@method_prefix = "ip_v4_address"
|
70
|
+
when :cidr, :macaddr
|
71
|
+
@class_prefix = "Internet."
|
72
|
+
@method_prefix = "mac_address"
|
73
|
+
when :bytea
|
74
|
+
@class_prefix = "Internet."
|
75
|
+
@method_prefix = "password"
|
76
|
+
when :bit, :bit_varying
|
77
|
+
@class_prefix = "Internet."
|
78
|
+
@method_prefix = "password"
|
79
|
+
when :money
|
80
|
+
@class_prefix = "Commerce."
|
81
|
+
@method_prefix = "price.to_s"
|
82
|
+
when :hstore
|
83
|
+
@class_prefix = "Json."
|
84
|
+
@method_prefix = "shallow_json"
|
85
|
+
@options = "(width: 3, options: { key: \"Name.first_name\", value: \"Number.number(digits: 2)\" })"
|
86
|
+
when :year
|
87
|
+
@class_prefix = "Number."
|
88
|
+
@method_prefix = "number"
|
89
|
+
@options = "(digits: 4)"
|
90
|
+
else
|
91
|
+
raise UnknownColumnTypeError, "Unknown column type: #{type}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_validation?(kind)
|
96
|
+
@validations.any? { |validation| validation.kind == kind }
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_numericality_validation
|
100
|
+
numericality_validator = @validations.find { |v| v.kind == :numericality }
|
101
|
+
options = numericality_validator.options
|
102
|
+
if options[:greater_than_or_equal_to] && options[:less_than_or_equal_to]
|
103
|
+
".between(from: #{options[:greater_than_or_equal_to]}, to: #{options[:less_than_or_equal_to]})"
|
104
|
+
elsif options[:greater_than_or_equal_to]
|
105
|
+
".between(from: #{options[:greater_than_or_equal_to]})"
|
106
|
+
elsif options[:less_than_or_equal_to]
|
107
|
+
".between(to: #{options[:less_than_or_equal_to]})"
|
108
|
+
else
|
109
|
+
""
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def handle_length_validation
|
114
|
+
length_validator = @validations.find { |v| v.kind == :length }
|
115
|
+
@method_prefix = "characters"
|
116
|
+
options = length_validator.options
|
117
|
+
if options[:minimum] && options[:maximum]
|
118
|
+
"(number: rand(#{options[:minimum]}..#{options[:maximum]}))"
|
119
|
+
elsif options[:minimum]
|
120
|
+
"(number: rand(#{options[:minimum]}..100))"
|
121
|
+
elsif options[:maximum]
|
122
|
+
"(number: rand(1..#{options[:maximum]}))"
|
123
|
+
else
|
124
|
+
""
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def handle_inclusion_validation
|
129
|
+
inclusion_validator = @validations.find { |v| v.kind == :inclusion }
|
130
|
+
options = inclusion_validator.options
|
131
|
+
@class_prefix = ""
|
132
|
+
@method_prefix = ""
|
133
|
+
@options = ""
|
134
|
+
@faker_expression = { "custom_attr_value" => { "values" => options[:in], "pick_strategy" => "random" } }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Seedie
|
2
|
+
class FieldValuesSet
|
3
|
+
attr_reader :attributes_config, :index
|
4
|
+
|
5
|
+
def initialize(model, model_config, index)
|
6
|
+
@model = model
|
7
|
+
@model_config = model_config
|
8
|
+
@index = index
|
9
|
+
@attributes_config = model_config["attributes"]
|
10
|
+
@model_fields = ModelFields.new(model, model_config)
|
11
|
+
@field_values = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_field_values
|
15
|
+
populate_values_for_model_fields
|
16
|
+
populate_values_for_virtual_fields if @attributes_config
|
17
|
+
|
18
|
+
@field_values
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate_field_value(name, column)
|
22
|
+
return generate_custom_field_value(name) if @attributes_config&.key?(name)
|
23
|
+
|
24
|
+
FieldValues::FakeValue.new(name, column).generate_fake_value
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def populate_values_for_model_fields
|
30
|
+
@field_values = @model.columns_hash.map do |name, column|
|
31
|
+
next if @model_fields.disabled_fields.include?(name)
|
32
|
+
next if @model_fields.foreign_fields.include?(name)
|
33
|
+
|
34
|
+
[name, generate_field_value(name, column)]
|
35
|
+
end.compact.to_h
|
36
|
+
end
|
37
|
+
|
38
|
+
def populate_values_for_virtual_fields
|
39
|
+
virtual_fields = @attributes_config.keys - @model.columns_hash.keys
|
40
|
+
|
41
|
+
virtual_fields.each do |name|
|
42
|
+
@field_values[name] = generate_custom_field_value(name) if @attributes_config[name]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_custom_field_value(name)
|
47
|
+
FieldValues::CustomValue.new(name, @attributes_config[name], @index).generate_custom_field_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Seedie
|
2
|
+
module Model
|
3
|
+
class Creator
|
4
|
+
include Reporters::Reportable
|
5
|
+
|
6
|
+
def initialize(model, reporters = [])
|
7
|
+
@model = model
|
8
|
+
@reporters = reporters
|
9
|
+
|
10
|
+
add_observers(@reporters)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create!(field_values_set)
|
14
|
+
record = @model.create!(field_values_set)
|
15
|
+
report(:record_created, name: "#{record.class}", id: "#{record.id}")
|
16
|
+
|
17
|
+
record
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(field_values_set)
|
21
|
+
begin
|
22
|
+
create!(field_values_set)
|
23
|
+
rescue ActiveRecord::RecordInvalid => e
|
24
|
+
report(:record_invalid, record: e.record)
|
25
|
+
return nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Seedie
|
2
|
+
module Model
|
3
|
+
class IdGenerator
|
4
|
+
def initialize(model)
|
5
|
+
@model = model
|
6
|
+
end
|
7
|
+
|
8
|
+
def random_id
|
9
|
+
id = @model.pluck(:id).sample
|
10
|
+
raise InvalidAssociationConfigError, "#{@model} has no records" unless id
|
11
|
+
|
12
|
+
return id
|
13
|
+
end
|
14
|
+
|
15
|
+
def unique_id_for(association_klass)
|
16
|
+
model_id_column = "#{@model.to_s.underscore}_id"
|
17
|
+
|
18
|
+
unless association_klass.column_names.include?(model_id_column)
|
19
|
+
raise InvalidAssociationConfigError, "#{model_id_column} does not exist in #{association_klass}"
|
20
|
+
end
|
21
|
+
|
22
|
+
unique_ids = @model.ids - association_klass.pluck(model_id_column)
|
23
|
+
|
24
|
+
if unique_ids.empty?
|
25
|
+
raise InvalidAssociationConfigError, "No unique ids for #{@model}"
|
26
|
+
end
|
27
|
+
|
28
|
+
unique_ids.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Seedie
|
2
|
+
module Model
|
3
|
+
class ModelSorter
|
4
|
+
def initialize(models)
|
5
|
+
@models = models
|
6
|
+
@model_dependencies = models.map {|m| [m, get_model_dependencies(m)]}.to_h
|
7
|
+
@resolved_queue = []
|
8
|
+
@unresolved = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def sort_by_dependency
|
12
|
+
add_independent_models_to_queue
|
13
|
+
|
14
|
+
@models.each do |model|
|
15
|
+
resolve_dependencies(model) unless @resolved_queue.include?(model)
|
16
|
+
end
|
17
|
+
|
18
|
+
@resolved_queue
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
|
24
|
+
# Independent models need to be added first
|
25
|
+
def add_independent_models_to_queue
|
26
|
+
@models.each do |model|
|
27
|
+
if @model_dependencies[model].empty?
|
28
|
+
@resolved_queue << model
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def resolve_dependencies(model)
|
34
|
+
if @unresolved.include?(model)
|
35
|
+
puts "Circular dependency detected for #{model}. Ignoring..."
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
@unresolved << model
|
40
|
+
dependencies = @model_dependencies[model]
|
41
|
+
|
42
|
+
if dependencies
|
43
|
+
dependencies.each do |dependency|
|
44
|
+
resolve_dependencies(dependency) unless @resolved_queue.include?(dependency)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@resolved_queue << model
|
49
|
+
@unresolved.delete(model)
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_model_dependencies(model)
|
53
|
+
associations = model.reflect_on_all_associations(:belongs_to).reject do |association|
|
54
|
+
association.options[:polymorphic] == true || # Excluded Polymorphic Associations
|
55
|
+
association.options[:optional] == true # Excluded Optional Associations
|
56
|
+
end
|
57
|
+
|
58
|
+
return [] if associations.blank?
|
59
|
+
|
60
|
+
associations.map do |association|
|
61
|
+
if association.options[:class_name]
|
62
|
+
constantize_class_name(association.options[:class_name], model.name)
|
63
|
+
else
|
64
|
+
association.klass
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def constantize_class_name(class_name, model_name)
|
72
|
+
namespaced_class_name = if model_name.include?("::")
|
73
|
+
"#{model_name.deconstantize}::#{class_name}"
|
74
|
+
else
|
75
|
+
class_name
|
76
|
+
end
|
77
|
+
begin
|
78
|
+
namespaced_class_name.constantize
|
79
|
+
rescue NameError
|
80
|
+
# If the class_name with the namespace doesn't exist, try without the namespace
|
81
|
+
puts "Class #{namespaced_class_name} not found. Trying without the namespace... #{class_name}"
|
82
|
+
class_name.constantize
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Seedie
|
2
|
+
class ModelFields
|
3
|
+
DEFAULT_DISABLED_FIELDS = %w[id created_at updated_at]
|
4
|
+
|
5
|
+
attr_reader :model_name, :model_config, :fields, :disabled_fields, :foreign_fields
|
6
|
+
|
7
|
+
def initialize(model, model_config)
|
8
|
+
@model_name = model.to_s
|
9
|
+
@model_config = model_config
|
10
|
+
@custom_fields = model_config["attributes"]&.keys || []
|
11
|
+
@disabled_fields = [(model_config["disabled_fields"] || []) + DEFAULT_DISABLED_FIELDS].flatten.uniq
|
12
|
+
@foreign_fields = model.reflect_on_all_associations(:belongs_to).map(&:foreign_key)
|
13
|
+
@other_fields = model.column_names - @disabled_fields - @custom_fields - @foreign_fields
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Seedie
|
2
|
+
class ModelSeeder
|
3
|
+
include Reporters::Reportable
|
4
|
+
|
5
|
+
DEFAULT_MODEL_COUNT = 1
|
6
|
+
|
7
|
+
attr_reader :model, :model_config, :config, :reporters
|
8
|
+
|
9
|
+
def initialize(model, model_config, config, reporters)
|
10
|
+
@model = model
|
11
|
+
@model_config = model_config
|
12
|
+
@config = config
|
13
|
+
@record_creator = Model::Creator.new(model, reporters)
|
14
|
+
@reporters = reporters
|
15
|
+
|
16
|
+
add_observers(@reporters)
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_records
|
20
|
+
report(:model_seed_start, name: "#{model.to_s}")
|
21
|
+
model_count(model_config).times do |index|
|
22
|
+
record = generate_record(model_config, index)
|
23
|
+
associations_config = model_config["associations"]
|
24
|
+
|
25
|
+
if associations_config.present?
|
26
|
+
Associations::HasMany.new(record, model, associations_config, reporters).generate_associations
|
27
|
+
Associations::HasOne.new(record, model, associations_config, reporters).generate_associations
|
28
|
+
end
|
29
|
+
end
|
30
|
+
report(:model_seed_finish, name: "#{model.to_s}")
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def model_count(model_config)
|
36
|
+
return model_config["count"] if model_config["count"].present?
|
37
|
+
return config["default_count"] if config["default_count"].present?
|
38
|
+
|
39
|
+
DEFAULT_MODEL_COUNT
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_record(model_config, index)
|
43
|
+
associated_field_set = generate_belongs_to_associations(model, model_config)
|
44
|
+
|
45
|
+
field_values_set = FieldValuesSet.new(model, model_config, index).generate_field_values
|
46
|
+
field_values_set.merge!(associated_field_set)
|
47
|
+
@record_creator.create!(field_values_set)
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_belongs_to_associations(model, model_config)
|
51
|
+
associations_config = model_config["associations"]
|
52
|
+
return {} unless associations_config.present?
|
53
|
+
|
54
|
+
belongs_to_associations = Associations::BelongsTo.new(model, associations_config, reporters)
|
55
|
+
belongs_to_associations.generate_associations
|
56
|
+
|
57
|
+
return belongs_to_associations.associated_field_set
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Reporters
|
2
|
+
class BaseReporter
|
3
|
+
INDENT_SIZE = 2
|
4
|
+
|
5
|
+
attr_reader :output, :reports
|
6
|
+
|
7
|
+
def initialize(output = nil)
|
8
|
+
@output = output || StringIO.new
|
9
|
+
@reports = []
|
10
|
+
@indent_level = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(event_type, options)
|
14
|
+
raise NotImplementedError, "Subclasses must define 'update'."
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
return if output.closed?
|
19
|
+
output.flush
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def messages(event_type, options)
|
25
|
+
case event_type
|
26
|
+
when :seed_start
|
27
|
+
"############ SEEDIE RUNNING #############"
|
28
|
+
when :seed_finish
|
29
|
+
"############ SEEDIE FINISHED ############"
|
30
|
+
when :model_seed_start
|
31
|
+
"Seeding #{options[:name]}"
|
32
|
+
when :model_seed_finish
|
33
|
+
"Seeding #{options[:name]} finished!"
|
34
|
+
when :record_created
|
35
|
+
"Created #{options[:name]} with id: #{options[:id]}"
|
36
|
+
when :has_many_start
|
37
|
+
"Creating HasMany associations:"
|
38
|
+
when :belongs_to_start
|
39
|
+
"Creating BelongsTo associations:"
|
40
|
+
when :has_one_start
|
41
|
+
"Creating HasOne associations:"
|
42
|
+
when :associated_records
|
43
|
+
"Creating #{options[:count]} #{options[:name]} for #{options[:parent_name]}"
|
44
|
+
when :random_association
|
45
|
+
"Randomly associating #{options[:name]} with id: #{options[:id]} for #{options[:parent_name]}"
|
46
|
+
when :unique_association
|
47
|
+
"Uniquely associating #{options[:name]} for #{options[:parent_name]}"
|
48
|
+
when :belongs_to_associations
|
49
|
+
"Creating a new #{options[:name].titleize} for #{options[:parent_name]}"
|
50
|
+
else
|
51
|
+
"Unknown event type"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def indent_level_for(event_type)
|
56
|
+
indent_levels = {
|
57
|
+
seed_start: 0,
|
58
|
+
seed_finish: 0,
|
59
|
+
model_seed_start: 1,
|
60
|
+
model_seed_finish: 1,
|
61
|
+
record_created: 1,
|
62
|
+
random_association: 1,
|
63
|
+
has_many_start: 2,
|
64
|
+
belongs_to_start: 2,
|
65
|
+
has_one_start: 2,
|
66
|
+
associated_records: 3,
|
67
|
+
belongs_to_associations: 3
|
68
|
+
}
|
69
|
+
|
70
|
+
indent_levels[event_type]
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_indent_level(event_type)
|
74
|
+
if event_type.in?([:record_created, :random_association, :unique_association])
|
75
|
+
@indent_level += 1 if !@reports.last[:event_type].in?([:record_created, :random_association, :unique_association])
|
76
|
+
elsif @reports.blank? || @reports.last[:event_type] != event_type
|
77
|
+
@indent_level = indent_level_for(event_type)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Reporters
|
2
|
+
class ConsoleReporter < BaseReporter
|
3
|
+
def initialize
|
4
|
+
super($stdout)
|
5
|
+
end
|
6
|
+
|
7
|
+
def update(event_type, options)
|
8
|
+
indent_level = set_indent_level(event_type)
|
9
|
+
message = messages(event_type, options)
|
10
|
+
@reports << { event_type: event_type, message: message }
|
11
|
+
|
12
|
+
output.print "#{" " * INDENT_SIZE * @indent_level}"
|
13
|
+
output.puts message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "observer"
|
2
|
+
|
3
|
+
module Reporters
|
4
|
+
module Reportable
|
5
|
+
include Observable
|
6
|
+
|
7
|
+
def report(event_type, options = {})
|
8
|
+
changed
|
9
|
+
notify_observers(event_type, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_observers(observers)
|
13
|
+
observers.each { |observer| add_observer(observer) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Seedie
|
2
|
+
class Seeder
|
3
|
+
include Reporters::Reportable
|
4
|
+
|
5
|
+
attr_reader :config
|
6
|
+
|
7
|
+
def initialize(config_path = nil)
|
8
|
+
@config_path = config_path
|
9
|
+
@config = load_config(config_path)
|
10
|
+
@reporters = []
|
11
|
+
@console_reporter = Reporters::ConsoleReporter.new
|
12
|
+
@reporters << @console_reporter
|
13
|
+
|
14
|
+
add_observers(@reporters)
|
15
|
+
end
|
16
|
+
|
17
|
+
def seed_models
|
18
|
+
report(:seed_start)
|
19
|
+
ActiveRecord::Base.transaction do
|
20
|
+
config["models"].each do |model_name, model_config|
|
21
|
+
model = model_name.classify.constantize
|
22
|
+
ModelSeeder.new(model, model_config, config, @reporters).generate_records
|
23
|
+
end
|
24
|
+
end
|
25
|
+
report(:seed_finish)
|
26
|
+
|
27
|
+
@reporters.each(&:close)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def load_config(path)
|
33
|
+
path = Rails.root.join("config", "seedie.yml") if path.nil?
|
34
|
+
raise ConfigFileNotFound, "Config file not found in #{path}" unless File.exist?(path)
|
35
|
+
|
36
|
+
YAML.load_file(path)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/seedie.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "seedie/reporters/reportable"
|
4
|
+
require_relative "seedie/reporters/base_reporter"
|
5
|
+
require_relative "seedie/reporters/console_reporter"
|
6
|
+
|
7
|
+
require_relative "seedie/field_values/fake_value"
|
8
|
+
require_relative "seedie/field_values/custom_value"
|
9
|
+
require_relative "seedie/field_values/faker_builder"
|
10
|
+
require_relative "seedie/field_values_set"
|
11
|
+
require_relative "seedie/model_fields"
|
12
|
+
require_relative "seedie/model_seeder"
|
13
|
+
|
14
|
+
require_relative "seedie/model/creator"
|
15
|
+
require_relative "seedie/model/model_sorter"
|
16
|
+
require_relative "seedie/model/id_generator"
|
17
|
+
|
18
|
+
require_relative "seedie/associations/base_association"
|
19
|
+
require_relative "seedie/associations/has_many"
|
20
|
+
require_relative "seedie/associations/has_one"
|
21
|
+
require_relative "seedie/associations/belongs_to"
|
22
|
+
|
23
|
+
require_relative "seedie/seeder"
|
24
|
+
require_relative "seedie/version"
|
25
|
+
|
26
|
+
require "seedie/railtie" if defined?(Rails)
|
27
|
+
|
28
|
+
require "active_record"
|
29
|
+
require "faker"
|
30
|
+
require "yaml"
|
31
|
+
|
32
|
+
module Seedie
|
33
|
+
class Error < StandardError; end
|
34
|
+
class InvalidFakerMethodError < StandardError; end
|
35
|
+
class UnknownColumnTypeError < StandardError; end
|
36
|
+
class ConfigFileNotFound < StandardError; end
|
37
|
+
class InvalidAssociationConfigError < StandardError; end
|
38
|
+
class InvalidCustomFieldKeysError < StandardError; end
|
39
|
+
class InvalidCustomFieldValuesError < StandardError; end
|
40
|
+
class CustomFieldNotEnoughValuesError < StandardError; end
|
41
|
+
class CustomFieldInvalidPickValueError < StandardError; end
|
42
|
+
end
|