seedify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e30b80245438020aa56db7572c512e6ca73e7963
4
+ data.tar.gz: 8305c4c190fa2b0b8a00ef283d84f2579a1842e0
5
+ SHA512:
6
+ metadata.gz: a4b44586fd7466317f4e6fff838e5daf8b4ecb8e05594edcba16ee7c9d407916031e9c751cef0c51e948077d7e730cc706bbaef1ad67182840f6ce3c24347d94
7
+ data.tar.gz: fccdcef4b2ecc48aa17ac3910347fb51ee3fed97f7650a02813afab5d672bb9ab0b2d6b519de0a27f917e5a031e72c6c8f8979d97c64221737308c293ccebde6
data/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # seedify
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/seedify.svg?style=flat-square&label=version)](https://rubygems.org/gems/seedify)
4
+ [![Downloads](https://img.shields.io/gem/dt/seedify.svg?style=flat-square)](https://rubygems.org/gems/seedify)
5
+ [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/visualitypl/seedify.svg?style=flat-square)](https://scrutinizer-ci.com/g/visualitypl/seedify/?branch=master)
6
+ [![Code Climate](https://img.shields.io/codeclimate/github/visualitypl/seedify.svg?style=flat-square)](https://codeclimate.com/github/visualitypl/seedify)
7
+
8
+ Let your seed code become a first-class member of the Rails app and put it into seed objects. **seedify** allows to implement and organize seeds in object-oriented fashion, putting them into `app/seeds` alongside the controllers, models etc. It also provides handy syntax for command line parameters and progress logging.
9
+
10
+ Here's an overview of what you can achieve with **seedify**:
11
+
12
+ - organize seed code in object-oriented, Rails convention fitting fashion
13
+ - take advantage of inheritance and modularization when writing seeds
14
+ - invoke seeds as rake tasks or from within the app/console
15
+ - allow to specify custom parameters for your seeds, typed and with defaults
16
+ - log the seed progress (e.g. mass creation) without effort
17
+ - combine with [factory_girl](https://github.com/thoughtbot/factory_girl) to simplify object creation and share fixtures with specs
18
+
19
+ ## Usage
20
+
21
+ ### Basic example
22
+
23
+ You should start by implementing the `ApplicationSeed` class:
24
+
25
+ ```ruby
26
+ # app/seeds/application_seed.rb
27
+ class ApplicationSeed < Seedify::Base
28
+ def call
29
+ if Admin.empty?
30
+ log 'create first admin'
31
+
32
+ Admin.create!(email: 'admin@example.com', password: 'password')
33
+ else
34
+ log "admin already exists: '#{Admin.first.email}'"
35
+ end
36
+ end
37
+ end
38
+ ```
39
+
40
+ This will create an admin if there's none and output existing admin's e-mail otherwise.
41
+
42
+ Now, call the seed as rake task:
43
+
44
+ ```sh
45
+ rake seedify
46
+ ```
47
+
48
+ or from Rails console:
49
+
50
+ ```ruby
51
+ ApplicationSeed.call
52
+ ```
53
+
54
+ ### Using fixtures
55
+
56
+ [factory_girl](https://github.com/thoughtbot/factory_girl) is great for sharing model fixtures between tests and seeds. Add `factory_girl_rails` gem to development group and include its methods, so you can use `#create` in seeds:
57
+
58
+ ```ruby
59
+ class ApplicationSeed < Seedify::Base
60
+ include FactoryGirl::Syntax::Methods
61
+
62
+ def call
63
+ create :admin
64
+ end
65
+ end
66
+ ```
67
+
68
+ ### Custom parameters
69
+
70
+ You'll often need to parameterize your seed. You can do this easily via `param_reader`:
71
+
72
+ ```ruby
73
+ class ApplicationSeed < Seedify::Base
74
+ include FactoryGirl::Syntax::Methods
75
+
76
+ param_reader :prefix, default: 'user'
77
+ param_reader :count, type: :integer, default: 5
78
+ param_reader :with_post, type: :boolean, default: false
79
+
80
+ def call
81
+ count.times do |index|
82
+ user = create :user, email: "#{prefix}-#{index}@example.com"
83
+
84
+ create :post, user: user if with_post?
85
+ end
86
+ end
87
+ end
88
+ ```
89
+
90
+ You can specify the type of your param and have it casted automatically. Supported types are:
91
+
92
+ - `:string` default type, default value: **nil**
93
+ - `:integer` default value: **0**
94
+ - `:boolean` default value: **false**, accessor with `?` suffix
95
+
96
+ Specify the parameters for rake task:
97
+
98
+ ```sh
99
+ rake seedify prefix=guy count=20 with_post=true
100
+ ```
101
+
102
+ or from Rails console:
103
+
104
+ ```ruby
105
+ ApplicationSeed.call prefix: 'guy', count: 20, with_post: true
106
+ ```
107
+
108
+ ### Logging
109
+
110
+ Seed with detailed progress logging feels responsive and is easier to debug in case of a failure. That's why **seedify** provides three log methods for your convenience:
111
+
112
+ **Simple message** (block is optional):
113
+
114
+ ```ruby
115
+ log "create user '#{email}'" do
116
+ create :user, email: email
117
+ end
118
+ ```
119
+
120
+ **Array evaluation with live progress**:
121
+
122
+ ```ruby
123
+ log_each ['a', 'b', 'c'], 'create users' do |prefix|
124
+ create :user, email: "#{prefix}@example.com"
125
+ end
126
+ ```
127
+
128
+ **N-times evaluation with live progress**:
129
+
130
+ ```ruby
131
+ log_times 5, 'create users' do |n|
132
+ create :user, email: "user-#{n}@example.com"
133
+ end
134
+ ```
135
+
136
+ Notes:
137
+
138
+ - `log_each`/`log_times` is great for creating large amounts of data with live progress indication
139
+ - texts between `'`, `"` or `*` (like in first example above) quotes are colorized for emphasis
140
+ - logging to `stdout` is enabled by default; disable it by setting the `log` parameter to false
141
+
142
+ ### Organizing seed code
143
+
144
+ Your seed code will grow along with application development so keeping it clean and well-separated will become important. It's easy thanks to **seedify**'s object-oriented approach. Imagine all-in-one application seed like below:
145
+
146
+ ```ruby
147
+ class ApplicationSeed < Seed::Base
148
+ include FactoryGirl::Syntax::Methods
149
+
150
+ param_reader :admin_email, default: 'admin@example.com'
151
+ param_reader :user_prefix, default: 'user'
152
+ param_reader :user_count, type: :integer, default: 5
153
+
154
+ param_reader :clear_method, default: 'destroy'
155
+
156
+ def call
157
+ clear_model(Admin)
158
+ clear_model(User)
159
+
160
+ log "create admin *#{admin_email}* with root priviliges" do
161
+ create :admin, :with_root_priviliges, email: admin_email
162
+ end
163
+
164
+ log user_count, 'create users' do |index|
165
+ create :user, email: "#{user_prefix}-#{index}@example.com"
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def clear_model(model)
172
+ case clear_method
173
+ when 'truncate'
174
+ ActiveRecord::Base.connection.execute("TRUNCATE #{model.table_name}")
175
+ when 'destroy'
176
+ model.destroy_all
177
+ end
178
+ end
179
+ end
180
+ ```
181
+
182
+ You can separate the seed code into domains that fit your application best (including modules). In this case, we'll create model-oriented seeds for `Admin` and `User` models and keep application-wide code and param readers in application seed:
183
+
184
+ ```ruby
185
+ # app/seeds/application_seed.rb
186
+ class ApplicationSeed < Seed::Base
187
+ include FactoryGirl::Syntax::Methods
188
+
189
+ param_reader :clear_method, default: 'destroy'
190
+
191
+ def call
192
+ AdminSeed.call
193
+ UserSeed.call
194
+ end
195
+
196
+ protected
197
+
198
+ def clear_model(model)
199
+ case clear_method
200
+ when 'truncate'
201
+ ActiveRecord::Base.connection.execute("TRUNCATE #{model.table_name}")
202
+ when 'destroy'
203
+ model.destroy_all
204
+ end
205
+ end
206
+ end
207
+ ```
208
+
209
+ ```ruby
210
+ # app/seeds/admin_seed.rb
211
+ class AdminSeed < ApplicationSeed
212
+ param_reader :admin_email, default: 'admin@example.com'
213
+
214
+ def call
215
+ clear_model(Admin)
216
+
217
+ log "create admin *#{admin_email}* with root priviliges" do
218
+ create :admin, :with_root_priviliges, email: admin_email
219
+ end
220
+ end
221
+ end
222
+ ```
223
+
224
+ ```ruby
225
+ # app/seeds/user_seed.rb
226
+ class UserSeed < ApplicationSeed
227
+ param_reader :user_prefix, default: 'user'
228
+ param_reader :user_count, type: :integer, default: 5
229
+
230
+ def call
231
+ clear_model(User)
232
+
233
+ log user_count, 'create users' do |index|
234
+ create :user, email: "#{user_prefix}-#{index}@example.com"
235
+ end
236
+ end
237
+ end
238
+ ```
239
+
240
+ > Params defined in application seed will be available in seeds that inherit from it too.
241
+
242
+ You can now invoke each seed separately:
243
+
244
+ ```sh
245
+ rake seedify:admin clear_method=none
246
+ rake seedify:user user_count=10
247
+ ```
248
+
249
+ or all at once like before with `rake seedify`.
250
+
251
+ You should try to write each seed object in a way that makes it possible to use it either stand-alone or as a sub-seed called from another seed object. This way, for instance, you'll be able to generate more objects in specific domain without touching rest of the system or recreate whole system data - depending on your needs - with the same seed code.
252
+
253
+ You can customize sub-seed invocation with a syntax you already know - the call parameters:
254
+
255
+ ```ruby
256
+ # excerpt from app/seeds/application_seed.rb
257
+ UserSeed.call user_prefix: 'user_generated_by_app_seed'
258
+ ```
259
+
260
+ This way, within **UserSeed**, the `user_prefix` param will equal to *user_generated_by_app_seed* regardless of the one specified when calling the application seed from console or command-line.
261
+
262
+ ## Contributing
263
+
264
+ 1. Fork it (https://github.com/visualitypl/seedify/fork)
265
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
266
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
267
+ 4. Push to the branch (`git push origin my-new-feature`)
268
+ 5. Create a new Pull Request
269
+
data/lib/seedify.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'seedify/railtie'
2
+ require 'seedify/param_reader'
3
+ require 'seedify/base'
4
+ require 'seedify/logger'
5
+ require 'seedify/storage'
6
+ require 'seedify/param_value'
7
+ require 'seedify/call_stack'
@@ -0,0 +1,42 @@
1
+ module Seedify
2
+ class Base
3
+ extend Seedify::ParamReader
4
+
5
+ attr_reader :params
6
+
7
+ param_reader :log, type: :boolean, default: true
8
+ param_reader :task, type: :boolean, default: false
9
+
10
+ def self.call(overrides = {})
11
+ seed = self.new(params(overrides))
12
+
13
+ Seedify::CallStack.call(seed)
14
+ end
15
+
16
+ def self.params(overrides)
17
+ stacked_params = (Seedify::CallStack.last.try(:params) || {}).merge(overrides)
18
+ defined_params = get_param_readers.map do |param_name, options|
19
+ [param_name, Seedify::ParamValue.get(param_name, options)]
20
+ end.to_h
21
+
22
+ defined_params.merge(stacked_params)
23
+ end
24
+
25
+ def initialize(params)
26
+ @params = params
27
+ @logger = Seedify::Logger.new(self)
28
+ end
29
+
30
+ def log(message, &block)
31
+ @logger.line(message, &block)
32
+ end
33
+
34
+ def log_each(array, message, &block)
35
+ @logger.each(array, message, &block)
36
+ end
37
+
38
+ def log_times(count, message, &block)
39
+ @logger.times(count, message, &block)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ module Seedify
2
+ class CallStack
3
+ class << self
4
+ def call(proc)
5
+ stack.push(proc)
6
+ proc.call
7
+ stack.pop
8
+ end
9
+
10
+ def last
11
+ stack.last
12
+ end
13
+
14
+ private
15
+
16
+ def stack
17
+ Thread.current[:seedify_call_stack] ||= []
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,82 @@
1
+ module Seedify
2
+ class Logger
3
+ attr_reader :context
4
+
5
+ def initialize(context)
6
+ @context = context
7
+ end
8
+
9
+ def line(message, &block)
10
+ print "#{name} #{formatted_message message}"
11
+ yield if block_given?
12
+ print "\n"
13
+ end
14
+
15
+ def each(array, message, &block)
16
+ process_items(array, array.length, message, &block)
17
+ end
18
+
19
+ def times(count, message, &block)
20
+ process_items(count.times.to_a, count, message, &block)
21
+ end
22
+
23
+ private
24
+
25
+ def process_items(array, count, message, &block)
26
+ if count > 0
27
+ done_count = yield_with_count(array, count, message, &block)
28
+ all_done = done_count == count
29
+ eraser = ' ' * (count.to_s.length - done_count.to_s.length)
30
+
31
+ print "\r#{name} #{formatted_message message} " +
32
+ colorize("#{done_count}/#{count}", all_done ? 35 : 31) + eraser
33
+ else
34
+ print "\r#{name} #{formatted_message message} #{colorize 'NONE', 31}"
35
+ end
36
+
37
+ print "\n"
38
+ end
39
+
40
+ def yield_with_count(array, count, message)
41
+ done_count = 0
42
+ array.each_with_index do |item, index|
43
+ percent = (index * 100) / count
44
+ print "\r#{name} #{formatted_message message} #{colorize "#{index + 1}/#{count}", 35}"
45
+
46
+ done_count += 1 if yield(item) != false
47
+ end
48
+
49
+ done_count
50
+ end
51
+
52
+ def formatted_message(message)
53
+ message = colorize_pairs message, '*', 93
54
+ message = colorize_pairs message, "'", 92
55
+ message = colorize_pairs message, '"', 96
56
+
57
+ message
58
+ end
59
+
60
+ def colorize(message, code)
61
+ "\033[#{code}m#{message}\033[0m"
62
+ end
63
+
64
+ def colorize_pairs(message, quote, code)
65
+ quote = '\\' + quote
66
+ message.gsub(/#{quote}(.*?)#{quote}/) do |match|
67
+ colorize(match.sub(/^#{quote}/, '').sub(/#{quote}$/, ''), code)
68
+ end
69
+ end
70
+
71
+ def print(message)
72
+ Kernel.print(message) if context.log?
73
+ end
74
+
75
+ def name
76
+ text = context.class.name
77
+ text = (' ' * [0, Seedify::Storage.max_seed_name_length - text.length].max) + text
78
+
79
+ colorize(text, 32)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,23 @@
1
+ module Seedify
2
+ module ParamReader
3
+ def inherited(subclass)
4
+ get_param_readers.each do |param_name, options|
5
+ subclass.param_reader param_name, options
6
+ end
7
+ end
8
+
9
+ def param_reader(param_name, options = {})
10
+ param_name = param_name.to_sym
11
+ getter_name = options[:type] == :boolean ? "#{param_name}?" : param_name
12
+
13
+ @params ||= {}
14
+ @params[param_name] = options
15
+
16
+ define_method(getter_name) { params[param_name] }
17
+ end
18
+
19
+ def get_param_readers
20
+ @params || {}
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Seedify
2
+ class ParamValue
3
+ class << self
4
+ def get(param_name, options)
5
+ value = get_env_value(param_name) || get_default_value(options[:default])
6
+ value = get_type_casted_value(value, options[:type])
7
+
8
+ value
9
+ end
10
+
11
+ private
12
+
13
+ def get_env_value(param_name)
14
+ ENV[param_name.to_s]
15
+ end
16
+
17
+ def get_default_value(default)
18
+ if default.respond_to?(:call)
19
+ default.call
20
+ else
21
+ default
22
+ end
23
+ end
24
+
25
+ def get_type_casted_value(value, type)
26
+ case type.to_s
27
+ when 'boolean'
28
+ value.to_s.in?(%{1 true})
29
+ when 'integer'
30
+ (value || 0).to_i
31
+ else
32
+ value
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module Seedify
2
+ class Railtie < Rails::Railtie
3
+ railtie_name :seedify
4
+
5
+ rake_tasks do
6
+ load 'tasks/seedify.rake'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Seedify
2
+ module Storage
3
+ class << self
4
+ def seed_directory
5
+ Rails.root.join('app', 'seeds')
6
+ end
7
+
8
+ def seed_list
9
+ @all_seeds ||= Dir[seed_directory.join('**', '*_seed.rb')].map do |file|
10
+ file.sub(/^#{seed_directory.to_s + "/"}/, '').sub(/#{".rb"}$/, '').classify
11
+ end
12
+ end
13
+
14
+ def max_seed_name_length
15
+ @max_seed_name_length ||= seed_list.map(&:length).max
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Seedify
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,21 @@
1
+ require 'seedify'
2
+
3
+ seed_list = Seedify::Storage.seed_list
4
+
5
+ if seed_list.include?('ApplicationSeed')
6
+ desc 'Call the Application seed'
7
+ task seedify: :environment do
8
+ ApplicationSeed.call(task: true)
9
+ end
10
+ end
11
+
12
+ namespace :seedify do
13
+ seed_list.each do |seed|
14
+ next if seed == 'ApplicationSeed'
15
+
16
+ desc "Call the #{seed.underscore.humanize}"
17
+ task seed.underscore.gsub('/', ':').sub(/_seed$/, '') => :environment do
18
+ seed.constantize.call(task: true)
19
+ end
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: seedify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Karol Słuszniak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: codeclimate-test-reporter
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: scrutinizer-ocular
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.9'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.9'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ description: Let your seed code become a first-class member of the Rails app and put
112
+ it into seed objects in "app/seeds" alongside the controllers, models etc. Invoke
113
+ seeds as rake tasks or from within the app/console, with or without the parameters.
114
+ Progress logging included.
115
+ email: k.sluszniak@visuality.pl
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files:
119
+ - README.md
120
+ files:
121
+ - README.md
122
+ - lib/seedify.rb
123
+ - lib/seedify/base.rb
124
+ - lib/seedify/call_stack.rb
125
+ - lib/seedify/logger.rb
126
+ - lib/seedify/param_reader.rb
127
+ - lib/seedify/param_value.rb
128
+ - lib/seedify/railtie.rb
129
+ - lib/seedify/storage.rb
130
+ - lib/seedify/version.rb
131
+ - lib/tasks/seedify.rake
132
+ homepage: http://github.com/visualitypl/seedify
133
+ licenses:
134
+ - MIT
135
+ metadata: {}
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.2.2
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Implement and organize seeds in Rail-ish, object-oriented fashion. Handy
156
+ syntax for command line parameters and progress logging.
157
+ test_files: []