ryanb-populator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ *0.1.0* (August 27th, 2008)
2
+
3
+ * 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,20 @@
1
+ CHANGELOG
2
+ lib/populator/adapters/abstract.rb
3
+ lib/populator/adapters/sqlite.rb
4
+ lib/populator/factory.rb
5
+ lib/populator/model_additions.rb
6
+ lib/populator/record.rb
7
+ lib/populator.rb
8
+ LICENSE
9
+ Manifest
10
+ populator.gemspec
11
+ README
12
+ spec/database.yml
13
+ spec/models/product.rb
14
+ spec/populator/factory_spec.rb
15
+ spec/populator/model_additions_spec.rb
16
+ spec/populator/record_spec.rb
17
+ spec/README
18
+ spec/spec_helper.rb
19
+ tasks/deployment.rake
20
+ tasks/spec.rake
data/README ADDED
@@ -0,0 +1,67 @@
1
+ = Populator
2
+
3
+ Populate an Active Record database with mass insert.
4
+
5
+ Special thanks to Zach Dennis for his ar-extensions gem which some of
6
+ this code is loosely based on.
7
+
8
+
9
+ == Install
10
+
11
+ Install the gem:
12
+
13
+ gem install ryanb-populator --source http://gems.github.com
14
+
15
+ And then load it in your project:
16
+
17
+ require 'populator'
18
+
19
+
20
+ == Usage
21
+
22
+ STILL IN EARLY DEVELOPMENT - so some of this doesn't work yet.
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. 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
+
37
+ The person object contains the "id" so you can set up associations.
38
+
39
+ Person.populate(3000) do |person|
40
+ person.first_name = "John"
41
+ person.last_name = "Smith"
42
+ Project.populate(30) do |project|
43
+ project.person_id = person.id
44
+ end
45
+ end
46
+
47
+ That will create 30 projects for each person.
48
+
49
+ Passing an range or array of values will randomly select one.
50
+
51
+ Person.populate(1000..5000) do |person|
52
+ person.gender = ['male', 'female']
53
+ person.annual_income = 10000..200000
54
+ end
55
+
56
+ This will create 1000 to 5000 men or women with the annual income
57
+ between 10,000 and 200,000.
58
+
59
+
60
+ == Development
61
+
62
+ This project can be found on github at the following URL.
63
+
64
+ http://github.com/ryanb/populator/
65
+
66
+ If you would like to contribute to this project, please fork the
67
+ repository and send me a pull request.
@@ -0,0 +1,18 @@
1
+ module Populator
2
+ module Adapters
3
+ module Abstract
4
+ # Executes multiple SQL statements in one query when joined with ";"
5
+ def execute_batch(sql, name = nil)
6
+ raise NotImplementedError, "execute_batch is an abstract method"
7
+ end
8
+
9
+ def populate(table, columns, rows, name = nil)
10
+ execute("INSERT INTO #{table} #{columns} VALUES #{rows.join(', ')}", name)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
17
+ include Populator::Adapters::Abstract
18
+ end
@@ -0,0 +1,22 @@
1
+ module Populator
2
+ module Adapters
3
+ module Sqlite
4
+ # Executes multiple SQL statements in one query when joined with ";"
5
+ def execute_batch(sql, name = nil)
6
+ catch_schema_changes { log(sql, name) { @connection.execute_batch(sql) } }
7
+ end
8
+
9
+ def populate(table, columns, rows, name = nil)
10
+ queries = []
11
+ rows.each do |row|
12
+ queries << "INSERT INTO #{table} #{columns} VALUES #{row}"
13
+ end
14
+ execute_batch(queries.join(';'), name)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ class ActiveRecord::ConnectionAdapters::SQLiteAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
21
+ include Populator::Adapters::Sqlite
22
+ end
@@ -0,0 +1,49 @@
1
+ module Populator
2
+ 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 = []
7
+ end
8
+
9
+ def run(&block)
10
+ build_records(&block)
11
+ save_records
12
+ end
13
+
14
+ private
15
+
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
20
+ end
21
+
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
24
+ end
25
+
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
30
+ @records << record
31
+ end
32
+ end
33
+
34
+ def save_records
35
+ @model_class.connection.populate(@model_class.quoted_table_name, columns_sql, rows_sql_arr, "#{@model_class.name} Populate")
36
+ end
37
+
38
+ def columns_sql
39
+ "(#{quoted_column_names.join(', ')})"
40
+ end
41
+
42
+ def rows_sql_arr
43
+ @records.map do |record|
44
+ quoted_attributes = record.attribute_values.map { |v| @model_class.sanitize(v) }
45
+ "(#{quoted_attributes.join(', ')})"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ module Populator
2
+ module ModelAdditions
3
+ def populate(amount, &block)
4
+ Factory.new(self, amount).run(&block)
5
+ end
6
+ end
7
+ end
8
+
9
+ class ActiveRecord::Base
10
+ extend Populator::ModelAdditions
11
+ end
@@ -0,0 +1,37 @@
1
+ module Populator
2
+ class Record
3
+ attr_accessor :attributes
4
+
5
+ def initialize(model_class, id)
6
+ @attributes = { :id => id }
7
+ @columns = model_class.column_names
8
+ @columns.each do |column|
9
+ 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
18
+ end
19
+ end
20
+
21
+ def attribute_values
22
+ @columns.map do |column|
23
+ @attributes[column.to_sym]
24
+ end
25
+ end
26
+
27
+ private
28
+
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
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/populator.rb ADDED
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'populator/model_additions'
3
+ require 'populator/factory'
4
+ require 'populator/record'
5
+
6
+ require 'populator/adapters/abstract'
7
+ require 'populator/adapters/sqlite'
data/populator.gemspec ADDED
@@ -0,0 +1,51 @@
1
+
2
+ # Gem::Specification for Populator-0.1.0
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{populator}
7
+ s.version = "0.1.0"
8
+
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.}
23
+
24
+ if s.respond_to? :specification_version then
25
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
+ s.specification_version = 2
27
+
28
+ if current_version >= 3 then
29
+ else
30
+ end
31
+ else
32
+ end
33
+ end
34
+
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 }
data/spec/README ADDED
@@ -0,0 +1,16 @@
1
+ Running Populator Specs
2
+ -----------------------
3
+
4
+ There are several rake tasks for running the specs.
5
+
6
+ rake spec # Run specs under all databases
7
+ rake spec:mysql # Run specs under mysql
8
+ rake spec:sqlite3 # Run specs under sqlite3
9
+ ...
10
+
11
+ To run the specs under mysql you have to create the database and user.
12
+ See spec/database.yml for specifics.
13
+
14
+ CREATE DATABASE populator_test;
15
+ GRANT ALL ON populator_test.* TO populator@localhost;
16
+
data/spec/database.yml ADDED
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,20 @@
1
+ class Product < ActiveRecord::Base
2
+ end
3
+
4
+ class CreateProducts < ActiveRecord::Migration
5
+ def self.up
6
+ 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
14
+ end
15
+ end
16
+
17
+ def self.down
18
+ drop_table :products
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe Populator::Factory do
4
+ describe "for 5 products" do
5
+ before(:each) do
6
+ @factory = Populator::Factory.new(Product, 5)
7
+ end
8
+
9
+ it "should only use one query when inserting records" do
10
+ $queries_executed = []
11
+ @factory.run
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.run 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 = Product.create
26
+ expected_id = product.id+1
27
+ @factory.run do |product|
28
+ product.id.should == expected_id
29
+ expected_id += 1
30
+ end
31
+ 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
+
39
+ it "should generate within range" do
40
+ Product.delete_all
41
+ @factory.run
42
+ Product.count.should >= 2
43
+ Product.count.should <= 4
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
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
+ end
@@ -0,0 +1,35 @@
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
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'active_support'
4
+ require 'active_record'
5
+ require File.dirname(__FILE__) + '/../lib/populator.rb'
6
+
7
+ ENV['POPULATOR_ADAPTER'] ||= 'sqlite3'
8
+
9
+ # setup database adapter
10
+ ActiveRecord::Base.establish_connection(
11
+ YAML.load(File.read(File.dirname(__FILE__) + "/database.yml"))[ENV['POPULATOR_ADAPTER']]
12
+ )
13
+
14
+ # keep track of which queries have been executed
15
+ unless ActiveRecord::Base.connection.respond_to? :record_query
16
+ ActiveRecord::Base.connection.class.class_eval do
17
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^begin /i, /^commit /i]
18
+
19
+ def record_query(sql)
20
+ $queries_executed ||= []
21
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
22
+ end
23
+
24
+ def execute_with_query_record(*args, &block)
25
+ record_query(args.first)
26
+ execute_without_query_record(*args, &block)
27
+ end
28
+ alias_method_chain :execute, :query_record
29
+
30
+ def execute_batch_with_query_record(*args, &block)
31
+ record_query(args.first)
32
+ execute_batch_without_query_record(*args, &block)
33
+ end
34
+ alias_method_chain :execute_batch, :query_record
35
+ end
36
+ end
37
+
38
+ # load models
39
+ # there's probably a better way to handle this
40
+ require File.dirname(__FILE__) + '/models/product.rb'
41
+ CreateProducts.migrate(:up) unless Product.table_exists?
42
+
43
+ Spec::Runner.configure do |config|
44
+ config.mock_with :mocha
45
+ 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 'spec/rake/spectask'
2
+
3
+ ADAPTERS = %w[sqlite3 mysql]
4
+
5
+ desc "Run specs under all supported databases"
6
+ task :spec => ADAPTERS.map { |a| "spec:#{a}" }
7
+
8
+ namespace :spec do
9
+ ADAPTERS.each do |adapter|
10
+ namespace :prepare do
11
+ task adapter do
12
+ ENV["POPULATOR_ADAPTER"] = adapter
13
+ end
14
+ end
15
+
16
+ desc "Run specs under #{adapter}"
17
+ Spec::Rake::SpecTask.new(adapter => "spec:prepare:#{adapter}") do |t|
18
+ t.spec_files = Rake::FileList["spec/**/*_spec.rb"]
19
+ t.spec_opts = ["-c"]
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ryanb-populator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Bates
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-28 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Mass populate an Active Record database.
17
+ email: ryan (at) railscasts (dot) com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - CHANGELOG
24
+ - lib/populator/adapters/abstract.rb
25
+ - lib/populator/adapters/sqlite.rb
26
+ - lib/populator/factory.rb
27
+ - lib/populator/model_additions.rb
28
+ - lib/populator/record.rb
29
+ - lib/populator.rb
30
+ - LICENSE
31
+ - README
32
+ - tasks/deployment.rake
33
+ - tasks/spec.rake
34
+ 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/record.rb
41
+ - lib/populator.rb
42
+ - LICENSE
43
+ - Manifest
44
+ - populator.gemspec
45
+ - README
46
+ - spec/database.yml
47
+ - spec/models/product.rb
48
+ - spec/populator/factory_spec.rb
49
+ - spec/populator/model_additions_spec.rb
50
+ - spec/populator/record_spec.rb
51
+ - spec/README
52
+ - spec/spec_helper.rb
53
+ - tasks/deployment.rake
54
+ - tasks/spec.rake
55
+ has_rdoc: true
56
+ homepage: http://github.com/ryanb/populator
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --line-numbers
60
+ - --inline-source
61
+ - --title
62
+ - Populator
63
+ - --main
64
+ - README
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ requirements: []
80
+
81
+ rubyforge_project: populator
82
+ rubygems_version: 1.2.0
83
+ signing_key:
84
+ specification_version: 2
85
+ summary: Mass populate an Active Record database.
86
+ test_files: []
87
+