populator 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +19 -0
- data/LICENSE +20 -0
- data/Manifest +26 -0
- data/README +86 -0
- data/Rakefile +14 -0
- data/TODO +22 -0
- data/lib/populator.rb +8 -0
- data/lib/populator/adapters/abstract.rb +18 -0
- data/lib/populator/adapters/sqlite.rb +26 -0
- data/lib/populator/factory.rb +77 -0
- data/lib/populator/model_additions.rb +11 -0
- data/lib/populator/random.rb +52 -0
- data/lib/populator/record.rb +33 -0
- data/populator.gemspec +106 -0
- data/spec/README +23 -0
- data/spec/database.yml +13 -0
- data/spec/example_database.yml +20 -0
- data/spec/models/category.rb +15 -0
- data/spec/models/product.rb +22 -0
- data/spec/populator/factory_spec.rb +69 -0
- data/spec/populator/model_additions_spec.rb +34 -0
- data/spec/populator/random_spec.rb +45 -0
- data/spec/populator/record_spec.rb +44 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +48 -0
- data/tasks/deployment.rake +2 -0
- data/tasks/spec.rake +22 -0
- metadata +105 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
*0.2.1* (August 30th, 2008)
|
2
|
+
|
3
|
+
* wrap sqlite inserts in transaction to improve performance
|
4
|
+
|
5
|
+
* default created_at/on and updated_at/on columns to current time
|
6
|
+
|
7
|
+
*0.2.0* (August 30th, 2008)
|
8
|
+
|
9
|
+
* adding :per_query option to limit how many inserts are made per query
|
10
|
+
|
11
|
+
* improving performance when nesting factories
|
12
|
+
|
13
|
+
* adding Populate.sentences to generate a lot of text
|
14
|
+
|
15
|
+
* adding Populate.words to fetch some random words
|
16
|
+
|
17
|
+
*0.1.0* (August 27th, 2008)
|
18
|
+
|
19
|
+
* initial release
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Ryan Bates
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
lib/populator/adapters/abstract.rb
|
3
|
+
lib/populator/adapters/sqlite.rb
|
4
|
+
lib/populator/factory.rb
|
5
|
+
lib/populator/model_additions.rb
|
6
|
+
lib/populator/random.rb
|
7
|
+
lib/populator/record.rb
|
8
|
+
lib/populator.rb
|
9
|
+
LICENSE
|
10
|
+
Manifest
|
11
|
+
populator.gemspec
|
12
|
+
README
|
13
|
+
spec/database.yml
|
14
|
+
spec/example_database.yml
|
15
|
+
spec/models/category.rb
|
16
|
+
spec/models/product.rb
|
17
|
+
spec/populator/factory_spec.rb
|
18
|
+
spec/populator/model_additions_spec.rb
|
19
|
+
spec/populator/random_spec.rb
|
20
|
+
spec/populator/record_spec.rb
|
21
|
+
spec/README
|
22
|
+
spec/spec.opts
|
23
|
+
spec/spec_helper.rb
|
24
|
+
tasks/deployment.rake
|
25
|
+
tasks/spec.rake
|
26
|
+
TODO
|
data/README
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
= Populator
|
2
|
+
|
3
|
+
Populate an Active Record database with mass insert.
|
4
|
+
|
5
|
+
Special thanks to Zach Dennis for his ar-extensions gem which some of
|
6
|
+
this code is loosely based on.
|
7
|
+
|
8
|
+
|
9
|
+
== Install
|
10
|
+
|
11
|
+
Install the gem:
|
12
|
+
|
13
|
+
gem install ryanb-populator --source http://gems.github.com
|
14
|
+
|
15
|
+
And then load it in your project:
|
16
|
+
|
17
|
+
require 'populator'
|
18
|
+
|
19
|
+
|
20
|
+
== Usage
|
21
|
+
|
22
|
+
This gem adds a "populate" method to all Active Record models. Pass the
|
23
|
+
number of records you want to create along with a block. In the block
|
24
|
+
you can set the column values for each record.
|
25
|
+
|
26
|
+
Person.populate(3000) do |person|
|
27
|
+
person.first_name = "John"
|
28
|
+
person.last_name = "Smith"
|
29
|
+
end
|
30
|
+
|
31
|
+
This will do a mass insert into the database so it is very fast.
|
32
|
+
The person object contains the "id" so you can set up associations.
|
33
|
+
|
34
|
+
Person.populate(3000) do |person|
|
35
|
+
person.first_name = "John"
|
36
|
+
person.last_name = "Smith"
|
37
|
+
Project.populate(30) do |project|
|
38
|
+
project.person_id = person.id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
That will create 30 projects for each person.
|
43
|
+
|
44
|
+
Passing an range or array of values will randomly select one.
|
45
|
+
|
46
|
+
Person.populate(1000..5000) do |person|
|
47
|
+
person.gender = ['male', 'female']
|
48
|
+
person.annual_income = 10000..200000
|
49
|
+
end
|
50
|
+
|
51
|
+
This will create 1000 to 5000 men or women with the annual income
|
52
|
+
between 10,000 and 200,000.
|
53
|
+
|
54
|
+
You can pass a :per_query option to limit how many records are saved
|
55
|
+
per query. This defaults to 1000.
|
56
|
+
|
57
|
+
Person.populate(2000, :per_query => 100)
|
58
|
+
|
59
|
+
If you need to generate fake data, there are a few methods to do this.
|
60
|
+
|
61
|
+
Populator.words(3) # generates 3 random words separated by spaces
|
62
|
+
Populator.words(10..20) # generates between 10 and 20 random words
|
63
|
+
Populator.sentences(5) # generates 5 sentences
|
64
|
+
|
65
|
+
For fancier data generation, try the Faker gem.
|
66
|
+
|
67
|
+
http://faker.rubyforge.org
|
68
|
+
|
69
|
+
|
70
|
+
== Important
|
71
|
+
|
72
|
+
For performance reasons, this gem does not use actual instances of the
|
73
|
+
model. This means validations and callbacks are bypassed. It is up to
|
74
|
+
you to ensure you're adding valid data.
|
75
|
+
|
76
|
+
|
77
|
+
== Development
|
78
|
+
|
79
|
+
See spec/README for instructions on running specs.
|
80
|
+
|
81
|
+
This project can be found on github at the following URL.
|
82
|
+
|
83
|
+
http://github.com/ryanb/populator/
|
84
|
+
|
85
|
+
If you would like to contribute to this project, please fork the
|
86
|
+
repository and send me a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('populator', '0.2.1') do |p|
|
6
|
+
p.summary = "Mass populate an Active Record database."
|
7
|
+
p.description = "Mass populate an Active Record database."
|
8
|
+
p.url = "http://github.com/ryanb/populator"
|
9
|
+
p.author = 'Ryan Bates'
|
10
|
+
p.email = "ryan (at) railscasts (dot) com"
|
11
|
+
p.ignore_pattern = ["script/*", "**/*.sqlite3"]
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/TODO
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Features
|
2
|
+
- get single table inheritance working.
|
3
|
+
|
4
|
+
Possible
|
5
|
+
- iterate through array instead of selecting at random to ensure all values are chosen
|
6
|
+
- if hash is passed, apply given value that number of times: { 'foo' => 3, 'bar' => 2 }
|
7
|
+
- randomly fill every column if no block is passed to populate
|
8
|
+
- add random_foo method to record for randomly generating content
|
9
|
+
|
10
|
+
Performance
|
11
|
+
- ruby-prof to see where the bottlenecks are
|
12
|
+
- benchmark to find faster solutions
|
13
|
+
- ensure instantiating the model is really that much of a performance hit.
|
14
|
+
|
15
|
+
Support Environments
|
16
|
+
- sqlite 2
|
17
|
+
- old rails
|
18
|
+
- edge rails
|
19
|
+
|
20
|
+
Tests
|
21
|
+
- automatically detect schema changes and rebuild database
|
22
|
+
- add db:reset, db:drop and db:create rake tasks
|
data/lib/populator.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Populator
|
2
|
+
module Adapters
|
3
|
+
module Abstract
|
4
|
+
# Executes multiple SQL statements in one query when joined with ";"
|
5
|
+
def execute_batch(sql, name = nil)
|
6
|
+
raise NotImplementedError, "execute_batch is an abstract method"
|
7
|
+
end
|
8
|
+
|
9
|
+
def populate(table, columns, rows, name = nil)
|
10
|
+
execute("INSERT INTO #{table} #{columns} VALUES #{rows.join(', ')}", name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ActiveRecord::ConnectionAdapters::AbstractAdapter
|
17
|
+
include Populator::Adapters::Abstract
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Populator
|
2
|
+
module Adapters
|
3
|
+
module Sqlite
|
4
|
+
# Executes multiple SQL statements in one query when joined with ";"
|
5
|
+
def execute_batch(sql, name = nil)
|
6
|
+
catch_schema_changes do
|
7
|
+
log(sql, name) do
|
8
|
+
@connection.transaction { |db| db.execute_batch(sql) }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def populate(table, columns, rows, name = nil)
|
14
|
+
queries = []
|
15
|
+
rows.each do |row|
|
16
|
+
queries << "INSERT INTO #{table} #{columns} VALUES #{row}"
|
17
|
+
end
|
18
|
+
execute_batch(queries.join(';'), name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ActiveRecord::ConnectionAdapters::SQLiteAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
|
25
|
+
include Populator::Adapters::Sqlite
|
26
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Populator
|
2
|
+
class Factory
|
3
|
+
DEFAULT_RECORDS_PER_QUERY = 1000
|
4
|
+
|
5
|
+
@factories = {}
|
6
|
+
@depth = 0
|
7
|
+
|
8
|
+
def self.for_model(model_class)
|
9
|
+
@factories[model_class] ||= new(model_class)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.save_remaining_records
|
13
|
+
@factories.values.each do |factory|
|
14
|
+
factory.save_records
|
15
|
+
end
|
16
|
+
@factories = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.remember_depth
|
20
|
+
@depth += 1
|
21
|
+
yield
|
22
|
+
@depth -= 1
|
23
|
+
save_remaining_records if @depth.zero?
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(model_class)
|
27
|
+
@model_class = model_class
|
28
|
+
@records = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def populate(amount, options = {}, &block)
|
32
|
+
self.class.remember_depth do
|
33
|
+
build_records(Populator.interpret_value(amount), options[:per_query] || DEFAULT_RECORDS_PER_QUERY, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_records(amount, per_query, &block)
|
38
|
+
amount.times do
|
39
|
+
record = Record.new(@model_class, last_id_in_database + @records.size + 1)
|
40
|
+
@records << record
|
41
|
+
block.call(record) if block
|
42
|
+
save_records if @records.size >= per_query
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def save_records
|
47
|
+
unless @records.empty?
|
48
|
+
@model_class.connection.populate(@model_class.quoted_table_name, columns_sql, rows_sql_arr, "#{@model_class.name} Populate")
|
49
|
+
@last_id_in_database = @records.last.id
|
50
|
+
@records.clear
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def quoted_column_names
|
57
|
+
@model_class.column_names.map do |column_name|
|
58
|
+
@model_class.connection.quote_column_name(column_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def last_id_in_database
|
63
|
+
@last_id_in_database ||= @model_class.connection.select_value("SELECT id FROM #{@model_class.quoted_table_name} ORDER BY id DESC", "#{@model_class.name} Last ID").to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
def columns_sql
|
67
|
+
"(#{quoted_column_names.join(', ')})"
|
68
|
+
end
|
69
|
+
|
70
|
+
def rows_sql_arr
|
71
|
+
@records.map do |record|
|
72
|
+
quoted_attributes = record.attribute_values.map { |v| @model_class.sanitize(v) }
|
73
|
+
"(#{quoted_attributes.join(', ')})"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Populator
|
2
|
+
module Random
|
3
|
+
WORDS = %w(alias consequatur aut perferendis sit voluptatem accusantium doloremque aperiam eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo aspernatur aut odit aut fugit sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt neque dolorem ipsum quia dolor sit amet consectetur adipisci velit sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem ut enim ad minima veniam quis nostrum exercitationem ullam corporis nemo enim ipsam voluptatem quia voluptas sit suscipit laboriosam nisi ut aliquid ex ea commodi consequatur quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae et iusto odio dignissimos ducimus qui blanditiis praesentium laudantium totam rem voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident sed ut perspiciatis unde omnis iste natus error similique sunt in culpa qui officia deserunt mollitia animi id est laborum et dolorum fuga et harum quidem rerum facilis est et expedita distinctio nam libero tempore cum soluta nobis est eligendi optio cumque nihil impedit quo porro quisquam est qui minus id quod maxime placeat facere possimus omnis voluptas assumenda est omnis dolor repellendus temporibus autem quibusdam et aut consequatur vel illum qui dolorem eum fugiat quo voluptas nulla pariatur at vero eos et accusamus officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae itaque earum rerum hic tenetur a sapiente delectus ut aut reiciendis voluptatibus maiores doloribus asperiores repellat)
|
4
|
+
|
5
|
+
def value_in_range(range)
|
6
|
+
case range.first
|
7
|
+
when Integer then number_in_range(range)
|
8
|
+
when Time then time_in_range(range)
|
9
|
+
when Date then date_in_range(range)
|
10
|
+
else range.to_a.rand
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def words(total)
|
15
|
+
(1..interpret_value(total)).map { WORDS.rand }.join(' ')
|
16
|
+
end
|
17
|
+
|
18
|
+
def sentences(total)
|
19
|
+
(1..interpret_value(total)).map do
|
20
|
+
words(5..20).capitalize
|
21
|
+
end.join('. ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def interpret_value(value)
|
25
|
+
case value
|
26
|
+
when Array then value.rand
|
27
|
+
when Range then value_in_range(value)
|
28
|
+
else value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def time_in_range(range)
|
35
|
+
Time.at number_in_range(Range.new(range.first.to_i, range.last.to_i, range.exclude_end?))
|
36
|
+
end
|
37
|
+
|
38
|
+
def date_in_range(range)
|
39
|
+
Date.jd number_in_range(Range.new(range.first.jd, range.last.jd, range.exclude_end?))
|
40
|
+
end
|
41
|
+
|
42
|
+
def number_in_range(range)
|
43
|
+
if range.exclude_end?
|
44
|
+
rand(range.last - range.first) + range.first
|
45
|
+
else
|
46
|
+
rand((range.last+1) - range.first) + range.first
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
extend Random # load it into the populator module directly so we can call the methods
|
52
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Populator
|
2
|
+
class Record
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
def initialize(model_class, id)
|
6
|
+
@attributes = { :id => id }
|
7
|
+
@columns = model_class.column_names
|
8
|
+
@columns.each do |column|
|
9
|
+
if column == 'created_at' || column == 'updated_at'
|
10
|
+
@attributes[column.to_sym] = Time.now
|
11
|
+
end
|
12
|
+
if column == 'created_on' || column == 'updated_on'
|
13
|
+
@attributes[column.to_sym] = Date.today
|
14
|
+
end
|
15
|
+
self.instance_eval <<-EOS
|
16
|
+
def #{column}=(value)
|
17
|
+
@attributes[:#{column}] = Populator.interpret_value(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def #{column}
|
21
|
+
@attributes[:#{column}]
|
22
|
+
end
|
23
|
+
EOS
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute_values
|
28
|
+
@columns.map do |column|
|
29
|
+
@attributes[column.to_sym]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/populator.gemspec
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
|
2
|
+
# Gem::Specification for Populator-0.2.1
|
3
|
+
# Originally generated by Echoe
|
4
|
+
|
5
|
+
--- !ruby/object:Gem::Specification
|
6
|
+
name: populator
|
7
|
+
version: !ruby/object:Gem::Version
|
8
|
+
version: 0.2.1
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Ryan Bates
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
|
15
|
+
date: 2008-09-01 00:00:00 -07:00
|
16
|
+
default_executable:
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: echoe
|
20
|
+
type: :development
|
21
|
+
version_requirement:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: "0"
|
27
|
+
version:
|
28
|
+
description: Mass populate an Active Record database.
|
29
|
+
email: ryan (at) railscasts (dot) com
|
30
|
+
executables: []
|
31
|
+
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files:
|
35
|
+
- CHANGELOG
|
36
|
+
- lib/populator/adapters/abstract.rb
|
37
|
+
- lib/populator/adapters/sqlite.rb
|
38
|
+
- lib/populator/factory.rb
|
39
|
+
- lib/populator/model_additions.rb
|
40
|
+
- lib/populator/random.rb
|
41
|
+
- lib/populator/record.rb
|
42
|
+
- lib/populator.rb
|
43
|
+
- LICENSE
|
44
|
+
- README
|
45
|
+
- tasks/deployment.rake
|
46
|
+
- tasks/spec.rake
|
47
|
+
- TODO
|
48
|
+
files:
|
49
|
+
- CHANGELOG
|
50
|
+
- lib/populator/adapters/abstract.rb
|
51
|
+
- lib/populator/adapters/sqlite.rb
|
52
|
+
- lib/populator/factory.rb
|
53
|
+
- lib/populator/model_additions.rb
|
54
|
+
- lib/populator/random.rb
|
55
|
+
- lib/populator/record.rb
|
56
|
+
- lib/populator.rb
|
57
|
+
- LICENSE
|
58
|
+
- Manifest
|
59
|
+
- populator.gemspec
|
60
|
+
- README
|
61
|
+
- spec/database.yml
|
62
|
+
- spec/example_database.yml
|
63
|
+
- spec/models/category.rb
|
64
|
+
- spec/models/product.rb
|
65
|
+
- spec/populator/factory_spec.rb
|
66
|
+
- spec/populator/model_additions_spec.rb
|
67
|
+
- spec/populator/random_spec.rb
|
68
|
+
- spec/populator/record_spec.rb
|
69
|
+
- spec/README
|
70
|
+
- spec/spec.opts
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- tasks/deployment.rake
|
73
|
+
- tasks/spec.rake
|
74
|
+
- TODO
|
75
|
+
- Rakefile
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/ryanb/populator
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options:
|
80
|
+
- --line-numbers
|
81
|
+
- --inline-source
|
82
|
+
- --title
|
83
|
+
- Populator
|
84
|
+
- --main
|
85
|
+
- README
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - "="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "1.2"
|
99
|
+
version:
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project: populator
|
103
|
+
rubygems_version: 1.2.0
|
104
|
+
specification_version: 2
|
105
|
+
summary: Mass populate an Active Record database.
|
106
|
+
test_files: []
|
data/spec/README
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Running Specs
|
2
|
+
-------------
|
3
|
+
|
4
|
+
To prepare the specs, run this command.
|
5
|
+
|
6
|
+
script/setup
|
7
|
+
|
8
|
+
This will generate the spec/database.yml file. Configure this to work
|
9
|
+
with your databases. You can add and remove entries here as well to
|
10
|
+
test different databases. A rake task is available to run the specs for
|
11
|
+
each database.
|
12
|
+
|
13
|
+
rake spec # Run specs under all databases
|
14
|
+
rake spec:mysql # Run specs under mysql
|
15
|
+
rake spec:sqlite3 # Run specs under sqlite3
|
16
|
+
rake spec:postgresql # Run specs under postgresql
|
17
|
+
...
|
18
|
+
|
19
|
+
Don't forget to create the user and database as necessary. You can do
|
20
|
+
so under MySQL with these commands.
|
21
|
+
|
22
|
+
CREATE DATABASE populator_test;
|
23
|
+
GRANT ALL ON populator_test.* TO populator@localhost;
|
data/spec/database.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# you can add and remove entries to support different databases in tests.
|
2
|
+
|
3
|
+
sqlite3:
|
4
|
+
adapter: sqlite3
|
5
|
+
database: spec/test.sqlite3
|
6
|
+
timeout: 5000
|
7
|
+
|
8
|
+
mysql:
|
9
|
+
adapter: mysql
|
10
|
+
database: populator_test
|
11
|
+
username: populator
|
12
|
+
password:
|
13
|
+
host: localhost
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# you can add and remove entries to support different databases in tests.
|
2
|
+
|
3
|
+
sqlite3:
|
4
|
+
adapter: sqlite3
|
5
|
+
database: spec/test.sqlite3
|
6
|
+
timeout: 5000
|
7
|
+
|
8
|
+
mysql:
|
9
|
+
adapter: mysql
|
10
|
+
database: populator_test
|
11
|
+
username: populator
|
12
|
+
password:
|
13
|
+
host: localhost
|
14
|
+
|
15
|
+
postgresql:
|
16
|
+
adapter: postgresql
|
17
|
+
database: populator_test
|
18
|
+
username: populator
|
19
|
+
password: populator
|
20
|
+
host: localhost
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Category < ActiveRecord::Base
|
2
|
+
has_many :products
|
3
|
+
end
|
4
|
+
|
5
|
+
class CreateCategories < ActiveRecord::Migration
|
6
|
+
def self.up
|
7
|
+
create_table :categories do |t|
|
8
|
+
t.string :name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :categories
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Product < ActiveRecord::Base
|
2
|
+
belongs_to :category
|
3
|
+
end
|
4
|
+
|
5
|
+
class CreateProducts < ActiveRecord::Migration
|
6
|
+
def self.up
|
7
|
+
create_table :products do |t|
|
8
|
+
t.string :name
|
9
|
+
t.text :description
|
10
|
+
t.integer :stock
|
11
|
+
t.float :weight
|
12
|
+
t.decimal :price
|
13
|
+
t.datetime :released_at
|
14
|
+
t.boolean :hidden
|
15
|
+
t.integer :category_id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.down
|
20
|
+
drop_table :products
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Populator::Factory do
|
4
|
+
describe "for products" do
|
5
|
+
before(:each) do
|
6
|
+
@factory = Populator::Factory.for_model(Product)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should only use one query when inserting records" do
|
10
|
+
$queries_executed = []
|
11
|
+
@factory.populate(5)
|
12
|
+
$queries_executed.grep(/^insert/i).should have(1).record
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should start id at 1 and increment when table is empty" do
|
16
|
+
Product.delete_all
|
17
|
+
expected_id = 1
|
18
|
+
@factory.populate(5) do |product|
|
19
|
+
product.id.should == expected_id
|
20
|
+
expected_id += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should start id at last id and increment" do
|
25
|
+
Product.delete_all
|
26
|
+
product = Product.create
|
27
|
+
expected_id = product.id+1
|
28
|
+
@factory.populate(5) do |product|
|
29
|
+
product.id.should == expected_id
|
30
|
+
expected_id += 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should generate within range" do
|
35
|
+
Product.delete_all
|
36
|
+
@factory.populate(2..4)
|
37
|
+
Product.count.should >= 2
|
38
|
+
Product.count.should <= 4
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should limit number of records per query" do
|
42
|
+
$queries_executed = []
|
43
|
+
@factory.populate(5, :per_query => 2)
|
44
|
+
$queries_executed.grep(/^insert/i).should have(3).records
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should only use two queries when nesting factories (one for each class)" do
|
49
|
+
$queries_executed = []
|
50
|
+
Populator::Factory.for_model(Category).populate(3) do |category|
|
51
|
+
Populator::Factory.for_model(Product).populate(3) do |product|
|
52
|
+
product.category_id = category.id
|
53
|
+
end
|
54
|
+
end
|
55
|
+
$queries_executed.grep(/^insert/i).should have(2).records
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should only use one query when nesting factories of the same type" do
|
59
|
+
$queries_executed = []
|
60
|
+
Populator::Factory.for_model(Product).populate(3) do |product|
|
61
|
+
Populator::Factory.for_model(Product).populate(3)
|
62
|
+
end
|
63
|
+
$queries_executed.grep(/^insert/i).should have(1).record
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should default to 1000 records per query" do
|
67
|
+
Populator::Factory::DEFAULT_RECORDS_PER_QUERY.should == 1000
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Populator::ModelAdditions do
|
4
|
+
it "should add populate method to active record class" do
|
5
|
+
Product.should respond_to(:populate)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should add 10 records to database" do
|
9
|
+
Product.delete_all
|
10
|
+
Product.populate(10)
|
11
|
+
Product.count.should == 10
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should set attribute columns" do
|
15
|
+
Product.populate(1) do |product|
|
16
|
+
product.name = "foo"
|
17
|
+
end
|
18
|
+
Product.last.name.should == "foo"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not pass in an instance of Active Record for performance reasons" do
|
22
|
+
Product.populate(1) do |product|
|
23
|
+
product.should_not be_kind_of(ActiveRecord::Base)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not pass options hash" do
|
28
|
+
$queries_executed = []
|
29
|
+
Product.populate(5, :per_query => 2) do |product|
|
30
|
+
product.should_not be_kind_of(ActiveRecord::Base)
|
31
|
+
end
|
32
|
+
$queries_executed.grep(/^insert/i).should have(3).records
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Populator::Random do
|
4
|
+
it "should pick a random number in range excluding last value" do
|
5
|
+
Populator.expects(:rand).with(5).returns(3)
|
6
|
+
Populator.value_in_range(10...15).should == 13
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should pick a random number in range including last value" do
|
10
|
+
Populator.expects(:rand).with(5).returns(3)
|
11
|
+
Populator.value_in_range(10..14).should == 13
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should pick a random time in range" do
|
15
|
+
start_time = 2.days.ago
|
16
|
+
end_time = Time.now
|
17
|
+
Populator.expects(:rand).with(end_time.to_i-start_time.to_i).returns(1)
|
18
|
+
Populator.value_in_range(start_time...end_time).should == Time.at(start_time.to_i + 1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should pick a random date in range" do
|
22
|
+
start_date = 2.years.ago.to_date
|
23
|
+
end_date = Date.today
|
24
|
+
Populator.expects(:rand).with(end_date.jd-start_date.jd).returns(1)
|
25
|
+
Populator.value_in_range(start_date...end_date).should == Date.jd(start_date.jd + 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should pick a random string by converting to array" do
|
29
|
+
Kernel.expects(:rand).with(5).returns(2)
|
30
|
+
Populator.value_in_range('a'..'e').should == 'c'
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should pick 3 random words" do
|
34
|
+
Populator.words(3).split.should have(3).records
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should pick a random number of random words" do
|
38
|
+
Populator.expects(:rand).with(5).returns(3)
|
39
|
+
Populator.words(10...15).split.should have(13).records
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should generate 3 random sentences" do
|
43
|
+
Populator.sentences(3).split(/\. [A-Z]/).should have(3).records
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Populator::Record do
|
4
|
+
it "should have a writer and reader methods for each column" do
|
5
|
+
record = Populator::Record.new(Product, 1)
|
6
|
+
Product.column_names.each do |column|
|
7
|
+
record.send("#{column}=", "foo")
|
8
|
+
record.send(column).should == "foo"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return attribute values in same order as columns" do
|
13
|
+
record = Populator::Record.new(Product, nil)
|
14
|
+
record.name = "foo"
|
15
|
+
expected = Product.column_names.map { |c| "foo" if c == 'name' }
|
16
|
+
record.attribute_values.should == expected
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should assign second parameter to id" do
|
20
|
+
Populator::Record.new(Product, 2).id.should == 2
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should pick random number from range" do
|
24
|
+
record = Populator::Record.new(Product, 1)
|
25
|
+
record.stock = 2..5
|
26
|
+
record.stock.should >= 2
|
27
|
+
record.stock.should <= 5
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should pick random value from array" do
|
31
|
+
record = Populator::Record.new(Product, 1)
|
32
|
+
record.name = %w[foo bar]
|
33
|
+
%w[foo bar].should include(record.name)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should automatically set created/updated columns" do
|
37
|
+
Product.stubs(:column_names).returns(%w[id created_at updated_at created_on updated_on])
|
38
|
+
record = Populator::Record.new(Product, 1)
|
39
|
+
record.created_at.to_date.should == Date.today
|
40
|
+
record.updated_at.to_date.should == Date.today
|
41
|
+
record.created_on.should == Date.today
|
42
|
+
record.updated_on.should == Date.today
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_record'
|
5
|
+
require File.dirname(__FILE__) + '/../lib/populator.rb'
|
6
|
+
|
7
|
+
adapter = ENV['POPULATOR_ADAPTER'] || 'sqlite3'
|
8
|
+
puts "Running specs on #{adapter}"
|
9
|
+
|
10
|
+
# setup database adapter
|
11
|
+
ActiveRecord::Base.establish_connection(
|
12
|
+
YAML.load(File.read(File.dirname(__FILE__) + "/database.yml"))[adapter]
|
13
|
+
)
|
14
|
+
|
15
|
+
# keep track of which queries have been executed
|
16
|
+
unless ActiveRecord::Base.connection.respond_to? :record_query
|
17
|
+
ActiveRecord::Base.connection.class.class_eval do
|
18
|
+
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^begin /i, /^commit /i]
|
19
|
+
|
20
|
+
def record_query(sql)
|
21
|
+
$queries_executed ||= []
|
22
|
+
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute_with_query_record(*args, &block)
|
26
|
+
record_query(args.first)
|
27
|
+
execute_without_query_record(*args, &block)
|
28
|
+
end
|
29
|
+
alias_method_chain :execute, :query_record
|
30
|
+
|
31
|
+
def execute_batch_with_query_record(*args, &block)
|
32
|
+
record_query(args.first)
|
33
|
+
execute_batch_without_query_record(*args, &block)
|
34
|
+
end
|
35
|
+
alias_method_chain :execute_batch, :query_record
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# load models
|
40
|
+
# there's probably a better way to handle this
|
41
|
+
require File.dirname(__FILE__) + '/models/category.rb'
|
42
|
+
require File.dirname(__FILE__) + '/models/product.rb'
|
43
|
+
CreateCategories.migrate(:up) unless Category.table_exists?
|
44
|
+
CreateProducts.migrate(:up) unless Product.table_exists?
|
45
|
+
|
46
|
+
Spec::Runner.configure do |config|
|
47
|
+
config.mock_with :mocha
|
48
|
+
end
|
data/tasks/spec.rake
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec/rake/spectask'
|
2
|
+
|
3
|
+
ADAPTERS = YAML.load(File.read(File.dirname(__FILE__) + "/../spec/database.yml")).keys
|
4
|
+
|
5
|
+
desc "Run specs under all supported databases"
|
6
|
+
task :spec => ADAPTERS.map { |a| "spec:#{a}" }
|
7
|
+
|
8
|
+
namespace :spec do
|
9
|
+
ADAPTERS.each do |adapter|
|
10
|
+
namespace :prepare do
|
11
|
+
task adapter do
|
12
|
+
ENV["POPULATOR_ADAPTER"] = adapter
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Run specs under #{adapter}"
|
17
|
+
Spec::Rake::SpecTask.new(adapter => "spec:prepare:#{adapter}") do |t|
|
18
|
+
t.spec_files = Rake::FileList["spec/**/*_spec.rb"]
|
19
|
+
t.spec_opts = ["-c"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: populator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Bates
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-09-01 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: echoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Mass populate an Active Record database.
|
26
|
+
email: ryan (at) railscasts (dot) com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- CHANGELOG
|
33
|
+
- lib/populator/adapters/abstract.rb
|
34
|
+
- lib/populator/adapters/sqlite.rb
|
35
|
+
- lib/populator/factory.rb
|
36
|
+
- lib/populator/model_additions.rb
|
37
|
+
- lib/populator/random.rb
|
38
|
+
- lib/populator/record.rb
|
39
|
+
- lib/populator.rb
|
40
|
+
- LICENSE
|
41
|
+
- README
|
42
|
+
- tasks/deployment.rake
|
43
|
+
- tasks/spec.rake
|
44
|
+
- TODO
|
45
|
+
files:
|
46
|
+
- CHANGELOG
|
47
|
+
- lib/populator/adapters/abstract.rb
|
48
|
+
- lib/populator/adapters/sqlite.rb
|
49
|
+
- lib/populator/factory.rb
|
50
|
+
- lib/populator/model_additions.rb
|
51
|
+
- lib/populator/random.rb
|
52
|
+
- lib/populator/record.rb
|
53
|
+
- lib/populator.rb
|
54
|
+
- LICENSE
|
55
|
+
- Manifest
|
56
|
+
- populator.gemspec
|
57
|
+
- README
|
58
|
+
- spec/database.yml
|
59
|
+
- spec/example_database.yml
|
60
|
+
- spec/models/category.rb
|
61
|
+
- spec/models/product.rb
|
62
|
+
- spec/populator/factory_spec.rb
|
63
|
+
- spec/populator/model_additions_spec.rb
|
64
|
+
- spec/populator/random_spec.rb
|
65
|
+
- spec/populator/record_spec.rb
|
66
|
+
- spec/README
|
67
|
+
- spec/spec.opts
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
- tasks/deployment.rake
|
70
|
+
- tasks/spec.rake
|
71
|
+
- TODO
|
72
|
+
- Rakefile
|
73
|
+
has_rdoc: true
|
74
|
+
homepage: http://github.com/ryanb/populator
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- --line-numbers
|
78
|
+
- --inline-source
|
79
|
+
- --title
|
80
|
+
- Populator
|
81
|
+
- --main
|
82
|
+
- README
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
version:
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "1.2"
|
96
|
+
version:
|
97
|
+
requirements: []
|
98
|
+
|
99
|
+
rubyforge_project: populator
|
100
|
+
rubygems_version: 1.2.0
|
101
|
+
signing_key:
|
102
|
+
specification_version: 2
|
103
|
+
summary: Mass populate an Active Record database.
|
104
|
+
test_files: []
|
105
|
+
|