seedie 0.1.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +127 -0
- data/README.md +72 -3
- data/lib/generators/seedie/install_generator.rb +105 -26
- data/lib/generators/seedie/templates/blank_seedie.yml +22 -0
- data/lib/generators/seedie/templates/seedie.yml +26 -4
- data/lib/generators/seedie/templates/seedie_initializer.rb +8 -0
- data/lib/seedie/associations/belongs_to.rb +41 -15
- data/lib/seedie/configuration.rb +10 -0
- data/lib/seedie/field_values/custom_value.rb +80 -35
- data/lib/seedie/field_values/fake_value.rb +2 -2
- data/lib/seedie/field_values/faker_builder.rb +10 -6
- data/lib/seedie/model/id_generator.rb +1 -3
- data/lib/seedie/model/model_sorter.rb +10 -3
- data/lib/seedie/polymorphic_association_helper.rb +20 -0
- data/lib/seedie/version.rb +1 -1
- data/lib/seedie.rb +14 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa80b46ccb1727a4451598e8f94657bc15b43dbb0b2b0e58e8bd744db0636af6
|
4
|
+
data.tar.gz: b8e49e8fecdbc3c63f62b55384e3860428c9eb579114552cc2da7212c1e4b92c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd0263009e5cd4b9d2287d56bde4ee2eb93337019218e018960efb9e5c9206b9f0323ccec95eaa986ddb9257d4acc9ccf684906f3f56319e1536fb6018201c4a
|
7
|
+
data.tar.gz: '0808e17b7a47e12367491571ade529974202d48eb0d93a0885143dba3e4b2982db13cac4a2196e478e39770d120cf4cd632b4436934d649f23e9f2f651263684'
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
## Version 0.3.0
|
2
|
+
|
3
|
+
### New Features
|
4
|
+
|
5
|
+
#### Generate a Blank Seedie.yml file with `--blank` option
|
6
|
+
|
7
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/20)
|
8
|
+
|
9
|
+
You can now generate a blank seedie.yml file with the `--blank` option:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
rails g seedie:install --blank
|
13
|
+
```
|
14
|
+
|
15
|
+
#### Exclude models from seedie.yml generation with `--excluded_models` option
|
16
|
+
|
17
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/21)
|
18
|
+
|
19
|
+
You can now exclude models from seedie.yml generation with the `--excluded_models` option:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
rails g seedie:install --excluded_models Post Comment
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Generate a Seedie.yml file with only specific models with `--include_only_models` option
|
26
|
+
|
27
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/22)
|
28
|
+
|
29
|
+
You can now generate a seedie.yml file with only specific models with the `--include_only_models` option:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
rails g seedie:install --include_only_models Post Comment
|
33
|
+
```
|
34
|
+
|
35
|
+
#### Bugfix: Now unique indexes will be generated with `unique` instead of `random` pick_strategy
|
36
|
+
|
37
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/23)
|
38
|
+
|
39
|
+
#### Introducing new Seedie.rb initializer
|
40
|
+
|
41
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/28)
|
42
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/29)
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Seedie.configure do |config|
|
46
|
+
# config.default_count = 10
|
47
|
+
|
48
|
+
config.custom_attributes[:email] = "{{Faker::Internet.unique.email}}"
|
49
|
+
# Add more custom attributes here
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
## Version 0.2.0
|
54
|
+
|
55
|
+
### New Features
|
56
|
+
|
57
|
+
#### Polymorphic Association
|
58
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/12)
|
59
|
+
|
60
|
+
You can now add a polymorphic association to your seedie.yml file:
|
61
|
+
|
62
|
+
```yaml
|
63
|
+
belongs_to:
|
64
|
+
commentable:
|
65
|
+
polymorphic: post # or [post, article] for multiple options
|
66
|
+
strategy: random
|
67
|
+
```
|
68
|
+
|
69
|
+
#### Automatic Polymorphic Association Generator
|
70
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/13)
|
71
|
+
|
72
|
+
When you run `rails g seedie:install`, it'll also generate the necessary polymorphic associations.
|
73
|
+
|
74
|
+
#### Custom Value Attributes with Validations
|
75
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/14)
|
76
|
+
|
77
|
+
Replaced custom_attr_value with a simplified value key:
|
78
|
+
|
79
|
+
Before:
|
80
|
+
```yaml
|
81
|
+
some_attribute:
|
82
|
+
custom_attr_value:
|
83
|
+
values: [1,2,3]
|
84
|
+
pick_strategy: random
|
85
|
+
```
|
86
|
+
|
87
|
+
After:
|
88
|
+
```yaml
|
89
|
+
some_attribute:
|
90
|
+
values: [1,2,3] # or value (in which case pick_strategy is not required)
|
91
|
+
options:
|
92
|
+
pick_strategy: random
|
93
|
+
```
|
94
|
+
|
95
|
+
#### Custom Value Generator
|
96
|
+
|
97
|
+
* [GitHub PR](https://github.com/keshavbiswa/seedie/pull/17)
|
98
|
+
|
99
|
+
Upon invoking `rails g seedie:install`, the generator will also add custom values.
|
100
|
+
|
101
|
+
#### Inclusion of Non-polymorphic _type Columns
|
102
|
+
* [Github PR](https://github.com/keshavbiswa/seedie/pull/18)
|
103
|
+
|
104
|
+
Earlier, columns with `_type` were skipped during seedie.yml generation, causing some attributes to be overlooked.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
class User < ApplicationRecord
|
108
|
+
enum role_type: { admin: 0, user: 1 }
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
We've resolved this, now only columns related to polymorphic foreign_types are excluded.
|
113
|
+
|
114
|
+
#### Range Inclusions
|
115
|
+
* [Github PR](https://github.com/keshavbiswa/seedie/pull/19)
|
116
|
+
|
117
|
+
Define ranges using the start and end keys:
|
118
|
+
|
119
|
+
```yaml
|
120
|
+
score:
|
121
|
+
values:
|
122
|
+
start: 0
|
123
|
+
end: 100
|
124
|
+
options: { pick_strategy: sequential }
|
125
|
+
```
|
126
|
+
|
127
|
+
This configuration will generate sequential numbers between 0 and 100.
|
data/README.md
CHANGED
@@ -34,8 +34,77 @@ $ rails generate seedie:install
|
|
34
34
|
```
|
35
35
|
This will create a seedie.yml file in your config directory, which will include configurations for your models.
|
36
36
|
|
37
|
+
Alternatively, you can also create a blank seedie.yml file by running:
|
38
|
+
|
39
|
+
```bash
|
40
|
+
$ rails generate seedie:install --blank
|
41
|
+
```
|
42
|
+
|
43
|
+
This will generate a blank seedie.yml config file for you that you can now customize according to your needs.
|
44
|
+
|
37
45
|
## Usage
|
38
46
|
|
47
|
+
### Generating blank seedie.yml
|
48
|
+
If you want to create a blank seedie.yml file, use the `--blank` option:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
$ rails generate seedie:install --blank
|
52
|
+
```
|
53
|
+
|
54
|
+
This will generate a blank seedie.yml config file for you that you can now customize according to your needs.
|
55
|
+
|
56
|
+
### Excluding Models
|
57
|
+
If you want to exclude certain models while generating the `seedie.yml`, use the `--exclude_models` option:
|
58
|
+
|
59
|
+
```bash
|
60
|
+
$ rails generate seedie:install --exclude_models User Admin Post
|
61
|
+
```
|
62
|
+
|
63
|
+
NOTE: Some models may not be excluded because of their dependencies. For example, if you have a model `Post` that belongs to a model `User`, then the `User` model will not be excluded even if you specify it in the `--exclude_models` option.
|
64
|
+
|
65
|
+
You'll get a warning in your console if any models are not excluded:
|
66
|
+
|
67
|
+
```bash
|
68
|
+
WARNING: User has dependencies with other models and cannot be excluded.
|
69
|
+
```
|
70
|
+
|
71
|
+
### Including only few Models
|
72
|
+
If you want to include only few particular models while generating the `seedie.yml`, use the `--include_only_models` option:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
$ rails generate seedie:install --include_only_models Post Comment
|
76
|
+
```
|
77
|
+
|
78
|
+
NOTE: Some models may be a dependency for the required models and will need to be included for successful seeding. For example, if you have a model `Post` that belongs to a model `User`, then the `User` model will need to be included even if you didn't specify it in the `--include_only_models` option.
|
79
|
+
|
80
|
+
You'll get a warning in your console if any other models are included:
|
81
|
+
|
82
|
+
```bash
|
83
|
+
WARNING: User is a dependency of included models and needs to be included.
|
84
|
+
```
|
85
|
+
|
86
|
+
### Add Custom Attributes inside seedie.rb initializer
|
87
|
+
|
88
|
+
While generating the seedie.yml file, there are default values.
|
89
|
+
For example, for every `string` field, the default value is `{{Faker::Lorem.word}}`.
|
90
|
+
This works fine for most attributes, but for some there are validations which breaks the seeding.
|
91
|
+
Take `email` as an example, the default value `{{Faker::Lorem.word}}` will not be a valid email.
|
92
|
+
By default, when we generate the seedie.yml file, we add a `seedie.rb` initializer file in the `config/initializers` directory.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
Seedie.configure do |config|
|
96
|
+
# config.default_count = 10
|
97
|
+
|
98
|
+
config.custom_attributes[:email] = "{{Faker::Internet.unique.email}}"
|
99
|
+
# Add more custom attributes here
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
This ensures that the `email` field is seeded with a valid email address.
|
104
|
+
You can add more custom attributes in the `seedie.rb` initializer file.
|
105
|
+
|
106
|
+
### Seeding Models
|
107
|
+
|
39
108
|
To seed your models, run the following Rake task:
|
40
109
|
|
41
110
|
```bash
|
@@ -63,8 +132,8 @@ models:
|
|
63
132
|
attributes:
|
64
133
|
title: "title {{index}}"
|
65
134
|
category:
|
66
|
-
|
67
|
-
|
135
|
+
values: [tech, sports, politics, entertainment]
|
136
|
+
options:
|
68
137
|
pick_strategy: random # or sequential
|
69
138
|
associations:
|
70
139
|
has_many:
|
@@ -98,7 +167,7 @@ In this file:
|
|
98
167
|
- `disabled_fields` is an array of fields that should not be automatically filled by Seedie.
|
99
168
|
- `associations` specify how associated models should be generated. Here, `has_many`, `belongs_to`, and `has_one` are supported.
|
100
169
|
- The specified number for `has_many` represents the number of associated records to create.
|
101
|
-
- For `belongs_to`, the value `random` means that a random existing record will be associated.
|
170
|
+
- For `belongs_to`, the value `random` means that a random existing record will be associated. If there is a unique index associated, then `unique` will be set or else `random` is the default.
|
102
171
|
- If attributes are specified under an association, those attributes will be used when creating the associated record(s)
|
103
172
|
- 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.
|
104
173
|
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require "rails/generators/base"
|
2
|
-
require "
|
2
|
+
require "seedie"
|
3
3
|
|
4
4
|
module Seedie
|
5
5
|
module Generators
|
6
6
|
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include PolymorphicAssociationHelper
|
8
|
+
|
7
9
|
EXCLUDED_MODELS = %w[
|
8
10
|
ActiveRecord::SchemaMigration
|
9
11
|
ActiveRecord::InternalMetadata
|
@@ -17,21 +19,44 @@ module Seedie
|
|
17
19
|
|
18
20
|
source_root File.expand_path("templates", __dir__)
|
19
21
|
|
22
|
+
class_option :blank, type: :boolean, default: false, desc: "Generate a blank seedie.yml with examples"
|
23
|
+
class_option :excluded_models, type: :array, default: [], desc: "Models to exclude from seedie.yml"
|
24
|
+
class_option :include_only_models, type: :array, default: [],
|
25
|
+
desc: "Models to be specifically included in seedie.yml. This will ignore all other models."
|
26
|
+
|
27
|
+
|
20
28
|
desc "Creates a seedie.yml for your application."
|
21
29
|
def generate_seedie_file(output = STDOUT)
|
22
|
-
|
30
|
+
if options[:include_only_models].present? && options[:excluded_models].present?
|
31
|
+
raise ArgumentError, "Cannot use both --include_only_models and --excluded_models together."
|
32
|
+
end
|
33
|
+
|
34
|
+
# This needs to be generated before anything else.
|
35
|
+
template "seedie_initializer.rb", "config/initializers/seedie.rb"
|
36
|
+
|
37
|
+
@excluded_models = options[:excluded_models] + EXCLUDED_MODELS
|
38
|
+
@output = output
|
39
|
+
|
40
|
+
if options[:blank]
|
41
|
+
template "blank_seedie.yml", "config/seedie.yml"
|
42
|
+
else
|
43
|
+
Rails.application.eager_load! # Load all models. This is required!!
|
44
|
+
|
45
|
+
@models = get_models
|
46
|
+
@models_config = build_models_config
|
47
|
+
template "seedie.yml", "config/seedie.yml"
|
48
|
+
end
|
23
49
|
|
24
|
-
|
25
|
-
@models_config = build_models_config
|
26
|
-
template "seedie.yml", "config/seedie.yml"
|
27
|
-
output_seedie_warning(output)
|
50
|
+
output_seedie_warning
|
28
51
|
end
|
29
52
|
|
30
53
|
private
|
31
54
|
|
32
|
-
|
33
55
|
def build_models_config
|
34
56
|
models = Model::ModelSorter.new(@models).sort_by_dependency
|
57
|
+
|
58
|
+
output_warning_for_extra_models(models)
|
59
|
+
|
35
60
|
models.reduce({}) do |config, model|
|
36
61
|
config[model.name.underscore] = model_configuration(model)
|
37
62
|
config
|
@@ -45,18 +70,32 @@ module Seedie
|
|
45
70
|
def attributes_configuration(model)
|
46
71
|
active_columns = []
|
47
72
|
disabled_columns = []
|
73
|
+
default_columns = []
|
74
|
+
foreign_keys = []
|
75
|
+
polymorphic_types = []
|
76
|
+
|
77
|
+
# Collect all foreign keys and polymorphic types
|
78
|
+
model.reflect_on_all_associations.each do |assoc|
|
79
|
+
foreign_keys << assoc.foreign_key
|
80
|
+
polymorphic_types << assoc.foreign_type if assoc.options[:polymorphic]
|
81
|
+
end
|
48
82
|
|
49
83
|
model.columns.each do |column|
|
50
84
|
# Excluding DEFAULT_DISABLED_FIELDS
|
51
85
|
# Excluding foreign_keys, polymorphic associations,
|
52
86
|
# password digest, columns with default functions or values
|
53
87
|
next if ModelFields::DEFAULT_DISABLED_FIELDS.include?(column.name)
|
54
|
-
next if column.name.end_with?("_id", "
|
55
|
-
|
56
|
-
|
57
|
-
|
88
|
+
next if column.name.end_with?("_id", "_digest")
|
89
|
+
|
90
|
+
if polymorphic_types.include?(column.name) || foreign_keys.include?(column.name)
|
91
|
+
next
|
92
|
+
end
|
93
|
+
|
94
|
+
# Adding default columns to default_columns
|
95
|
+
if column.default.present? || column.default_function.present?
|
96
|
+
default_columns << column
|
97
|
+
elsif column.null == false || has_presence_validator?(model, column.name)
|
58
98
|
# Only add to active if its required or has presence validator
|
59
|
-
if column.null == false || has_presence_validator?(model, column.name)
|
60
99
|
active_columns << column
|
61
100
|
else
|
62
101
|
disabled_columns << column
|
@@ -65,6 +104,9 @@ module Seedie
|
|
65
104
|
|
66
105
|
# Add atleast one column to active columns
|
67
106
|
active_columns << disabled_columns.pop if active_columns.empty? && disabled_columns.present?
|
107
|
+
|
108
|
+
# Disable all default columns
|
109
|
+
disabled_columns += default_columns
|
68
110
|
|
69
111
|
{
|
70
112
|
"attributes" => active_columns_configuration(model, active_columns),
|
@@ -100,14 +142,27 @@ module Seedie
|
|
100
142
|
|
101
143
|
def belongs_to_associations_configuration(model)
|
102
144
|
belongs_to_associations = model.reflect_on_all_associations(:belongs_to).reject do |association|
|
103
|
-
association.options[:polymorphic] == true || # Excluded Polymorphic Associations
|
104
145
|
association.options[:optional] == true # Excluded Optional Associations
|
105
146
|
end
|
106
147
|
|
148
|
+
unique_indexes = model.connection.indexes(model.table_name).select(&:unique).flat_map(&:columns)
|
149
|
+
|
107
150
|
belongs_to_associations.reduce({}) do |config, association|
|
108
|
-
|
151
|
+
if association.polymorphic?
|
152
|
+
config[association.name.to_s] = set_polymorphic_association_config(model, association)
|
153
|
+
else
|
154
|
+
association_has_unique_index = unique_indexes.include?(association.foreign_key.to_s)
|
155
|
+
config[association.name.to_s] = association_has_unique_index ? "unique" : "random"
|
156
|
+
end
|
109
157
|
config
|
110
158
|
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def set_polymorphic_association_config(model, association)
|
162
|
+
{
|
163
|
+
"polymorphic" => find_polymorphic_types(model, association.name),
|
164
|
+
"strategy" => "random"
|
165
|
+
}
|
111
166
|
end
|
112
167
|
|
113
168
|
def has_presence_validator?(model, column_name)
|
@@ -115,21 +170,45 @@ module Seedie
|
|
115
170
|
end
|
116
171
|
|
117
172
|
def get_models
|
118
|
-
@get_models ||=
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
173
|
+
@get_models ||= begin
|
174
|
+
all_models = ActiveRecord::Base.descendants
|
175
|
+
|
176
|
+
if options[:include_only_models].present?
|
177
|
+
all_models.select! { |model| options[:include_only_models].include?(model.name) }
|
178
|
+
end
|
179
|
+
|
180
|
+
all_models.reject do |model|
|
181
|
+
@excluded_models.include?(model.name) || # Excluded Reserved Models
|
182
|
+
model.abstract_class? || # Excluded Abstract Models
|
183
|
+
model.table_exists? == false || # Excluded Models without tables
|
184
|
+
model.name.blank? || # Excluded Anonymous Models
|
185
|
+
model.name.start_with?("HABTM_") # Excluded HABTM Models
|
186
|
+
end
|
124
187
|
end
|
125
188
|
end
|
126
189
|
|
127
|
-
def output_seedie_warning
|
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 "##################################################"
|
190
|
+
def output_seedie_warning
|
191
|
+
@output.puts "Seedie config file generated at config/seedie.yml"
|
192
|
+
@output.puts "##################################################"
|
193
|
+
@output.puts "WARNING: Please review the generated config file before running the seeds."
|
194
|
+
@output.puts "There might be some things that you might need to change to ensure that the generated seeds run successfully."
|
195
|
+
@output.puts "##################################################"
|
196
|
+
end
|
197
|
+
|
198
|
+
def output_warning_for_extra_models(models)
|
199
|
+
if options[:excluded_models].present?
|
200
|
+
required_excluded_models = models.map(&:name) & @excluded_models
|
201
|
+
|
202
|
+
required_excluded_models.each do |model_name|
|
203
|
+
@output.puts "WARNING: #{model_name} has dependencies with other models and cannot be excluded."
|
204
|
+
end
|
205
|
+
elsif options[:include_only_models].present?
|
206
|
+
dependent_models = models.map(&:name) - @models.map(&:name)
|
207
|
+
|
208
|
+
dependent_models.each do |model_name|
|
209
|
+
@output.puts "WARNING: #{model_name} is a dependency of included models and needs to be included."
|
210
|
+
end
|
211
|
+
end
|
133
212
|
end
|
134
213
|
end
|
135
214
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
### This is a blank seedie.yml file. ###
|
2
|
+
|
3
|
+
# default_count: 10
|
4
|
+
#
|
5
|
+
# models:
|
6
|
+
# model_name:
|
7
|
+
# attributes:
|
8
|
+
# string_field: '{{Faker::Lorem.word}}'
|
9
|
+
# integer_field:
|
10
|
+
# values: [1, 2, 3]
|
11
|
+
# options:
|
12
|
+
# pick_strategy: random
|
13
|
+
# jsonb_field:
|
14
|
+
# value: '{"key": "value"}'
|
15
|
+
# disabled_fields: ["unwanted_field"]
|
16
|
+
# associations:
|
17
|
+
# belongs_to:
|
18
|
+
# parent_model: random
|
19
|
+
# has_one:
|
20
|
+
# associated_model: random
|
21
|
+
# has_many:
|
22
|
+
# other_models: random
|
@@ -4,11 +4,26 @@ models:
|
|
4
4
|
<%= model %>:
|
5
5
|
attributes:
|
6
6
|
<% config['attributes'].each do |name, value| -%>
|
7
|
-
<% if value.is_a?(Hash)
|
7
|
+
<% if value.is_a?(Hash) -%>
|
8
8
|
<%= name %>:
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
<% if value['values'] -%>
|
10
|
+
<% if value['values'].is_a?(Array) -%>
|
11
|
+
values: <%= value['values'] %>
|
12
|
+
<% elsif value['values'].is_a?(Hash) -%>
|
13
|
+
values:
|
14
|
+
<% value['values'].each do |key, value| -%>
|
15
|
+
<%= key %>: <%= value %>
|
16
|
+
<% end -%>
|
17
|
+
<% end -%>
|
18
|
+
<% elsif value['value'] -%>
|
19
|
+
value: '<%= value['value'] %>'
|
20
|
+
<% end -%>
|
21
|
+
<% if value['options'] -%>
|
22
|
+
options:
|
23
|
+
<% value['options'].each do |key, value| -%>
|
24
|
+
<%= key %>: '<%= value %>'
|
25
|
+
<% end -%>
|
26
|
+
<% end -%>
|
12
27
|
<% else -%>
|
13
28
|
<%= name %>: '<%= value %>'
|
14
29
|
<% end -%>
|
@@ -19,8 +34,15 @@ models:
|
|
19
34
|
<% config['associations'].each do |type, associations| -%>
|
20
35
|
<%= type %>:
|
21
36
|
<% associations.each do |association, value| -%>
|
37
|
+
<% if value.is_a?(Hash) && value.key?("polymorphic") -%>
|
38
|
+
<% next if value["polymorphic"].blank? -%>
|
39
|
+
<%= association %>:
|
40
|
+
polymorphic: <%= value["polymorphic"] %>
|
41
|
+
strategy: <%= value["strategy"] %>
|
42
|
+
<% else -%>
|
22
43
|
<%= association %>: <%= value %>
|
23
44
|
<% end -%>
|
24
45
|
<% end -%>
|
25
46
|
<% end -%>
|
26
47
|
<% end -%>
|
48
|
+
<% end -%>
|
@@ -30,45 +30,71 @@ module Seedie
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def handle_association_config_type(reflection, association_name, association_config)
|
33
|
-
|
33
|
+
if reflection.polymorphic?
|
34
|
+
handle_polymorphic_config_type(reflection, association_config)
|
35
|
+
else
|
36
|
+
klass = reflection.klass
|
37
|
+
strategy = get_type(association_config)
|
38
|
+
handle_strategy(klass, reflection, strategy)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def handle_polymorphic_config_type(reflection, association_config)
|
44
|
+
type_name = get_polymorphic_class_name(association_config["polymorphic"])
|
45
|
+
klass = type_name.classify.constantize
|
46
|
+
strategy = get_type(association_config["strategy"])
|
47
|
+
associated_field_set.merge!(generate_associated_field(klass.to_s, reflection.foreign_type))
|
48
|
+
|
49
|
+
handle_strategy(klass, reflection, strategy)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Handles the strategy for belongs_to associations
|
53
|
+
# For polymorphic reflection, we might not add a strategy
|
54
|
+
# so we need to default it to random
|
55
|
+
def handle_strategy(klass, reflection, strategy)
|
56
|
+
strategy ||= reflection.polymorphic? ? "random" : nil
|
57
|
+
|
58
|
+
case get_type(strategy)
|
34
59
|
when "random"
|
35
|
-
handle_random_config_type(reflection)
|
60
|
+
handle_random_config_type(klass, reflection)
|
36
61
|
when "unique"
|
37
|
-
handle_unique_config_type(reflection)
|
62
|
+
handle_unique_config_type(klass, reflection)
|
38
63
|
when "new"
|
39
|
-
handle_new_config_type(reflection)
|
64
|
+
handle_new_config_type(klass, reflection)
|
40
65
|
else
|
41
|
-
handle_other_config_type(reflection,
|
66
|
+
handle_other_config_type(klass, reflection, strategy)
|
42
67
|
end
|
43
68
|
end
|
44
69
|
|
45
|
-
|
46
|
-
|
70
|
+
# polymorphic option can either be single value "post" or an array ["post", "game_room"]
|
71
|
+
# type pick strategy will be either the single value or a random value from the array
|
72
|
+
def get_polymorphic_class_name(polymorphic_types)
|
73
|
+
polymorphic_types.is_a?(String) ? polymorphic_types : polymorphic_types.sample
|
74
|
+
end
|
75
|
+
|
76
|
+
def handle_random_config_type(klass, reflection)
|
47
77
|
id = Model::IdGenerator.new(klass).random_id
|
48
78
|
|
49
79
|
report(:random_association, name: klass.to_s, parent_name: model.to_s, id: id)
|
50
80
|
associated_field_set.merge!(generate_associated_field(id, reflection.foreign_key))
|
51
81
|
end
|
52
82
|
|
53
|
-
def handle_unique_config_type(reflection)
|
54
|
-
klass = reflection.klass
|
83
|
+
def handle_unique_config_type(klass, reflection)
|
55
84
|
report(:unique_association, name: klass.to_s, parent_name: @model.to_s)
|
56
85
|
|
57
|
-
id = Model::IdGenerator.new(klass).unique_id_for(@model)
|
86
|
+
id = Model::IdGenerator.new(klass).unique_id_for(@model, reflection.foreign_key)
|
58
87
|
associated_field_set.merge!(generate_associated_field(id, reflection.foreign_key))
|
59
88
|
end
|
60
89
|
|
61
|
-
def handle_new_config_type(reflection)
|
62
|
-
klass = reflection.klass
|
90
|
+
def handle_new_config_type(klass, reflection)
|
63
91
|
report(:belongs_to_associations, name: klass.to_s, parent_name: model.to_s)
|
64
92
|
|
65
93
|
new_associated_record = generate_association(klass, {}, INDEX)
|
66
94
|
associated_field_set.merge!(generate_associated_field(new_associated_record.id, reflection.foreign_key))
|
67
95
|
end
|
68
96
|
|
69
|
-
def handle_other_config_type(reflection, association_config)
|
70
|
-
klass = reflection.klass
|
71
|
-
|
97
|
+
def handle_other_config_type(klass, reflection, association_config)
|
72
98
|
report(:belongs_to_associations, name: klass.to_s, parent_name: model.to_s)
|
73
99
|
|
74
100
|
new_associated_record = generate_association(klass, association_config, INDEX)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Seedie
|
2
2
|
module FieldValues
|
3
3
|
class CustomValue
|
4
|
-
VALID_KEYS = ["values", "
|
5
|
-
|
6
|
-
|
4
|
+
VALID_KEYS = ["values", "value", "options"].freeze
|
5
|
+
PICK_STRATEGIES = ["random", "sequential"].freeze
|
6
|
+
|
7
7
|
attr_reader :name, :parsed_value
|
8
8
|
|
9
9
|
def initialize(name, value_template, index)
|
@@ -11,9 +11,8 @@ module Seedie
|
|
11
11
|
@value_template = value_template
|
12
12
|
@index = index
|
13
13
|
@parsed_value = ""
|
14
|
-
|
15
|
-
|
16
|
-
validate_template if @value_template.is_a?(Hash) && @value_template.has_key?(CUSTOM_VALUE)
|
14
|
+
|
15
|
+
validate_value_template
|
17
16
|
end
|
18
17
|
|
19
18
|
def generate_custom_field_value
|
@@ -28,40 +27,77 @@ module Seedie
|
|
28
27
|
|
29
28
|
private
|
30
29
|
|
31
|
-
def
|
32
|
-
|
33
|
-
@custom_attr_value = true
|
30
|
+
def validate_value_template
|
31
|
+
return unless @value_template.is_a?(Hash)
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
validate_keys
|
34
|
+
validate_values if @value_template.key?("values")
|
35
|
+
validate_options if @value_template.key?("options")
|
38
36
|
end
|
39
37
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
def validate_values
|
39
|
+
values = @value_template["values"]
|
40
|
+
options = @value_template["options"]
|
41
|
+
|
42
|
+
if values.is_a?(Array) || values.is_a?(Hash)
|
43
|
+
validate_sequential_values_length
|
44
|
+
else
|
45
|
+
raise InvalidCustomFieldValuesError, "The values key for #{@name} must be an array or a hash with start and end keys."
|
46
|
+
end
|
46
47
|
end
|
47
48
|
|
48
|
-
def
|
49
|
-
|
49
|
+
def validate_options
|
50
|
+
options = @value_template["options"]
|
51
|
+
pick_strategy = options["pick_strategy"]
|
50
52
|
|
51
|
-
|
53
|
+
if pick_strategy.present? && !PICK_STRATEGIES.include?(pick_strategy)
|
54
|
+
raise InvalidCustomFieldOptionsError,
|
55
|
+
"The pick_strategy for #{@name} must be either 'sequential' or 'random'."
|
56
|
+
end
|
52
57
|
end
|
53
58
|
|
54
|
-
|
55
|
-
|
59
|
+
## If pick strategy is sequential, we need to ensure there is a value for each index
|
60
|
+
# If there isn't sufficient values, we raise an error
|
61
|
+
def validate_sequential_values_length
|
62
|
+
return unless @value_template.key?("options")
|
63
|
+
return unless @value_template["options"]["pick_strategy"] == "sequential"
|
64
|
+
|
65
|
+
values = @value_template["values"]
|
66
|
+
|
67
|
+
if values.is_a?(Hash) && values.keys.sort == ["end", "start"]
|
68
|
+
# Assuming the values are an inclusive range
|
69
|
+
values_length = values["end"] - values["start"] + 1
|
70
|
+
else
|
71
|
+
values_length = values.length
|
72
|
+
end
|
56
73
|
|
57
|
-
|
74
|
+
if values_length < @index + 1
|
75
|
+
raise CustomFieldNotEnoughValuesError,
|
76
|
+
"There are not enough values for #{@name}. Please add more values."
|
77
|
+
end
|
58
78
|
end
|
59
79
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
80
|
+
def validate_keys
|
81
|
+
invalid_keys = @value_template.keys - VALID_KEYS
|
82
|
+
|
83
|
+
if invalid_keys.present?
|
84
|
+
raise InvalidCustomFieldKeysError,
|
85
|
+
"Invalid keys for #{@name}: #{invalid_keys.join(", ")}. Only #{VALID_KEYS} are allowed."
|
86
|
+
end
|
87
|
+
|
88
|
+
if @value_template.key?("values")
|
89
|
+
if @value_template.key?("value")
|
90
|
+
raise InvalidCustomFieldKeysError,
|
91
|
+
"Invalid keys for #{@name}: values and value cannot be used together."
|
92
|
+
end
|
63
93
|
|
64
|
-
|
94
|
+
if @value_template["values"].is_a?(Hash)
|
95
|
+
if !@value_template["values"].key?("start") || !@value_template["values"].key?("end")
|
96
|
+
raise InvalidCustomFieldValuesError,
|
97
|
+
"The values key for #{@name} must be an array or a hash with start and end keys."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
65
101
|
end
|
66
102
|
|
67
103
|
def generate_custom_value_from_string
|
@@ -79,19 +115,28 @@ module Seedie
|
|
79
115
|
end
|
80
116
|
|
81
117
|
def generate_custom_value_from_hash
|
82
|
-
if @
|
83
|
-
values = @value_template["values"]
|
84
|
-
|
85
|
-
|
86
|
-
|
118
|
+
if @value_template.key?("values")
|
119
|
+
values = if @value_template["values"].is_a?(Array)
|
120
|
+
@value_template["values"]
|
121
|
+
else
|
122
|
+
# generate_custom_value_from_range
|
123
|
+
generate_custom_values_from_range(@value_template["values"]["start"], @value_template["values"]["end"])
|
124
|
+
end
|
125
|
+
options = @value_template["options"]
|
126
|
+
|
127
|
+
if options.present? && options["pick_strategy"] == "sequential"
|
87
128
|
@parsed_value = values[@index]
|
88
129
|
else
|
89
130
|
@parsed_value = values.sample
|
90
131
|
end
|
91
|
-
|
92
|
-
@parsed_value = @value_template
|
132
|
+
elsif @value_template.key?("value")
|
133
|
+
@parsed_value = @value_template["value"]
|
93
134
|
end
|
94
135
|
end
|
136
|
+
|
137
|
+
def generate_custom_values_from_range(start, ending)
|
138
|
+
(start..ending).to_a
|
139
|
+
end
|
95
140
|
end
|
96
141
|
end
|
97
142
|
end
|
@@ -25,7 +25,7 @@ module Seedie
|
|
25
25
|
when :boolean
|
26
26
|
Faker::Boolean.boolean
|
27
27
|
when :json, :jsonb
|
28
|
-
{ "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) }
|
28
|
+
{ "value" => { "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) } }
|
29
29
|
when :inet
|
30
30
|
Faker::Internet.ip_v4_address
|
31
31
|
when :cidr, :macaddr
|
@@ -37,7 +37,7 @@ module Seedie
|
|
37
37
|
when :money
|
38
38
|
Faker::Commerce.price.to_s
|
39
39
|
when :hstore
|
40
|
-
{ "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) }
|
40
|
+
{ "value" => { "key1" => Faker::Lorem.word, "key2" => Faker::Number.number(digits: 2) } }
|
41
41
|
when :year
|
42
42
|
rand(1901..2155)
|
43
43
|
else
|
@@ -10,9 +10,12 @@ module Seedie
|
|
10
10
|
@class_prefix = ""
|
11
11
|
@method_prefix = ""
|
12
12
|
@options = ""
|
13
|
+
@seedie_config_custom_attributes = Seedie.configuration.custom_attributes
|
13
14
|
end
|
14
15
|
|
15
16
|
def build_faker_constant
|
17
|
+
return @seedie_config_custom_attributes[@name.to_sym] if @seedie_config_custom_attributes.key?(@name.to_sym)
|
18
|
+
|
16
19
|
@unique_prefix = "unique." if has_validation?(:uniqueness)
|
17
20
|
|
18
21
|
add_faker_class_and_method(@column.type)
|
@@ -62,8 +65,7 @@ module Seedie
|
|
62
65
|
@class_prefix = "Boolean."
|
63
66
|
@method_prefix = "boolean"
|
64
67
|
when :json, :jsonb
|
65
|
-
@
|
66
|
-
@method_prefix = "shallow_json(width: 3, options: { key: \"Name.first_name\", value: \"Number.number(digits: 2)\" })"
|
68
|
+
@faker_expression = { "value" => "Json.shallow_json(width: 3, options: { key: 'Name.first_name', value: 'Number.number(digits: 2)' })" }
|
67
69
|
when :inet
|
68
70
|
@class_prefix = "Internet."
|
69
71
|
@method_prefix = "ip_v4_address"
|
@@ -80,9 +82,7 @@ module Seedie
|
|
80
82
|
@class_prefix = "Commerce."
|
81
83
|
@method_prefix = "price.to_s"
|
82
84
|
when :hstore
|
83
|
-
@
|
84
|
-
@method_prefix = "shallow_json"
|
85
|
-
@options = "(width: 3, options: { key: \"Name.first_name\", value: \"Number.number(digits: 2)\" })"
|
85
|
+
@faker_expression = { "value" => "Json.shallow_json(width: 3, options: { key: 'Name.first_name', value: 'Number.number(digits: 2)' })" }
|
86
86
|
when :year
|
87
87
|
@class_prefix = "Number."
|
88
88
|
@method_prefix = "number"
|
@@ -131,7 +131,11 @@ module Seedie
|
|
131
131
|
@class_prefix = ""
|
132
132
|
@method_prefix = ""
|
133
133
|
@options = ""
|
134
|
-
|
134
|
+
if options[:in].is_a?(Range)
|
135
|
+
@faker_expression = { "values" => { "start" => options[:in].first, "end" => options[:in].last }, "options" => { "pick_strategy" => "random" } }
|
136
|
+
else
|
137
|
+
@faker_expression = { "values" => options[:in], "options" => { "pick_strategy" => "random" } }
|
138
|
+
end
|
135
139
|
end
|
136
140
|
end
|
137
141
|
end
|
@@ -12,9 +12,7 @@ module Seedie
|
|
12
12
|
return id
|
13
13
|
end
|
14
14
|
|
15
|
-
def unique_id_for(association_klass)
|
16
|
-
model_id_column = "#{@model.to_s.underscore}_id"
|
17
|
-
|
15
|
+
def unique_id_for(association_klass, model_id_column)
|
18
16
|
unless association_klass.column_names.include?(model_id_column)
|
19
17
|
raise InvalidAssociationConfigError, "#{model_id_column} does not exist in #{association_klass}"
|
20
18
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Seedie
|
2
2
|
module Model
|
3
3
|
class ModelSorter
|
4
|
+
include PolymorphicAssociationHelper
|
5
|
+
|
4
6
|
def initialize(models)
|
5
7
|
@models = models
|
6
8
|
@model_dependencies = models.map {|m| [m, get_model_dependencies(m)]}.to_h
|
@@ -20,7 +22,6 @@ module Seedie
|
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
|
-
|
24
25
|
# Independent models need to be added first
|
25
26
|
def add_independent_models_to_queue
|
26
27
|
@models.each do |model|
|
@@ -51,7 +52,6 @@ module Seedie
|
|
51
52
|
|
52
53
|
def get_model_dependencies(model)
|
53
54
|
associations = model.reflect_on_all_associations(:belongs_to).reject do |association|
|
54
|
-
association.options[:polymorphic] == true || # Excluded Polymorphic Associations
|
55
55
|
association.options[:optional] == true # Excluded Optional Associations
|
56
56
|
end
|
57
57
|
|
@@ -60,10 +60,17 @@ module Seedie
|
|
60
60
|
associations.map do |association|
|
61
61
|
if association.options[:class_name]
|
62
62
|
constantize_class_name(association.options[:class_name], model.name)
|
63
|
+
elsif association.polymorphic?
|
64
|
+
types = find_polymorphic_types(model, association.name)
|
65
|
+
|
66
|
+
if types.blank?
|
67
|
+
puts "Polymorphic type not found for #{model.name}. Ignoring..."
|
68
|
+
next
|
69
|
+
end
|
63
70
|
else
|
64
71
|
association.klass
|
65
72
|
end
|
66
|
-
end
|
73
|
+
end.compact
|
67
74
|
end
|
68
75
|
|
69
76
|
private
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PolymorphicAssociationHelper
|
2
|
+
# Returns the type of the polymorphic association
|
3
|
+
# We need only one polymorphic association while generating config
|
4
|
+
# this makes it easier to sort according to dependencies
|
5
|
+
def find_polymorphic_types(model, association_name)
|
6
|
+
type = @models.find { |potential_model| has_association?(potential_model, association_name) }
|
7
|
+
type&.name&.underscore
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_association?(model, association_name)
|
11
|
+
associations = select_associations(model)
|
12
|
+
associations.any? { |association| association.options[:as] == association_name }
|
13
|
+
end
|
14
|
+
|
15
|
+
def select_associations(model)
|
16
|
+
model.reflect_on_all_associations.select do |reflection|
|
17
|
+
%i[has_many has_one].include?(reflection.macro)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/seedie/version.rb
CHANGED
data/lib/seedie.rb
CHANGED
@@ -11,6 +11,8 @@ require_relative "seedie/field_values_set"
|
|
11
11
|
require_relative "seedie/model_fields"
|
12
12
|
require_relative "seedie/model_seeder"
|
13
13
|
|
14
|
+
require_relative "seedie/polymorphic_association_helper"
|
15
|
+
|
14
16
|
require_relative "seedie/model/creator"
|
15
17
|
require_relative "seedie/model/model_sorter"
|
16
18
|
require_relative "seedie/model/id_generator"
|
@@ -22,6 +24,7 @@ require_relative "seedie/associations/belongs_to"
|
|
22
24
|
|
23
25
|
require_relative "seedie/seeder"
|
24
26
|
require_relative "seedie/version"
|
27
|
+
require_relative "seedie/configuration"
|
25
28
|
|
26
29
|
require "seedie/railtie" if defined?(Rails)
|
27
30
|
|
@@ -37,6 +40,16 @@ module Seedie
|
|
37
40
|
class InvalidAssociationConfigError < StandardError; end
|
38
41
|
class InvalidCustomFieldKeysError < StandardError; end
|
39
42
|
class InvalidCustomFieldValuesError < StandardError; end
|
43
|
+
class InvalidCustomFieldOptionsError < StandardError; end
|
40
44
|
class CustomFieldNotEnoughValuesError < StandardError; end
|
41
|
-
|
45
|
+
|
46
|
+
class << self
|
47
|
+
def configure
|
48
|
+
yield configuration if block_given?
|
49
|
+
end
|
50
|
+
|
51
|
+
def configuration
|
52
|
+
@configuration ||= Configuration.new
|
53
|
+
end
|
54
|
+
end
|
42
55
|
end
|
metadata
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seedie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keshav Biswa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faker
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '2.9'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.9'
|
27
27
|
- !ruby/object:Gem::Dependency
|
@@ -118,18 +118,22 @@ extra_rdoc_files: []
|
|
118
118
|
files:
|
119
119
|
- ".rspec"
|
120
120
|
- ".rubocop.yml"
|
121
|
+
- CHANGELOG.md
|
121
122
|
- Gemfile
|
122
123
|
- LICENSE.txt
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|
125
126
|
- lib/generators/seedie/USAGE
|
126
127
|
- lib/generators/seedie/install_generator.rb
|
128
|
+
- lib/generators/seedie/templates/blank_seedie.yml
|
127
129
|
- lib/generators/seedie/templates/seedie.yml
|
130
|
+
- lib/generators/seedie/templates/seedie_initializer.rb
|
128
131
|
- lib/seedie.rb
|
129
132
|
- lib/seedie/associations/base_association.rb
|
130
133
|
- lib/seedie/associations/belongs_to.rb
|
131
134
|
- lib/seedie/associations/has_many.rb
|
132
135
|
- lib/seedie/associations/has_one.rb
|
136
|
+
- lib/seedie/configuration.rb
|
133
137
|
- lib/seedie/field_values/custom_value.rb
|
134
138
|
- lib/seedie/field_values/fake_value.rb
|
135
139
|
- lib/seedie/field_values/faker_builder.rb
|
@@ -139,6 +143,7 @@ files:
|
|
139
143
|
- lib/seedie/model/model_sorter.rb
|
140
144
|
- lib/seedie/model_fields.rb
|
141
145
|
- lib/seedie/model_seeder.rb
|
146
|
+
- lib/seedie/polymorphic_association_helper.rb
|
142
147
|
- lib/seedie/railtie.rb
|
143
148
|
- lib/seedie/reporters/base_reporter.rb
|
144
149
|
- lib/seedie/reporters/console_reporter.rb
|