ryanb-populator 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +20 -0
- data/Manifest +7 -0
- data/README +27 -8
- data/Rakefile +14 -0
- data/TODO +20 -0
- data/lib/populator/adapters/abstract.rb +1 -1
- data/lib/populator/adapters/sqlite.rb +12 -3
- data/lib/populator/factory.rb +60 -19
- data/lib/populator/model_additions.rb +24 -3
- data/lib/populator/random.rb +61 -0
- data/lib/populator/record.rb +30 -14
- data/lib/populator.rb +5 -0
- data/populator.gemspec +99 -44
- data/spec/README +13 -6
- data/spec/example_database.yml +20 -0
- data/spec/models/category.rb +15 -0
- data/spec/models/product.rb +9 -7
- data/spec/populator/factory_spec.rb +35 -12
- data/spec/populator/model_additions_spec.rb +8 -0
- data/spec/populator/random_spec.rb +45 -0
- data/spec/populator/record_spec.rb +9 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +5 -2
- data/tasks/spec.rake +1 -1
- metadata +26 -9
- data/spec/database.yml +0 -11
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
* performance improvements
|
2
|
+
|
3
|
+
* improving inline documentation
|
4
|
+
|
5
|
+
*0.2.1* (August 30th, 2008)
|
6
|
+
|
7
|
+
* wrap sqlite inserts in transaction to improve performance
|
8
|
+
|
9
|
+
* default created_at/on and updated_at/on columns to current time
|
10
|
+
|
11
|
+
*0.2.0* (August 30th, 2008)
|
12
|
+
|
13
|
+
* adding :per_query option to limit how many inserts are made per query
|
14
|
+
|
15
|
+
* improving performance when nesting factories
|
16
|
+
|
17
|
+
* adding Populator.sentences to generate a lot of text
|
18
|
+
|
19
|
+
* adding Populator.words to fetch some random words
|
20
|
+
|
1
21
|
*0.1.0* (August 27th, 2008)
|
2
22
|
|
3
23
|
* initial release
|
data/Manifest
CHANGED
@@ -3,18 +3,25 @@ lib/populator/adapters/abstract.rb
|
|
3
3
|
lib/populator/adapters/sqlite.rb
|
4
4
|
lib/populator/factory.rb
|
5
5
|
lib/populator/model_additions.rb
|
6
|
+
lib/populator/random.rb
|
6
7
|
lib/populator/record.rb
|
7
8
|
lib/populator.rb
|
8
9
|
LICENSE
|
9
10
|
Manifest
|
10
11
|
populator.gemspec
|
12
|
+
Rakefile
|
11
13
|
README
|
12
14
|
spec/database.yml
|
15
|
+
spec/example_database.yml
|
16
|
+
spec/models/category.rb
|
13
17
|
spec/models/product.rb
|
14
18
|
spec/populator/factory_spec.rb
|
15
19
|
spec/populator/model_additions_spec.rb
|
20
|
+
spec/populator/random_spec.rb
|
16
21
|
spec/populator/record_spec.rb
|
17
22
|
spec/README
|
23
|
+
spec/spec.opts
|
18
24
|
spec/spec_helper.rb
|
19
25
|
tasks/deployment.rake
|
20
26
|
tasks/spec.rake
|
27
|
+
TODO
|
data/README
CHANGED
@@ -10,7 +10,7 @@ this code is loosely based on.
|
|
10
10
|
|
11
11
|
Install the gem:
|
12
12
|
|
13
|
-
|
13
|
+
gem install populator
|
14
14
|
|
15
15
|
And then load it in your project:
|
16
16
|
|
@@ -19,8 +19,6 @@ And then load it in your project:
|
|
19
19
|
|
20
20
|
== Usage
|
21
21
|
|
22
|
-
STILL IN EARLY DEVELOPMENT - so some of this doesn't work yet.
|
23
|
-
|
24
22
|
This gem adds a "populate" method to all Active Record models. Pass the
|
25
23
|
number of records you want to create along with a block. In the block
|
26
24
|
you can set the column values for each record.
|
@@ -30,10 +28,7 @@ you can set the column values for each record.
|
|
30
28
|
person.last_name = "Smith"
|
31
29
|
end
|
32
30
|
|
33
|
-
This will do a mass insert into the database so it is very fast.
|
34
|
-
performance reasons, "person" is not an instance of the model. It's
|
35
|
-
just a simple interface for setting the column values.
|
36
|
-
|
31
|
+
This will do a mass insert into the database so it is very fast.
|
37
32
|
The person object contains the "id" so you can set up associations.
|
38
33
|
|
39
34
|
Person.populate(3000) do |person|
|
@@ -56,12 +51,36 @@ Passing an range or array of values will randomly select one.
|
|
56
51
|
This will create 1000 to 5000 men or women with the annual income
|
57
52
|
between 10,000 and 200,000.
|
58
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
|
+
|
59
76
|
|
60
77
|
== Development
|
61
78
|
|
79
|
+
See spec/README for instructions on running specs.
|
80
|
+
|
62
81
|
This project can be found on github at the following URL.
|
63
82
|
|
64
|
-
http://github.com/ryanb/populator
|
83
|
+
http://github.com/ryanb/populator
|
65
84
|
|
66
85
|
If you would like to contribute to this project, please fork the
|
67
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,20 @@
|
|
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
|
+
- ensure instantiating the model is really that much of a performance hit.
|
12
|
+
|
13
|
+
Support Environments
|
14
|
+
- sqlite 2
|
15
|
+
- old rails
|
16
|
+
- edge rails
|
17
|
+
|
18
|
+
Tests
|
19
|
+
- automatically detect schema changes and rebuild database
|
20
|
+
- add db:reset, db:drop and db:create rake tasks
|
@@ -3,7 +3,11 @@ module Populator
|
|
3
3
|
module Sqlite
|
4
4
|
# Executes multiple SQL statements in one query when joined with ";"
|
5
5
|
def execute_batch(sql, name = nil)
|
6
|
-
catch_schema_changes
|
6
|
+
catch_schema_changes do
|
7
|
+
log(sql, name) do
|
8
|
+
@connection.transaction { |db| db.execute_batch(sql) }
|
9
|
+
end
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
def populate(table, columns, rows, name = nil)
|
@@ -17,6 +21,11 @@ module Populator
|
|
17
21
|
end
|
18
22
|
end
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
# TODO find a better way to load the SQLite adapter
|
25
|
+
module ActiveRecord # :nodoc: all
|
26
|
+
module ConnectionAdapters
|
27
|
+
class SQLiteAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
|
28
|
+
include Populator::Adapters::Sqlite
|
29
|
+
end
|
30
|
+
end
|
22
31
|
end
|
data/lib/populator/factory.rb
CHANGED
@@ -1,38 +1,79 @@
|
|
1
1
|
module Populator
|
2
|
+
# Builds multiple Populator::Record instances and saves them to the database
|
2
3
|
class Factory
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
DEFAULT_RECORDS_PER_QUERY = 1000
|
5
|
+
|
6
|
+
@factories = {}
|
7
|
+
@depth = 0
|
8
|
+
|
9
|
+
# Fetches the factory dedicated to a given model class. You should always use this
|
10
|
+
# method instead of instatiating a factory directly so that a single factory is
|
11
|
+
# shared on multiple calls.
|
12
|
+
def self.for_model(model_class)
|
13
|
+
@factories[model_class] ||= new(model_class)
|
7
14
|
end
|
8
15
|
|
9
|
-
|
10
|
-
|
11
|
-
|
16
|
+
# Find all remaining factories and call save_records on them.
|
17
|
+
def self.save_remaining_records
|
18
|
+
@factories.values.each do |factory|
|
19
|
+
factory.save_records
|
20
|
+
end
|
21
|
+
@factories = {}
|
12
22
|
end
|
13
23
|
|
14
|
-
|
24
|
+
# Keep track of nested factory calls so we can save the remaining records once we
|
25
|
+
# are done with the base factory. This makes Populator more efficient when nesting
|
26
|
+
# factories.
|
27
|
+
def self.remember_depth
|
28
|
+
@depth += 1
|
29
|
+
yield
|
30
|
+
@depth -= 1
|
31
|
+
save_remaining_records if @depth.zero?
|
32
|
+
end
|
15
33
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
34
|
+
# Use for_model instead of instatiating a record directly.
|
35
|
+
def initialize(model_class)
|
36
|
+
@model_class = model_class
|
37
|
+
@records = []
|
20
38
|
end
|
21
39
|
|
22
|
-
|
23
|
-
|
40
|
+
# Entry method for building records. Delegates to build_records after remember_depth.
|
41
|
+
def populate(amount, options = {}, &block)
|
42
|
+
self.class.remember_depth do
|
43
|
+
build_records(Populator.interpret_value(amount), options[:per_query] || DEFAULT_RECORDS_PER_QUERY, &block)
|
44
|
+
end
|
24
45
|
end
|
25
46
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
47
|
+
# Builds multiple Populator::Record instances and calls save_records them when
|
48
|
+
# :per_query limit option is reached.
|
49
|
+
def build_records(amount, per_query, &block)
|
50
|
+
amount.times do
|
51
|
+
record = Record.new(@model_class, last_id_in_database + @records.size + 1)
|
30
52
|
@records << record
|
53
|
+
block.call(record) if block
|
54
|
+
save_records if @records.size >= per_query
|
31
55
|
end
|
32
56
|
end
|
33
57
|
|
58
|
+
# Saves the records to the database by calling populate on the current database adapter.
|
34
59
|
def save_records
|
35
|
-
|
60
|
+
unless @records.empty?
|
61
|
+
@model_class.connection.populate(@model_class.quoted_table_name, columns_sql, rows_sql_arr, "#{@model_class.name} Populate")
|
62
|
+
@last_id_in_database = @records.last.id
|
63
|
+
@records.clear
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def quoted_column_names
|
70
|
+
@model_class.column_names.map do |column_name|
|
71
|
+
@model_class.connection.quote_column_name(column_name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def last_id_in_database
|
76
|
+
@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
|
36
77
|
end
|
37
78
|
|
38
79
|
def columns_sql
|
@@ -1,11 +1,32 @@
|
|
1
1
|
module Populator
|
2
2
|
module ModelAdditions
|
3
|
-
|
4
|
-
|
3
|
+
# Call populate on any ActiveRecord model to fill it with data.
|
4
|
+
# Pass the number of records you want to create, and a block to
|
5
|
+
# set the attributes. You can nest calls to handle associations
|
6
|
+
# and use ranges or arrays to randomize the values.
|
7
|
+
#
|
8
|
+
# Person.populate(3000) do |person|
|
9
|
+
# person.name = "John Doe"
|
10
|
+
# person.gender = ['male', 'female']
|
11
|
+
# Project.populate(10..30, :per_query => 100) do |project|
|
12
|
+
# project.person_id = person.id
|
13
|
+
# project.due_at = 5.days.from_now..2.years.from_now
|
14
|
+
# project.name = Populator.words(1..3).titleize
|
15
|
+
# project.description = Populator.sentences(2..10)
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# The following options are supported.
|
20
|
+
#
|
21
|
+
# * <tt>:per_query</tt> - limit how many records are inserted per query, defaults to 1000
|
22
|
+
#
|
23
|
+
# Populator::Factory is where all the work happens.
|
24
|
+
def populate(amount, options = {}, &block)
|
25
|
+
Factory.for_model(self).populate(amount, options, &block)
|
5
26
|
end
|
6
27
|
end
|
7
28
|
end
|
8
29
|
|
9
|
-
|
30
|
+
ActiveRecord::Base.class_eval do
|
10
31
|
extend Populator::ModelAdditions
|
11
32
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Populator
|
2
|
+
# This module adds several methods for generating random data which can be
|
3
|
+
# called directly on Populator.
|
4
|
+
module Random
|
5
|
+
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)
|
6
|
+
|
7
|
+
# Pick a random value out of a given range.
|
8
|
+
def value_in_range(range)
|
9
|
+
case range.first
|
10
|
+
when Integer then number_in_range(range)
|
11
|
+
when Time then time_in_range(range)
|
12
|
+
when Date then date_in_range(range)
|
13
|
+
else range.to_a.rand
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Generate a given number of words. If a range is passed, it will generate
|
18
|
+
# a random number of words within that range.
|
19
|
+
def words(total)
|
20
|
+
(1..interpret_value(total)).map { WORDS.rand }.join(' ')
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generate a given number of sentences. If a range is passed, it will generate
|
24
|
+
# a random number of sentences within that range.
|
25
|
+
def sentences(total)
|
26
|
+
(1..interpret_value(total)).map do
|
27
|
+
words(5..20).capitalize
|
28
|
+
end.join('. ')
|
29
|
+
end
|
30
|
+
|
31
|
+
# If an array or range is passed, a random value will be selected to match.
|
32
|
+
# All other values are simply returned.
|
33
|
+
def interpret_value(value)
|
34
|
+
case value
|
35
|
+
when Array then value.rand
|
36
|
+
when Range then value_in_range(value)
|
37
|
+
else value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def time_in_range(range)
|
44
|
+
Time.at number_in_range(Range.new(range.first.to_i, range.last.to_i, range.exclude_end?))
|
45
|
+
end
|
46
|
+
|
47
|
+
def date_in_range(range)
|
48
|
+
Date.jd number_in_range(Range.new(range.first.jd, range.last.jd, range.exclude_end?))
|
49
|
+
end
|
50
|
+
|
51
|
+
def number_in_range(range)
|
52
|
+
if range.exclude_end?
|
53
|
+
rand(range.last - range.first) + range.first
|
54
|
+
else
|
55
|
+
rand((range.last+1) - range.first) + range.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
extend Random # load it into the populator module directly so we can call the methods
|
61
|
+
end
|
data/lib/populator/record.rb
CHANGED
@@ -1,23 +1,34 @@
|
|
1
1
|
module Populator
|
2
|
+
# This is what is passed to the block when calling populate.
|
2
3
|
class Record
|
3
4
|
attr_accessor :attributes
|
4
5
|
|
6
|
+
# Creates a new instance of Record. Some attributes are set by default:
|
7
|
+
#
|
8
|
+
# * <tt>id</tt> - defaults to id passed
|
9
|
+
# * <tt>created_at</tt> - defaults to current time
|
10
|
+
# * <tt>updated_at</tt> - defaults to current time
|
11
|
+
# * <tt>created_on</tt> - defaults to current date
|
12
|
+
# * <tt>updated_on</tt> - defaults to current date
|
5
13
|
def initialize(model_class, id)
|
6
14
|
@attributes = { :id => id }
|
7
15
|
@columns = model_class.column_names
|
8
16
|
@columns.each do |column|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@attributes[:#{column}]
|
16
|
-
end
|
17
|
-
EOS
|
17
|
+
if column == 'created_at' || column == 'updated_at'
|
18
|
+
@attributes[column.to_sym] = Time.now
|
19
|
+
end
|
20
|
+
if column == 'created_on' || column == 'updated_on'
|
21
|
+
@attributes[column.to_sym] = Date.today
|
22
|
+
end
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
26
|
+
# override id since method_missing won't catch this column name
|
27
|
+
def id
|
28
|
+
@attributes[:id]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return values for all columns inside an array.
|
21
32
|
def attribute_values
|
22
33
|
@columns.map do |column|
|
23
34
|
@attributes[column.to_sym]
|
@@ -26,11 +37,16 @@ module Populator
|
|
26
37
|
|
27
38
|
private
|
28
39
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
40
|
+
def method_missing(sym, *args, &block)
|
41
|
+
name = sym.to_s
|
42
|
+
if @columns.include?(name.sub('=', ''))
|
43
|
+
if name.include? '='
|
44
|
+
@attributes[name.sub('=', '').to_sym] = Populator.interpret_value(args.first)
|
45
|
+
else
|
46
|
+
@attributes[sym]
|
47
|
+
end
|
48
|
+
else
|
49
|
+
super
|
34
50
|
end
|
35
51
|
end
|
36
52
|
end
|
data/lib/populator.rb
CHANGED
@@ -2,6 +2,11 @@ $:.unshift(File.dirname(__FILE__))
|
|
2
2
|
require 'populator/model_additions'
|
3
3
|
require 'populator/factory'
|
4
4
|
require 'populator/record'
|
5
|
+
require 'populator/random'
|
5
6
|
|
6
7
|
require 'populator/adapters/abstract'
|
7
8
|
require 'populator/adapters/sqlite'
|
9
|
+
|
10
|
+
# Populator is made up of several parts. To start, see Populator::ModelAdditions.
|
11
|
+
module Populator
|
12
|
+
end
|
data/populator.gemspec
CHANGED
@@ -1,51 +1,106 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Populator-0.1
|
2
|
+
# Gem::Specification for Populator-0.2.1
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
|
-
Gem::Specification
|
6
|
-
|
7
|
-
|
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
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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: []
|
23
31
|
|
24
|
-
|
25
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
-
s.specification_version = 2
|
32
|
+
extensions: []
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
+
- Rakefile
|
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
|
+
- populator.gemspec
|
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: []
|
34
101
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# require 'echoe'
|
41
|
-
#
|
42
|
-
# Echoe.new('populator', '0.1.0') do |p|
|
43
|
-
# p.summary = "Mass populate an Active Record database."
|
44
|
-
# p.description = "Mass populate an Active Record database."
|
45
|
-
# p.url = "http://github.com/ryanb/populator"
|
46
|
-
# p.author = 'Ryan Bates'
|
47
|
-
# p.email = "ryan (at) railscasts (dot) com"
|
48
|
-
# p.ignore_pattern = ["script/*", "**/*.sqlite3"]
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
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
CHANGED
@@ -1,16 +1,23 @@
|
|
1
|
-
Running
|
2
|
-
|
1
|
+
Running Specs
|
2
|
+
-------------
|
3
3
|
|
4
|
-
|
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.
|
5
12
|
|
6
13
|
rake spec # Run specs under all databases
|
7
14
|
rake spec:mysql # Run specs under mysql
|
8
15
|
rake spec:sqlite3 # Run specs under sqlite3
|
16
|
+
rake spec:postgresql # Run specs under postgresql
|
9
17
|
...
|
10
18
|
|
11
|
-
|
12
|
-
|
19
|
+
Don't forget to create the user and database as necessary. You can do
|
20
|
+
so under MySQL with these commands.
|
13
21
|
|
14
22
|
CREATE DATABASE populator_test;
|
15
23
|
GRANT ALL ON populator_test.* TO populator@localhost;
|
16
|
-
|
@@ -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
|
data/spec/models/product.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
class Product < ActiveRecord::Base
|
2
|
+
belongs_to :category
|
2
3
|
end
|
3
4
|
|
4
5
|
class CreateProducts < ActiveRecord::Migration
|
5
6
|
def self.up
|
6
7
|
create_table :products do |t|
|
7
|
-
t.string
|
8
|
-
t.text
|
9
|
-
t.integer
|
10
|
-
t.float
|
11
|
-
t.decimal
|
12
|
-
t.datetime
|
13
|
-
t.boolean
|
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
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
@@ -1,46 +1,69 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
2
|
|
3
3
|
describe Populator::Factory do
|
4
|
-
describe "for
|
4
|
+
describe "for products" do
|
5
5
|
before(:each) do
|
6
|
-
@factory = Populator::Factory.
|
6
|
+
@factory = Populator::Factory.for_model(Product)
|
7
7
|
end
|
8
8
|
|
9
9
|
it "should only use one query when inserting records" do
|
10
10
|
$queries_executed = []
|
11
|
-
@factory.
|
11
|
+
@factory.populate(5)
|
12
12
|
$queries_executed.grep(/^insert/i).should have(1).record
|
13
13
|
end
|
14
14
|
|
15
15
|
it "should start id at 1 and increment when table is empty" do
|
16
16
|
Product.delete_all
|
17
17
|
expected_id = 1
|
18
|
-
@factory.
|
18
|
+
@factory.populate(5) do |product|
|
19
19
|
product.id.should == expected_id
|
20
20
|
expected_id += 1
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
it "should start id at last id and increment" do
|
25
|
+
Product.delete_all
|
25
26
|
product = Product.create
|
26
27
|
expected_id = product.id+1
|
27
|
-
@factory.
|
28
|
+
@factory.populate(5) do |product|
|
28
29
|
product.id.should == expected_id
|
29
30
|
expected_id += 1
|
30
31
|
end
|
31
32
|
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe "between 2 and 4 products" do
|
35
|
-
before(:each) do
|
36
|
-
@factory = Populator::Factory.new(Product, 2..4)
|
37
|
-
end
|
38
33
|
|
39
34
|
it "should generate within range" do
|
40
35
|
Product.delete_all
|
41
|
-
@factory.
|
36
|
+
@factory.populate(2..4)
|
42
37
|
Product.count.should >= 2
|
43
38
|
Product.count.should <= 4
|
44
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
|
45
68
|
end
|
46
69
|
end
|
@@ -23,4 +23,12 @@ describe Populator::ModelAdditions do
|
|
23
23
|
product.should_not be_kind_of(ActiveRecord::Base)
|
24
24
|
end
|
25
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
|
26
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
|
@@ -32,4 +32,13 @@ describe Populator::Record do
|
|
32
32
|
record.name = %w[foo bar]
|
33
33
|
%w[foo bar].should include(record.name)
|
34
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
|
35
44
|
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
CHANGED
@@ -4,11 +4,12 @@ require 'active_support'
|
|
4
4
|
require 'active_record'
|
5
5
|
require File.dirname(__FILE__) + '/../lib/populator.rb'
|
6
6
|
|
7
|
-
ENV['POPULATOR_ADAPTER']
|
7
|
+
adapter = ENV['POPULATOR_ADAPTER'] || 'sqlite3'
|
8
|
+
puts "Running specs on #{adapter}"
|
8
9
|
|
9
10
|
# setup database adapter
|
10
11
|
ActiveRecord::Base.establish_connection(
|
11
|
-
YAML.load(File.read(File.dirname(__FILE__) + "/database.yml"))[
|
12
|
+
YAML.load(File.read(File.dirname(__FILE__) + "/database.yml"))[adapter]
|
12
13
|
)
|
13
14
|
|
14
15
|
# keep track of which queries have been executed
|
@@ -37,7 +38,9 @@ end
|
|
37
38
|
|
38
39
|
# load models
|
39
40
|
# there's probably a better way to handle this
|
41
|
+
require File.dirname(__FILE__) + '/models/category.rb'
|
40
42
|
require File.dirname(__FILE__) + '/models/product.rb'
|
43
|
+
CreateCategories.migrate(:up) unless Category.table_exists?
|
41
44
|
CreateProducts.migrate(:up) unless Product.table_exists?
|
42
45
|
|
43
46
|
Spec::Runner.configure do |config|
|
data/tasks/spec.rake
CHANGED
metadata
CHANGED
@@ -1,18 +1,26 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ryanb-populator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Bates
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
|
-
cert_chain:
|
11
|
-
|
12
|
-
date: 2008-08-28 00:00:00 -07:00
|
10
|
+
cert_chain:
|
11
|
+
date: 2008-09-01 00:00:00 -07:00
|
13
12
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: echoe
|
16
|
+
type: :development
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
16
24
|
description: Mass populate an Active Record database.
|
17
25
|
email: ryan (at) railscasts (dot) com
|
18
26
|
executables: []
|
@@ -25,33 +33,42 @@ extra_rdoc_files:
|
|
25
33
|
- lib/populator/adapters/sqlite.rb
|
26
34
|
- lib/populator/factory.rb
|
27
35
|
- lib/populator/model_additions.rb
|
36
|
+
- lib/populator/random.rb
|
28
37
|
- lib/populator/record.rb
|
29
38
|
- lib/populator.rb
|
30
39
|
- LICENSE
|
31
40
|
- README
|
32
41
|
- tasks/deployment.rake
|
33
42
|
- tasks/spec.rake
|
43
|
+
- TODO
|
34
44
|
files:
|
35
45
|
- CHANGELOG
|
36
46
|
- lib/populator/adapters/abstract.rb
|
37
47
|
- lib/populator/adapters/sqlite.rb
|
38
48
|
- lib/populator/factory.rb
|
39
49
|
- lib/populator/model_additions.rb
|
50
|
+
- lib/populator/random.rb
|
40
51
|
- lib/populator/record.rb
|
41
52
|
- lib/populator.rb
|
42
53
|
- LICENSE
|
43
54
|
- Manifest
|
44
|
-
-
|
55
|
+
- Rakefile
|
45
56
|
- README
|
46
57
|
- spec/database.yml
|
58
|
+
- spec/example_database.yml
|
59
|
+
- spec/models/category.rb
|
47
60
|
- spec/models/product.rb
|
48
61
|
- spec/populator/factory_spec.rb
|
49
62
|
- spec/populator/model_additions_spec.rb
|
63
|
+
- spec/populator/random_spec.rb
|
50
64
|
- spec/populator/record_spec.rb
|
51
65
|
- spec/README
|
66
|
+
- spec/spec.opts
|
52
67
|
- spec/spec_helper.rb
|
53
68
|
- tasks/deployment.rake
|
54
69
|
- tasks/spec.rake
|
70
|
+
- TODO
|
71
|
+
- populator.gemspec
|
55
72
|
has_rdoc: true
|
56
73
|
homepage: http://github.com/ryanb/populator
|
57
74
|
post_install_message:
|
@@ -72,9 +89,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
89
|
version:
|
73
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
91
|
requirements:
|
75
|
-
- - "
|
92
|
+
- - "="
|
76
93
|
- !ruby/object:Gem::Version
|
77
|
-
version: "
|
94
|
+
version: "1.2"
|
78
95
|
version:
|
79
96
|
requirements: []
|
80
97
|
|