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
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
|