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