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 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
- gem install ryanb-populator --source http://gems.github.com
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. For
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
@@ -13,6 +13,6 @@ module Populator
13
13
  end
14
14
  end
15
15
 
16
- class ActiveRecord::ConnectionAdapters::AbstractAdapter
16
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
17
17
  include Populator::Adapters::Abstract
18
18
  end
@@ -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 { log(sql, name) { @connection.execute_batch(sql) } }
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
- class ActiveRecord::ConnectionAdapters::SQLiteAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
21
- include Populator::Adapters::Sqlite
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
@@ -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
- def initialize(model_class, amount)
4
- @model_class = model_class
5
- @amount = amount.kind_of?(Integer) ? amount : amount.to_a.rand
6
- @records = []
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
- def run(&block)
10
- build_records(&block)
11
- save_records
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
- private
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
- def quoted_column_names
17
- @model_class.column_names.map do |column_name|
18
- @model_class.connection.quote_column_name(column_name)
19
- end
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
- def last_id
23
- @model_class.connection.select_value("SELECT id FROM #{@model_class.quoted_table_name} ORDER BY id DESC", "#{@model_class.name} Last ID").to_i
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
- def build_records(&block)
27
- (1..@amount).map do |i|
28
- record = Record.new(@model_class, last_id+i)
29
- block.call(record) if block
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
- @model_class.connection.populate(@model_class.quoted_table_name, columns_sql, rows_sql_arr, "#{@model_class.name} Populate")
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
- def populate(amount, &block)
4
- Factory.new(self, amount).run(&block)
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
- class ActiveRecord::Base
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
@@ -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
- self.instance_eval <<-EOS
10
- def #{column}=(value)
11
- @attributes[:#{column}] = interpret_value(value)
12
- end
13
-
14
- def #{column}
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 interpret_value(value)
30
- case value
31
- when Array then value.rand
32
- when Range then value.to_a.rand
33
- else value
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.0
2
+ # Gem::Specification for Populator-0.2.1
3
3
  # Originally generated by Echoe
4
4
 
5
- Gem::Specification.new do |s|
6
- s.name = %q{populator}
7
- s.version = "0.1.0"
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
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
- s.authors = ["Ryan Bates"]
11
- s.date = %q{2008-08-28}
12
- s.description = %q{Mass populate an Active Record database.}
13
- s.email = %q{ryan (at) railscasts (dot) com}
14
- s.extra_rdoc_files = ["CHANGELOG", "lib/populator/adapters/abstract.rb", "lib/populator/adapters/sqlite.rb", "lib/populator/factory.rb", "lib/populator/model_additions.rb", "lib/populator/record.rb", "lib/populator.rb", "LICENSE", "README", "tasks/deployment.rake", "tasks/spec.rake"]
15
- s.files = ["CHANGELOG", "lib/populator/adapters/abstract.rb", "lib/populator/adapters/sqlite.rb", "lib/populator/factory.rb", "lib/populator/model_additions.rb", "lib/populator/record.rb", "lib/populator.rb", "LICENSE", "Manifest", "populator.gemspec", "README", "spec/database.yml", "spec/models/product.rb", "spec/populator/factory_spec.rb", "spec/populator/model_additions_spec.rb", "spec/populator/record_spec.rb", "spec/README", "spec/spec_helper.rb", "tasks/deployment.rake", "tasks/spec.rake"]
16
- s.has_rdoc = true
17
- s.homepage = %q{http://github.com/ryanb/populator}
18
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Populator", "--main", "README"]
19
- s.require_paths = ["lib"]
20
- s.rubyforge_project = %q{populator}
21
- s.rubygems_version = %q{1.2.0}
22
- s.summary = %q{Mass populate an Active Record database.}
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
- if s.respond_to? :specification_version then
25
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
- s.specification_version = 2
32
+ extensions: []
27
33
 
28
- if current_version >= 3 then
29
- else
30
- end
31
- else
32
- end
33
- end
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
- # # Original Rakefile source (requires the Echoe gem):
37
- #
38
- # require 'rubygems'
39
- # require 'rake'
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 Populator Specs
2
- -----------------------
1
+ Running Specs
2
+ -------------
3
3
 
4
- There are several rake tasks for running the specs.
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
- To run the specs under mysql you have to create the database and user.
12
- See spec/database.yml for specifics.
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
@@ -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 :name
8
- t.text :description
9
- t.integer :stock
10
- t.float :weight
11
- t.decimal :price
12
- t.datetime :released_at
13
- t.boolean :hidden
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 5 products" do
4
+ describe "for products" do
5
5
  before(:each) do
6
- @factory = Populator::Factory.new(Product, 5)
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.run
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.run do |product|
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.run do |product|
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.run
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'] ||= 'sqlite3'
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"))[ENV['POPULATOR_ADAPTER']]
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
@@ -1,6 +1,6 @@
1
1
  require 'spec/rake/spectask'
2
2
 
3
- ADAPTERS = %w[sqlite3 mysql]
3
+ ADAPTERS = YAML.load(File.read(File.dirname(__FILE__) + "/../spec/database.yml")).keys
4
4
 
5
5
  desc "Run specs under all supported databases"
6
6
  task :spec => ADAPTERS.map { |a| "spec:#{a}" }
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.0
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
- - populator.gemspec
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: "0"
94
+ version: "1.2"
78
95
  version:
79
96
  requirements: []
80
97
 
data/spec/database.yml DELETED
@@ -1,11 +0,0 @@
1
- sqlite3:
2
- adapter: sqlite3
3
- database: spec/test.sqlite3
4
- timeout: 5000
5
-
6
- mysql:
7
- adapter: mysql
8
- database: populator_test
9
- username: populator
10
- password:
11
- host: localhost