fill 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+