planter 0.4.2 → 0.5.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.
@@ -4,30 +4,35 @@ module Planter
4
4
  ##
5
5
  # Class that seeders should inherit from. Seeders should be in +db/seeds+,
6
6
  # and named +TABLE_seeder.rb+, where +TABLE+ is the name of the table being
7
- # seeded (I.E. +users_seeder.rb+). If your seeder is named differently than
8
- # the table, you'll need to specify the table with the +model+ option. The
7
+ # seeded (for example, +users_seeder.rb+). If your seeder is named differently
8
+ # than the table, you'll need to specify the table with the +table+ option. The
9
9
  # seeder's class name should be the same as the file name, but camelized. So,
10
10
  # +UsersSeeder+. The directory where the seeder files are located can be
11
11
  # changed via an initializer.
12
12
  #
13
+ # To generate a seeder with a specific style, pass +--seeding-method=csv+,
14
+ # +--seeding-method=data-array+, or +--seeding-method=custom+ to
15
+ # +rails generate planter:seeder+. When the +csv+ method is used, the generator
16
+ # also creates a CSV file with headers pulled from the table.
17
+ #
13
18
  # The most basic way to seed is to have a CSV file with the same name as the
14
19
  # table in +db/seed_files/+. So, +users.csv+. This CSV should have the
15
- # table's column names as header. To seed using this method, your class
16
- # should look like the following. Note that +:csv_name+ and +:model+ are only
17
- # required if your seeder or csv are named differently than the table being
20
+ # table's column names as headers. To seed using this method, your class
21
+ # should look like the following. Note that +:csv_name+ and +:table+ are only
22
+ # required if your seeder or CSV are named differently than the table being
18
23
  # seeded. The directory where the seed files are kept can be changed via an
19
24
  # initializer.
20
25
  # # db/seeds/users_seeder.rb
21
26
  # require 'planter'
22
27
  # class UsersSeeder < Planter::Seeder
23
- # seeding_method :csv, csv_name: :users, model: 'User'
28
+ # seeding_method :csv, csv_name: :users, table: :users
24
29
  # end
25
30
  #
26
31
  # Another way to seed is to create records from a data array. To do this,
27
32
  # your class must implement a +data+ attribute or method, which is an array
28
33
  # of hashes. Note that this class already provides the +attr_reader+ for this
29
34
  # attribute, so the most you have to do is create instance variables in your
30
- # constructor. If if you want your data to be different for each new record
35
+ # constructor. If you want your data to be different for each new record
31
36
  # (via Faker, +Array#sample+, etc.), you'll probably want to supply a method
32
37
  # called data that returns an array of new data each time.
33
38
  # require 'planter'
@@ -39,13 +44,12 @@ module Planter
39
44
  # end
40
45
  #
41
46
  # In both of the above methods, you can specify a +parent+ association, which
42
- # is the +belongs_to+ association name in your model, which, when specified,
43
- # records will be created for each record in the parent table. For example,
44
- # if we're seeding the users table, and the model is +User+, which belongs to
45
- # +Person+, then doing the following will create a user record for each
46
- # record in the Person table. Note that nothing is automatically done to
47
- # prevent any validation errors; you must do this on your own, mostly likely
48
- # using +Faker+ or a similar library.
47
+ # is interpreted by the configured adapter. With the default Active Record
48
+ # adapter, +parent+ is the +belongs_to+ association name on a model-backed
49
+ # table. When specified, records will be created for each record in the parent
50
+ # table. Note that nothing is automatically done to prevent any validation
51
+ # errors; you must do this on your own, most likely using +Faker+ or a similar
52
+ # library.
49
53
  # require 'planter'
50
54
  # class UsersSeeder < Planter::Seeder
51
55
  # seeding_method :data_array, parent: :person
@@ -55,7 +59,7 @@ module Planter
55
59
  # end
56
60
  #
57
61
  # You can also set +number_of_records+ to determine how many times each
58
- # record in the +data+ array will get created. The default is 1. Note that if
62
+ # record in the +data+ array will be created. The default is 1. Note that if
59
63
  # this attribute is set alongside +parent+, +number_of_records+ will be how
60
64
  # many records will be created for each record in the parent table.
61
65
  # require 'planter'
@@ -68,7 +72,7 @@ module Planter
68
72
  #
69
73
  # By default, all fields are used to look up the record. If it already
70
74
  # exists, it is not re-created. If you have specific fields that a record
71
- # should be looked-up by, you can pass the +unique_columns+ option. This will
75
+ # should be looked up by, you can pass the +unique_columns+ option. This will
72
76
  # attempt to look up the record by those fields only, and if one doesn't
73
77
  # exist, one will be created with the rest of the attributes. An example of
74
78
  # when this would be useful is with Devise; you can't pass +password+ in the
@@ -97,14 +101,14 @@ module Planter
97
101
  # attribute when using +data_array+ seeding method, although it's probably
98
102
  # more likely that you'll want to define a method that returns a new set of
99
103
  # data each time (via +Faker+, +Array#sample+, etc.). When using +csv+,
100
- # +data+ will be set to the data within the csv. You can override this.
104
+ # +data+ will be set to the data within the CSV. You can override this.
101
105
  #
102
106
  # @return [Array]
103
107
  attr_reader :data
104
108
 
105
109
  ##
106
110
  # A hash of user-defined column names and procs to be run on values. This
107
- # is most useful for when seeding from csv, and you need to transform, say,
111
+ # is most useful for when seeding from CSV, and you need to transform, say,
108
112
  # 'true' (String) into true (Boolean). The user may define this as an
109
113
  # instance variable, or define a method that returns the hash.
110
114
  #
@@ -145,16 +149,15 @@ module Planter
145
149
  class_attribute :unique_columns
146
150
 
147
151
  ##
148
- # The model for the table being seeded. If the model name you need is
149
- # different, change via +seeding_method+.
152
+ # The table being seeded. If the table name you need is different, change
153
+ # via +seeding_method+.
150
154
  #
151
155
  # @return [String]
152
- class_attribute :model
156
+ class_attribute :table_name
153
157
 
154
158
  ##
155
- # The model of the parent. When provided with +association+, records in the
156
- # +data+ array, will be created for each record in the parent table. Your
157
- # class must set this attribute via +seeding_method+.
159
+ # Adapter-defined parent relation. Records in the +data+ array will be
160
+ # created for each record in the parent relation.
158
161
  #
159
162
  # @return [String]
160
163
  class_attribute :parent
@@ -168,7 +171,7 @@ module Planter
168
171
  class_attribute :number_of_records
169
172
 
170
173
  ##
171
- # The csv file corresponding to the model.
174
+ # The CSV file corresponding to the table.
172
175
  #
173
176
  # @return [String]
174
177
  class_attribute :csv_name
@@ -188,7 +191,7 @@ module Planter
188
191
  #
189
192
  # @kwarg [Integer] number_of_records
190
193
  #
191
- # @kwarg [String] model
194
+ # @kwarg [String, Symbol] table
192
195
  #
193
196
  # @kwarg [Symbol, String] parent
194
197
  #
@@ -203,7 +206,7 @@ module Planter
203
206
  # class UsersSeeder < Planter::Seeder
204
207
  # seeding_method :csv,
205
208
  # number_of_records: 2,
206
- # model: 'User'
209
+ # table: :users
207
210
  # parent: :person,
208
211
  # csv_name: :awesome_users,
209
212
  # unique_columns %i[username email],
@@ -212,7 +215,7 @@ module Planter
212
215
  def self.seeding_method(
213
216
  seed_method,
214
217
  number_of_records: 1,
215
- model: nil,
218
+ table: nil,
216
219
  parent: nil,
217
220
  csv_name: nil,
218
221
  unique_columns: nil,
@@ -224,9 +227,9 @@ module Planter
224
227
 
225
228
  self.seed_method = seed_method
226
229
  self.number_of_records = number_of_records
227
- self.model = model || to_s.delete_suffix("Seeder").singularize
230
+ self.table_name = (table || default_table_name).to_s
228
231
  self.parent = parent
229
- self.csv_name = csv_name || to_s.delete_suffix("Seeder").underscore
232
+ self.csv_name = (csv_name || table_name).to_s
230
233
  self.erb_trim_mode = erb_trim_mode || Planter.config.erb_trim_mode
231
234
  self.unique_columns =
232
235
  case unique_columns
@@ -235,14 +238,22 @@ module Planter
235
238
  end
236
239
  end
237
240
 
241
+ ##
242
+ # Return the table name inferred from the seeder class name.
243
+ #
244
+ # @return [String]
245
+ def self.default_table_name
246
+ to_s.delete_suffix("Seeder").underscore
247
+ end
248
+
238
249
  ##
239
250
  # The default seed method. To use this method, your class must provide a
240
251
  # valid +seeding_method+, and not implement its own +seed+ method.
241
252
  def seed
242
253
  validate_attributes
243
- extract_data_from_csv if seed_method == :csv
254
+ extract_data_from_csv if context.seed_method == :csv
244
255
 
245
- parent ? create_records_from_parent : create_records
256
+ context.parent ? create_records_from_parent : create_records
246
257
  end
247
258
 
248
259
  private
@@ -250,7 +261,7 @@ module Planter
250
261
  ##
251
262
  # Creates records from the +data+ attribute.
252
263
  def create_records
253
- number_of_records.times do
264
+ context.number_of_records.times do
254
265
  data.each { |record| create_record(record) }
255
266
  end
256
267
  end
@@ -258,48 +269,30 @@ module Planter
258
269
  ##
259
270
  # Create records from the +data+ attribute for each record in the +parent+.
260
271
  def create_records_from_parent
261
- adapter.parent_ids(model_name: model, parent: parent).each do |parent_id|
262
- number_of_records.times do
272
+ adapter.parent_ids(context: context).each do |parent_id|
273
+ context.number_of_records.times do
263
274
  data.each { |record| create_record(record, parent_id: parent_id) }
264
275
  end
265
276
  end
266
277
  end
267
278
 
268
279
  def create_record(record, parent_id: nil)
269
- unique, attrs = split_record(apply_transformations(record))
270
- unique = unique.merge(foreign_key => parent_id) if parent_id
271
- unique, attrs = filter_lookup_attributes(unique, attrs)
280
+ lookup_attributes, create_attributes = record_attributes.prepare(record, parent_id: parent_id)
272
281
  adapter.create_record(
273
- model_name: model,
274
- lookup_attributes: unique,
275
- create_attributes: attrs
282
+ context: context,
283
+ lookup_attributes: lookup_attributes,
284
+ create_attributes: create_attributes
276
285
  )
277
286
  end
278
287
 
279
- def full_csv_name
280
- @full_csv_name ||=
281
- %W[#{csv_name}.csv #{csv_name}.csv.erb #{csv_name}.erb.csv]
282
- .map { |f| Rails.root.join(Planter.config.csv_files_directory, f).to_s }
283
- .find { |f| ::File.file?(f) }
284
- end
285
-
286
288
  def extract_data_from_csv
287
- contents = ::File.read(full_csv_name)
288
- if full_csv_name.include?(".erb")
289
- contents = ERB.new(contents, trim_mode: erb_trim_mode).result(binding)
290
- end
291
-
292
- @data ||= ::CSV.parse(
293
- contents,
294
- headers: true,
295
- header_converters: :symbol
296
- ).map(&:to_hash)
289
+ @data ||= csv_data_source.data
297
290
  end
298
291
 
299
292
  def validate_attributes # :nodoc:
300
- case seed_method&.intern
293
+ case context.seed_method
301
294
  when :csv
302
- raise "Couldn't find csv for #{model}" unless full_csv_name
295
+ raise "Couldn't find csv for #{context.table_name}" unless csv_data_source.path
303
296
  when :data_array
304
297
  raise "data is not defined in the seeder" if public_send(:data).nil?
305
298
  else
@@ -307,74 +300,30 @@ module Planter
307
300
  end
308
301
  end
309
302
 
310
- def apply_transformations(record)
311
- return record if public_send(:transformations).nil?
312
-
313
- record.map { |field, value| map_record(field, value, record) }.to_h
314
- end
315
-
316
- def map_record(field, value, record)
317
- [
318
- field,
319
- transformations.key?(field) ? transform(field, value, record) : value
320
- ]
321
- end
322
-
323
- def transform(field, value, record)
324
- case transformations[field].arity
325
- when 0 then transformations[field].call
326
- when 1 then transformations[field].call(value)
327
- when 2 then transformations[field].call(value, record)
328
- end
329
- end
330
-
331
- def split_record(rec) # :nodoc:
332
- return [rec, {}] unless unique_columns
333
-
334
- u = unique_columns.each_with_object({}) { |c, h| h[c] = rec[c] }
335
- [u, rec.except(*unique_columns)]
303
+ def context
304
+ @context ||= Planter::SeedContext.new(
305
+ table_name: table_name,
306
+ seed_method: seed_method,
307
+ csv_name: csv_name,
308
+ parent: parent,
309
+ number_of_records: number_of_records,
310
+ unique_columns: unique_columns,
311
+ erb_trim_mode: erb_trim_mode
312
+ )
336
313
  end
337
314
 
338
- def filter_lookup_attributes(lookup_attributes, create_attributes)
339
- return [lookup_attributes, create_attributes] unless adapter.respond_to?(:table_columns)
340
-
341
- table_columns = adapter.table_columns(model_name: model).map(&:to_s)
342
- native_lookup_attributes = lookup_attributes.select do |field, _value|
343
- table_columns.include?(field.to_s)
344
- end
345
- non_column_lookup_attributes = lookup_attributes.except(*native_lookup_attributes.keys)
346
-
347
- if non_column_lookup_attributes.any?
348
- warn_non_column_lookup_attributes(non_column_lookup_attributes.keys)
349
- end
350
-
351
- if native_lookup_attributes.empty?
352
- raise "No native lookup columns found for #{model}. " \
353
- "Add a native table column to the seed data or unique_columns."
354
- end
355
-
356
- [
357
- native_lookup_attributes,
358
- non_column_lookup_attributes.merge(create_attributes)
359
- ]
315
+ def csv_data_source
316
+ @csv_data_source ||= Planter::CsvDataSource.new(context: context, seeder: self)
360
317
  end
361
318
 
362
- def warn_non_column_lookup_attributes(fields)
363
- warning_key = [model, fields.map(&:to_s).sort]
364
- @warned_non_column_lookup_attributes ||= []
365
- return if @warned_non_column_lookup_attributes.include?(warning_key)
366
-
367
- @warned_non_column_lookup_attributes << warning_key
368
- warn(
369
- "WARNING: Planter moved non-column lookup attributes for #{model} " \
370
- "into create attributes: #{warning_key.last.join(", ")}"
319
+ def record_attributes
320
+ @record_attributes ||= Planter::RecordAttributes.new(
321
+ context: context,
322
+ adapter: adapter,
323
+ transformations_provider: -> { public_send(:transformations) }
371
324
  )
372
325
  end
373
326
 
374
- def foreign_key
375
- adapter.foreign_key(model_name: model, parent: parent)
376
- end
377
-
378
327
  def adapter
379
328
  Planter.config.adapter
380
329
  end
@@ -15,13 +15,13 @@ module Planter
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 4
18
+ MINOR = 5
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
 
data/lib/planter.rb CHANGED
@@ -5,6 +5,9 @@ require "erb"
5
5
  require "planter/version"
6
6
  require "planter/railtie"
7
7
  require "planter/config"
8
+ require "planter/seed_context"
9
+ require "planter/csv_data_source"
10
+ require "planter/record_attributes"
8
11
  require "planter/seeder"
9
12
 
10
13
  ##
@@ -59,9 +62,9 @@ module Planter
59
62
  end
60
63
 
61
64
  ##
62
- # This is the method to call from your +db/seeds.rb+. It callse the seeders
65
+ # This is the method to call from your +db/seeds.rb+. It calls the seeders
63
66
  # listed in +Planter.config.seeders+. To call specific seeders at runtime,
64
- # you can set the +SEEDERS+ environmental variable to a comma-separated list
67
+ # you can set the +SEEDERS+ environment variable to a comma-separated list
65
68
  # of seeders, like +rails db:seed SEEDERS=users,accounts+.
66
69
  #
67
70
  # @example
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: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
@@ -102,7 +102,10 @@ files:
102
102
  - lib/planter.rb
103
103
  - lib/planter/adapters/active_record.rb
104
104
  - lib/planter/config.rb
105
+ - lib/planter/csv_data_source.rb
105
106
  - lib/planter/railtie.rb
107
+ - lib/planter/record_attributes.rb
108
+ - lib/planter/seed_context.rb
106
109
  - lib/planter/seeder.rb
107
110
  - lib/planter/version.rb
108
111
  - lib/tasks/planter_tasks.rake