planter 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2283785564306f87b6399b8af3e5343930b451e4bd64a10d8ac66acb0a7a7fa
4
- data.tar.gz: ee9993b9e3d076dd5ecee3bbb61135904b0f1e8467bf11feadf043d89c47b42f
3
+ metadata.gz: 8b575630b7898231e26acae46bac6a745e46d9f60f596e3fe05af92adb6b36be
4
+ data.tar.gz: 3bac546364e78b3a9e84d40b29ead7ddf4b9453201ae4e9754f45227c6cf9963
5
5
  SHA512:
6
- metadata.gz: 8fda738209e6d82abff3d7bc8c3db1d29206924f63d95799d61265b228b09d48d2bded51e0997655225f509e2b70c4b2a3666e6d1ff766f4d7f321951ed7136d
7
- data.tar.gz: 7e080b039a593a04e2f278a74aad865db841c8235d1921f47b2ba28da4be7ccf75ec7eb562d5ea7b5ab12b30ba46093ba7952503e39fd44b22d1298c511604da
6
+ metadata.gz: efe6a57b32e765863036c28eaad8c505812ee841aef607d39963f1430d6539c79c7942aa10f0014a711b92295150bc3a4bc41ff1343110e437f342f9137a4871
7
+ data.tar.gz: cae77c70d311dcbb8bb2618a7a14d06fcb393e6928f50cb378351ed81b972ddfa5250c9876633da398dac09af68f455793119884e164f829fc4064c6f5a50815
data/README.md CHANGED
@@ -51,6 +51,7 @@ Planter.configure do |config|
51
51
  # The adapter used to create records, discover parent records, and
52
52
  # inspect database table names. Active Record is used by default.
53
53
  # To use a custom adapter, replace this line with your own adapter.
54
+ # The Active Record adapter can be configured with a block.
54
55
  config.adapter = Planter::Adapters::ActiveRecord.new
55
56
 
56
57
  ##
@@ -214,6 +215,34 @@ test1@example.com,test1
214
215
  test2@example.com,test2
215
216
  ```
216
217
 
218
+ By default, the Active Record adapter raises when model validations prevent a
219
+ record from being created. To keep seeding after validation failures, configure
220
+ the adapter to warn instead.
221
+
222
+ ```ruby
223
+ Planter.configure do |config|
224
+ config.adapter = Planter::Adapters::ActiveRecord.new do |adapter_config|
225
+ adapter_config.validation_failure = :warn
226
+ end
227
+ end
228
+ ```
229
+
230
+ In `:warn` mode, Planter prints a warning with the failed lookup attributes and
231
+ validation errors. This only applies to model-backed tables; model-less tables
232
+ are inserted directly and can still raise database errors. A seeder can override
233
+ the adapter default by defining `validation_failure`; Planter passes that value
234
+ to adapters through `context.adapter_options`.
235
+
236
+ ```ruby
237
+ class UsersSeeder < Planter::Seeder
238
+ seeding_method :csv, unique_columns: :email
239
+
240
+ def validation_failure
241
+ :raise
242
+ end
243
+ end
244
+ ```
245
+
217
246
  CSV seeders can also be useful for simple model-less tables, such as join
218
247
  tables. With the default Active Record adapter, Planter will use the model when
219
248
  one exists and fall back to direct table inserts when one does not.
@@ -454,6 +483,10 @@ class MyAdapter
454
483
  end
455
484
  ```
456
485
 
486
+ Planter passes adapter-specific per-seeder settings through
487
+ `context.adapter_options`. Custom adapters can read options they support and
488
+ ignore the rest.
489
+
457
490
  ## License
458
491
  The gem is available as open source under the terms of the [MIT
459
492
  License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -2,8 +2,13 @@ require "bundler/setup"
2
2
  require "bundler/gem_tasks"
3
3
  require "rake/testtask"
4
4
  require "rdoc/task"
5
+ require "semverve/task"
5
6
  require "standard/rake"
6
7
 
8
+ Semverve::Task.new do |t|
9
+ t.bundle_lock = true
10
+ end
11
+
7
12
  Rake::TestTask.new(:test) do |t|
8
13
  t.libs << "test"
9
14
  t.pattern = "test/**/*_test.rb"
@@ -17,48 +22,3 @@ RDoc::Task.new do |rdoc|
17
22
  end
18
23
 
19
24
  task default: :test
20
-
21
- namespace :version do
22
- desc "Print the current version from the version.rb file"
23
- task :current do
24
- puts Planter::VERSION
25
- end
26
-
27
- namespace :increment do
28
- desc "Increment the version's PATCH level"
29
- task :patch do
30
- File.join(__dir__, "lib", "planter", "version.rb").then do |version_file|
31
- File.write(
32
- version_file,
33
- File.read(version_file).sub(/(PATCH\s=\s)(\d+)/) { "#{$1}#{$2.next}" }
34
- )
35
- end
36
- system("bundle lock")
37
- end
38
- desc "Increment the version's MINOR level"
39
- task :minor do
40
- File.join(__dir__, "lib", "planter", "version.rb").then do |version_file|
41
- File.write(
42
- version_file,
43
- File.read(version_file)
44
- .sub(/(PATCH\s=\s)(\d+)/) { "#{$1}0" }
45
- .sub(/(MINOR\s=\s)(\d+)/) { "#{$1}#{$2.next}" }
46
- )
47
- end
48
- system("bundle lock")
49
- end
50
- desc "Increment the version's MAJOR level"
51
- task :major do
52
- File.join(__dir__, "lib", "planter", "version.rb").then do |version_file|
53
- File.write(
54
- version_file,
55
- File.read(version_file)
56
- .sub(/(PATCH\s=\s)(\d+)/) { "#{$1}0" }
57
- .sub(/(MINOR\s=\s)(\d+)/) { "#{$1}0" }
58
- .sub(/(MAJOR\s=\s)(\d+)/) { "#{$1}#{$2.next}" }
59
- )
60
- end
61
- system("bundle lock")
62
- end
63
- end
64
- end
@@ -19,6 +19,7 @@ module Planter
19
19
  # The adapter used to create records, discover parent records, and
20
20
  # inspect database table names. Active Record is used by default.
21
21
  # To use a custom adapter, replace this line with your own adapter.
22
+ # The Active Record adapter can be configured with a block.
22
23
  config.adapter = Planter::Adapters::ActiveRecord.new
23
24
 
24
25
  ##
@@ -22,6 +22,57 @@ module Planter
22
22
  # resolving those values into whatever persistence or reflection objects
23
23
  # they need.
24
24
  class ActiveRecord
25
+ ##
26
+ # Validation failure actions supported by the Active Record adapter.
27
+ #
28
+ # @return [Array<Symbol>]
29
+ VALIDATION_FAILURE_ACTIONS = %i[raise warn].freeze
30
+
31
+ ##
32
+ # Configuration for the Active Record adapter.
33
+ class Configuration
34
+ ##
35
+ # How model validation failures should be handled when creating records.
36
+ #
37
+ # @return [Symbol]
38
+ attr_reader :validation_failure
39
+
40
+ ##
41
+ # Create a new Active Record adapter configuration.
42
+ def initialize
43
+ @validation_failure = :raise
44
+ end
45
+
46
+ ##
47
+ # Set how model validation failures should be handled.
48
+ #
49
+ # @param [String, Symbol] action either +:raise+ or +:warn+
50
+ def validation_failure=(action)
51
+ action = action.to_sym
52
+ unless VALIDATION_FAILURE_ACTIONS.include?(action)
53
+ raise ArgumentError, "validation_failure must be: #{VALIDATION_FAILURE_ACTIONS.join(", ")}"
54
+ end
55
+
56
+ @validation_failure = action
57
+ end
58
+ end
59
+
60
+ ##
61
+ # The adapter configuration.
62
+ #
63
+ # @return [Planter::Adapters::ActiveRecord::Configuration]
64
+ attr_reader :configuration
65
+
66
+ ##
67
+ # Create a new Active Record adapter.
68
+ #
69
+ # @yield [configuration] optional configuration block
70
+ # @yieldparam configuration [Planter::Adapters::ActiveRecord::Configuration]
71
+ def initialize
72
+ @configuration = Configuration.new
73
+ yield configuration if block_given?
74
+ end
75
+
25
76
  ##
26
77
  # Create a record unless one already exists.
27
78
  #
@@ -35,9 +86,7 @@ module Planter
35
86
  # @return [Object]
36
87
  def create_record(context:, lookup_attributes:, create_attributes:)
37
88
  if (model = model(context))
38
- model
39
- .where(lookup_attributes)
40
- .first_or_create!(create_attributes)
89
+ create_model_record(model, context, lookup_attributes, create_attributes)
41
90
  else
42
91
  create_table_record(context, lookup_attributes, create_attributes)
43
92
  end
@@ -99,6 +148,48 @@ module Planter
99
148
  end
100
149
  end
101
150
 
151
+ def create_model_record(model, context, lookup_attributes, create_attributes)
152
+ relation = model.where(lookup_attributes)
153
+ case validation_failure(context)
154
+ when :raise
155
+ relation.first_or_create!(create_attributes)
156
+ when :warn
157
+ record = relation.first_or_create(create_attributes)
158
+ warn_validation_failure(context, lookup_attributes, record) if failed_create?(record)
159
+ record
160
+ end
161
+ end
162
+
163
+ def validation_failure(context)
164
+ adapter_options = context.adapter_options if context.respond_to?(:adapter_options)
165
+ context_action = adapter_options&.[](:validation_failure)
166
+ action = context_action || configuration.validation_failure
167
+ action = action.to_sym
168
+ return action if VALIDATION_FAILURE_ACTIONS.include?(action)
169
+
170
+ raise ArgumentError, "validation_failure must be: #{VALIDATION_FAILURE_ACTIONS.join(", ")}"
171
+ end
172
+
173
+ def failed_create?(record)
174
+ record == false || (record.respond_to?(:persisted?) && !record.persisted?)
175
+ end
176
+
177
+ def warn_validation_failure(context, lookup_attributes, record)
178
+ warn(
179
+ [
180
+ "WARNING: Planter could not create #{context.table_name} with lookup attributes",
181
+ lookup_attributes.inspect,
182
+ validation_errors(record)
183
+ ].compact.join(" ")
184
+ )
185
+ end
186
+
187
+ def validation_errors(record)
188
+ return unless record.respond_to?(:errors) && record.errors.any?
189
+
190
+ "Errors: #{record.errors.full_messages.join(", ")}"
191
+ end
192
+
102
193
  def association_options(context)
103
194
  model!(context).reflect_on_association(context.parent).options
104
195
  end
@@ -6,13 +6,29 @@ module Planter
6
6
  ##
7
7
  # Builds progress bars for seed runs.
8
8
  class ProgressBar
9
+ ##
10
+ # The format string used by visible progress bars.
11
+ #
12
+ # @return [String]
9
13
  FORMAT = "%t |%B| %p%% %c/%C"
10
14
 
15
+ ##
16
+ # Progress bar object used when visible progress bars are disabled.
11
17
  class NullProgressBar
18
+ ##
19
+ # No-op progress increment.
12
20
  def increment
13
21
  end
14
22
  end
15
23
 
24
+ ##
25
+ # Create a visible progress bar, or a null progress bar when disabled.
26
+ #
27
+ # @param [String] title progress bar title
28
+ # @param [Integer] total total number of records to seed
29
+ # @param [IO] output progress bar output stream
30
+ #
31
+ # @return [ProgressBar, NullProgressBar]
16
32
  def self.create(title:, total:, output: $stdout)
17
33
  return NullProgressBar.new unless enabled?(total)
18
34
 
@@ -46,6 +46,14 @@ module Planter
46
46
  # @return [String, nil]
47
47
  attr_reader :erb_trim_mode
48
48
 
49
+ ##
50
+ # Adapter-specific options collected from the seeder.
51
+ #
52
+ # Adapters may ignore any options they do not support.
53
+ #
54
+ # @return [Hash]
55
+ attr_reader :adapter_options
56
+
49
57
  ##
50
58
  # Create a new seed context.
51
59
  #
@@ -62,6 +70,8 @@ module Planter
62
70
  # @param [Array<Symbol>, nil] unique_columns
63
71
  #
64
72
  # @param [String, nil] erb_trim_mode
73
+ #
74
+ # @param [Hash] adapter_options adapter-specific per-seeder options
65
75
  def initialize(
66
76
  table_name:,
67
77
  seed_method:,
@@ -69,7 +79,8 @@ module Planter
69
79
  parent:,
70
80
  number_of_records:,
71
81
  unique_columns:,
72
- erb_trim_mode:
82
+ erb_trim_mode:,
83
+ adapter_options: {}
73
84
  )
74
85
  @table_name = table_name.to_s
75
86
  @seed_method = seed_method&.intern
@@ -78,6 +89,7 @@ module Planter
78
89
  @number_of_records = number_of_records
79
90
  @unique_columns = unique_columns
80
91
  @erb_trim_mode = erb_trim_mode
92
+ @adapter_options = adapter_options.to_h.transform_keys(&:to_sym)
81
93
  end
82
94
  end
83
95
  end
@@ -89,6 +89,11 @@ module Planter
89
89
  #
90
90
  # If you need to seed a different way, put your own custom +seed+ method in
91
91
  # your seeder class and do whatever needs to be done.
92
+ #
93
+ # Adapter-specific settings can be provided by defining methods on your
94
+ # seeder. For example, the Active Record adapter reads +validation_failure+
95
+ # into +SeedContext#adapter_options+ when deciding whether failed model
96
+ # validations should raise or warn.
92
97
  class Seeder
93
98
  ##
94
99
  # The allowed seeding methods.
@@ -326,10 +331,21 @@ module Planter
326
331
  parent: parent,
327
332
  number_of_records: number_of_records,
328
333
  unique_columns: unique_columns,
329
- erb_trim_mode: erb_trim_mode
334
+ erb_trim_mode: erb_trim_mode,
335
+ adapter_options: seeder_adapter_options
330
336
  )
331
337
  end
332
338
 
339
+ def seeder_adapter_options
340
+ {
341
+ validation_failure: seeder_validation_failure
342
+ }.compact
343
+ end
344
+
345
+ def seeder_validation_failure
346
+ public_send(:validation_failure) if respond_to?(:validation_failure)
347
+ end
348
+
333
349
  def csv_data_source
334
350
  @csv_data_source ||= Planter::CsvDataSource.new(context: context, seeder: self)
335
351
  end
@@ -15,13 +15,13 @@ module Planter
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 0
18
+ MINOR = 1
19
19
 
20
20
  ##
21
21
  # Patch version.
22
22
  #
23
23
  # @return [Integer]
24
- PATCH = 2
24
+ PATCH = 0
25
25
 
26
26
  module_function
27
27
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: planter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
@@ -71,6 +71,20 @@ dependencies:
71
71
  - - "<"
72
72
  - !ruby/object:Gem::Version
73
73
  version: '2.0'
74
+ - !ruby/object:Gem::Dependency
75
+ name: semverve
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
74
88
  - !ruby/object:Gem::Dependency
75
89
  name: simplecov
76
90
  requirement: !ruby/object:Gem::Requirement