planter 0.1.1 → 0.2.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: b346415e35c538e46222ced2131792a27f778fafe169143116c6908ddca88aac
4
- data.tar.gz: ed3952a7fd92566b2acb31e0fee881549675f40aa579ebe9115db2f86f302eb0
3
+ metadata.gz: 85e12676f9e3c992f26939f709bb8ff9785a75cfbf13a5279075e2e84ae2adc9
4
+ data.tar.gz: 130e7dd566d20b09dcfb38c4d62c76d872b169b459f68eff0a2d8715c13f320a
5
5
  SHA512:
6
- metadata.gz: d07501dd0544476e04b768277d870f035626f6c00218a9f248e6fe6f7dcae833757162b688a269066bc8daa5dae1d2fb6f20db7c45273245da60f900cde80f3f
7
- data.tar.gz: '09e7ba1054d15f7aa68aedaac7915fdda444eac1bff9630402e5f5999f872283e978b5e60b9581310cdd2bbc9ea4688d73b3fd7d6c8781b802b8bb21cee7fb8c'
6
+ metadata.gz: 535f7b4909895f4f2ed6219919cbd620b3f283daecd693457ce52dfa7ec3d8281d49d7487dc530ced549a52d55719759e3f0975efa833763475fe03a1b863171
7
+ data.tar.gz: 36df7d4e59476de2f1526b66e1571d70221ff77204eac23e2d16408ddf7ca6bfcdacbfb351e0a9f4323ca2439c407afb5ecbd3a88725493d62ef8c0ac594f1f4
data/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # Planter
2
2
  [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fevanthegrayt%2Fplanter%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/evanthegrayt/planter/goto?ref=master)
3
3
  [![Gem Version](https://badge.fury.io/rb/planter.svg)](https://badge.fury.io/rb/planter)
4
+ ![Language: Ruby](https://img.shields.io/static/v1?label=language&message=Ruby&color=CC342D&style=flat&logo=ruby&logoColor=CC342D)
4
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
6
 
6
- > Pre-release version! Anything is subject to change in the near future!
7
-
8
7
  Seeds for Rails applications can get complicated fast, and Rails doesn't provide
9
8
  much for assisting with this process. This plugin seeks to rectify that by
10
9
  providing easy ways to seed specific tables.
@@ -21,10 +20,10 @@ You can view the documentation [here](https://evanthegrayt.github.io/planter/).
21
20
  ## Installation
22
21
  Add the following line to your application's Gemfile. Because this plugin is
23
22
  currently a pre-release version, it's recommended to lock it to a specific
24
- version, as breaking changes may occur, even at the patch level.
23
+ version, as breaking changes may occur, even at the minor level.
25
24
 
26
25
  ```ruby
27
- gem 'planter', '0.0.10'
26
+ gem 'planter', '0.2.0'
28
27
  ```
29
28
 
30
29
  And then execute:
@@ -83,6 +82,7 @@ allows you to use `db:seed` for other purposes. If you want Planter to hook
83
82
  into the existing `db:seed` task, simply add the following to `db/seeds.rb`.
84
83
 
85
84
  ```ruby
85
+ # db/seeds.rb
86
86
  Planter.seed
87
87
  ```
88
88
 
@@ -144,13 +144,24 @@ class UsersSeeder < Planter::Seeder
144
144
  end
145
145
  ```
146
146
 
147
- `ERB` can be used in the CSV files if you end the file name with `.csv.erb`.
148
- For example, `users.csv.erb`.
147
+ `ERB` can be used in the CSV files if you end the file name with `.csv.erb` or
148
+ `.erb.csv`. For example, `users.csv.erb`. When using ERB, instance variables set
149
+ in the seeder can be used in the CSV.
150
+
151
+ ```ruby
152
+ class UsersSeeder < Planter::Seeder
153
+ seeding_method :csv, csv_name: :people
154
+
155
+ def initialize
156
+ @name_prefix = 'Test User'
157
+ end
158
+ end
159
+ ```
149
160
 
150
161
  ```
151
162
  participant_id,name
152
- <%= Participant.find_by(email: 'test1@example.com').id %>,"Test User1"
153
- <%= Participant.find_by(email: 'test2@example.com').id %>,"Test User2"
163
+ <%= Participant.find_by(email: 'test1@example.com').id %>,<%= @name_prefix %> 1
164
+ <%= Participant.find_by(email: 'test2@example.com').id %>,<%= @name_prefix %> 2
154
165
  ```
155
166
 
156
167
  Note that, if you need to change the trim mode for ERB, you can set a default in
@@ -175,6 +186,38 @@ end
175
186
 
176
187
  For help with `erb_trim_mode`, see the help documentation for `ERB::new`.
177
188
 
189
+ Lastly, it's worth mentioning `transformations` under the CSV section, as that's
190
+ usually the pace where they're needed most, but it will work with any method.
191
+
192
+ If you're seeding with a CSV, and it contains values that need to have code
193
+ executed on them before it's imported into the database, you can define an
194
+ instance variable called `@transformations`, or a method called
195
+ `transformations`, that returns a Hash of field names, and Procs to run on the
196
+ value. For example, if you have an `admin` column, and the CSV contains "true",
197
+ it will come through as a String, but you probably want it to be a Boolean. This
198
+ can be solved with the following.
199
+
200
+ ```ruby
201
+ class UsersSeeder < Planter::Seeder
202
+ seeding_method :csv
203
+
204
+ def transformations
205
+ {
206
+ admin: ->(value) { value == 'true' },
207
+ last_name: ->(value, row) { "#{value} #{row[:suffix]}".squish }
208
+ }
209
+ end
210
+ end
211
+ ```
212
+
213
+ When defining a Proc/Lambda, you can make it accept 0, 1, or 2 arguments.
214
+ - When `0`, the value is replaced by the result of the Lambda
215
+ - When `1`, the value is passed to the Lambda, and is subsequently replaced by
216
+ the result of the Lambda
217
+ - When `2`, the value is the first argument, and the entire row, as a Hash, is
218
+ the second argument. This allows for more complicated transformations that can
219
+ be dependent on other fields and values in the record.
220
+
178
221
  Running `rails planter:seed` will now seed your `users` table.
179
222
 
180
223
  ## Seeding from a data array
@@ -244,7 +287,7 @@ class UsersSeeder < Planter::Seeder
244
287
  }
245
288
 
246
289
  def seed
247
- USERS.each { |email, attrs| User.where(email).first_or_create!(attrs) }
290
+ USERS.each { |email, attrs| User.where(email: email).first_or_create!(attrs) }
248
291
  end
249
292
  end
250
293
  ```
@@ -102,6 +102,35 @@ module Planter
102
102
  # @return [Array]
103
103
  attr_reader :data
104
104
 
105
+ ##
106
+ # 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,
108
+ # 'true' (String) into true (Boolean). The user may define this as an
109
+ # instance variable, or define a method that returns the hash.
110
+ #
111
+ # When defining a Proc/Lambda, you can make it accept 0, 1, or 2 arguments.
112
+ # - When 0, the value is replaced by the result of the Lambda.
113
+ # - When 1, the value is passed to the Lambda, and is subsequently
114
+ # replaced by the result of the Lambda.
115
+ # - When 2, the value is the first argument, and the entire row, as a
116
+ # Hash, is the second argument. This allows for more complicated
117
+ # transformations that can be dependent on other fields and values in the
118
+ # record.
119
+ #
120
+ # @return [Hash, nil]
121
+ #
122
+ # @example
123
+ # class UsersSeeder < Planter::Seeder
124
+ # seeding_method :csv
125
+ # def transformations
126
+ # {
127
+ # admin: ->(v) { v == 'true' },
128
+ # last_name: ->(value, row) { "#{value} #{row[:suffix]}".squish }
129
+ # }
130
+ # end
131
+ # end
132
+ attr_reader :transformations
133
+
105
134
  ##
106
135
  # What trim mode should ERB use?
107
136
  #
@@ -142,7 +171,7 @@ module Planter
142
171
  # The csv file corresponding to the model.
143
172
  #
144
173
  # @return [String]
145
- class_attribute :csv_file
174
+ class_attribute :csv_name
146
175
 
147
176
  ##
148
177
  # The seeding method specified.
@@ -151,79 +180,59 @@ module Planter
151
180
  class_attribute :seed_method
152
181
 
153
182
  ##
154
- # Access the metaclass so we can define public and private class methods.
155
- class << self
156
- ##
157
- # If your class is going to use the inherited +seed+ method, you must tell
158
- # it which +seeding_method+ to use. The argument to this method must be
159
- # included in the +SEEDING_METHODS+ array.
160
- #
161
- # @param [Symbol] seed_method
162
- #
163
- # @kwarg [Integer] number_of_records
164
- #
165
- # @kwarg [String] model
166
- #
167
- # @kwarg [String] parent
168
- #
169
- # @kwarg [Symbol, String] parent
170
- #
171
- # @kwarg [Symbol, String] csv_name
172
- #
173
- # @kwarg [Symbol, String] unique_columns
174
- #
175
- # @kwarg [String] erb_trim_mode
176
- #
177
- # @example
178
- # require 'planter'
179
- # class UsersSeeder < Planter::Seeder
180
- # seeding_method :csv,
181
- # number_of_records: 2,
182
- # model: 'User'
183
- # parent: :person,
184
- # csv_name: :awesome_users,
185
- # unique_columns %i[username email],
186
- # erb_trim_mode: '<>'
187
- # end
188
- def seeding_method(
189
- seed_method,
190
- number_of_records: 1,
191
- model: nil,
192
- parent: nil,
193
- csv_name: nil,
194
- unique_columns: nil,
195
- erb_trim_mode: nil
196
- )
197
- if !SEEDING_METHODS.include?(seed_method.intern)
198
- raise ArgumentError, "Method must be: #{SEEDING_METHODS.join(', ')}"
199
- end
200
-
201
- self.seed_method = seed_method
202
- self.number_of_records = number_of_records
203
- self.model = model || to_s.delete_suffix('Seeder').singularize
204
- self.parent = parent
205
- self.csv_file = determine_csv_filename(csv_name) if seed_method == :csv
206
- self.erb_trim_mode = erb_trim_mode || Planter.config.erb_trim_mode
207
- self.unique_columns =
208
- case unique_columns
209
- when String, Symbol then [unique_columns.intern]
210
- when Array then unique_columns.map(&:intern)
211
- end
183
+ # If your class is going to use the inherited +seed+ method, you must tell
184
+ # it which +seeding_method+ to use. The argument to this method must be
185
+ # included in the +SEEDING_METHODS+ array.
186
+ #
187
+ # @param [Symbol] seed_method
188
+ #
189
+ # @kwarg [Integer] number_of_records
190
+ #
191
+ # @kwarg [String] model
192
+ #
193
+ # @kwarg [Symbol, String] parent
194
+ #
195
+ # @kwarg [Symbol, String] csv_name
196
+ #
197
+ # @kwarg [Symbol, String] unique_columns
198
+ #
199
+ # @kwarg [String] erb_trim_mode
200
+ #
201
+ # @example
202
+ # require 'planter'
203
+ # class UsersSeeder < Planter::Seeder
204
+ # seeding_method :csv,
205
+ # number_of_records: 2,
206
+ # model: 'User'
207
+ # parent: :person,
208
+ # csv_name: :awesome_users,
209
+ # unique_columns %i[username email],
210
+ # erb_trim_mode: '<>'
211
+ # end
212
+ def self.seeding_method(
213
+ seed_method,
214
+ number_of_records: 1,
215
+ model: nil,
216
+ parent: nil,
217
+ csv_name: nil,
218
+ unique_columns: nil,
219
+ erb_trim_mode: nil
220
+ )
221
+ unless SEEDING_METHODS.include?(seed_method.intern)
222
+ raise ArgumentError, "Method must be: #{SEEDING_METHODS.join(', ')}"
212
223
  end
213
224
 
214
- private
215
-
216
- def determine_csv_filename(csv_name) # :nodoc:
217
- file = (
218
- csv_name || "#{to_s.delete_suffix('Seeder').underscore}"
219
- ).to_s + '.csv'
220
- [file, "#{file}.erb"].each do |f|
221
- fname = Rails.root.join(Planter.config.csv_files_directory, f).to_s
222
- return fname if ::File.file?(fname)
225
+ self.seed_method = seed_method
226
+ self.number_of_records = number_of_records
227
+ self.model = model || to_s.delete_suffix('Seeder').singularize
228
+ self.parent = parent
229
+ self.csv_name = csv_name || to_s.delete_suffix('Seeder').underscore
230
+ self.erb_trim_mode = erb_trim_mode || Planter.config.erb_trim_mode
231
+ self.unique_columns =
232
+ case unique_columns
233
+ when String, Symbol then [unique_columns.intern]
234
+ when Array then unique_columns.map(&:intern)
223
235
  end
224
-
225
- raise ArgumentError, "Couldn't find csv for #{model}"
226
- end
227
236
  end
228
237
 
229
238
  ##
@@ -231,11 +240,12 @@ module Planter
231
240
  # valid +seeding_method+, and not implement its own +seed+ method.
232
241
  def seed
233
242
  validate_attributes
243
+ extract_data_from_csv if seed_method == :csv
234
244
 
235
245
  parent ? create_records_from_parent : create_records
236
246
  end
237
247
 
238
- protected
248
+ private
239
249
 
240
250
  ##
241
251
  # Creates records from the +data+ attribute.
@@ -253,7 +263,7 @@ module Planter
253
263
 
254
264
  def create_record(record, parent_id: nil)
255
265
  number_of_records.times do
256
- unique, attrs = split_record(record)
266
+ unique, attrs = split_record(apply_transformations(record))
257
267
  model.constantize.where(
258
268
  unique.tap { |u| u[foreign_key] = parent_id if parent_id }
259
269
  ).first_or_create!(attrs)
@@ -263,18 +273,32 @@ module Planter
263
273
  def validate_attributes # :nodoc:
264
274
  case seed_method.intern
265
275
  when :csv
266
- contents = ::File.read(csv_file)
267
- if csv_file.end_with?('.erb')
268
- contents = ERB.new(contents, trim_mode: erb_trim_mode).result(binding)
269
- end
270
-
271
- @data ||= ::CSV.parse(
272
- contents, headers: true, header_converters: :symbol
273
- ).map(&:to_hash)
276
+ raise "Couldn't find csv for #{model}" unless full_csv_name
274
277
  when :data_array
275
- raise "Must define '@data'" if public_send(:data).nil?
278
+ raise 'data is not defined in the seeder' if public_send(:data).nil?
276
279
  else
277
- raise("Must set 'seeding_method'")
280
+ raise 'seeding_method not defined in the seeder'
281
+ end
282
+ end
283
+
284
+ def apply_transformations(record)
285
+ return record if public_send(:transformations).nil?
286
+
287
+ Hash[record.map { |field, value| map_record(field, value, record) }]
288
+ end
289
+
290
+ def map_record(field, value, record)
291
+ [
292
+ field,
293
+ transformations.key?(field) ? transform(field, value, record) : value
294
+ ]
295
+ end
296
+
297
+ def transform(field, value, record)
298
+ case transformations[field].arity
299
+ when 0 then transformations[field].call
300
+ when 1 then transformations[field].call(value)
301
+ when 2 then transformations[field].call(value, record)
278
302
  end
279
303
  end
280
304
 
@@ -304,5 +328,25 @@ module Planter
304
328
  @parent_model ||=
305
329
  association_options.fetch(:class_name, parent.to_s.classify)
306
330
  end
331
+
332
+ def full_csv_name
333
+ @full_csv_name ||=
334
+ %W[#{csv_name}.csv #{csv_name}.csv.erb #{csv_name}.erb.csv]
335
+ .map { |f| Rails.root.join(Planter.config.csv_files_directory, f).to_s }
336
+ .find { |f| ::File.file?(f) }
337
+ end
338
+
339
+ def extract_data_from_csv
340
+ contents = ::File.read(full_csv_name)
341
+ if full_csv_name.include?('.erb')
342
+ contents = ERB.new(contents, trim_mode: erb_trim_mode).result(binding)
343
+ end
344
+
345
+ @data ||= ::CSV.parse(
346
+ contents,
347
+ headers: true,
348
+ header_converters: :symbol
349
+ ).map(&:to_hash)
350
+ end
307
351
  end
308
352
  end
@@ -15,19 +15,21 @@ module Planter
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 1
18
+ MINOR = 2
19
19
 
20
20
  ##
21
21
  # Patch version.
22
22
  #
23
23
  # @return [Integer]
24
- PATCH = 1
24
+ PATCH = 0
25
+
26
+ module_function
25
27
 
26
28
  ##
27
29
  # Version as +[MAJOR, MINOR, PATCH]+
28
30
  #
29
31
  # @return [Array]
30
- def self.to_a
32
+ def to_a
31
33
  [MAJOR, MINOR, PATCH]
32
34
  end
33
35
 
@@ -35,7 +37,7 @@ module Planter
35
37
  # Version as +MAJOR.MINOR.PATCH+
36
38
  #
37
39
  # @return [String]
38
- def self.to_s
40
+ def to_s
39
41
  to_a.join('.')
40
42
  end
41
43
 
@@ -43,7 +45,7 @@ module Planter
43
45
  # Version as +{major: MAJOR, minor: MINOR, patch: PATCH}+
44
46
  #
45
47
  # @return [Hash]
46
- def self.to_h
48
+ def to_h
47
49
  Hash[%i[major minor patch].zip(to_a)]
48
50
  end
49
51
  end
data/lib/planter.rb CHANGED
@@ -24,11 +24,13 @@ require 'planter/seeder'
24
24
  #
25
25
  # Planter.seed
26
26
  module Planter
27
+ module_function
28
+
27
29
  ##
28
30
  # The seeder configuration.
29
31
  #
30
32
  # @return [Planter::Config]
31
- def self.config
33
+ def config
32
34
  @config ||= Planter::Config.new
33
35
  end
34
36
 
@@ -36,7 +38,7 @@ module Planter
36
38
  # Resets the config back to its initial state.
37
39
  #
38
40
  # @return [Planter::Config]
39
- def self.reset_config
41
+ def reset_config
40
42
  @config = Planter::Config.new
41
43
  end
42
44
 
@@ -52,7 +54,7 @@ module Planter
52
54
  # config.seeders_directory = 'db/seeds'
53
55
  # config.csv_files_directory = 'db/seed_files'
54
56
  # end
55
- def self.configure
57
+ def configure
56
58
  config.tap { |c| yield c }
57
59
  end
58
60
 
@@ -65,7 +67,7 @@ module Planter
65
67
  # @example
66
68
  # # db/seeds.rb, assuming your +configure+ block is in an initializer.
67
69
  # Planter.seed
68
- def self.seed
70
+ def seed
69
71
  seeders = ENV['SEEDERS']&.split(',') || config.seeders&.map(&:to_s)
70
72
  raise RuntimeError, 'No seeders specified' if seeders.blank?
71
73
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: planter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-29 00:00:00.000000000 Z
11
+ date: 2022-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 6.1.4.4
19
+ version: 7.0.2.3
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
- version: 6.1.4.4
26
+ version: 7.0.2.3
27
27
  description: Create a seeder for each table in your database, and easily seed from
28
28
  CSV or custom methods
29
29
  email:
@@ -66,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  requirements: []
69
- rubygems_version: 3.2.22
69
+ rubygems_version: 3.2.33
70
70
  signing_key:
71
71
  specification_version: 4
72
72
  summary: Framework for seeding rails applications.