populator3 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,39 @@
1
+ * adding Oracle adapter (thanks Andrew N)
2
+
3
+ 0.2.4 (September 9th, 2008)
4
+
5
+ * removing echoe from gem development dependencies, which didn't seem to work in the first place.
6
+
7
+ * adding Populator.paragraphs to generate paragraphs of text
8
+
9
+ 0.2.3 (September 2nd, 2008)
10
+
11
+ * support single table inhertance by setting inheritance_column to class name
12
+
13
+ * support custom primary_key in model if they don't use "id"
14
+
15
+ 0.2.2 (September 1st, 2008)
16
+
17
+ * performance improvements
18
+
19
+ * improving inline documentation
20
+
21
+ 0.2.1 (August 30th, 2008)
22
+
23
+ * wrap sqlite inserts in transaction to improve performance
24
+
25
+ * default created_at/on and updated_at/on columns to current time
26
+
27
+ 0.2.0 (August 30th, 2008)
28
+
29
+ * adding :per_query option to limit how many inserts are made per query
30
+
31
+ * improving performance when nesting factories
32
+
33
+ * adding Populator.sentences to generate a lot of text
34
+
35
+ * adding Populator.words to fetch some random words
36
+
37
+ 0.1.0 (August 27th, 2008)
38
+
39
+ * 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,28 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.rdoc
5
+ Rakefile
6
+ TODO
7
+ lib/populator.rb
8
+ lib/populator/adapters/abstract.rb
9
+ lib/populator/adapters/oracle.rb
10
+ lib/populator/adapters/postgresql.rb
11
+ lib/populator/adapters/sqlite.rb
12
+ lib/populator/factory.rb
13
+ lib/populator/model_additions.rb
14
+ lib/populator/random.rb
15
+ lib/populator/record.rb
16
+ populator3.gemspec
17
+ spec/README
18
+ spec/example_database.yml
19
+ spec/models/category.rb
20
+ spec/models/product.rb
21
+ spec/populator/factory_spec.rb
22
+ spec/populator/model_additions_spec.rb
23
+ spec/populator/random_spec.rb
24
+ spec/populator/record_spec.rb
25
+ spec/spec.opts
26
+ spec/spec_helper.rb
27
+ tasks/deployment.rake
28
+ tasks/spec.rake
data/README.rdoc ADDED
@@ -0,0 +1,91 @@
1
+ = Populator
2
+
3
+ Populate an Active Record database with mass insert.
4
+
5
+ You can find the rdocs at http://populator.rubyforge.org.
6
+
7
+ Special thanks to Zach Dennis for his ar-extensions gem which some of
8
+ this code is loosely based on.
9
+
10
+
11
+ == Install
12
+
13
+ Install the gem:
14
+
15
+ gem install populator
16
+
17
+ And then load it in your project:
18
+
19
+ require 'populator'
20
+
21
+
22
+ == Usage
23
+
24
+ This gem adds a "populate" method to all Active Record models. Pass the
25
+ number of records you want to create along with a block. In the block
26
+ you can set the column values for each record.
27
+
28
+ Person.populate(3000) do |person|
29
+ person.first_name = "John"
30
+ person.last_name = "Smith"
31
+ end
32
+
33
+ This will do a mass insert into the database so it is very fast.
34
+ The person object contains the "id" so you can set up associations.
35
+
36
+ Person.populate(3000) do |person|
37
+ person.first_name = "John"
38
+ person.last_name = "Smith"
39
+ Project.populate(30) do |project|
40
+ project.person_id = person.id
41
+ end
42
+ end
43
+
44
+ That will create 30 projects for each person.
45
+
46
+ Passing a range or array of values will randomly select one.
47
+
48
+ Person.populate(1000..5000) do |person|
49
+ person.gender = ['male', 'female']
50
+ person.annual_income = 10000..200000
51
+ end
52
+
53
+ This will create 1000 to 5000 men or women with the annual income
54
+ between 10,000 and 200,000.
55
+
56
+ You can pass a :per_query option to limit how many records are saved
57
+ per query. This defaults to 1000.
58
+
59
+ Person.populate(2000, :per_query => 100)
60
+
61
+ If you need to generate fake data, there are a few methods to do this.
62
+
63
+ Populator.words(3) # generates 3 random words separated by spaces
64
+ Populator.words(10..20) # generates between 10 and 20 random words
65
+ Populator.sentences(5) # generates 5 sentences
66
+ Populator.paragraphs(3) # generates 3 paragraphs
67
+
68
+ For fancier data generation, try the Faker gem.
69
+
70
+ http://faker.rubyforge.org
71
+
72
+
73
+ == Important
74
+
75
+ For performance reasons, this gem does not use actual instances of the
76
+ model. This means validations and callbacks are bypassed. It is up to
77
+ you to ensure you're adding valid data.
78
+
79
+
80
+ == Development
81
+
82
+ See spec/README for instructions on running specs.
83
+
84
+ This project can be found on github at the following URL.
85
+
86
+ http://github.com/ryanb/populator
87
+
88
+ If you find a bug, please send me a message on GitHub.
89
+
90
+ If you would like to contribute to this project, please fork the
91
+ repository and send me a pull request.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('populator3', '0.2.4') 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", "tmp/*"]
12
+ p.development_dependencies = []
13
+ end
14
+
15
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
data/TODO ADDED
@@ -0,0 +1,11 @@
1
+ Possible
2
+ - randomly fill every column if no block is passed to populate
3
+ - add random_foo method to record for randomly generating content
4
+
5
+ Support Environments
6
+ - sqlite 2
7
+ - old rails
8
+ - edge rails
9
+
10
+ Tests
11
+ - add db:reset, db:drop and db:create rake tasks
@@ -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
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
17
+ include Populator::Adapters::Abstract
18
+ end
@@ -0,0 +1,27 @@
1
+ module Populator
2
+ module Adapters
3
+ module Oracle
4
+
5
+ # Executes SQL statements one at a time.
6
+
7
+ def populate(table, columns, rows, name = nil)
8
+ rows.each do |row|
9
+ sql = "INSERT INTO #{table} #{columns} VALUES #{row}"
10
+ log(sql, name) do
11
+ @connection.exec(sql)
12
+ end
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+
20
+ module ActiveRecord # :nodoc: all
21
+ module ConnectionAdapters
22
+ class OracleAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
23
+ include Populator::Adapters::Oracle
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,22 @@
1
+ module Populator
2
+ module Adapters
3
+ module Postgresql
4
+ def populate(table, columns, rows, name = nil)
5
+ queries = []
6
+ rows.each do |row|
7
+ row.gsub!(/^\(\d{1,}/, "(DEFAULT")
8
+ queries << "INSERT INTO #{table} #{columns} VALUES #{row}"
9
+ end
10
+ execute(queries.join("; "), name)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ module ActiveRecord # :nodoc: all
17
+ module ConnectionAdapters
18
+ class PostgreSQLAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
19
+ include Populator::Adapters::Postgresql
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
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
+ log(sql, name) do
7
+ @connection.transaction { |db| db.execute_batch(sql) }
8
+ end
9
+ end
10
+
11
+ def populate(table, columns, rows, name = nil)
12
+ queries = []
13
+ rows.each do |row|
14
+ queries << "INSERT INTO #{table} #{columns} VALUES #{row}"
15
+ end
16
+ execute_batch(queries.join(';'), name)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # TODO find a better way to load the SQLite adapter
23
+ module ActiveRecord # :nodoc: all
24
+ module ConnectionAdapters
25
+ class SQLiteAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
26
+ include Populator::Adapters::Sqlite
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,90 @@
1
+ module Populator
2
+ # Builds multiple Populator::Record instances and saves them to the database
3
+ class Factory
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)
14
+ end
15
+
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 = {}
22
+ end
23
+
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
33
+
34
+ # Use for_model instead of instatiating a record directly.
35
+ def initialize(model_class)
36
+ @model_class = model_class
37
+ @records = []
38
+ end
39
+
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
45
+ end
46
+
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)
52
+ @records << record
53
+ block.call(record) if block
54
+ save_records if @records.size >= per_query
55
+ end
56
+ end
57
+
58
+ # Saves the records to the database by calling populate on the current database adapter.
59
+ def save_records
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
77
+ end
78
+
79
+ def columns_sql
80
+ "(#{quoted_column_names.join(', ')})"
81
+ end
82
+
83
+ def rows_sql_arr
84
+ @records.map do |record|
85
+ quoted_attributes = record.attribute_values.map { |v| @model_class.sanitize(v) }
86
+ "(#{quoted_attributes.join(', ')})"
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,32 @@
1
+ module Populator
2
+ module ModelAdditions
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)
26
+ end
27
+ end
28
+ end
29
+
30
+ ActiveRecord::Base.class_eval do
31
+ extend Populator::ModelAdditions
32
+ end
@@ -0,0 +1,69 @@
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
+ # Generate a given number of paragraphs. If a range is passed, it will generate
32
+ # a random number of paragraphs within that range.
33
+ def paragraphs(total)
34
+ (1..interpret_value(total)).map do
35
+ sentences(3..8).capitalize
36
+ end.join("\n\n")
37
+ end
38
+
39
+ # If an array or range is passed, a random value will be selected to match.
40
+ # All other values are simply returned.
41
+ def interpret_value(value)
42
+ case value
43
+ when Array then value.rand
44
+ when Range then value_in_range(value)
45
+ else value
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def time_in_range(range)
52
+ Time.at number_in_range(Range.new(range.first.to_i, range.last.to_i, range.exclude_end?))
53
+ end
54
+
55
+ def date_in_range(range)
56
+ Date.jd number_in_range(Range.new(range.first.jd, range.last.jd, range.exclude_end?))
57
+ end
58
+
59
+ def number_in_range(range)
60
+ if range.exclude_end?
61
+ rand(range.last - range.first) + range.first
62
+ else
63
+ rand((range.last+1) - range.first) + range.first
64
+ end
65
+ end
66
+ end
67
+
68
+ extend Random # load it into the populator module directly so we can call the methods
69
+ end
@@ -0,0 +1,68 @@
1
+ module Populator
2
+ # This is what is passed to the block when calling populate.
3
+ class Record
4
+ attr_accessor :attributes
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
13
+ # * <tt>type</tt> - defaults to class name (for STI)
14
+ def initialize(model_class, id)
15
+ @attributes = { model_class.primary_key.to_sym => id }
16
+ @columns = model_class.column_names
17
+ @columns.each do |column|
18
+ case column
19
+ when 'created_at', 'updated_at'
20
+ @attributes[column.to_sym] = Time.now
21
+ when 'created_on', 'updated_on'
22
+ @attributes[column.to_sym] = Date.today
23
+ when model_class.inheritance_column
24
+ @attributes[column.to_sym] = model_class.to_s
25
+ end
26
+ end
27
+ end
28
+
29
+ # override id since method_missing won't catch this column name
30
+ def id
31
+ @attributes[:id]
32
+ end
33
+
34
+ # override type since method_missing won't catch this column name
35
+ def type
36
+ @attributes[:type]
37
+ end
38
+
39
+ # Return values for all columns inside an array.
40
+ def attribute_values
41
+ @columns.map do |column|
42
+ @attributes[column.to_sym]
43
+ end
44
+ end
45
+
46
+ def attributes=(values_hash)
47
+ values_hash.each_pair do |key, value|
48
+ value = value.call if value.is_a?(Proc)
49
+ self.send((key.to_s + "=").to_sym, value)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def method_missing(sym, *args, &block)
56
+ name = sym.to_s
57
+ if @columns.include?(name.sub('=', ''))
58
+ if name.include? '='
59
+ @attributes[name.sub('=', '').to_sym] = Populator.interpret_value(args.first)
60
+ else
61
+ @attributes[sym]
62
+ end
63
+ else
64
+ super
65
+ end
66
+ end
67
+ end
68
+ end
data/lib/populator.rb ADDED
@@ -0,0 +1,14 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'populator/model_additions'
3
+ require 'populator/factory'
4
+ require 'populator/record'
5
+ require 'populator/random'
6
+
7
+ require 'populator/adapters/abstract'
8
+ require 'populator/adapters/sqlite'
9
+ require 'populator/adapters/oracle'
10
+ require 'populator/adapters/postgresql'
11
+
12
+ # Populator is made up of several parts. To start, see Populator::ModelAdditions.
13
+ module Populator
14
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{populator3}
5
+ s.version = "0.2.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ryan Bates"]
9
+ s.date = %q{2010-11-05}
10
+ s.description = %q{Mass populate an Active Record database.}
11
+ s.email = %q{ryan (at) railscasts (dot) com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.rdoc", "TODO", "lib/populator.rb", "lib/populator/adapters/abstract.rb", "lib/populator/adapters/oracle.rb", "lib/populator/adapters/postgresql.rb", "lib/populator/adapters/sqlite.rb", "lib/populator/factory.rb", "lib/populator/model_additions.rb", "lib/populator/random.rb", "lib/populator/record.rb", "tasks/deployment.rake", "tasks/spec.rake"]
13
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.rdoc", "Rakefile", "TODO", "lib/populator.rb", "lib/populator/adapters/abstract.rb", "lib/populator/adapters/oracle.rb", "lib/populator/adapters/postgresql.rb", "lib/populator/adapters/sqlite.rb", "lib/populator/factory.rb", "lib/populator/model_additions.rb", "lib/populator/random.rb", "lib/populator/record.rb", "populator3.gemspec", "spec/README", "spec/example_database.yml", "spec/models/category.rb", "spec/models/product.rb", "spec/populator/factory_spec.rb", "spec/populator/model_additions_spec.rb", "spec/populator/random_spec.rb", "spec/populator/record_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/deployment.rake", "tasks/spec.rake"]
14
+ s.homepage = %q{http://github.com/ryanb/populator}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Populator3", "--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{populator3}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Mass populate an Active Record database.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
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;
@@ -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,49 @@
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
+
46
+ it "should generate 3 random paragraphs" do
47
+ Populator.paragraphs(3).split(/\n\n/).should have(3).records
48
+ end
49
+ end
@@ -0,0 +1,74 @@
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
+
45
+ it "should use custom primary_key for auto-increment if specified" do
46
+ Product.stubs(:primary_key).returns('foo')
47
+ Product.stubs(:column_names).returns(['foo', 'name'])
48
+ Populator::Record.new(Product, 123).foo.should == 123
49
+ end
50
+
51
+ it "should default type to class name" do
52
+ Product.stubs(:column_names).returns(['id', 'type'])
53
+ Populator::Record.new(Product, 1).type.should == 'Product'
54
+ end
55
+
56
+ it "should default specified inheritance_column to class name" do
57
+ Product.stubs(:inheritance_column).returns('foo')
58
+ Product.stubs(:column_names).returns(['id', 'foo'])
59
+ Populator::Record.new(Product, 1).foo.should == 'Product'
60
+ end
61
+
62
+ it "should allow set via attributes hash" do
63
+ record = Populator::Record.new(Product, 1)
64
+ record.attributes = {:stock => 2..5}
65
+ record.stock.should >= 2
66
+ record.stock.should <= 5
67
+ end
68
+
69
+ it "should take a proc object via attributes hash" do
70
+ record = Populator::Record.new(Product, 1)
71
+ record.attributes = {:stock => lambda {15}}
72
+ record.stock.should == 15
73
+ end
74
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rspec'
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 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
+ RSpec.configure do |config|
47
+ config.mock_with :mocha
48
+ end
@@ -0,0 +1,2 @@
1
+ desc "Build the manifest and gemspec files."
2
+ task :build => [:build_manifest, :build_gemspec]
data/tasks/spec.rake ADDED
@@ -0,0 +1,22 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ ADAPTERS = YAML.load(File.read(File.dirname(__FILE__) + "/../spec/example_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
+ RSpec::Core::RakeTask.new(adapter => "spec:prepare:#{adapter}") do |t|
18
+ #t.spec_files = Rake::FileList["spec/**/*_spec.rb"]
19
+ t.rspec_opts = ["-c"]
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: populator3
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 4
9
+ version: 0.2.4
10
+ platform: ruby
11
+ authors:
12
+ - Ryan Bates
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-05 00:00:00 +03:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Mass populate an Active Record database.
22
+ email: ryan (at) railscasts (dot) com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - CHANGELOG
29
+ - LICENSE
30
+ - README.rdoc
31
+ - TODO
32
+ - lib/populator.rb
33
+ - lib/populator/adapters/abstract.rb
34
+ - lib/populator/adapters/oracle.rb
35
+ - lib/populator/adapters/postgresql.rb
36
+ - lib/populator/adapters/sqlite.rb
37
+ - lib/populator/factory.rb
38
+ - lib/populator/model_additions.rb
39
+ - lib/populator/random.rb
40
+ - lib/populator/record.rb
41
+ - tasks/deployment.rake
42
+ - tasks/spec.rake
43
+ files:
44
+ - CHANGELOG
45
+ - LICENSE
46
+ - Manifest
47
+ - README.rdoc
48
+ - Rakefile
49
+ - TODO
50
+ - lib/populator.rb
51
+ - lib/populator/adapters/abstract.rb
52
+ - lib/populator/adapters/oracle.rb
53
+ - lib/populator/adapters/postgresql.rb
54
+ - lib/populator/adapters/sqlite.rb
55
+ - lib/populator/factory.rb
56
+ - lib/populator/model_additions.rb
57
+ - lib/populator/random.rb
58
+ - lib/populator/record.rb
59
+ - populator3.gemspec
60
+ - spec/README
61
+ - spec/example_database.yml
62
+ - spec/models/category.rb
63
+ - spec/models/product.rb
64
+ - spec/populator/factory_spec.rb
65
+ - spec/populator/model_additions_spec.rb
66
+ - spec/populator/random_spec.rb
67
+ - spec/populator/record_spec.rb
68
+ - spec/spec.opts
69
+ - spec/spec_helper.rb
70
+ - tasks/deployment.rake
71
+ - tasks/spec.rake
72
+ has_rdoc: true
73
+ homepage: http://github.com/ryanb/populator
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options:
78
+ - --line-numbers
79
+ - --inline-source
80
+ - --title
81
+ - Populator3
82
+ - --main
83
+ - README.rdoc
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 1
101
+ - 2
102
+ version: "1.2"
103
+ requirements: []
104
+
105
+ rubyforge_project: populator3
106
+ rubygems_version: 1.3.7
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Mass populate an Active Record database.
110
+ test_files: []
111
+