planter 0.1.1 → 0.2.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: 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.