planter 0.0.2 → 0.0.9

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: fe6195656eb33a7e774e6f5fb37e6f1dbcf6d7be33230c97605f291201d04de0
4
- data.tar.gz: 68e04cc88dde75734fd06b69e0fd783f8f1849cb9d7f6e73fd33c18814cb1c61
3
+ metadata.gz: 61c2952a2b44b89c48245554cd6be4b19598891aa958b3f2f0dd3e3d2548a079
4
+ data.tar.gz: 91e099c652ce4bfb0bf87320f9efa3d1e0e0826ae031e17e967563beb8561f19
5
5
  SHA512:
6
- metadata.gz: dbcea200bc37aa0d691cc001a918bcafea73a6c37754f83dea3f56867c4cb0773f474d276477c71081c03a5cc83d592b5ff511e745005e3bb17919436943b33a
7
- data.tar.gz: 26f4beb0e5613aedbb6308bcad145a5cd39c20f328fbfa6fb7db0f6bb588128c6c031c014ef00a61d519f246e89eea9e14b05d4126a990fca7235349a9615c9f
6
+ metadata.gz: d70a187f4064ab1081a5a4b3df180f4ced5436601151029fca74b985cc0658e0422b56f338693b08a885a5dfc7d241357ef86144373825d8553117c3e7f6f30e
7
+ data.tar.gz: 923979e971e3cbc573a9eaffbc39481d8fc8a4930894de1ea8d8f4ef79c3f6d7bbaba0ab111d0099ee67298b336373a0836802126c5d5bba4c237eb41ab0c5d8
data/README.md CHANGED
@@ -1,18 +1,23 @@
1
1
  # Planter
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
+ [![Gem Version](https://badge.fury.io/rb/planter.svg)](https://badge.fury.io/rb/planter)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
2
6
  > Pre-release version! Anything is subject to change in the near future!
3
7
 
4
8
  Seeds for Rails applications can get complicated fast, and Rails doesn't provide
5
9
  much for assisting with this process. This plugin seeks to rectify that by
6
- providing easy ways to seed specific tables by hooking into the existing `rails
7
- db:seed` task.
10
+ providing easy ways to seed specific tables.
8
11
 
9
12
  Features include:
10
13
 
11
14
  - Seed tables from CSV files, an array of hashes, or custom methods.
12
- - Seed specific tables with `rails db:seed TABLES=users,addresses`.
15
+ - Call specific seeders with `rails planter:seed SEEDERS=users,addresses`.
13
16
  - Control the number of records being created.
14
17
  - Seed associations.
15
18
 
19
+ You can view the documentation [here](https://evanthegrayt.github.io/planter/).
20
+
16
21
  ## Installation
17
22
  Add this line to your application's Gemfile:
18
23
 
@@ -35,30 +40,69 @@ $ gem install planter
35
40
  ## Usage
36
41
  Let's assume you'd like to seed your `users` table.
37
42
 
38
- To get started, simply add the following to your `db/seeds.rb` file. Note that
39
- the `config.tables` should be an array of the tables to seed. They should be in
40
- the correct order to successfully seed the tables when considering associations.
43
+ To get started, run `rails generate planter:initializer`, which will create
44
+ `config/initializers/planter.rb` with the following contents.
41
45
 
42
46
  ```ruby
43
47
  require 'planter'
44
48
 
45
49
  Planter.configure do |config|
46
- config.tables = %i[ users ]
50
+ ##
51
+ # The list of seeders. These files are stored in the
52
+ # config.seeders_directory, which can be changed below. When a new
53
+ # seeder is generated, it will be appended to the bottom of this
54
+ # list. If the order is incorrect, you'll need to adjust the it.
55
+ # Just be sure to keep the ending bracket on its own line, or the
56
+ # generator won't know where to put new elements.
57
+ config.seeders = %i[
58
+ ]
59
+
60
+ ##
61
+ # The directory where the seeder files are kept.
62
+ # config.seeders_directory = 'db/seeds'
63
+
64
+ ##
65
+ # The directory where CSVs are kept.
66
+ # config.csv_files_directory = 'db/seed_files'
47
67
  end
68
+ ```
69
+
70
+ By default, a `planter:seed` task is provided for seeding your application. This
71
+ allows you to use `db:seed` for other purposes. If you want Planter to hook
72
+ into the existing `db:seed` task, simply add the following to `db/seeds.rb`.
48
73
 
74
+ ```ruby
49
75
  Planter.seed
50
76
  ```
51
77
 
52
- Then, create a directory called `db/seeds`, and create a file called
53
- `db/seeds/users_seeder.rb`. In that file, create the following class. Note the
54
- name of the seeder is the name of the table, plus `Seeder`, and it inherits from
55
- `Planter::Seeder`.
78
+ To create a users seeder, run `rails generate planter:seeder users`. Usually,
79
+ seeders seed a specific table, so it's recommended to name your seeders after
80
+ the table. If you don't, you'll need to manually specify a few things. More on
81
+ that later. This will create a file named `db/seeds/users_seeder.rb` (the
82
+ directory will be created if it doesn't exist) with the following contents.
56
83
 
57
84
  ```ruby
58
85
  class UsersSeeder < Planter::Seeder
86
+ # TODO: Choose a seeding_method. For example:
87
+ # seeding_method :csv
88
+
89
+ # For now, we overload the seed method so no exception will be raised.
90
+ def seed
91
+ end
59
92
  end
60
93
  ```
61
94
 
95
+ This also adds `users` to the `config.seeders` array in our initializer. A few
96
+ things to note.
97
+
98
+ - The seeder will always be appended at the end of the array. If this is not the
99
+ correct order, you'll need to adjust the array manually.
100
+ - When adjusting the array, always keep the closing bracket on its own line, or
101
+ the generator won't know where to put the new seeders.
102
+
103
+ If you want to generate a seeder for every table currently in your database, run
104
+ `rails generate planter:seeder ALL`.
105
+
62
106
  You then need to choose a seeding method, of which there are currently three.
63
107
 
64
108
  ### Seeding from CSV
@@ -66,7 +110,7 @@ To seed from CSV, you simply need to add the following to your seeder class.
66
110
 
67
111
  ```ruby
68
112
  class UsersSeeder < Planter::Seeder
69
- seeding_method :standard_csv
113
+ seeding_method :csv
70
114
  end
71
115
  ```
72
116
 
@@ -80,16 +124,28 @@ test1@example.com,test1
80
124
  test2@example.com,test2
81
125
  ```
82
126
 
83
- If the CSV files are not located in the project, you can specify a `:csv_file`
84
- option. Note that the value must be a full path.
127
+ If the CSV file is named differently than the seeder, you can specify the
128
+ `:csv_name` option. Note that the value should not include the file extension.
85
129
 
86
130
  ```ruby
87
131
  class UsersSeeder < Planter::Seeder
88
- seeding_method :standard_csv, csv_file: '/home/me/users.csv'
132
+ seeding_method :csv, csv_name: :people
89
133
  end
90
134
  ```
91
135
 
92
- Running `rails db:seed` will now seed your `users` table.
136
+ `ERB` can be used in the CSV files if you name it with `.erb` at the end of the
137
+ file name. For example, `users.csv.erb`. Note that lines starting with `<%` and
138
+ ending with `%>` will not be considered rows, so you can use `ERB` rows to set
139
+ values. For example:
140
+
141
+ ```csv.erb
142
+ email,login_attempts
143
+ <% count = 1 %>
144
+ test2@example.com,<%= count += 1 %>
145
+ test2@example.com,<%= count += 1 %>
146
+ ```
147
+
148
+ Running `rails planter:seed` will now seed your `users` table.
93
149
 
94
150
  ## Seeding from a data array
95
151
  If you need dynamic seeds, you can add something similar to the following to
@@ -97,7 +153,7 @@ your seeder class. In this example, we'll use
97
153
  [faker](https://github.com/faker-ruby/faker).
98
154
 
99
155
  ```ruby
100
- require 'faker' # You should really just require this in `db/seeds.rb`.
156
+ require 'faker' # You could just require this in `db/seeds.rb`.
101
157
 
102
158
  class UsersSeeder < Planter::Seeder
103
159
  seeding_method :data_array, number_of_records: 10
@@ -117,7 +173,7 @@ ten elements to create ten records. It's also worth noting that setting an
117
173
  instance variable called `@data` from an `initialize` method would also work, as
118
174
  the `Planter::Seeder` parent class automatically provides `attr_reader :data`.
119
175
 
120
- Running `rails db:seed` should now seed your `users` table.
176
+ Running `rails planter:seed` should now seed your `users` table.
121
177
 
122
178
  You can also seed children records for every existing record of a parent model.
123
179
  For example, to seed an address for every user, you'd need to create an
@@ -142,10 +198,8 @@ end
142
198
 
143
199
  Note that specifying `number_of_records` in this instance will create that many
144
200
  records *for each record of the parent model*. You can also specify the
145
- association if it's different from the table name, using the `assocation:`
146
- option. Currently, associations are assumed to be as `has_many`, so the
147
- association is plural by default. Any help with making this more heuristically
148
- complete would be welcome.
201
+ association if it's different from the table name, using the `:assocation`
202
+ option.
149
203
 
150
204
  ### Custom seeds
151
205
  To write your own custom seeds, just overload the `seed` method and do whatever
@@ -154,7 +208,7 @@ you need to do.
154
208
  ```ruby
155
209
  class UsersSeeder < Planter::Seeder
156
210
  USERS = {
157
- 'test1@example.com' => { username: 'John Smith' }
211
+ 'test1@example.com' => { username: 'John Smith' },
158
212
  'test2@example.com' => { username: 'Jane Smith' }
159
213
  }
160
214
 
@@ -164,26 +218,6 @@ class UsersSeeder < Planter::Seeder
164
218
  end
165
219
  ```
166
220
 
167
- ## Customization
168
- You can change the directories of both the seeder files and the csv files. In
169
- your `configure` block in `db/seeds.rb`, you can add the following. Note that,
170
- in both instances, the path should be relative to `Rails.root`.
171
-
172
- ```ruby
173
- require 'planter'
174
-
175
- Planter.configure do |config|
176
- config.seeders_directory = 'db/seeder_classes'
177
- config.csv_files_directory = 'db/csvs'
178
- config.tables = %i[
179
- users
180
- addresses
181
- ]
182
- end
183
-
184
- Planter.seed
185
- ```
186
-
187
221
  ## License
188
222
  The gem is available as open source under the terms of the [MIT
189
223
  License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ end
11
11
 
12
12
  RDoc::Task.new do |rdoc|
13
13
  rdoc.main = 'README.md'
14
- rdoc.rdoc_dir = 'doc'
14
+ rdoc.rdoc_dir = 'docs'
15
15
  rdoc.rdoc_files.include('README.md', 'lib/**/*.rb')
16
16
  end
17
17
 
@@ -0,0 +1,33 @@
1
+ module Planter
2
+ module Generators
3
+ class InitializerGenerator < Rails::Generators::Base
4
+ desc 'Genrates an initializer for Planter at config/initializers/planter.rb'
5
+
6
+ def create_initializer_file
7
+ create_file 'config/initializers/planter.rb', <<~EOF
8
+ require 'planter'
9
+
10
+ Planter.configure do |config|
11
+ ##
12
+ # The list of seeders. These files are stored in the
13
+ # config.seeders_directory, which can be changed below. When a new
14
+ # seeder is generated, it will be appended to the bottom of this
15
+ # list. If the order is incorrect, you'll need to adjust the it.
16
+ # Just be sure to keep the ending bracket on its own line, or the
17
+ # generator won't know where to put new elements.
18
+ config.seeders = %i[
19
+ ]
20
+
21
+ ##
22
+ # The directory where the seeder files are kept.
23
+ # config.seeders_directory = 'db/seeds'
24
+
25
+ ##
26
+ # The directory where CSVs are kept.
27
+ # config.csv_files_directory = 'db/seed_files'
28
+ end
29
+ EOF
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ module Planter
2
+ module Generators
3
+ class SeederGenerator < Rails::Generators::Base
4
+ argument :seeder, required: true
5
+
6
+ desc "This generator creates a seeder file at #{::Planter.config.seeders_directory}"
7
+
8
+ def generate_seeders
9
+ seeder == 'ALL' ? tables.each { |t| generate(t) } : generate(seeder)
10
+ end
11
+
12
+ private
13
+
14
+ def generate(seeder)
15
+ empty_directory ::Planter.config.seeders_directory
16
+
17
+ create_file "#{::Planter.config.seeders_directory}/#{seeder}_seeder.rb", <<~EOF
18
+ class #{seeder.camelize}Seeder < Planter::Seeder
19
+ # TODO: Choose a seeding_method. For example:
20
+ # seeding_method :csv
21
+
22
+ # For now, we overload the seed method so no exception will be raised.
23
+ def seed
24
+ end
25
+ end
26
+ EOF
27
+
28
+ inject_into_file 'config/initializers/planter.rb',
29
+ " #{seeder}\n",
30
+ before: /^\s*\]\s*$/
31
+ end
32
+
33
+ def tables
34
+ @tables ||= ActiveRecord::Base.connection.tables.reject do |table|
35
+ %w[ar_internal_metadata schema_migrations].include?(table)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
data/lib/planter.rb CHANGED
@@ -1,74 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'csv'
4
+ require 'erb'
4
5
  require 'planter/version'
5
6
  require 'planter/railtie'
6
7
  require 'planter/config'
7
8
  require 'planter/seeder'
8
9
 
9
10
  ##
10
- # Class that seeders should inherit from. Seeder files should be in +db/seeds+,
11
- # and named +TABLE_seeder.rb+, where +TABLE+ is the name of the table being
12
- # seeded (I.E. +users_seeder.rb+). The seeder's class name should be the same
13
- # as the file name, but camelized. So, +UsersSeeder+. The directory where the
14
- # seeder files are located can be changed via an initializer.
11
+ # The main module for the plugin. It nicely wraps the +Planter::Config+ class
12
+ # so that you can customize the plugin via an initializer or in the
13
+ # +db/seeds.rb+ file. This is how you'll specify your list of seeders to use,
14
+ # along with customizing the +seeders_directory+ and +csv_files_directory+.
15
15
  #
16
- # The most basic way to seed is to have a CSV file with the same name as the
17
- # table in +db/seed_files/+. So, +users.csv+. This CSV should have the table's
18
- # column names as header. To seed using this method, your class should look
19
- # like the following. Note that +:csv_file+ is not required; it defaults to the
20
- # table name with a +csv+ file extension. The directory where the seed files
21
- # are kept can be changed via an initializer.
22
- # # db/seeds/users_seeder.rb
23
- # require 'planter'
24
- # class UsersSeeder < Planter::Seeder
25
- # seeding_method :standard_csv, csv_file: '/home/me/users.csv'
16
+ # Planter.configure do |config|
17
+ # config.seeders = %i[users]
18
+ # config.seeders_directory = 'db/seeds'
19
+ # config.csv_files_directory = 'db/seed_files'
26
20
  # end
27
21
  #
28
- # Another way to seed is to create records from a data array. To do this, your
29
- # class must implement a +data+ attribute or method, which is an array of
30
- # hashes. Note that this class already provides the +attr_reader+ for this
31
- # attribute, so the most you have to do it create instance variables in your
32
- # constructor. If if you want your data to be different for each new record
33
- # (via Faker, +Array#sample+, etc.), you'll probably want to supply a method
34
- # called data that returns an array of new data each time.
35
- # require 'planter'
36
- # class UsersSeeder < Planter::Seeder
37
- # seeding_method :data_array
38
- # def data
39
- # [{foo: 'bar', baz: 'bar'}]
40
- # end
41
- # end
42
- #
43
- # In both of the above methods, you can specify +parent_model+ and
44
- # +association+. If specified, records will be created via that parent model's
45
- # association. If +association+ is not provided, it will be assumed to be the
46
- # model name, pluralized and snake-cased (implying a +has_many+ relationship).
47
- # For example, if we're seeding the users table, and the model is +User+, the
48
- # association will default to +users+.
49
- # require 'planter'
50
- # class UsersSeeder < Planter::Seeder
51
- # seeding_method :data_array, parent_model: 'Person', association: :users
52
- # def data
53
- # [{foo: 'bar', baz: 'bar'}]
54
- # end
55
- # end
22
+ # To then seed your application, simply call the +seed+ method from your
23
+ # +db/seeds.rb+ file (or wherever you need to call it from).
56
24
  #
57
- # You can also set +number_of_records+ to determine how many times each record
58
- # in the +data+ array will get created. The default is 1. Note that if this
59
- # attribute is set alongside +parent_model+ and +association+,
60
- # +number_of_records+ will be how many records will be created for each record
61
- # in the parent table.
62
- # require 'planter'
63
- # class UsersSeeder < Planter::Seeder
64
- # seeding_method :data_array, number_of_records: 5
65
- # def data
66
- # [{foo: 'bar', baz: 'bar'}]
67
- # end
68
- # end
69
- #
70
- # If you need to seed a different way, put your own custom +seed+ method in
71
- # your seeder class and do whatever needs to be done.
25
+ # Planter.seed
72
26
  module Planter
73
27
  ##
74
28
  # The seeder configuration.
@@ -89,34 +43,36 @@ module Planter
89
43
  ##
90
44
  # Quick way of configuring the directories via an initializer.
91
45
  #
92
- # @return [self]
46
+ # @return [Planter::Config]
93
47
  #
94
48
  # @example
95
- # Planter.configure do |app_seeder|
96
- # app_seeder.tables = %i[users]
97
- # app_seeder.seeders_directory = 'db/seeds'
98
- # app_seeder.csv_files_directory = 'db/seed_files'
49
+ # require 'planter'
50
+ # Planter.configure do |config|
51
+ # config.seeders = %i[users]
52
+ # config.seeders_directory = 'db/seeds'
53
+ # config.csv_files_directory = 'db/seed_files'
99
54
  # end
100
55
  def self.configure
101
56
  config.tap { |c| yield c }
102
57
  end
103
58
 
104
59
  ##
105
- # This is the method to call from your +db/seeds.rb+. It seeds the tables
106
- # listed in +Planter.config.tables+. To seed specific tables at
107
- # runtime, you can set the +TABLES+ environmental variable to a
108
- # comma-separated list of tables.
60
+ # This is the method to call from your +db/seeds.rb+. It callse the seeders
61
+ # listed in +Planter.config.seeders+. To call specific seeders at runtime,
62
+ # you can set the +SEEDERS+ environmental variable to a comma-separated list
63
+ # of seeders, like +rails db:seed SEEDERS=users,accounts+.
109
64
  #
110
65
  # @example
111
- # rails db:seed TABLES=users,accounts
66
+ # # db/seeds.rb, assuming your +configure+ block is in an initializer.
67
+ # Planter.seed
112
68
  def self.seed
113
- tables = ENV['TABLES']&.split(',') || config.tables&.map(&:to_s)
114
- raise RuntimeError, 'No tables specified; nothing to do' unless tables&.any?
69
+ seeders = ENV['SEEDERS']&.split(',') || config.seeders&.map(&:to_s)
70
+ raise RuntimeError, 'No seeders specified' unless seeders&.any?
115
71
 
116
- tables.each do |table|
117
- require Rails.root.join(config.seeders_directory, "#{table}_seeder.rb").to_s
118
- puts "Seeding #{table}" unless config.quiet
119
- "#{table.camelize}Seeder".constantize.new.seed
72
+ seeders.each do |s|
73
+ require Rails.root.join(config.seeders_directory, "#{s}_seeder.rb").to_s
74
+ puts "Seeding #{s}" unless config.quiet
75
+ "#{s.camelize}Seeder".constantize.new.seed
120
76
  end
121
77
  end
122
78
  end
@@ -5,7 +5,7 @@ module Planter
5
5
  # Configure the application seeder.
6
6
  #
7
7
  # @example
8
- # Planter.configure { |seeder| seeder.tables = %i[users] }
8
+ # Planter.configure { |seeder| seeder.seeders = %i[users] }
9
9
  class Config
10
10
  ##
11
11
  # Tell the application where the seeder classes are kept. Must be a path
@@ -26,13 +26,13 @@ module Planter
26
26
  attr_accessor :csv_files_directory
27
27
 
28
28
  ##
29
- # Tell the application what tables to seed. Elements should be in the correct
30
- # order, and can be strings or symbols.
29
+ # Tell the application what seeders exist. Elements should be in the correct
30
+ # order to seed the tables successfully, and can be strings or symbols.
31
31
  #
32
- # @param [Array] tables
32
+ # @param [Array] seeders
33
33
  #
34
34
  # @return [Array]
35
- attr_accessor :tables
35
+ attr_accessor :seeders
36
36
 
37
37
  ##
38
38
  # When true, don't print output when seeding.
@@ -2,5 +2,13 @@
2
2
 
3
3
  module Planter
4
4
  class Railtie < ::Rails::Railtie # :nodoc:
5
+ rake_tasks do
6
+ load File.join(
7
+ File.expand_path(File.dirname(__FILE__)),
8
+ '..',
9
+ 'tasks',
10
+ 'planter_tasks.rake'
11
+ )
12
+ end
5
13
  end
6
14
  end
@@ -2,21 +2,101 @@
2
2
 
3
3
  module Planter
4
4
  ##
5
- # The class your seeder files should inherit from.
5
+ # Class that seeders should inherit from. Seeder files should be in
6
+ # +db/seeds+, and named +TABLE_seeder.rb+, where +TABLE+ is the name of the
7
+ # table being seeded (I.E. +users_seeder.rb+). If your seeder is named
8
+ # differently than the table, you'll need to specify the table with the
9
+ # +model+ option. The seeder's class name should be the same as the file
10
+ # name, but camelized. So, +UsersSeeder+. The directory where the seeder
11
+ # files are located can be changed via an initializer.
12
+ #
13
+ # The most basic way to seed is to have a CSV file with the same name as the
14
+ # 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
18
+ # seeded. The directory where the seed files are kept can be changed via an
19
+ # initializer.
20
+ # # db/seeds/users_seeder.rb
21
+ # require 'planter'
22
+ # class UsersSeeder < Planter::Seeder
23
+ # seeding_method :csv, csv_name: :users, model: 'User'
24
+ # end
25
+ #
26
+ # Another way to seed is to create records from a data array. To do this,
27
+ # your class must implement a +data+ attribute or method, which is an array
28
+ # of hashes. Note that this class already provides the +attr_reader+ for this
29
+ # attribute, so the most you have to do it create instance variables in your
30
+ # constructor. If if you want your data to be different for each new record
31
+ # (via Faker, +Array#sample+, etc.), you'll probably want to supply a method
32
+ # called data that returns an array of new data each time.
33
+ # require 'planter'
34
+ # class UsersSeeder < Planter::Seeder
35
+ # seeding_method :data_array
36
+ # def data
37
+ # [{foo: 'bar', baz: 'bar'}]
38
+ # end
39
+ # end
40
+ #
41
+ # In both of the above methods, you can specify +parent_model+ and
42
+ # +association+. If specified, records will be created via that parent
43
+ # model's association. If +association+ is not provided, it will be assumed
44
+ # to be the model name, pluralized and snake-cased (implying a +has_many+
45
+ # relationship). For example, if we're seeding the users table, and the
46
+ # model is +User+, the association will default to +users+.
47
+ # require 'planter'
48
+ # class UsersSeeder < Planter::Seeder
49
+ # seeding_method :data_array, parent_model: 'Person', association: :users
50
+ # def data
51
+ # [{foo: 'bar', baz: 'bar'}]
52
+ # end
53
+ # end
54
+ #
55
+ # You can also set +number_of_records+ to determine how many times each
56
+ # record in the +data+ array will get created. The default is 1. Note that if
57
+ # this attribute is set alongside +parent_model+ and +association+,
58
+ # +number_of_records+ will be how many records will be created for each
59
+ # record in the parent table.
60
+ # require 'planter'
61
+ # class UsersSeeder < Planter::Seeder
62
+ # seeding_method :data_array, number_of_records: 5
63
+ # def data
64
+ # [{foo: 'bar', baz: 'bar'}]
65
+ # end
66
+ # end
67
+ #
68
+ # By default, all fields are used to look up the record. If it already
69
+ # exists, it is not re-created. If you have specific fields that a record
70
+ # should be looked-up by, you can pass the +unique_columns+ option. This will
71
+ # attempt to look up the record by those fields only, and if one doesn't
72
+ # exist, one will be created with the rest of the attributes. An example of
73
+ # when this would be useful is with Devise; you can't pass +password+ in the
74
+ # create method, so specifying +unique_columns+ on everything except
75
+ # +password+ allows it to be passed as an attribute to the +first_or_create+
76
+ # call.
77
+ # require 'planter'
78
+ # class UsersSeeder < Planter::Seeder
79
+ # seeding_method :data_array, unique_columns: %i[username email]
80
+ # def data
81
+ # [{username: 'foo', email: 'bar', password: 'Example'}]
82
+ # end
83
+ # end
84
+ #
85
+ # If you need to seed a different way, put your own custom +seed+ method in
86
+ # your seeder class and do whatever needs to be done.
6
87
  class Seeder
7
88
  ##
8
89
  # The allowed seeding methods.
9
90
  #
10
91
  # @return [Array]
11
- SEEDING_METHODS = %i[standard_csv data_array].freeze
92
+ SEEDING_METHODS = %i[csv data_array].freeze
12
93
 
13
94
  ##
14
95
  # Array of hashes used to create records. Your class must set this
15
- # attribute when using +data_hash+ seeding method, although it's probably
96
+ # attribute when using +data_array+ seeding method, although it's probably
16
97
  # more likely that you'll want to define a method that returns a new set of
17
- # data each time (via +Faker+, +Array#sample+, etc.). When using
18
- # +standard_csv+, +data+ will be set to the data within the csv. You can
19
- # override this.
98
+ # data each time (via +Faker+, +Array#sample+, etc.). When using +csv+,
99
+ # +data+ will be set to the data within the csv. You can override this.
20
100
  #
21
101
  # @return [Array]
22
102
  attr_reader :data
@@ -28,40 +108,58 @@ module Planter
28
108
  #
29
109
  # @param [Symbol] seeding_method
30
110
  #
31
- # @param [Hash] options
111
+ # @kwarg [Integer] number_of_records
112
+ #
113
+ # @kwarg [String] model
114
+ #
115
+ # @kwarg [String] parent_model
116
+ #
117
+ # @kwarg [Symbol, String] association
118
+ #
119
+ # @kwarg [Symbol, String] csv_name
120
+ #
121
+ # @kwarg [Symbol, String] unique_columns
32
122
  #
33
123
  # @example
34
124
  # require 'planter'
35
125
  # class UsersSeeder < Planter::Seeder
36
- # seeding_method :data_array,
126
+ # seeding_method :csv,
127
+ # number_of_records: 2,
37
128
  # model: 'User'
38
129
  # parent_model: 'Person',
39
130
  # association: :users,
40
- # number_of_records: 2
131
+ # csv_name: :awesome_users,
132
+ # unique_columns %i[username email]
41
133
  # end
42
- def self.seeding_method(method, **options)
134
+ def self.seeding_method(
135
+ method,
136
+ number_of_records: 1,
137
+ model: to_s.delete_suffix('Seeder').singularize,
138
+ parent_model: nil,
139
+ association: nil,
140
+ csv_name: nil,
141
+ unique_columns: nil
142
+ )
43
143
  if !SEEDING_METHODS.include?(method.intern)
44
144
  raise ArgumentError, "Method must be one of #{SEEDING_METHODS.join(', ')}"
45
- elsif options[:association] && !options[:parent_model]
145
+ elsif association && !parent_model
46
146
  raise ArgumentError, "Must specify :parent_model with :association"
47
147
  end
48
148
 
49
149
  @seeding_method = method
50
- @number_of_records = options.fetch(:number_of_records, 1)
51
- @model = options.fetch(:model, to_s.delete_suffix('Seeder').singularize)
52
- @parent_model = options[:parent_model]
53
- @association = @parent_model && options.fetch(:association) do
54
- determine_association(options)
55
- end
56
- return unless @seeding_method == :standard_csv
57
-
58
- @csv_file = options.fetch(:csv_file, Rails.root.join(
59
- Planter.config.csv_files_directory,
60
- "#{to_s.delete_suffix('Seeder').underscore}.csv"
61
- ).to_s)
150
+ @number_of_records = number_of_records
151
+ @model = model
152
+ @parent_model = parent_model
153
+ @association = @parent_model && (association || determine_association)
154
+ @csv_file = determine_csv_filename(csv_name) if @seeding_method == :csv
155
+ @unique_columns =
156
+ case unique_columns
157
+ when String, Symbol then [unique_columns.intern]
158
+ when Array then unique_columns.map(&:intern)
159
+ end
62
160
  end
63
161
 
64
- def self.determine_association(options) # :nodoc:
162
+ def self.determine_association # :nodoc:
65
163
  associations =
66
164
  @parent_model.constantize.reflect_on_all_associations.map(&:name)
67
165
  table = to_s.delete_suffix('Seeder').underscore.split('/').last
@@ -70,10 +168,23 @@ module Planter
70
168
  return t if associations.include?(t)
71
169
  end
72
170
 
73
- raise ArgumentError, 'Could not determine association name'
171
+ raise ArgumentError, "Couldn't determine association name"
74
172
  end
75
173
  private_class_method :determine_association
76
174
 
175
+ def self.determine_csv_filename(csv_name) # :nodoc:
176
+ file = (
177
+ csv_name || "#{to_s.delete_suffix('Seeder').underscore}"
178
+ ).to_s + '.csv'
179
+ [file, "#{file}.erb"].each do |f|
180
+ fname = Rails.root.join(Planter.config.csv_files_directory, f).to_s
181
+ return fname if ::File.file?(fname)
182
+ end
183
+
184
+ raise ArgumentError, "Couldn't find csv for #{@model}"
185
+ end
186
+ private_class_method :determine_csv_filename
187
+
77
188
  ##
78
189
  # The default seed method. To use this method, your class must provide a
79
190
  # valid +seeding_method+, and not implement its own +seed+ method.
@@ -140,14 +251,23 @@ module Planter
140
251
  @csv_file ||= self.class.instance_variable_get('@csv_file')
141
252
  end
142
253
 
254
+ ##
255
+ # When creating a record, the fields that will be used to look up the
256
+ # record. If it already exists, a new one will not be created.
257
+ #
258
+ # @return [Array]
259
+ def unique_columns
260
+ @unique_columns ||= self.class.instance_variable_get('@unique_columns')
261
+ end
262
+
143
263
  ##
144
264
  # Creates records from the +data+ attribute.
145
265
  def create_records
146
266
  data.each do |rec|
147
267
  number_of_records.times do
148
- model.constantize.where(
149
- rec.transform_values { |value| value == 'NULL' ? nil : value }
150
- ).first_or_create!
268
+ rec.transform_values { |value| value == 'NULL' ? nil : value }
269
+ unique, attrs = split_record(rec)
270
+ model.constantize.where(unique).first_or_create!(attrs)
151
271
  end
152
272
  end
153
273
  end
@@ -165,31 +285,47 @@ module Planter
165
285
 
166
286
  private
167
287
 
168
- def create_method
288
+ def create_method # :nodoc:
169
289
  parent_model.constantize.reflect_on_association(
170
290
  association
171
291
  ).macro.to_s.include?('many') ? :create_has_many : :create_has_one
172
292
  end
173
293
 
174
- def create_has_many(assoc_rec, association, rec)
175
- assoc_rec.public_send(association).where(rec).first_or_create!
294
+ def create_has_many(assoc_rec, association, rec) # :nodoc:
295
+ unique, attrs = split_record(rec)
296
+ assoc_rec.public_send(association).where(unique).first_or_create!(attrs)
176
297
  end
177
298
 
178
- def create_has_one(assoc_rec, association, rec)
179
- assoc_rec.public_send("create_#{association}", rec)
299
+ def create_has_one(assoc_rec, association, rec) # :nodoc:
300
+ if assoc_rec.public_send(association)
301
+ assoc_rec.public_send(association).update_attributes(rec)
302
+ else
303
+ assoc_rec.public_send("create_#{association}", rec)
304
+ end
180
305
  end
181
306
 
182
307
  def validate_attributes # :nodoc:
183
308
  case seeding_method.intern
184
- when :standard_csv
185
- raise "#{csv_file} does not exist" unless ::File.file?(csv_file)
309
+ when :csv
310
+ contents = ::File.read(csv_file)
311
+ if csv_file.end_with?('.erb')
312
+ contents = ERB.new(contents, trim_mode: '<>').result(binding)
313
+ end
186
314
 
187
- @data ||= ::CSV.table(csv_file).map(&:to_hash)
315
+ @data ||= ::CSV.parse(
316
+ contents, headers: true, header_converters: :symbol
317
+ ).map(&:to_hash)
188
318
  when :data_array
189
319
  raise "Must define '@data'" if public_send(:data).nil?
190
320
  else
191
321
  raise("Must set 'seeding_method'")
192
322
  end
193
323
  end
324
+
325
+ def split_record(rec) # :nodoc:
326
+ return [rec, {}] unless unique_columns
327
+ u = unique_columns.each_with_object({}) { |c, h| h[c] = rec.delete(c) }
328
+ [u, rec]
329
+ end
194
330
  end
195
331
  end
@@ -1,7 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Planter
4
+ ##
5
+ # Module that contains all gem version information. Follows semantic
6
+ # versioning. Read: https://semver.org/
7
+ module Version
8
+ ##
9
+ # Major version.
10
+ #
11
+ # @return [Integer]
12
+ MAJOR = 0
13
+
14
+ ##
15
+ # Minor version.
16
+ #
17
+ # @return [Integer]
18
+ MINOR = 0
19
+
20
+ ##
21
+ # Patch version.
22
+ #
23
+ # @return [Integer]
24
+ PATCH = 9
25
+
26
+ ##
27
+ # Version as +[MAJOR, MINOR, PATCH]+
28
+ #
29
+ # @return [Array]
30
+ def self.to_a
31
+ [MAJOR, MINOR, PATCH]
32
+ end
33
+
34
+ ##
35
+ # Version as +MAJOR.MINOR.PATCH+
36
+ #
37
+ # @return [String]
38
+ def self.to_s
39
+ to_a.join('.')
40
+ end
41
+
42
+ ##
43
+ # Version as +{major: MAJOR, minor: MINOR, patch: PATCH}+
44
+ #
45
+ # @return [Hash]
46
+ def self.to_h
47
+ Hash[%i[major minor patch].zip(to_a)]
48
+ end
49
+ end
50
+
4
51
  ##
5
52
  # Gem version, semantic.
6
- VERSION = '0.0.2'.freeze
53
+ VERSION = Version.to_s.freeze
7
54
  end
@@ -1,4 +1,13 @@
1
- # desc "Explaining what the task does"
2
- # task :planter do
3
- # # Task goes here
4
- # end
1
+ require 'planter'
2
+
3
+ namespace :planter do
4
+ desc 'Seed application. Use this to keep planter separate from db:seed'
5
+ task seed: :environment do
6
+ Planter.configure do |config|
7
+ # NOTE: the seed method already looks for ENV['SEEDERS']
8
+ ENV['SEEDERS_DIRECTORY'] && config.seeders_directory = ENV['SEEDERS_DIRECTORY']
9
+ ENV['CSV_FILES_DIRECTORY'] && config.csv_files_directory = ENV['CSV_FILES_DIRECTORY']
10
+ end
11
+ Planter.seed
12
+ end
13
+ end
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.0.2
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-23 00:00:00.000000000 Z
11
+ date: 2021-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -41,6 +41,8 @@ files:
41
41
  - LICENSE
42
42
  - README.md
43
43
  - Rakefile
44
+ - lib/generators/planter/initializer_generator.rb
45
+ - lib/generators/planter/seeder_generator.rb
44
46
  - lib/planter.rb
45
47
  - lib/planter/config.rb
46
48
  - lib/planter/railtie.rb
@@ -54,7 +56,8 @@ metadata:
54
56
  allowed_push_host: https://rubygems.org
55
57
  homepage_uri: https://github.com/evanthegrayt/planter
56
58
  source_code_uri: https://github.com/evanthegrayt/planter
57
- post_install_message:
59
+ documentation_uri: https://evanthegrayt.github.io/planter/
60
+ post_install_message:
58
61
  rdoc_options: []
59
62
  require_paths:
60
63
  - lib
@@ -69,8 +72,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
72
  - !ruby/object:Gem::Version
70
73
  version: '0'
71
74
  requirements: []
72
- rubygems_version: 3.2.3
73
- signing_key:
75
+ rubygems_version: 3.2.15
76
+ signing_key:
74
77
  specification_version: 4
75
78
  summary: Framework for seeding rails applications.
76
79
  test_files: []