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 +4 -4
- data/README.md +52 -9
- data/lib/planter/seeder.rb +127 -83
- data/lib/planter/version.rb +7 -5
- data/lib/planter.rb +6 -4
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85e12676f9e3c992f26939f709bb8ff9785a75cfbf13a5279075e2e84ae2adc9
|
4
|
+
data.tar.gz: 130e7dd566d20b09dcfb38c4d62c76d872b169b459f68eff0a2d8715c13f320a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
23
|
+
version, as breaking changes may occur, even at the minor level.
|
25
24
|
|
26
25
|
```ruby
|
27
|
-
gem 'planter', '0.0
|
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
|
153
|
-
<%= Participant.find_by(email: 'test2@example.com').id
|
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
|
```
|
data/lib/planter/seeder.rb
CHANGED
@@ -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 :
|
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
|
-
#
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
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
|
-
|
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
|
278
|
+
raise 'data is not defined in the seeder' if public_send(:data).nil?
|
276
279
|
else
|
277
|
-
raise
|
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
|
data/lib/planter/version.rb
CHANGED
@@ -15,19 +15,21 @@ module Planter
|
|
15
15
|
# Minor version.
|
16
16
|
#
|
17
17
|
# @return [Integer]
|
18
|
-
MINOR =
|
18
|
+
MINOR = 2
|
19
19
|
|
20
20
|
##
|
21
21
|
# Patch version.
|
22
22
|
#
|
23
23
|
# @return [Integer]
|
24
|
-
PATCH =
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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.
|
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:
|
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:
|
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:
|
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.
|
69
|
+
rubygems_version: 3.2.33
|
70
70
|
signing_key:
|
71
71
|
specification_version: 4
|
72
72
|
summary: Framework for seeding rails applications.
|