seedie 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c94e296c2303e454021934b3891b47e0b0974217e1df8bcbc7178f1c6b71452
4
- data.tar.gz: a9c447bafa471eb10123209fdaa245a587cbf9ed1ef7ee56d07b875a719f40fc
3
+ metadata.gz: fa80b46ccb1727a4451598e8f94657bc15b43dbb0b2b0e58e8bd744db0636af6
4
+ data.tar.gz: b8e49e8fecdbc3c63f62b55384e3860428c9eb579114552cc2da7212c1e4b92c
5
5
  SHA512:
6
- metadata.gz: 0b423fde9186493e8b83febbbda6abd4b7b25966fcde1396e67d0af6011f28e4f0fbf67cae25d9a30293aba0668cdf794a73c080c2becb796a876094b19523ef
7
- data.tar.gz: 2eaca5df71b6721becf3e61497d828ff8524822de1e79ba3d5c9bc8bbf6d8d2a18f0913eca1e1ad4db6055df726af0c85573f07f7c56535fc8790a97f244c51b
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
- custom_attr_value:
67
- values: [tech, sports, politics, entertainment]
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 "active_record"
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
- Rails.application.eager_load! # Load all models. This is required!!
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
- @models = get_models
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", "_type", "_digest")
55
- next if column.default_function.present?
56
- next if column.default.present?
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
- config[association.name.to_s] = "random"
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 ||= 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
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(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 "##################################################"
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) && value.key?("custom_attr_value") -%>
7
+ <% if value.is_a?(Hash) -%>
8
8
  <%= name %>:
9
- custom_attr_value:
10
- values: <%= value["custom_attr_value"]["values"] %>
11
- pick_strategy: <%= value["custom_attr_value"]["pick_strategy"] %>
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 -%>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ Seedie.configure do |config|
4
+ # config.default_count = 10
5
+
6
+ config.custom_attributes[:email] = "{{Faker::Internet.unique.email}}"
7
+ # Add more custom attributes here
8
+ 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
- case get_type(association_config)
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, association_config)
66
+ handle_other_config_type(klass, reflection, strategy)
42
67
  end
43
68
  end
44
69
 
45
- def handle_random_config_type(reflection)
46
- klass = reflection.klass
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)
@@ -0,0 +1,10 @@
1
+ module Seedie
2
+ class Configuration
3
+ attr_accessor :default_count, :custom_attributes
4
+
5
+ def initialize
6
+ @default_count = nil
7
+ @custom_attributes = {}
8
+ end
9
+ end
10
+ end
@@ -1,9 +1,9 @@
1
1
  module Seedie
2
2
  module FieldValues
3
3
  class CustomValue
4
- VALID_KEYS = ["values", "pick_strategy"]
5
- CUSTOM_VALUE = "custom_attr_value"
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
- @custom_attr_value = false
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 validate_template
32
- @value_template = @value_template[CUSTOM_VALUE]
33
- @custom_attr_value = true
30
+ def validate_value_template
31
+ return unless @value_template.is_a?(Hash)
34
32
 
35
- validate_hash_keys
36
- validate_values_key
37
- validate_pick_strategy_key
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 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."
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 validate_values_key
49
- return if @value_template["values"].is_a?(Array)
49
+ def validate_options
50
+ options = @value_template["options"]
51
+ pick_strategy = options["pick_strategy"]
50
52
 
51
- raise InvalidCustomFieldValuesError, "The values key for #{@name} must be an array."
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
- def validate_values_length
55
- return if @value_template["values"].length >= @index
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
- raise CustomFieldNotEnoughValuesError, "There are not enough values for name. Please add more values."
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 validate_pick_strategy_key
61
- @pick_strategy = @value_template["pick_strategy"] || "random"
62
- return if %w[random sequential].include?(@pick_strategy)
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
- raise CustomFieldInvalidPickValueError, "The pick_strategy for #{@name} must be either 'sequential' or 'random'."
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 @custom_attr_value
83
- values = @value_template["values"]
84
- if @pick_strategy == "sequential"
85
- validate_values_length
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
- else
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
- @class_prefix = "Json."
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
- @class_prefix = "Json."
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
- @faker_expression = { "custom_attr_value" => { "values" => options[:in], "pick_strategy" => "random" } }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Seedie
4
- VERSION = "0.1.1"
4
+ VERSION = "0.3.0"
5
5
  end
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
- class CustomFieldInvalidPickValueError < StandardError; end
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.1.1
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-07-30 00:00:00.000000000 Z
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