planter 0.0.2 → 0.0.9

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: 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: []