fill 0.1.0

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.
@@ -0,0 +1,12 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ VERSION
5
+ fill.gemspec
6
+ lib/fill.rb
7
+ lib/fill/configure.rb
8
+ lib/fill/presenter.rb
9
+ lib/fill/procedure.rb
10
+ spec/fill_spec.rb
11
+ spec/spec.opts
12
+ spec/spec_helper.rb
@@ -0,0 +1,131 @@
1
+ = Fill
2
+
3
+ Rails 2.3.4 introduced db/seeds. Basically that means that the rake task
4
+ "rake db:seed" will run db/seeds.rb, so you can have a centralized place
5
+ to prepare your database by adding seed data.
6
+
7
+ "Fill" takes this one step further and provides methods to easily define
8
+ data in db/seeds.rb. Here's how:
9
+
10
+ Fill.database do |db|
11
+
12
+ db.produce :projects do
13
+ 10.times { Factory(:project) }
14
+ end
15
+
16
+ end
17
+
18
+ (I use factory_girl here, do whatever you like off course)
19
+
20
+ Now, when running "rake db:seed", it'll delete all records in the Project
21
+ model and run the contents off the produce block. It'll also measure how
22
+ long it took, resulting in this output:
23
+
24
+ +-------+--------+---------+----------+
25
+ | After | Before | Models | Time |
26
+ +-------+--------+---------+----------+
27
+ | 10 | 0 | Project | 0.018627 |
28
+ +-------+--------+---------+----------+
29
+
30
+ You'll need to install hirb for this pretty output. Otherwise it won't print
31
+ out this pretty table. I recommend using Hirb anyway. Find it at
32
+ http://github.com/cldwalker/hirb
33
+
34
+ == Why?
35
+
36
+ At my company, we taught testers, customers and developers to use
37
+ webistrano and run the db:seed" rake task themselves. This way, they can
38
+ test and experiment as much as they want, and they easily reset the
39
+ database when they fucked it up.
40
+
41
+ == Usage
42
+
43
+ === db.produce
44
+
45
+ By specifying a block, do whatever you need to fill a model.
46
+
47
+ Example:
48
+
49
+ db.produce :users, :memberships do
50
+ 10.times do
51
+ user = Factory(:user)
52
+ membership = Factory(:membership, :user => user)
53
+ end
54
+ end
55
+
56
+ Specify the models that you want. The models that you specified will be
57
+ emptied, which is handy if you're building relational models, like users
58
+ and their memberships.
59
+
60
+ The models are named *plural*.
61
+
62
+ === db.fill
63
+
64
+ Provide a simple list of values.
65
+
66
+ Example
67
+
68
+ db.fill :projects, :name, "Foo", "Bar", "Baz", "etc"
69
+
70
+ For simple models with only one distinct attribute, you can just specify
71
+ the model, attribute and the values.
72
+
73
+ See also the iain/root_table plugin if you have many of these.
74
+
75
+ === db.invoke
76
+
77
+ Invoke a rake task.
78
+
79
+ db.invoke "some:task", :projects
80
+
81
+ I use a seperate rake task whenever I need to import files. Of course you
82
+ can do that in the +db.produce+, but that clutters your seeds.rb.
83
+
84
+ === Global options
85
+
86
+ These options work on all above mentioned methods.
87
+
88
+ ==== :needs
89
+
90
+ Specify one or many dependencies, tables to be filled before filling the ones
91
+ you're specifying now.
92
+
93
+ Example
94
+
95
+ db.produce :memberships, :needs => :users do
96
+ User.all.each { |user| Factory(:membership, :user => user) }
97
+ end
98
+
99
+ ==== :delete
100
+
101
+ Set to false if you don't want to delete all records before filling it.
102
+
103
+ Example:
104
+
105
+ db.invoke "import:zipcodes", :zips, :delete => false
106
+
107
+ In this example, the zipcodes import takes a long time to complete, so it
108
+ doesn't insert them when the database is already filled.
109
+
110
+ ==== :name
111
+
112
+ The output uses Rails i18n methods to determine how the output calls the
113
+ models filled, but if you want to specify your own name, use this option.
114
+
115
+ Example:
116
+
117
+ db.produce :users, :name => "Accounts" do
118
+ ....
119
+ end
120
+
121
+ == Installation
122
+
123
+ Add this to config/environment.rb:
124
+
125
+ config.gem "iain-fill", :lib => "fill", :source => "http://gems.github.com"
126
+
127
+ And run "+rake gems:install+" and you're done.
128
+
129
+ ---
130
+
131
+ Made by Iain, http://iain.nl/, Released under the MIT License
@@ -0,0 +1,24 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+
4
+ require 'spec/rake/spectask'
5
+ desc "Run all specs in spec directory"
6
+ Spec::Rake::SpecTask.new(:spec) do |t|
7
+ t.spec_opts = ['--options spec/spec.opts']
8
+ t.spec_files = FileList['spec/**/*_spec.rb']
9
+ end
10
+
11
+
12
+ begin
13
+ require 'echoe'
14
+ Echoe.new('fill', File.read('VERSION').chomp) do |p|
15
+ p.description = "Simple DSL for filling (seeding) your database"
16
+ p.url = "http://github.com/iain/fill"
17
+ p.author = "iain"
18
+ p.email = "iain@iain.nl"
19
+ p.ignore_pattern = [ "pkg/*" ]
20
+ p.development_dependencies = []
21
+ p.runtime_dependencies = []
22
+ end
23
+ rescue LoadError
24
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{fill}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["iain"]
9
+ s.date = %q{2009-09-25}
10
+ s.description = %q{Simple DSL for filling (seeding) your database}
11
+ s.email = %q{iain@iain.nl}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/fill.rb", "lib/fill/configure.rb", "lib/fill/presenter.rb", "lib/fill/procedure.rb"]
13
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "VERSION", "fill.gemspec", "lib/fill.rb", "lib/fill/configure.rb", "lib/fill/presenter.rb", "lib/fill/procedure.rb", "spec/fill_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
14
+ s.homepage = %q{http://github.com/iain/fill}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Fill", "--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{fill}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Simple DSL for filling (seeding) your database}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ require 'activesupport'
2
+ require File.dirname(__FILE__) + '/fill/configure'
3
+ require File.dirname(__FILE__) + '/fill/presenter'
4
+ require File.dirname(__FILE__) + '/fill/procedure'
5
+
6
+ module Fill
7
+
8
+ VERSION = File.read(File.dirname(__FILE__) + '/../VERSION').chomp
9
+
10
+ class << self
11
+
12
+
13
+ attr_writer :out
14
+ def out
15
+ @out ||= STDOUT
16
+ end
17
+
18
+ def database
19
+ db = Configure.new
20
+ yield db
21
+ perform!(db)
22
+ end
23
+
24
+ def perform!(configuration)
25
+ bm = time { configuration.perform! }
26
+ out.puts Presenter
27
+ out.puts "Database filled in %.2f seconds" % bm
28
+ Presenter.clear!
29
+ end
30
+
31
+ def time
32
+ started_at = Time.now
33
+ yield
34
+ Time.now - started_at
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,69 @@
1
+ module Fill
2
+
3
+ class Configure
4
+
5
+ def produce(*models, &block)
6
+ options = models.extract_options!
7
+ needs = options.delete(:needs) || []
8
+ register models, Procedure.new(models, options, &block)
9
+ dependent models, needs
10
+ end
11
+
12
+ def fill(model, field, *values)
13
+ options = values.extract_options!
14
+ self.produce model, options do
15
+ values.each do |value|
16
+ model.to_s.singularize.camelize.constantize.create!(field => value)
17
+ end
18
+ end
19
+ end
20
+
21
+ def invoke(task, *models)
22
+ self.produce *models do
23
+ Rake::Task[task].invoke
24
+ end
25
+ end
26
+
27
+ def environment(env, which, options = {})
28
+
29
+ end
30
+
31
+ def perform!
32
+ registered.each_key { |model| perform(model) }
33
+ end
34
+
35
+ private
36
+
37
+ def results
38
+ results = registered.values.uniq.compact.map { |data| data.to_hash }
39
+ end
40
+
41
+ def perform(model)
42
+ raise "No fill data provided for #{model}" unless registered.has_key? model
43
+ dependencies[model].each { |dep| perform(dep) } if dependencies.has_key? model
44
+ registered[model].perform!
45
+ end
46
+
47
+ def register(models, data)
48
+ models.each do |model|
49
+ registered.update model => data
50
+ end
51
+ end
52
+
53
+ def dependent(models, dependent)
54
+ models.each do |model|
55
+ dependencies.update model => [dependent].flatten
56
+ end
57
+ end
58
+
59
+ def registered
60
+ @registered ||= {}
61
+ end
62
+
63
+ def dependencies
64
+ @dependencies ||= {}
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,67 @@
1
+ module Fill
2
+
3
+ class Presenter
4
+
5
+ def self.present(data)
6
+ presenter.add(data)
7
+ end
8
+
9
+ def self.presenter
10
+ @presenter ||= new
11
+ end
12
+
13
+ def self.to_s
14
+ presenter.to_s
15
+ end
16
+
17
+ def self.hirb?
18
+ require 'hirb'
19
+ true
20
+ rescue LoadError
21
+ false
22
+ end
23
+
24
+ def self.clear!
25
+ @presenter = nil
26
+ end
27
+
28
+ def add(data)
29
+ presented.push(data) if data && !presented.include?(data)
30
+ end
31
+
32
+ def hirb?
33
+ self.class.hirb?
34
+ end
35
+
36
+ def presented
37
+ @presented ||= []
38
+ end
39
+
40
+ def presentable
41
+ presented.map(&:to_hash)
42
+ end
43
+
44
+ def present_with_hirb
45
+ Hirb::Helpers::Table.render(presentable, :description => false)
46
+ end
47
+
48
+ def to_s
49
+ hirb? ? present_with_hirb : present_hash.join("\n")
50
+ end
51
+
52
+ def present_hash
53
+ presentable.map do |row|
54
+ format_row(row).join(" - ")
55
+ end
56
+ end
57
+
58
+ def format_row(row)
59
+ row.map do |key, value|
60
+ value = "%.2f" % value if key == "Time"
61
+ "#{key}: #{value}"
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,55 @@
1
+ module Fill
2
+
3
+ class Procedure
4
+
5
+ attr_accessor :block, :options
6
+
7
+ def initialize(models, options = {}, &block)
8
+ @block = block
9
+ @options = { :delete => true }.merge(options)
10
+ @models = models
11
+ end
12
+
13
+ def perform!
14
+ @performed ||= perform
15
+ end
16
+
17
+ def to_hash
18
+ { "Models" => human_models, "Before" => @before.join(', '), "After" => @after.join(', '), "Time" => @time }
19
+ end
20
+
21
+ def human_models
22
+ @human_models ||= (options[:name] || humanize_models)
23
+ end
24
+
25
+ def humanize_models
26
+ models.map { |model| i18n_name(model) }.join(', ')
27
+ end
28
+
29
+ def i18n_name(model)
30
+ model.respond_to?(:human_name) ? model.human_name : model.to_s
31
+ end
32
+
33
+ def delete_all
34
+ models.map { |model| model.delete_all }
35
+ end
36
+
37
+ def models
38
+ @models.map { |model| model.to_s.singularize.camelize.constantize }
39
+ end
40
+
41
+ def count
42
+ models.map { |model| model.count }
43
+ end
44
+
45
+ def perform
46
+ @before = options[:delete] ? delete_all : count
47
+ @time = Fill.time { block.call }
48
+ @after = count
49
+ Presenter.present self
50
+ true
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,249 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Fill do
4
+
5
+ before do
6
+ Output.output = []
7
+ end
8
+
9
+ describe "the the internal workings of db.produce" do
10
+
11
+ it "should perform the content only after the block is closed" do
12
+ # This is so that all dependencies can be initialized
13
+ # before anything is executed.
14
+ lambda {
15
+ Fill.database do |db|
16
+ lambda {
17
+ db.produce :projects do
18
+ throw :fill_executed
19
+ end
20
+ }.should_not throw_symbol(:fill_executed)
21
+ end
22
+ }.should throw_symbol(:fill_executed)
23
+ end
24
+
25
+ it "should count the records" do
26
+ mock(Project).count
27
+ produce!
28
+ end
29
+
30
+ it "should delete all the records" do
31
+ mock(Project).delete_all
32
+ produce!
33
+ end
34
+
35
+ it "should get the human name of a model" do
36
+ mock(Project).human_name
37
+ produce!
38
+ end
39
+
40
+ it "should report the total time" do
41
+ produce!
42
+ Output.output.last.should =~ /\ADatabase filled in (.*) seconds\Z/
43
+ end
44
+
45
+ context "without hirb" do
46
+
47
+ before { mock(Fill::Presenter).hirb? { false } }
48
+
49
+ describe "output per model" do
50
+
51
+ before do
52
+ stub(Project).human_name { "FOOBAR" }
53
+ stub(Project).delete_all { 123456789 }
54
+ stub(Project).count { 987654321 }
55
+ produce!
56
+ end
57
+
58
+ subject { Output.output.first }
59
+
60
+ it { should =~ /Time: ([0-9.]+)/ }
61
+ it { should include("Before: 123456789") }
62
+ it { should include("After: 987654321") }
63
+ it { should include("Models: FOOBAR") }
64
+
65
+ end
66
+
67
+ it "should count the time of the block" do
68
+
69
+ # Fill itself shouldn't take up more than 1 second
70
+ # so this test should return just about 1 second
71
+ # I _am_ sorry about this crude example ;)
72
+
73
+ produce! { sleep 1 } # zzzzzzzzzz
74
+
75
+ Output.output.first =~ /Time: ([0-9.]+)/
76
+ $1.to_i.should == 1
77
+
78
+ # Test total time, not in a seperate example to shorten testrun
79
+ Output.output.last =~ /([0-9.]+) seconds/
80
+ $1.to_i.should == 1
81
+ end
82
+
83
+ end
84
+
85
+ it "should use hirb when possible" do
86
+ out = "---HIRB OUTPUT---"
87
+ mock(Fill::Presenter).hirb? { true }
88
+ mock(Hirb::Helpers::Table).render(is_a(Array), :description => false) { out }
89
+ produce!
90
+ Output.output.first.should == out
91
+ end
92
+
93
+ end
94
+
95
+ describe "arguments for db.produce" do
96
+
97
+ describe "multiple models" do
98
+
99
+ after do
100
+ produce! :projects, :users, :memberships do
101
+ User.create
102
+ end
103
+ end
104
+
105
+ it "calls delete_all on all models" do
106
+ mock(Project).delete_all
107
+ mock(User).delete_all
108
+ mock(Membership).delete_all
109
+ end
110
+
111
+ it "calls count on all models" do
112
+ mock(Project).count
113
+ mock(User).count
114
+ mock(Membership).count
115
+ end
116
+
117
+ it "calls human_name on all models" do
118
+ mock(Project).human_name
119
+ mock(User).human_name
120
+ mock(Membership).human_name
121
+ end
122
+
123
+ it "should only execute the procedure once" do
124
+ mock(User).create.times(1)
125
+ end
126
+
127
+ end
128
+
129
+ describe "dependencies" do
130
+
131
+ it "should run dependencies first" do
132
+ # This works because the dont_allow should be executed AFTER
133
+ # the call was actually done! Similarly, the mock should be
134
+ # defined BEFORE it was done.
135
+ Fill.database do |db|
136
+ db.produce :users, :needs => :projects do
137
+ dont_allow(Project).create
138
+ User.create
139
+ end
140
+ db.produce :projects do
141
+ Project.create
142
+ mock(User).create
143
+ end
144
+ end
145
+ end
146
+
147
+ it "should execute once, even with multiple dependencies" do
148
+ mock(User).create.times(1)
149
+ Fill.database do |db|
150
+ db.produce(:memberships, :needs => :users) {}
151
+ db.produce(:projects, :needs => :users) {}
152
+ db.produce(:users) { User.create }
153
+ end
154
+ end
155
+
156
+ it "should handle multiple dependencies" do
157
+ Fill.database do |db|
158
+ db.produce(:memberships, :needs => [:users, :projects]) do
159
+ # Again, don't allow, because these should already have
160
+ # been executed before this block is executed.
161
+ dont_allow(Project).create
162
+ dont_allow(User).create
163
+ Membership.create # twice because mocked twice
164
+ Membership.create
165
+ end
166
+ db.produce(:projects, :needs => :users) do
167
+ Project.create
168
+ mock(Membership).create
169
+ dont_allow(User).create
170
+ end
171
+ db.produce(:users) do
172
+ User.create
173
+ mock(Project).create
174
+ mock(Membership).create
175
+ end
176
+ end
177
+ end
178
+
179
+ context "without hirb" do
180
+
181
+ before { stub(Fill::Presenter).hirb? { false } }
182
+
183
+ it "should join model names" do
184
+ mock(Project).human_name { "Projects" }
185
+ mock(User).human_name { "Accounts" }
186
+ produce! :projects, :users
187
+ Output.output.first.should include("Models: Projects, Accounts")
188
+ end
189
+
190
+ it "should join before counts" do
191
+ mock(Project).delete_all { 1234 }
192
+ mock(User).delete_all { 4321 }
193
+ produce! :projects, :users
194
+ Output.output.first.should include("Before: 1234, 4321")
195
+ end
196
+
197
+ it "should join before counts" do
198
+ mock(Project).count { 789 }
199
+ mock(User).count { 456 }
200
+ produce! :projects, :users
201
+ Output.output.first.should include("After: 789, 456")
202
+ end
203
+
204
+ it "should add up times" do
205
+ produce! :projects, :users
206
+ Output.output.first.should =~ /Time: [0-9.][^,]/ # no comma, no join(',')
207
+ end
208
+
209
+ end
210
+
211
+ end
212
+
213
+ describe "names" do
214
+
215
+ before { stub(Fill::Presenter).hirb? { false } }
216
+
217
+ it "should override name" do
218
+ name = ":::MYNAME:::"
219
+ dont_allow(Project).human_name
220
+ produce! :projects, :name => name
221
+ Output.output.first.should include("Models: #{name}")
222
+ end
223
+
224
+ it "should override name with multiple models" do
225
+ name = ":::FOOBAR:::"
226
+ dont_allow(Project).human_name
227
+ dont_allow(User).human_name
228
+ dont_allow(Membership).human_name
229
+ produce! :projects, :users, :memberships, :name => name
230
+ Output.output.first.should include("Models: #{name}")
231
+ end
232
+
233
+ end
234
+
235
+ describe "delete option" do
236
+
237
+ it "should not delete with delete option to false" do
238
+ dont_allow(Project).delete_all
239
+ dont_allow(User).delete_all
240
+ mock(Project).count.times(2) # before and after
241
+ mock(User).count.times(2) # before and after
242
+ produce! :users, :projects, :delete => false
243
+ end
244
+
245
+ end
246
+
247
+ end
248
+
249
+ end
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,35 @@
1
+ require File.dirname(__FILE__) + '/../lib/fill'
2
+
3
+ Spec::Runner.configure do |config|
4
+ config.mock_with :rr
5
+ end
6
+
7
+ def produce!(*args)
8
+ args = [:projects] if args.empty?
9
+ Fill.database do |db|
10
+ db.produce *args do
11
+ yield if block_given?
12
+ end
13
+ end
14
+ end
15
+
16
+ class ActiveRecordMimic
17
+ class << self
18
+ attr_reader :delete_all, :count, :create, :human_name
19
+ end
20
+ end
21
+
22
+ class Output
23
+ class << self
24
+ attr_accessor :output
25
+ def puts(string)
26
+ @output ||= []
27
+ @output << string.to_s
28
+ end
29
+ end
30
+ end
31
+ Fill.out = Output
32
+
33
+ class User < ActiveRecordMimic; end
34
+ class Project < ActiveRecordMimic; end
35
+ class Membership < ActiveRecordMimic; end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fill
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - iain
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-25 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Simple DSL for filling (seeding) your database
17
+ email: iain@iain.nl
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - lib/fill.rb
25
+ - lib/fill/configure.rb
26
+ - lib/fill/presenter.rb
27
+ - lib/fill/procedure.rb
28
+ files:
29
+ - Manifest
30
+ - README.rdoc
31
+ - Rakefile
32
+ - VERSION
33
+ - fill.gemspec
34
+ - lib/fill.rb
35
+ - lib/fill/configure.rb
36
+ - lib/fill/presenter.rb
37
+ - lib/fill/procedure.rb
38
+ - spec/fill_spec.rb
39
+ - spec/spec.opts
40
+ - spec/spec_helper.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/iain/fill
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --line-numbers
48
+ - --inline-source
49
+ - --title
50
+ - Fill
51
+ - --main
52
+ - README.rdoc
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "1.2"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: fill
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Simple DSL for filling (seeding) your database
74
+ test_files: []
75
+