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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 561377deea81325062cee19ab2cd29e48d6d434e2872b97474712c1ab07a4d87
|
4
|
+
data.tar.gz: 06a8f58b9488ec48da96b6678696bbfc672fd71f881395a795a8a97eff4d1478
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: eda91d6bd4be5e638ef13e963d517f25055e9a96bbccfd222d565caeed5eddaf929fff9742a45bc260c27cafb5c2f0d37e4d91948c1d7786e17cc10202c05289
|
7
|
+
data.tar.gz: 18f0f86fadc97d6e9be4a736d0795cdf09a6a8df044fd5f6d1a7ae4d1e3f0c75a3be706111b56c5c44bacfaf3f7d072fc5479091f412be6f319591b83616e8c8
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.6
|
3
|
+
DisabledByDefault: true
|
4
|
+
|
5
|
+
Style/StringLiterals:
|
6
|
+
Enabled: true
|
7
|
+
EnforcedStyle: double_quotes
|
8
|
+
|
9
|
+
Style/StringLiteralsInInterpolation:
|
10
|
+
Enabled: true
|
11
|
+
EnforcedStyle: double_quotes
|
12
|
+
|
13
|
+
Layout/LineLength:
|
14
|
+
Max: 120
|
15
|
+
Exclude:
|
16
|
+
- "spec/dummy/**/*"
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in seedie.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
gem "rubocop", "~> 1.21"
|
10
|
+
|
11
|
+
gem "pry", "~> 0.14.2"
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem "simplecov", "~> 0.22.0", require: false
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Keshav Biswa
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Seedie
|
2
|
+
|
3
|
+
Seedie is a Ruby gem designed to streamline the seeding of your database.
|
4
|
+
Utilizing the Faker library, Seedie generates realistic data for ActiveRecord models.
|
5
|
+
Currently supports only PostrgreSQL and SQLite3 databases.
|
6
|
+
The gem includes a Rake task for seeding models and a Rails generator for easy setup.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add the following line to your application's Gemfile:
|
11
|
+
|
12
|
+
```bash
|
13
|
+
gem 'seedie'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
$ bundle install
|
20
|
+
```
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
$ gem install seedie
|
26
|
+
```
|
27
|
+
Next, run the install generator:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
$ rails generate seedie:install
|
31
|
+
```
|
32
|
+
This will create a seedie.yml file in your config directory, which will include configurations for your models.
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
To seed your models, run the following Rake task:
|
37
|
+
|
38
|
+
```bash
|
39
|
+
$ rake seedie:seed
|
40
|
+
```
|
41
|
+
|
42
|
+
This will use the configurations specified in seedie.yml to seed your models.
|
43
|
+
|
44
|
+
The seedie.yml file has entries for each model in your application, and you can customize the configuration for each one.
|
45
|
+
|
46
|
+
Here's an example of a more advanced configuration in seedie.yml:
|
47
|
+
|
48
|
+
|
49
|
+
```yaml
|
50
|
+
default_count: 5
|
51
|
+
models:
|
52
|
+
user:
|
53
|
+
attributes:
|
54
|
+
name: "name {{index}}"
|
55
|
+
email: "{{Faker::Internet.email}}"
|
56
|
+
address: "{{Faker::Address.street_address}}"
|
57
|
+
disabled_fields: [nickname password password_digest]
|
58
|
+
post: &post
|
59
|
+
count: 2
|
60
|
+
attributes:
|
61
|
+
title: "title {{index}}"
|
62
|
+
associations:
|
63
|
+
has_many:
|
64
|
+
comments: 4
|
65
|
+
belongs_to:
|
66
|
+
user: random
|
67
|
+
has_one:
|
68
|
+
post_metadatum:
|
69
|
+
attributes:
|
70
|
+
seo_text: "{{Faker::Lorem.paragraph}}"
|
71
|
+
disabled_fields: []
|
72
|
+
comment:
|
73
|
+
attributes:
|
74
|
+
title: "title {{index}}"
|
75
|
+
associations:
|
76
|
+
belongs_to:
|
77
|
+
post:
|
78
|
+
attributes:
|
79
|
+
title: "Comment Post {{index}}"
|
80
|
+
|
81
|
+
```
|
82
|
+
|
83
|
+
In this file:
|
84
|
+
|
85
|
+
- `default_count` specifies the number of records to be generated for each model when no specific count is provided in the model's configuration.
|
86
|
+
- `models` is a hash that contains a configuration for each model that should be seeded.
|
87
|
+
- `attributes` is a hash that maps field names to the values that should be used. If attributes are not defined, Seedie will use Faker to generate a value for the field.
|
88
|
+
- The special `{{index}}` placeholder will be replaced by the index of the current record being created, starting from 1. This allows you to have unique values for each record.
|
89
|
+
- Additionally, we can use placeholders like `{{Faker::Internet.email}}` to generate dynamic and unique data for each record using Faker.
|
90
|
+
- `disabled_fields` is an array of fields that should not be automatically filled by Seedie.
|
91
|
+
- `associations` specify how associated models should be generated. Here, `has_many`, `belongs_to`, and `has_one` are supported.
|
92
|
+
- The specified number for `has_many` represents the number of associated records to create.
|
93
|
+
- For `belongs_to`, the value `random` means that a random existing record will be associated.
|
94
|
+
- If attributes are specified under an association, those attributes will be used when creating the associated record(s)
|
95
|
+
- When using associations, it's important to define the models in the correct order in the `seedie.yml` file. Associated models should be defined before the models that reference them.
|
96
|
+
|
97
|
+
## Development
|
98
|
+
|
99
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
100
|
+
Then, run `bundle exec rspec` to run the tests.
|
101
|
+
By default, the tests will supress output of the seeds progress.
|
102
|
+
Use `DEBUG_OUTPUT=true bundle exec rspec` to see the output of the seeds.
|
103
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
104
|
+
|
105
|
+
## Contributing
|
106
|
+
|
107
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/keshavbiswa/seedie.
|
108
|
+
|
109
|
+
## License
|
110
|
+
|
111
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
|
6
|
+
desc "Run all examples"
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
8
|
+
t.ruby_opts = %w[-w]
|
9
|
+
t.rspec_opts = %w[--color]
|
10
|
+
end
|
11
|
+
|
12
|
+
require "rubocop/rake_task"
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[spec rubocop]
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require "rails/generators/base"
|
2
|
+
require "active_record"
|
3
|
+
|
4
|
+
module Seedie
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
EXCLUDED_MODELS = %w[
|
8
|
+
ActiveRecord::SchemaMigration
|
9
|
+
ActiveRecord::InternalMetadata
|
10
|
+
ActiveStorage::Attachment
|
11
|
+
ActiveStorage::Blob
|
12
|
+
ActiveStorage::VariantRecord
|
13
|
+
ActionText::RichText
|
14
|
+
ActionMailbox::InboundEmail
|
15
|
+
ActionText::EncryptedRichText
|
16
|
+
]
|
17
|
+
|
18
|
+
source_root File.expand_path("templates", __dir__)
|
19
|
+
|
20
|
+
desc "Creates a seedie.yml for your application."
|
21
|
+
def generate_seedie_file(output = STDOUT)
|
22
|
+
Rails.application.eager_load! # Load all models. This is required!!
|
23
|
+
|
24
|
+
@models = get_models
|
25
|
+
@models_config = build_models_config
|
26
|
+
template "seedie.yml", "config/seedie.yml"
|
27
|
+
output_seedie_warning(output)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
|
33
|
+
def build_models_config
|
34
|
+
models = Model::ModelSorter.new(@models).sort_by_dependency
|
35
|
+
models.reduce({}) do |config, model|
|
36
|
+
config[model.name.underscore] = model_configuration(model)
|
37
|
+
config
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def model_configuration(model)
|
42
|
+
attributes_configuration(model).merge(associations_configuration(model))
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes_configuration(model)
|
46
|
+
active_columns = []
|
47
|
+
disabled_columns = []
|
48
|
+
|
49
|
+
model.columns.each do |column|
|
50
|
+
# Excluding DEFAULT_DISABLED_FIELDS
|
51
|
+
# Excluding foreign_keys, polymorphic associations,
|
52
|
+
# password digest, columns with default functions or values
|
53
|
+
next if ModelFields::DEFAULT_DISABLED_FIELDS.include?(column.name)
|
54
|
+
next if column.name.end_with?("_id", "_type", "_digest")
|
55
|
+
next if column.default_function.present?
|
56
|
+
next if column.default.present?
|
57
|
+
|
58
|
+
# Only add to active if its required or has presence validator
|
59
|
+
if column.null == false || has_presence_validator?(model, column.name)
|
60
|
+
active_columns << column
|
61
|
+
else
|
62
|
+
disabled_columns << column
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add atleast one column to active columns
|
67
|
+
active_columns << disabled_columns.pop if active_columns.empty? && disabled_columns.present?
|
68
|
+
|
69
|
+
{
|
70
|
+
"attributes" => active_columns_configuration(model, active_columns),
|
71
|
+
"disabled_fields" => disabled_columns_configuration(disabled_columns)
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def active_columns_configuration(model, columns)
|
76
|
+
columns.reduce({}) do |config, column|
|
77
|
+
validations = model.validators_on(column.name)
|
78
|
+
config[column.name] = if validations.present?
|
79
|
+
FieldValues::FakerBuilder.new(column.name, column, validations).build_faker_constant
|
80
|
+
else
|
81
|
+
FieldValues::FakeValue.new(column.name, column).generate_fake_value
|
82
|
+
end
|
83
|
+
config
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def disabled_columns_configuration(disabled_columns)
|
88
|
+
disabled_columns.map(&:name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def associations_configuration(model)
|
92
|
+
{
|
93
|
+
"associations" => {
|
94
|
+
"belongs_to" => belongs_to_associations_configuration(model),
|
95
|
+
"has_one" => {}, # TODO: Add has_one associations
|
96
|
+
"has_many" => {}, # TODO: Add has_many associations
|
97
|
+
}
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def belongs_to_associations_configuration(model)
|
102
|
+
belongs_to_associations = model.reflect_on_all_associations(:belongs_to).reject do |association|
|
103
|
+
association.options[:polymorphic] == true || # Excluded Polymorphic Associations
|
104
|
+
association.options[:optional] == true # Excluded Optional Associations
|
105
|
+
end
|
106
|
+
|
107
|
+
belongs_to_associations.reduce({}) do |config, association|
|
108
|
+
config[association.name.to_s] = "random"
|
109
|
+
config
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def has_presence_validator?(model, column_name)
|
114
|
+
model.validators_on(column_name).any? { |v| v.kind == :presence }
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_models
|
118
|
+
@get_models ||= ActiveRecord::Base.descendants.reject do |model|
|
119
|
+
EXCLUDED_MODELS.include?(model.name) || # Excluded Reserved Models
|
120
|
+
model.abstract_class? || # Excluded Abstract Models
|
121
|
+
model.table_exists? == false || # Excluded Models without tables
|
122
|
+
model.name.blank? || # Excluded Anonymous Models
|
123
|
+
model.name.start_with?("HABTM_") # Excluded HABTM Models
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def output_seedie_warning(output)
|
128
|
+
output.puts "Seedie config file generated at config/seedie.yml"
|
129
|
+
output.puts "##################################################"
|
130
|
+
output.puts "WARNING: Please review the generated config file before running the seeds."
|
131
|
+
output.puts "There might be some things that you might need to change to ensure that the generated seeds run successfully."
|
132
|
+
output.puts "##################################################"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
default_count: 5
|
2
|
+
models:
|
3
|
+
<% @models_config.each do |model, config| -%>
|
4
|
+
<%= model %>:
|
5
|
+
attributes:
|
6
|
+
<% config['attributes'].each do |name, value| -%>
|
7
|
+
<% if value.is_a?(Hash) && value.key?("custom_attr_value") -%>
|
8
|
+
<%= name %>:
|
9
|
+
custom_attr_value:
|
10
|
+
values: <%= value["custom_attr_value"]["values"] %>
|
11
|
+
pick_strategy: <%= value["custom_attr_value"]["pick_strategy"] %>
|
12
|
+
<% else -%>
|
13
|
+
<%= name %>: '<%= value %>'
|
14
|
+
<% end -%>
|
15
|
+
<% end -%>
|
16
|
+
disabled_fields: <%= config['disabled_fields'] %>
|
17
|
+
<% if config['associations'] -%>
|
18
|
+
associations:
|
19
|
+
<% config['associations'].each do |type, associations| -%>
|
20
|
+
<%= type %>:
|
21
|
+
<% associations.each do |association, value| -%>
|
22
|
+
<%= association %>: <%= value %>
|
23
|
+
<% end -%>
|
24
|
+
<% end -%>
|
25
|
+
<% end -%>
|
26
|
+
<% end -%>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Seedie
|
2
|
+
class BaseAssociation
|
3
|
+
include Reporters::Reportable
|
4
|
+
|
5
|
+
DEFAULT_COUNT = 1
|
6
|
+
INDEX = 0
|
7
|
+
|
8
|
+
attr_reader :record, :model, :association_config, :reporters
|
9
|
+
|
10
|
+
def initialize(record, model, association_config, reporters = [])
|
11
|
+
@record = record
|
12
|
+
@model = model
|
13
|
+
@association_config = association_config
|
14
|
+
@reporters = reporters
|
15
|
+
|
16
|
+
add_observers(@reporters)
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_associations
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_association
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def get_association_count(config)
|
30
|
+
return config if only_count_given?(config)
|
31
|
+
return config["count"] if config["count"].present?
|
32
|
+
|
33
|
+
DEFAULT_COUNT
|
34
|
+
end
|
35
|
+
|
36
|
+
def only_count_given?(config)
|
37
|
+
config.is_a?(Numeric) || config.is_a?(String)
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_associated_field(id, association_name)
|
41
|
+
{ "#{association_name}" => id }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Seedie
|
2
|
+
module Associations
|
3
|
+
class BelongsTo < BaseAssociation
|
4
|
+
attr_reader :associated_field_set
|
5
|
+
|
6
|
+
def initialize(model, association_config, reporters = [])
|
7
|
+
super(nil, model, association_config, reporters)
|
8
|
+
|
9
|
+
@associated_field_set = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_associations
|
13
|
+
return if association_config["belongs_to"].nil?
|
14
|
+
|
15
|
+
report(:belongs_to_start)
|
16
|
+
|
17
|
+
association_config["belongs_to"].each do |association_name, association_config|
|
18
|
+
reflection = model.reflect_on_association(association_name)
|
19
|
+
|
20
|
+
handle_association_config_type(reflection, association_name, association_config)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_association(klass, config, index)
|
25
|
+
field_values_set = FieldValuesSet.new(klass, config, index).generate_field_values
|
26
|
+
|
27
|
+
Model::Creator.new(klass).create!(field_values_set)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def handle_association_config_type(reflection, association_name, association_config)
|
33
|
+
case get_type(association_config)
|
34
|
+
when "random"
|
35
|
+
handle_random_config_type(reflection)
|
36
|
+
when "unique"
|
37
|
+
handle_unique_config_type(reflection)
|
38
|
+
when "new"
|
39
|
+
handle_new_config_type(reflection)
|
40
|
+
else
|
41
|
+
handle_other_config_type(reflection, association_config)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_random_config_type(reflection)
|
46
|
+
klass = reflection.klass
|
47
|
+
id = Model::IdGenerator.new(klass).random_id
|
48
|
+
|
49
|
+
report(:random_association, name: klass.to_s, parent_name: model.to_s, id: id)
|
50
|
+
associated_field_set.merge!(generate_associated_field(id, reflection.foreign_key))
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_unique_config_type(reflection)
|
54
|
+
klass = reflection.klass
|
55
|
+
report(:unique_association, name: klass.to_s, parent_name: @model.to_s)
|
56
|
+
|
57
|
+
id = Model::IdGenerator.new(klass).unique_id_for(@model)
|
58
|
+
associated_field_set.merge!(generate_associated_field(id, reflection.foreign_key))
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_new_config_type(reflection)
|
62
|
+
klass = reflection.klass
|
63
|
+
report(:belongs_to_associations, name: klass.to_s, parent_name: model.to_s)
|
64
|
+
|
65
|
+
new_associated_record = generate_association(klass, {}, INDEX)
|
66
|
+
associated_field_set.merge!(generate_associated_field(new_associated_record.id, reflection.foreign_key))
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_other_config_type(reflection, association_config)
|
70
|
+
klass = reflection.klass
|
71
|
+
|
72
|
+
report(:belongs_to_associations, name: klass.to_s, parent_name: model.to_s)
|
73
|
+
|
74
|
+
new_associated_record = generate_association(klass, association_config, INDEX)
|
75
|
+
associated_field_set.merge!(generate_associated_field(new_associated_record.id, reflection.foreign_key))
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_type(association_config)
|
79
|
+
if association_config.is_a?(String)
|
80
|
+
raise InvalidAssociationConfigError, "Invalid association config" unless ["random", "new", "unique"].include?(association_config)
|
81
|
+
|
82
|
+
return association_config
|
83
|
+
else
|
84
|
+
association_config
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Seedie
|
2
|
+
module Associations
|
3
|
+
class HasMany < BaseAssociation
|
4
|
+
def generate_associations
|
5
|
+
return if association_config["has_many"].nil?
|
6
|
+
|
7
|
+
report(:has_many_start)
|
8
|
+
association_config["has_many"].each do |association_name, association_config|
|
9
|
+
association_class = association_name.to_s.classify.constantize
|
10
|
+
count = get_association_count(association_config)
|
11
|
+
config = only_count_given?(association_config) ? {} : association_config
|
12
|
+
|
13
|
+
report(:associated_records, name: association_name, count: count, parent_name: model.to_s)
|
14
|
+
count.times do |index|
|
15
|
+
field_values_set = FieldValuesSet.new(association_class, config, index).generate_field_values
|
16
|
+
record_creator = Model::Creator.new(record.send(association_name), reporters)
|
17
|
+
|
18
|
+
record_creator.create!(field_values_set)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Seedie
|
2
|
+
module Associations
|
3
|
+
class HasOne < BaseAssociation
|
4
|
+
def generate_associations
|
5
|
+
return if association_config["has_one"].nil?
|
6
|
+
|
7
|
+
report(:has_one_start)
|
8
|
+
|
9
|
+
association_config["has_one"].each do |association_name, association_config|
|
10
|
+
reflection = model.reflect_on_association(association_name)
|
11
|
+
association_class = reflection.klass
|
12
|
+
count = get_association_count(association_config)
|
13
|
+
|
14
|
+
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
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Seedie
|
2
|
+
module FieldValues
|
3
|
+
class CustomValue
|
4
|
+
VALID_KEYS = ["values", "pick_strategy"]
|
5
|
+
CUSTOM_VALUE = "custom_attr_value"
|
6
|
+
|
7
|
+
attr_reader :name, :parsed_value
|
8
|
+
|
9
|
+
def initialize(name, value_template, index)
|
10
|
+
@name = name
|
11
|
+
@value_template = value_template
|
12
|
+
@index = index
|
13
|
+
@parsed_value = ""
|
14
|
+
@custom_attr_value = false
|
15
|
+
|
16
|
+
validate_template if @value_template.is_a?(Hash) && @value_template.has_key?(CUSTOM_VALUE)
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_custom_field_value
|
20
|
+
if @value_template.is_a?(String)
|
21
|
+
generate_custom_value_from_string
|
22
|
+
else
|
23
|
+
generate_custom_value_from_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
parsed_value
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_template
|
32
|
+
@value_template = @value_template[CUSTOM_VALUE]
|
33
|
+
@custom_attr_value = true
|
34
|
+
|
35
|
+
validate_hash_keys
|
36
|
+
validate_values_key
|
37
|
+
validate_pick_strategy_key
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_hash_keys
|
41
|
+
invalid_keys = @value_template.keys - VALID_KEYS
|
42
|
+
return if invalid_keys.empty?
|
43
|
+
|
44
|
+
raise InvalidCustomFieldKeysError,
|
45
|
+
"Invalid keys for #{@name}: #{invalid_keys.join(", ")}. Only 'values' and 'pick_strategy' are allowed."
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_values_key
|
49
|
+
return if @value_template["values"].is_a?(Array)
|
50
|
+
|
51
|
+
raise InvalidCustomFieldValuesError, "The values key for #{@name} must be an array."
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_values_length
|
55
|
+
return if @value_template["values"].length >= @index
|
56
|
+
|
57
|
+
raise CustomFieldNotEnoughValuesError, "There are not enough values for name. Please add more values."
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_pick_strategy_key
|
61
|
+
@pick_strategy = @value_template["pick_strategy"] || "random"
|
62
|
+
return if %w[random sequential].include?(@pick_strategy)
|
63
|
+
|
64
|
+
raise CustomFieldInvalidPickValueError, "The pick_strategy for #{@name} must be either 'sequential' or 'random'."
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_custom_value_from_string
|
68
|
+
@parsed_value = @value_template.gsub("{{index}}", @index.to_s)
|
69
|
+
|
70
|
+
@parsed_value.gsub!(/\{\{(.+?)\}\}/) do
|
71
|
+
method_string = $1
|
72
|
+
|
73
|
+
if method_string.start_with?("Faker::")
|
74
|
+
eval($1)
|
75
|
+
else
|
76
|
+
raise InvalidFakerMethodError, "Invalid method: #{method_string}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate_custom_value_from_hash
|
82
|
+
if @custom_attr_value
|
83
|
+
values = @value_template["values"]
|
84
|
+
if @pick_strategy == "sequential"
|
85
|
+
validate_values_length
|
86
|
+
|
87
|
+
@parsed_value = values[@index]
|
88
|
+
else
|
89
|
+
@parsed_value = values.sample
|
90
|
+
end
|
91
|
+
else
|
92
|
+
@parsed_value = @value_template
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|