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