blahed-populator 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,27 @@
1
+ CHANGELOG
2
+ lib/populator/adapters/abstract.rb
3
+ lib/populator/adapters/oracle.rb
4
+ lib/populator/adapters/sqlite.rb
5
+ lib/populator/factory.rb
6
+ lib/populator/model_additions.rb
7
+ lib/populator/random.rb
8
+ lib/populator/record.rb
9
+ lib/populator.rb
10
+ LICENSE
11
+ Manifest
12
+ Rakefile
13
+ README.rdoc
14
+ spec/database.yml
15
+ spec/example_database.yml
16
+ spec/models/category.rb
17
+ spec/models/product.rb
18
+ spec/populator/factory_spec.rb
19
+ spec/populator/model_additions_spec.rb
20
+ spec/populator/random_spec.rb
21
+ spec/populator/record_spec.rb
22
+ spec/README
23
+ spec/spec.opts
24
+ spec/spec_helper.rb
25
+ tasks/deployment.rake
26
+ tasks/spec.rake
27
+ TODO
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('populator', '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
data/lib/populator.rb ADDED
@@ -0,0 +1,13 @@
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
+
11
+ # Populator is made up of several parts. To start, see Populator::ModelAdditions.
12
+ module Populator
13
+ end
@@ -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,31 @@
1
+ module Populator
2
+ module Adapters
3
+ module Sqlite
4
+ # Executes multiple SQL statements in one query when joined with ";"
5
+ def execute_batch(sql, name = nil)
6
+ catch_schema_changes do
7
+ log(sql, name) do
8
+ @connection.transaction { |db| db.execute_batch(sql) }
9
+ end
10
+ end
11
+ end
12
+
13
+ def populate(table, columns, rows, name = nil)
14
+ queries = []
15
+ rows.each do |row|
16
+ queries << "INSERT INTO #{table} #{columns} VALUES #{row}"
17
+ end
18
+ execute_batch(queries.join(';'), name)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # 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
31
+ 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,88 @@
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
+ ADVERBS = %w(appropriately assertively authoritatively collaboratively compellingly competently completely continually conveniently credibly distinctively dramatically dynamically efficiently energistically enthusiastically globally holisticly interactively intrinsicly monotonectally objectively phosfluorescently proactively professionally progressively quickly rapidiously seamlessly synergistically uniquely)
7
+ VERBS = %w(actualize administrate aggregate architect benchmark brand build communicate conceptualize coordinate create cultivate customize deliver deploy develop disintermediate disseminate drive embrace e-enable empower enable engage engineer enhance envisioneer evisculate evolve expedite exploit extend fabricate facilitate fashion formulate foster generate grow harness impact implement incentivize incubate initiate innovate integrate iterate leverage existing leverage others maintain matrix maximize mesh monetize morph myocardinate negotiate network optimize orchestrate parallel task plagiarize pontificate predominate procrastinate productivate productize promote provide access to pursue recaptiualize reconceptualize redefine re-engineer reintermediate reinvent repurpose restore revolutionize scale seize simplify strategize streamline supply syndicate synergize synthesize target transform transition underwhelm unleash utilize visualize whiteboard)
8
+ ADJECTIVES = %w(accurate adaptive alternative an expanded array of B2B B2C backend backward-compatible best-of-breed bleeding-edge bricks-and-clicks business clicks-and-mortar client-based client-centered client-centric client-focused collaborative compelling competitive cooperative corporate cost effective covalent cross functional cross-media cross-platform cross-unit customer directed customized cutting-edge distinctive distributed diverse dynamic e-business economically sound effective efficient emerging empowered enabled end-to-end enterprise enterprise-wide equity invested error-free ethical excellent exceptional extensible extensive flexible focused frictionless front-end fully researched fully tested functional functionalized future-proof global go forward goal-oriented granular high standards in high-payoff high-quality highly efficient holistic impactful inexpensive innovative installed base integrated interactive interdependent intermandated interoperable intuitive just in time leading-edge leveraged long-term high-impact low-risk high-yield magnetic maintainable market positioning market-driven mission-critical multidisciplinary multifunctional multimedia based next-generation one-to-one open-source optimal orthogonal out-of-the-box pandemic parallel performance based plug-and-play premier premium principle-centered proactive process-centric professional progressive prospective quality real-time reliable resource sucking resource maximizing resource-leveling revolutionary robust scalable seamless stand-alone standardized standards compliant state of the art sticky strategic superior sustainable synergistic tactical team building team driven technically sound timely top-line transparent turnkey ubiquitous unique user-centric user friendly value-added vertical viral virtual visionary web-enabled wireless world-class worldwide 2.0)
9
+ NOUNS = %w(action items alignments applications architectures bandwidth benefits best practices catalysts for change channels collaboration and idea-sharing communities content convergence core competencies customer service data deliverables e-business e-commerce e-markets e-tailers e-services experiences expertise functionalities growth strategies human capital ideas imperatives infomediaries information infrastructures initiatives innovation intellectual capital interfaces internal or "organic" sources leadership leadership skills manufactured products markets materials meta-services methodologies methods of empowerment metrics mindshare models networks niches niche markets opportunities "outside the box" thinking outsourcing paradigms partnerships platforms portals potentialities process improvements processes products quality vectors relationships resources results ROI scenarios schemas services solutions sources strategic theme areas supply chains synergy systems technologies technology testing procedures total linkage users value vortals web-readiness web services)
10
+ CONJUNCTIONS = %w(through via vis-a-vis with without and before after whereas for rather than)
11
+
12
+ # Pick a random value out of a given range.
13
+ def value_in_range(range)
14
+ case range.first
15
+ when Integer then number_in_range(range)
16
+ when Time then time_in_range(range)
17
+ when Date then date_in_range(range)
18
+ else range.to_a.rand
19
+ end
20
+ end
21
+
22
+ # Generate a readable sentence
23
+ def readable_sentences(total)
24
+ (1..interpret_value(total)).map do
25
+ ADVERBS.rand.capitalize + " " + [ VERBS.rand, ADJECTIVES.rand, NOUNS.rand, CONJUNCTIONS.rand, ADJECTIVES.rand, NOUNS.rand ].join(' ') + ". "
26
+ end
27
+ end
28
+
29
+ # Generate a readable paragraph
30
+ def readable_paragraphs(total)
31
+ (1..interpret_value(total)).map do
32
+ readable_sentences(3..8).capitalize
33
+ end.join("\n\n")
34
+ end
35
+
36
+ # Generate a given number of words. If a range is passed, it will generate
37
+ # a random number of words within that range.
38
+ def words(total)
39
+ (1..interpret_value(total)).map { WORDS.rand }.join(' ')
40
+ end
41
+
42
+ # Generate a given number of sentences. If a range is passed, it will generate
43
+ # a random number of sentences within that range.
44
+ def sentences(total)
45
+ (1..interpret_value(total)).map do
46
+ words(5..20).capitalize
47
+ end.join('. ')
48
+ end
49
+
50
+ # Generate a given number of paragraphs. If a range is passed, it will generate
51
+ # a random number of paragraphs within that range.
52
+ def paragraphs(total)
53
+ (1..interpret_value(total)).map do
54
+ sentences(3..8).capitalize
55
+ end.join("\n\n")
56
+ end
57
+
58
+ # If an array or range is passed, a random value will be selected to match.
59
+ # All other values are simply returned.
60
+ def interpret_value(value)
61
+ case value
62
+ when Array then value.rand
63
+ when Range then value_in_range(value)
64
+ else value
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def time_in_range(range)
71
+ Time.at number_in_range(Range.new(range.first.to_i, range.last.to_i, range.exclude_end?))
72
+ end
73
+
74
+ def date_in_range(range)
75
+ Date.jd number_in_range(Range.new(range.first.jd, range.last.jd, range.exclude_end?))
76
+ end
77
+
78
+ def number_in_range(range)
79
+ if range.exclude_end?
80
+ rand(range.last - range.first) + range.first
81
+ else
82
+ rand((range.last+1) - range.first) + range.first
83
+ end
84
+ end
85
+ end
86
+
87
+ extend Random # load it into the populator module directly so we can call the methods
88
+ 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/populator.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{populator}
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{2009-04-08}
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", "lib/populator/adapters/abstract.rb", "lib/populator/adapters/oracle.rb", "lib/populator/adapters/sqlite.rb", "lib/populator/factory.rb", "lib/populator/model_additions.rb", "lib/populator/random.rb", "lib/populator/record.rb", "lib/populator.rb", "LICENSE", "README.rdoc", "tasks/deployment.rake", "tasks/spec.rake", "TODO"]
13
+ s.files = ["CHANGELOG", "lib/populator/adapters/abstract.rb", "lib/populator/adapters/oracle.rb", "lib/populator/adapters/sqlite.rb", "lib/populator/factory.rb", "lib/populator/model_additions.rb", "lib/populator/random.rb", "lib/populator/record.rb", "lib/populator.rb", "LICENSE", "Manifest", "Rakefile", "README.rdoc", "spec/database.yml", "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/README", "spec/spec.opts", "spec/spec_helper.rb", "tasks/deployment.rake", "tasks/spec.rake", "TODO", "populator.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/ryanb/populator}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Populator", "--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{populator}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Mass populate an Active Record database.}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ 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 'spec'
3
+ require 'active_support'
4
+ require 'active_record'
5
+ require File.dirname(__FILE__) + '/../lib/populator.rb'
6
+
7
+ adapter = ENV['POPULATOR_ADAPTER'] || 'sqlite3'
8
+ puts "Running on #{adapter}"
9
+
10
+ # setup database adapter
11
+ ActiveRecord::Base.establish_connection(
12
+ YAML.load(File.read(File.dirname(__FILE__) + "/database.yml"))[adapter]
13
+ )
14
+
15
+ # keep track of which queries have been executed
16
+ unless ActiveRecord::Base.connection.respond_to? :record_query
17
+ ActiveRecord::Base.connection.class.class_eval do
18
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^begin /i, /^commit /i]
19
+
20
+ def record_query(sql)
21
+ $queries_executed ||= []
22
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
23
+ end
24
+
25
+ def execute_with_query_record(*args, &block)
26
+ record_query(args.first)
27
+ execute_without_query_record(*args, &block)
28
+ end
29
+ alias_method_chain :execute, :query_record
30
+
31
+ def execute_batch_with_query_record(*args, &block)
32
+ record_query(args.first)
33
+ execute_batch_without_query_record(*args, &block)
34
+ end
35
+ alias_method_chain :execute_batch, :query_record
36
+ end
37
+ end
38
+
39
+ # load models
40
+ # there's probably a better way to handle this
41
+ require File.dirname(__FILE__) + '/models/category.rb'
42
+ require File.dirname(__FILE__) + '/models/product.rb'
43
+ CreateCategories.migrate(:up) unless Category.table_exists?
44
+ CreateProducts.migrate(:up) unless Product.table_exists?
45
+
46
+ Spec::Runner.configure do |config|
47
+ config.mock_with :mocha
48
+ end
@@ -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 = YAML.load(File.read(File.dirname(__FILE__) + "/../spec/database.yml")).keys
4
+
5
+ desc "Run specs under all supported databases"
6
+ task :spec => ADAPTERS.map { |a| "spec:#{a}" }
7
+
8
+ namespace :spec do
9
+ ADAPTERS.each do |adapter|
10
+ namespace :prepare do
11
+ task adapter do
12
+ ENV["POPULATOR_ADAPTER"] = adapter
13
+ end
14
+ end
15
+
16
+ desc "Run specs under #{adapter}"
17
+ Spec::Rake::SpecTask.new(adapter => "spec:prepare:#{adapter}") do |t|
18
+ t.spec_files = Rake::FileList["spec/**/*_spec.rb"]
19
+ t.spec_opts = ["-c"]
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blahed-populator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.4
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Bates
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-08 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/oracle.rb
26
+ - lib/populator/adapters/sqlite.rb
27
+ - lib/populator/factory.rb
28
+ - lib/populator/model_additions.rb
29
+ - lib/populator/random.rb
30
+ - lib/populator/record.rb
31
+ - lib/populator.rb
32
+ - LICENSE
33
+ - README.rdoc
34
+ - tasks/deployment.rake
35
+ - tasks/spec.rake
36
+ - TODO
37
+ files:
38
+ - CHANGELOG
39
+ - lib/populator/adapters/abstract.rb
40
+ - lib/populator/adapters/oracle.rb
41
+ - lib/populator/adapters/sqlite.rb
42
+ - lib/populator/factory.rb
43
+ - lib/populator/model_additions.rb
44
+ - lib/populator/random.rb
45
+ - lib/populator/record.rb
46
+ - lib/populator.rb
47
+ - LICENSE
48
+ - Manifest
49
+ - Rakefile
50
+ - README.rdoc
51
+ - spec/database.yml
52
+ - spec/example_database.yml
53
+ - spec/models/category.rb
54
+ - spec/models/product.rb
55
+ - spec/populator/factory_spec.rb
56
+ - spec/populator/model_additions_spec.rb
57
+ - spec/populator/random_spec.rb
58
+ - spec/populator/record_spec.rb
59
+ - spec/README
60
+ - spec/spec.opts
61
+ - spec/spec_helper.rb
62
+ - tasks/deployment.rake
63
+ - tasks/spec.rake
64
+ - TODO
65
+ - populator.gemspec
66
+ has_rdoc: true
67
+ homepage: http://github.com/ryanb/populator
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --line-numbers
71
+ - --inline-source
72
+ - --title
73
+ - Populator
74
+ - --main
75
+ - README.rdoc
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "1.2"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project: populator
93
+ rubygems_version: 1.2.0
94
+ signing_key:
95
+ specification_version: 2
96
+ summary: Mass populate an Active Record database.
97
+ test_files: []
98
+