factory_data_preloader 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ == 0.5.2 / 2009-07-09
2
+
3
+ * Improved backtrace output.
4
+
5
+ == 0.5.1 / 2009-07-07
6
+
7
+ * Fixed a couple of bugs that caused the data to be preloaded multiple times or deleted multiple times.
8
+
9
+ == 0.5.0 / 2009-07-07
10
+
11
+ * Added better error handling. The data[:key] = record form is deprecated in favor of data.add(:key) { record }.
12
+ * Allow preloaders to be redefined. This should make this more compatible with autotest.
13
+
14
+ == 0.4.3 / 2009-06-05
15
+
16
+ * Added shoulda and mocha development dependencies.
17
+
18
+ == 0.4.2 / 2009-06-02
19
+
20
+ * Raise an appropriate error when the developer tries to get a record for a preloader that was never run.
21
+
22
+ == 0.4.1 / 2009-06-01
23
+
24
+ * Updated documentation (Forgot to for the 0.4.0 release).
25
+
26
+ == 0.4.0 / 2009-06-01
27
+
28
+ * Added ability to only preload some of the types.
29
+ * During preloading, print to the console to indicate the records being preloaded and a benchmark.
30
+
31
+ == 0.3.2 / 2009-04-07
32
+
33
+ * Fixed a bug with the ordering of the dependent preloaders.
34
+
35
+ == 0.3.1 / 2009-03-30
36
+
37
+ * Updated documentation. (Forgot to for 0.3.0 release)
38
+
39
+ == 0.3.0 / 2009-03-30
40
+
41
+ * Added logic to auto load the factory_data files.
42
+
43
+ == 0.2.0 / 2009-03-30
44
+
45
+ * Added :depends_on option to the preloader, to force the preloaders to load in the correct order based on your foreign keys and table dependencies.
46
+
47
+ == 0.1.0 / 2009-03-30
48
+
49
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Myron Marston, Kashless.org
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.
@@ -0,0 +1,132 @@
1
+ = factory_data_preloader
2
+
3
+ If you're like me, you really dislike using rails test fixtures. On the rails projects I've worked on that
4
+ have used test fixtures, we've had a number of tests that passed as false positives because the test
5
+ fixtures don't represent "real data". Fixtures just dump the data you've defined directly into the database,
6
+ without going through the model--meaning you don't get any of the validations or before/after save callback
7
+ behavior defined on your models.
8
+
9
+ There are multiple gems and plugins that address this; my personal favorite is {factory girl}[http://github.com/thoughtbot/factory_girl/].
10
+
11
+ However, none of the solutions I've tried are as fast as using test fixtures. When you use test fixtures,
12
+ rails rolls back the database transaction used in each test and allows you to re-use your fixture data.
13
+ This is much much quicker then creating the data you will use directly in each test.
14
+
15
+ This gem attempts to solve this issue, and give you the best of both worlds: create your test data using
16
+ factory girl, the models themselves, or any other solution you want, while also being able to pre-load it
17
+ and re-use it in each test when rails rolls back the transaction.
18
+
19
+ == Download
20
+
21
+ Github: http://github.com/myronmarston/factory_data_preloader/tree/master
22
+
23
+ Gem:
24
+ gem install myronmarston-factory_data_preloader --source http://gems.github.com
25
+
26
+ == Usage
27
+
28
+ Load the gem using Rails' 2.1+ gem support, in either config/environment.rb, or config/environments/test.rb:
29
+ config.gem 'myronmarston-factory_data_preloader',
30
+ :lib => 'factory_data_preloader',
31
+ :source => 'http://gems.github.com'
32
+
33
+ Define your preloaded data. FactoryData will automatically require test/factory_data.rb or test/factory_data/*.rb.
34
+ Define your data in these files like this:
35
+
36
+ FactoryData.preload(:users) do |data|
37
+ data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
38
+ data.add(:john) { User.create(:first_name => 'John', :last_name => 'Doe') }
39
+ end
40
+
41
+ FactoryData.preload(:posts, :depends_on => :users) do |data|
42
+ # note the use of the :depends_on option to force the users to be loaded first.
43
+ data.add(:tour) { FactoryData.users(:thom).posts.create(:title => 'Tour!', :body => 'Radiohead will tour soon.') }
44
+ end
45
+
46
+ FactoryData.preload(:some_other_posts, :model_class => Post, :depends_on => :users) do |data|
47
+ # note the use of the :model_class option when the model class cannot be inferred from the symbol.
48
+ data.add(:another_post) { Post.create(:user => FactoryData.users(:john), :title => 'Life is good') }
49
+ end
50
+
51
+ FactoryData.preload(:comments, :depends_on => [:users, :posts]) do |data|
52
+ # :depends_on lets you pass an array
53
+ data.add(:woohoo) { FactoryData.users(:john).comments.create(:post => FactoryData.posts(:tour), :comment => "I can't wait!") }
54
+ end
55
+
56
+ Finally, use this preloaded data in your tests:
57
+
58
+ # test/user_test.rb
59
+ class UserTest < ActiveSupport::TestCase
60
+ def test_thom_has_last_name
61
+ user = FactoryData.users(:thom)
62
+ assert_equal 'York', user.last_name
63
+ end
64
+ end
65
+
66
+ # test/post_test.rb
67
+ class PostTest < ActiveSupport::TestCase
68
+ def test_post_has_body
69
+ post = FactoryData.posts(:tour)
70
+ assert_not_nil post.body
71
+ end
72
+ end
73
+
74
+ All factory data is automatically preloaded for all tests. In a large rails application, this can incur a significant performance penalty
75
+ at the start of each test run. If you want finer grain control over which preloaders run, you can configure it like so:
76
+
77
+ # in test_helper.rb
78
+ FactoryDataPreloader.preload_all = false
79
+
80
+ # test/user_test.rb
81
+ class UserTest < ActiveSupport::TestCase
82
+ preload_factory_data :users # multiple types can be listed as necessary
83
+ # tests go here...
84
+ end
85
+
86
+ # test/post_test.rb
87
+ class PostTest < ActiveSupport::TestCase
88
+ preload_factory_data :posts # dependencies are taken into account, so users will automatically be preloaded as well.
89
+ # tests go here...
90
+ end
91
+
92
+ == Notes, etc.
93
+
94
+ * This gem has been tested with Rails 2.2.2 and Rails 2.3.2.
95
+ * You can create the data using any fixture replacement you want. In this contrived example, I just used ActiveRecord's
96
+ built in methods for simplicity's sake.
97
+ * FactoryData#preload does not actually preload the data. It simply defines the data that will be automatically preloaded
98
+ at the appropriate time (namely, at the same time when rails loads the fixtures).
99
+ * FactoryData#preload defines a new method on FactoryData using the same name as the symbol.
100
+ * This can be mixed-n-matched with fixtures. You may want to keep using fixtures in an existing code base,
101
+ or migrate over to this slowly.
102
+ * If you have dependencies between your preloaded data, you can use the :depends_on option to force some records to be preloaded
103
+ before others. Where no dependencies exist, the preloaders are run in the order they are defined.
104
+ * FactoryData#preload attempts to infer the appropriate model class from the symbol you pass. If your symbol doesn't
105
+ match the model class, pass the model class using the :model_class option.
106
+ * The preloader will also delete all records from the database, before any preloading begins. This is done at the same
107
+ time that rails deletes records for test fixtures. The tables are cleared using the reverse of the order defined by
108
+ your :depends_on options, so be sure to use :depends_on if you have foreign key constraints.
109
+ * Errors that occur during preloading are handled smartly: a warning is printed when one occurs, and an exception is raised
110
+ when the record that had the error is accessed. This should allow the rest of the data to be preloaded, and cause only
111
+ the tests that access the records with errors to fail.
112
+ * The syntax used before the 0.5.0 release is still allowed but has been deprecated. Instead of this:
113
+
114
+ FactoryData.preload(:users) do |data|
115
+ data[:thom] = User.create(:first_name => 'Thom', :last_name => 'York')
116
+ data[:john] = User.create(:first_name => 'John', :last_name => 'Doe')
117
+ end
118
+
119
+ Use this:
120
+
121
+ FactoryData.preload(:users) do |data|
122
+ data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
123
+ data.add(:john) { User.create(:first_name => 'John', :last_name => 'Doe') }
124
+ end
125
+
126
+ == Known Issues
127
+
128
+ * This gem doesn't play nice with autotest.
129
+
130
+ == Copyright
131
+
132
+ Copyright (c) 2009 Myron Marston, Kashless.org. See LICENSE for details.
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 5
4
+ :patch: 2
@@ -0,0 +1,31 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ if ENV['RAILS_VERSION']
5
+ puts "loading Rails version #{ENV['RAILS_VERSION']}"
6
+ gem "activesupport", "= #{ENV['RAILS_VERSION']}"
7
+ gem "activerecord", "= #{ENV['RAILS_VERSION']}"
8
+ end
9
+
10
+ require 'active_support'
11
+ require 'active_record'
12
+ require 'active_record/fixtures'
13
+
14
+ require 'factory_data_preloader/core_ext'
15
+ require 'factory_data_preloader/preloader'
16
+ require 'factory_data_preloader/preloader_collection'
17
+ require 'factory_data_preloader/preloaded_data_hash'
18
+ require 'factory_data_preloader/factory_data'
19
+ require 'factory_data_preloader/rails_core_ext'
20
+
21
+ if defined? Rails.configuration
22
+ Rails.configuration.after_initialize do
23
+ FactoryData.definition_file_paths = [
24
+ File.join(RAILS_ROOT, 'test', 'factory_data'),
25
+ File.join(RAILS_ROOT, 'spec', 'factory_data')
26
+ ]
27
+ FactoryData.find_definitions
28
+ end
29
+ else
30
+ FactoryData.find_definitions
31
+ end
@@ -0,0 +1,23 @@
1
+ # http://github.com/rails/rails/tree/823b623fe2de8846c37aa13250010809ac940b57/activesupport/lib/active_support/core_ext/object/misc.rb
2
+
3
+ unless Object.respond_to?(:try) # Object#try is in Rails 2.3 but not in 2.2.
4
+ class Object
5
+ # Tries to send the method only if object responds to it. Return +nil+ otherwise.
6
+ # It will also forward any arguments and/or block like Object#send does.
7
+ #
8
+ # ==== Example :
9
+ #
10
+ # # Without try
11
+ # @person ? @person.name : nil
12
+ #
13
+ # With try
14
+ # @person.try(:name)
15
+ #
16
+ # # try also accepts arguments/blocks for the method it is trying
17
+ # Person.try(:find, 1)
18
+ # @people.try(:map) {|p| p.name}
19
+ def try(method, *args, &block)
20
+ send(method, *args, &block) if respond_to?(method, true)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ require 'ostruct'
2
+
3
+ module FactoryDataPreloader
4
+ class PreloaderAlreadyDefinedError < StandardError; end
5
+ class PreloadedRecordNotFound < StandardError; end
6
+ class DefinedPreloaderNotRunError < StandardError; end
7
+ class ErrorWhilePreloadingRecord < StandardError; end
8
+
9
+ module DataMethods
10
+ end
11
+
12
+ class FactoryData
13
+ @@single_test_cache = {}
14
+
15
+ extend DataMethods
16
+
17
+ class << self
18
+ # An Array of strings specifying locations that should be searched for
19
+ # factory_data definitions. By default, factory_data_preloader will attempt to require
20
+ # "factory_data," "test/factory_data," and "spec/factory_data." Only the first
21
+ # existing file will be loaded.
22
+ attr_accessor :definition_file_paths
23
+
24
+ def preload(model_type, options = {}, &proc)
25
+ if existing_preloader = AllPreloaders.instance.from_symbol(model_type, false)
26
+ existing_preloader.remove!
27
+ end
28
+
29
+ FactoryDataPreloader::Preloader.new(model_type, options[:model_class], proc, options[:depends_on])
30
+
31
+ DataMethods.class_eval do
32
+ define_method model_type do |key|
33
+ FactoryData.send(:get_record, model_type, key)
34
+ end
35
+ end
36
+ end
37
+
38
+ def delete_preload_data!
39
+ # Delete them in the reverse order of the dependencies, to handle foreign keys
40
+ FactoryDataPreloader.requested_preloaders.dependency_order.reverse.each do |preloader|
41
+ preloader.delete_table_data!
42
+ end
43
+ end
44
+
45
+ def preload_data!
46
+ FactoryDataPreloader.requested_preloaders.dependency_order.each do |preloader|
47
+ preloader.preload!
48
+ end
49
+ end
50
+
51
+ def reset_cache!
52
+ @@single_test_cache = {}
53
+ end
54
+
55
+ def find_definitions
56
+ definition_file_paths.each do |path|
57
+ require("#{path}.rb") if File.exists?("#{path}.rb")
58
+
59
+ if File.directory? path
60
+ Dir[File.join(path, '*.rb')].each do |file|
61
+ require file
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def get_record(type, key)
70
+ preloader = AllPreloaders.instance.from_symbol(type)
71
+ @@single_test_cache[type] ||= {}
72
+ @@single_test_cache[type][key] ||= preloader.get_record(key)
73
+ end
74
+ end
75
+
76
+ self.definition_file_paths = %w(factory_data test/factory_data spec/factory_data)
77
+ end
78
+ end
79
+
80
+ # alias this class so that apps that use it don't have to use the fully qualified name.
81
+ FactoryData = FactoryDataPreloader::FactoryData
@@ -0,0 +1,53 @@
1
+ class PreloadedDataHash
2
+ attr_reader :preloader
3
+
4
+ def initialize(preloader)
5
+ @preloader, @backing_hash = preloader, {}
6
+ end
7
+
8
+ def []=(key, record)
9
+ puts "DEPRECATION WARNING: Instead of 'data[:#{key}] = record' please use 'data.add(:#{key}) { record }'"
10
+ add_to_backing_hash(key, record)
11
+ end
12
+
13
+ def [](key)
14
+ @backing_hash[key]
15
+ end
16
+
17
+ def record_ids
18
+ @backing_hash.values.select { |value| value.is_a?(Fixnum) }
19
+ end
20
+
21
+ def add(key)
22
+ raise "You must pass a block to PreloaderDataHash#add. You forgot to use the block in your #{preloader.model_type} prelodaer for the #{key.inspect} record." unless block_given?
23
+ begin
24
+ add_to_backing_hash(key, yield)
25
+ rescue => e
26
+ puts "WARNING: an error occurred while preloading the #{preloader.model_type.to_s}(:#{key}) record: #{e.class.to_s}: #{e.message}"
27
+ add_to_backing_hash(key, nil, e)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def add_to_backing_hash(key, record, error = nil)
34
+ print '.'
35
+ if record
36
+ if record.new_record? && !record.save
37
+ raise StandardError.new("Error preloading factory data. #{preloader.model_class.to_s} :#{key.to_s} could not be saved. Errors: #{pretty_error_messages(record)}")
38
+ else
39
+ @backing_hash[key] = record.id
40
+ end
41
+ else
42
+ @backing_hash[key] = error
43
+ end
44
+ end
45
+
46
+ # Borrowed from shoulda: http://github.com/thoughtbot/shoulda/blob/e02228d45a879ff92cb72b84f5fccc6a5f856a65/lib/shoulda/active_record/helpers.rb#L4-9
47
+ def pretty_error_messages(obj)
48
+ obj.errors.map do |a, m|
49
+ msg = "#{a} #{m}"
50
+ msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,102 @@
1
+ require 'active_support/deprecation'
2
+
3
+ module FactoryDataPreloader
4
+ class PreloaderNotDefinedError < StandardError; end
5
+
6
+ mattr_accessor :preload_all
7
+ self.preload_all = true
8
+
9
+ mattr_accessor :preload_types
10
+ self.preload_types = []
11
+
12
+ class << self
13
+ alias :preload_all? :preload_all
14
+
15
+ def requested_preloaders
16
+ if preload_all?
17
+ AllPreloaders.instance
18
+ else
19
+ preloaders = self.preload_types.collect { |type| AllPreloaders.instance.from_symbol(type) }
20
+ preloaders += (preloaders.collect { |p| p.all_dependencies }).flatten
21
+ preloaders.uniq!
22
+ PreloaderCollection.new(preloaders)
23
+ end
24
+ end
25
+ end
26
+
27
+ class Preloader
28
+ attr_accessor :model_type, :model_class, :proc, :depends_on
29
+ attr_reader :data
30
+
31
+ def initialize(model_type, model_class, proc, depends_on)
32
+ model_class ||= model_type.to_s.pluralize.classify.constantize
33
+
34
+ @model_type, @model_class, @proc, @depends_on = model_type, model_class, proc, [depends_on].compact.flatten
35
+ AllPreloaders.instance << self
36
+
37
+ DataMethods.class_eval do
38
+ define_method model_type do |key|
39
+ FactoryData.send(:get_record, model_type, key)
40
+ end
41
+ end
42
+ end
43
+
44
+ def preload!
45
+ return if preloaded?
46
+ @data = PreloadedDataHash.new(self)
47
+ print "Preloading #{model_type}:"
48
+ benchmark_measurement = Benchmark.measure { self.proc.try(:call, @data) }
49
+ print "(#{format('%.3f', benchmark_measurement.real)} secs)\n"
50
+ end
51
+
52
+ def preloaded?
53
+ !@data.nil?
54
+ end
55
+
56
+ def delete_table_data!
57
+ unless @table_data_deleted
58
+ self.model_class.delete_all
59
+ @table_data_deleted = true
60
+ end
61
+ end
62
+
63
+ def dependencies
64
+ self.depends_on.collect { |dependency| AllPreloaders.instance.from_symbol(dependency) }
65
+ end
66
+
67
+ def all_dependencies
68
+ (self.dependencies + (self.dependencies.collect { |d| d.all_dependencies }).flatten).uniq
69
+ end
70
+
71
+ def get_record(key)
72
+ unless self.preloaded?
73
+ raise DefinedPreloaderNotRunError.new, "The :#{self.model_type} preloader has never been run. Did you forget to add the 'preload_factory_data :#{self.model_type}' declaration to your test case? You'll need this at the top of your test case class if you want to use the factory data defined by this preloader."
74
+ end
75
+
76
+ unless record_id_or_error = self.data[key]
77
+ raise PreloadedRecordNotFound.new, "Could not find a preloaded record #{self.model_type} record for :#{key}. Did you mispell :#{key}?"
78
+ end
79
+
80
+ if record_id_or_error.is_a?(Exception)
81
+ raise ErrorWhilePreloadingRecord.new, "An error occurred while preloading #{self.model_type}(:#{key}): #{record_id_or_error.class.to_s}: #{record_id_or_error.message}\n\nBacktrace:\n\n #{record_id_or_error.backtrace.join("\n ")}\n"
82
+ end
83
+
84
+ self.model_class.find_by_id(record_id_or_error)
85
+ end
86
+
87
+ def remove!
88
+ preloader = self
89
+ DataMethods.class_eval do
90
+ remove_method(preloader.model_type) if method_defined?(preloader.model_type)
91
+ end
92
+
93
+ if @data
94
+ self.model_class.delete_all(:id => @data.record_ids)
95
+ @data = nil
96
+ end
97
+
98
+ AllPreloaders.instance.delete(self)
99
+ end
100
+ end
101
+
102
+ end
@@ -0,0 +1,30 @@
1
+ module FactoryDataPreloader
2
+ class PreloaderCollection < Array
3
+ def dependency_order
4
+ unordered_preloaders = Array.new(self) # rather than using self.dup since singleton doesn't allow duping.
5
+ ordered_preloaders = []
6
+
7
+ until unordered_preloaders.empty?
8
+ unordered_preloaders.each do |preloader|
9
+ if preloader.dependencies.all? { |dependency| ordered_preloaders.include?(dependency) }
10
+ ordered_preloaders << unordered_preloaders.delete(preloader)
11
+ end
12
+ end
13
+ end
14
+
15
+ ordered_preloaders
16
+ end
17
+
18
+ def from_symbol(symbol, raise_error = true)
19
+ unless preloader = self.detect { |p| p.model_type == symbol }
20
+ raise PreloaderNotDefinedError, "The preloader for :#{symbol} has not been defined." if raise_error
21
+ end
22
+ preloader
23
+ end
24
+ end
25
+
26
+ class AllPreloaders < PreloaderCollection
27
+ include Singleton
28
+ end
29
+
30
+ end
@@ -0,0 +1,39 @@
1
+ # TODO: add tests for this. I've manually tested this in a Rails 2.2 and 2.3 app, but haven't found a way to add
2
+ # a test for this to our test suite. It's difficult to test this since it just modifies what happens before the
3
+ # tests are run.
4
+
5
+ # Between Rails 2.2 and 2.3, the fixture loading code was moved from
6
+ # Test::Unit::TestCase to ActiveRecord::TestFixtures. See this commit:
7
+ # http://github.com/rails/rails/commit/b0ee1bdf2650d7a8380d4e9be58bba8d9c5bd40e
8
+ patch_module = defined?(ActiveRecord::TestFixtures) ? ActiveRecord::TestFixtures : Test::Unit::TestCase
9
+
10
+ patch_module.class_eval do
11
+ def load_fixtures_with_preloaded_factory_data
12
+ val = load_fixtures_without_preloaded_factory_data
13
+ FactoryData.preload_data!
14
+ val
15
+ end
16
+
17
+ def teardown_fixtures_with_preloaded_factory_data
18
+ FactoryData.reset_cache!
19
+ teardown_fixtures_without_preloaded_factory_data
20
+ end
21
+
22
+ alias_method_chain :load_fixtures, :preloaded_factory_data
23
+ alias_method_chain :teardown_fixtures, :preloaded_factory_data
24
+ end
25
+
26
+ class Fixtures
27
+ def delete_existing_fixtures_with_preloaded_factory_data
28
+ delete_existing_fixtures_without_preloaded_factory_data
29
+ FactoryData.delete_preload_data!
30
+ end
31
+
32
+ alias_method_chain :delete_existing_fixtures, :preloaded_factory_data
33
+ end
34
+
35
+ class ActiveSupport::TestCase
36
+ def self.preload_factory_data(*types)
37
+ types.each { |t| FactoryDataPreloader.preload_types << t }
38
+ end
39
+ end
@@ -0,0 +1,149 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class FactoryDataTest < Test::Unit::TestCase
4
+ def setup
5
+ FactoryDataPreloader.reset!
6
+ end
7
+
8
+ context 'Calling FactoryData.preload(:users)' do
9
+ setup do
10
+ FactoryData.preload(:users) do |data|
11
+ data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
12
+ end
13
+ end
14
+
15
+ should_not_change 'User.count'
16
+ should_change "FactoryData.methods.include?('users')", :from => false, :to => true
17
+
18
+ context 'when there was a previous user record in the database' do
19
+ setup { User.create(:first_name => 'Barack', :last_name => 'Obama') }
20
+
21
+ context 'and calling FactoryData.delete_preload_data!' do
22
+ setup { FactoryData.delete_preload_data! }
23
+ should_change 'User.count', :to => 0
24
+ end
25
+ end
26
+
27
+ context 'and later calling FactoryData.preload_data!' do
28
+ setup do
29
+ @out, @err = OutputCapturer.capture do
30
+ FactoryData.preload_data!
31
+ end
32
+ end
33
+
34
+ should_change 'User.count', :by => 1
35
+
36
+ context 'and later re-defining the preloaders' do
37
+ setup do
38
+ FactoryData.preload(:users) do |data|
39
+ data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
40
+ data.add(:john) { User.create(:first_name => 'John', :last_name => 'Doe') }
41
+ end
42
+ end
43
+
44
+ should_change 'User.count', :by => -1
45
+
46
+ context 'and preloading the re-defined preloader' do
47
+ setup do
48
+ @out, @err = OutputCapturer.capture do
49
+ FactoryData.preload_data!
50
+ end
51
+ end
52
+
53
+ should_change 'User.count', :by => 2
54
+ end
55
+ end
56
+
57
+ context 'and later calling FactoryData.users(key)' do
58
+ setup { @user = FactoryData.users(:thom) }
59
+
60
+ should 'retrieve the correct user' do
61
+ assert_equal 'Thom', @user.first_name
62
+ assert_equal 'York', @user.last_name
63
+ assert !@user.new_record?
64
+ end
65
+
66
+ should 'raise the appropriate error for a non-existant key' do
67
+ assert_raise FactoryDataPreloader::PreloadedRecordNotFound do
68
+ FactoryData.users(:not_a_user)
69
+ end
70
+ end
71
+
72
+ should 'cache the record so as not to use User.find more than necessary' do
73
+ User.expects(:find).never
74
+ user2 = FactoryData.users(:thom)
75
+ assert_equal @user.object_id, user2.object_id
76
+ end
77
+
78
+ context 'and later calling FactoryData.reset_cache!' do
79
+ setup { FactoryData.reset_cache! }
80
+
81
+ should 'reload the record from the database the next time FactoryData.users(key) is called' do
82
+ User.expects(:find).once.returns(@user)
83
+ FactoryData.users(:thom)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ context 'Preloading with an explicit :model_class option' do
91
+ setup do
92
+ FactoryData.preload(:posts, :model_class => User) do |data|
93
+ data.add(:george) { User.create(:first_name => 'George', :last_name => 'Washington') }
94
+ end
95
+ @out, @err = OutputCapturer.capture do
96
+ FactoryData.preload_data!
97
+ end
98
+ end
99
+
100
+ should 'use the passed model_class rather than inferring the class from the symbol' do
101
+ assert_equal User, FactoryData.posts(:george).class
102
+ end
103
+ end
104
+
105
+ context 'Preloading multiple record types, with dependencies' do
106
+ setup do
107
+ FactoryData.preload(:comments, :depends_on => [:users, :posts]) do |data|
108
+ data.add(:woohoo) { FactoryData.users(:john).comments.create(:post => FactoryData.posts(:tour), :comment => "I can't wait!") }
109
+ end
110
+
111
+ FactoryData.preload(:posts, :depends_on => :users) do |data|
112
+ data.add(:tour) { FactoryData.users(:thom).posts.create(:title => 'Tour!', :body => 'Radiohead will tour soon.') }
113
+ end
114
+
115
+ FactoryData.preload(:users) do |data|
116
+ data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
117
+ data.add(:john) { User.create(:first_name => 'John', :last_name => 'Doe') }
118
+ end
119
+ end
120
+
121
+ should "raise the appropriate error when a developer tries to access a record that wasn't preloaded" do
122
+ FactoryDataPreloader.preload_all = false
123
+ FactoryDataPreloader.preload_types << :users
124
+
125
+ @out, @err = OutputCapturer.capture do
126
+ FactoryData.preload_data!
127
+ end
128
+
129
+ assert FactoryData.users(:thom)
130
+ assert_raise FactoryDataPreloader::DefinedPreloaderNotRunError do
131
+ FactoryData.posts(:tour)
132
+ end
133
+ end
134
+
135
+ should 'preload them in the proper order, allowing you to use the dependencies' do
136
+ @out, @err = OutputCapturer.capture do
137
+ FactoryData.preload_data!
138
+ end
139
+
140
+ assert_equal 'Thom', FactoryData.users(:thom).first_name
141
+ assert_equal 'John', FactoryData.users(:john).first_name
142
+
143
+ assert_equal FactoryData.users(:thom), FactoryData.posts(:tour).user
144
+
145
+ assert_equal FactoryData.users(:john), FactoryData.comments(:woohoo).user
146
+ assert_equal FactoryData.posts(:tour), FactoryData.comments(:woohoo).post
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,31 @@
1
+ class EmailAddress < ActiveRecord::Base
2
+ end
3
+
4
+ class User < ActiveRecord::Base
5
+ has_many :posts
6
+ has_many :comments
7
+ validates_presence_of :last_name
8
+ end
9
+
10
+ class Post < ActiveRecord::Base
11
+ belongs_to :user
12
+ has_many :comments
13
+ has_many :post_images
14
+ end
15
+
16
+ class Comment < ActiveRecord::Base
17
+ belongs_to :post
18
+ belongs_to :user
19
+ end
20
+
21
+ class PostImage < ActiveRecord::Base
22
+ belongs_to :post
23
+ has_many :post_image_ratings
24
+ end
25
+
26
+ class PostImageRating < ActiveRecord::Base
27
+ belongs_to :post_image
28
+ end
29
+
30
+ class IpAddress < ActiveRecord::Base
31
+ end
@@ -0,0 +1,54 @@
1
+ OutputCapturer.capture do
2
+ ActiveRecord::Schema.define do
3
+ create_table :email_addresses, :force => true do |t|
4
+ t.string :address
5
+ end
6
+ end
7
+
8
+ ActiveRecord::Schema.define do
9
+ create_table :users, :force => true do |t|
10
+ t.string :first_name
11
+ t.string :last_name
12
+ t.timestamps
13
+ end
14
+ end
15
+
16
+ ActiveRecord::Schema.define do
17
+ create_table :posts, :force => true do |t|
18
+ t.references :user
19
+ t.string :title
20
+ t.string :body
21
+ t.timestamps
22
+ end
23
+ end
24
+
25
+ ActiveRecord::Schema.define do
26
+ create_table :comments, :force => true do |t|
27
+ t.references :user
28
+ t.references :post
29
+ t.string :comment
30
+ t.timestamps
31
+ end
32
+ end
33
+
34
+ ActiveRecord::Schema.define do
35
+ create_table :post_images, :force => true do |t|
36
+ t.references :post
37
+ t.timestamps
38
+ end
39
+ end
40
+
41
+ ActiveRecord::Schema.define do
42
+ create_table :post_image_ratings, :force => true do |t|
43
+ t.references :post_image
44
+ t.timestamps
45
+ end
46
+ end
47
+
48
+ ActiveRecord::Schema.define do
49
+ create_table :ip_addresses, :force => true do |t|
50
+ t.string :ip_address
51
+ t.timestamps
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PreloadedDataHashTest < Test::Unit::TestCase
4
+ def setup
5
+ FactoryDataPreloader.reset!
6
+ end
7
+
8
+ def self.test_add_valid_record(desc, &block)
9
+ context desc do
10
+ setup do
11
+ @out, @err = OutputCapturer.capture do
12
+ @preloaded_data_hash.add(:record) { @user = instance_eval(&block) }
13
+ end
14
+ end
15
+
16
+ should 'print a dot' do
17
+ assert_equal '', @err
18
+ assert_equal '.', @out
19
+ end
20
+
21
+ should 'add the record id to the hash' do
22
+ assert_equal @user.id, @preloaded_data_hash[:record]
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.test_add_invalid_record(desc, error_msg_regex, &block)
28
+ context desc do
29
+ setup do
30
+ @out, @err = OutputCapturer.capture do
31
+ @preloaded_data_hash.add(:record) { instance_eval(&block) }
32
+ end
33
+ end
34
+
35
+ should 'print a warning message' do
36
+ assert_equal '', @err
37
+ assert_match /WARNING: an error occurred while preloading/, @out
38
+ end
39
+
40
+ should 'add an error message to the hash' do
41
+ assert @preloaded_data_hash[:record].is_a?(Exception)
42
+ assert_match error_msg_regex, @preloaded_data_hash[:record].message
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'For a new preloader data hash' do
48
+ setup do
49
+ @preloaded_data_hash = PreloadedDataHash.new(stub_everything(:model_class => User))
50
+ end
51
+
52
+ test_add_valid_record('when a saved record is added') { User.create!(:first_name => 'Barack', :last_name => 'Obama') }
53
+ test_add_valid_record('when a valid unsaved record is added') { User.new(:first_name => 'Barack', :last_name => 'Obama') }
54
+ test_add_invalid_record('when an invalid unsaved record is added', /Error preloading factory data.*could not be saved/) { User.new }
55
+ test_add_invalid_record('when an error occurs while adding the preloading a record', /This is an error/) { raise 'This is an error' }
56
+
57
+ context 'adding a record through the deprecated []= method' do
58
+ setup do
59
+ @out, @err = OutputCapturer.capture do
60
+ @preloaded_data_hash[:record] = (@user = User.create!(:first_name => 'Barack', :last_name => 'Obama'))
61
+ end
62
+ end
63
+
64
+ should 'print a deprecation warning' do
65
+ assert_equal '', @err
66
+ assert_match /DEPRECATION WARNING: Instead of .* please use .*/, @out
67
+ end
68
+
69
+ should 'add the record id to the hash' do
70
+ assert_equal @user.id, @preloaded_data_hash[:record]
71
+ end
72
+ end
73
+
74
+ context 'when multiple records and errors have been added' do
75
+ setup do
76
+ @out, @err = OutputCapturer.capture do
77
+ @preloaded_data_hash.add(:barack) { @barack = User.create!(:first_name => 'Barack', :last_name => 'Obama') }
78
+ @preloaded_data_hash.add(:error) { raise 'An error' }
79
+ @preloaded_data_hash.add(:george) { @george = User.create!(:first_name => 'George', :last_name => 'Washington') }
80
+ end
81
+ end
82
+
83
+ should 'return the record ids for #record_ids' do
84
+ assert_same_elements [@barack.id, @george.id], @preloaded_data_hash.record_ids
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,180 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PreloaderTest < Test::Unit::TestCase
4
+ def setup
5
+ FactoryDataPreloader.reset!
6
+ end
7
+
8
+ context 'A new preloader' do
9
+ setup do
10
+ proc = lambda { |data|
11
+ data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
12
+ data.add(:john) { User.create(:first_name => 'John', :last_name => 'Doe') }
13
+ }
14
+ @preloader = FactoryDataPreloader::Preloader.new(:users, User, proc, [])
15
+ end
16
+
17
+ should 'be automatically added to the PreloaderCollection' do
18
+ assert_equal [@preloader], FactoryDataPreloader::AllPreloaders.instance
19
+ end
20
+
21
+ context 'when preloaded' do
22
+ setup do
23
+ @out, @err = OutputCapturer.capture do
24
+ @preloader.preload!
25
+ end
26
+ end
27
+
28
+ should_change 'User.count', :by => 2
29
+
30
+ should 'return the preloaded data when #get_record is called' do
31
+ assert_equal 'York', @preloader.get_record(:thom).last_name
32
+ assert_equal 'Doe', @preloader.get_record(:john).last_name
33
+ end
34
+
35
+ should 'print out a preloader message, a dot for each record and a benchmark' do
36
+ assert_equal '', @err
37
+ assert_match /Preloading users:\.\.\([\d\.]+ secs\)/, @out
38
+ end
39
+
40
+ context 'when preloaded again' do
41
+ setup do
42
+ @out, @err = OutputCapturer.capture do
43
+ @preloader.preload!
44
+ end
45
+ end
46
+
47
+ should 'print nothing' do
48
+ assert_equal '', @err
49
+ assert_equal '', @out
50
+ end
51
+
52
+ should_not_change 'User.count'
53
+ end
54
+
55
+ should 'issue a delete statement if #delete_table_data! is called' do
56
+ User.expects(:delete_all).once
57
+ @preloader.delete_table_data!
58
+ end
59
+
60
+ context 'when #delete_table_data! is called' do
61
+ setup do
62
+ @preloader.delete_table_data!
63
+ end
64
+
65
+ should 'not issue another delete statement if #delete_table_data! is later called on the same preloader' do
66
+ User.expects(:delete_all).never
67
+ @preloader.delete_table_data!
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'A new preloader for email_addresses' do
74
+ setup do
75
+ @preloader = FactoryDataPreloader::Preloader.new(:email_addresses, nil, lambda { }, [])
76
+ end
77
+
78
+ should 'infer the model class' do
79
+ assert_equal EmailAddress, @preloader.model_class
80
+ end
81
+ end
82
+
83
+ context 'A preloader with errors' do
84
+ setup do
85
+ proc = lambda { |data|
86
+ data.add(:thom) { raise StandardError('Error for thom') }
87
+ data.add(:john) { @john = User.create(:first_name => 'John', :last_name => 'Doe') }
88
+ }
89
+ @preloader = FactoryDataPreloader::Preloader.new(:users, User, proc, [])
90
+ @out, @err = OutputCapturer.capture do
91
+ @preloader.preload!
92
+ end
93
+ end
94
+
95
+ should 'raise an exception when the record with the error is accessed' do
96
+ assert_raise FactoryDataPreloader::ErrorWhilePreloadingRecord do
97
+ @preloader.get_record(:thom)
98
+ end
99
+ end
100
+
101
+ should 'allow the error-free records to be accessed, even when they were created after the error record' do
102
+ assert_equal @john, @preloader.get_record(:john)
103
+ end
104
+ end
105
+
106
+ context 'A preloader with dependencies' do
107
+ setup do
108
+ @comments = FactoryDataPreloader::Preloader.new(:comments, Comment, nil, [:users, :posts])
109
+ end
110
+
111
+ should 'raise PreloaderNotDefinedError for #dependencies if the preloader it depends on are not defined' do
112
+ assert_raise FactoryDataPreloader::PreloaderNotDefinedError do
113
+ @comments.dependencies
114
+ end
115
+ end
116
+
117
+ context 'when the dependency preloaders have also been defined' do
118
+ setup do
119
+ @posts = FactoryDataPreloader::Preloader.new(:posts, Post, nil, [:users])
120
+ @users = FactoryDataPreloader::Preloader.new(:users, User, nil, [])
121
+ end
122
+
123
+ should 'return the preloader objects for #dependencies' do
124
+ assert_equal [@users, @posts], @comments.dependencies
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'A series of preloaders, with dependencies,' do
130
+ setup do
131
+ @post_image_ratings = FactoryDataPreloader::Preloader.new(:post_image_ratings, PostImageRating, nil, [:post_images])
132
+ @post_images = FactoryDataPreloader::Preloader.new(:post_images, PostImage, nil, [:posts])
133
+ @ip_addresses = FactoryDataPreloader::Preloader.new(:ip_addresses, IpAddress, nil, [])
134
+ @posts = FactoryDataPreloader::Preloader.new(:posts, Post, nil, [:users])
135
+ @users = FactoryDataPreloader::Preloader.new(:users, User, nil, [])
136
+ end
137
+
138
+ should 'sort correctly for PreloaderCollection.instance.dependency_order' do
139
+ expected = [@ip_addresses, @users, @posts, @post_images, @post_image_ratings]
140
+ assert_equal expected.map(&:model_type), FactoryDataPreloader::AllPreloaders.instance.dependency_order.map(&:model_type)
141
+ end
142
+
143
+ should 'return the correct preloader objects for #all_dependencies' do
144
+ assert_same_elements [@post_images, @posts, @users], @post_image_ratings.all_dependencies
145
+ assert_same_elements [@posts, @users], @post_images.all_dependencies
146
+ assert_same_elements [], @ip_addresses.all_dependencies
147
+ assert_same_elements [@users], @posts.all_dependencies
148
+ assert_same_elements [], @users.all_dependencies
149
+ end
150
+
151
+ context 'when FactoryDataPreloader.preload_all = true' do
152
+ setup do
153
+ FactoryDataPreloader.preload_all = true
154
+ end
155
+
156
+ should 'return all preloaders for FactoryDataPreloader.requested_preloaders' do
157
+ expected = [@ip_addresses, @users, @posts, @post_images, @post_image_ratings]
158
+ assert_equal expected.map(&:model_type), FactoryDataPreloader.requested_preloaders.dependency_order.map(&:model_type)
159
+ end
160
+ end
161
+
162
+ context 'when FactoryDataPreloader.preload_all = false' do
163
+ setup do
164
+ FactoryDataPreloader.preload_all = false
165
+ end
166
+
167
+ should 'return no preloaders when for FactoryDataPreloader.requested_preloaders when preload_types is empty' do
168
+ assert_equal [], FactoryDataPreloader.preload_types
169
+ assert_equal [], FactoryDataPreloader.requested_preloaders
170
+ end
171
+
172
+ should 'return just the requested preloaders for FactoryDataPreloader.requested_preloaders' do
173
+ FactoryDataPreloader.preload_types << :post_images
174
+ FactoryDataPreloader.preload_types << :ip_addresses
175
+ expected = [@ip_addresses, @users, @posts, @post_images]
176
+ assert_equal expected.map(&:model_type), FactoryDataPreloader.requested_preloaders.dependency_order.map(&:model_type)
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ begin
6
+ require 'ruby-debug'
7
+ Debugger.start
8
+ Debugger.settings[:autoeval] = true if Debugger.respond_to?(:settings)
9
+ rescue LoadError
10
+ # ruby-debug wasn't available so neither can the debugging be
11
+ end
12
+
13
+
14
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
15
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
16
+
17
+ require 'factory_data_preloader'
18
+
19
+ ActiveRecord::Base.establish_connection({ :database => ":memory:", :adapter => 'sqlite3', :timeout => 500 })
20
+
21
+ module OutputCapturer
22
+ # borrowed from zentest assertions...
23
+ def self.capture
24
+ require 'stringio'
25
+ orig_stdout = $stdout.dup
26
+ orig_stderr = $stderr.dup
27
+ captured_stdout = StringIO.new
28
+ captured_stderr = StringIO.new
29
+ $stdout = captured_stdout
30
+ $stderr = captured_stderr
31
+ yield
32
+ captured_stdout.rewind
33
+ captured_stderr.rewind
34
+ return captured_stdout.string, captured_stderr.string
35
+ ensure
36
+ $stdout = orig_stdout
37
+ $stderr = orig_stderr
38
+ end
39
+ end
40
+
41
+ module FactoryDataPreloader
42
+ def self.reset!
43
+ self.preload_all = true
44
+ self.preload_types = []
45
+
46
+ preloaders = Array.new(FactoryDataPreloader::AllPreloaders.instance)
47
+ preloaders.each do |preloader|
48
+ preloader.remove!
49
+ end
50
+
51
+ FactoryData.reset_cache!
52
+ end
53
+ end
54
+
55
+ require 'lib/schema'
56
+ require 'lib/models'
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: factory_data_preloader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.2
5
+ platform: ruby
6
+ authors:
7
+ - Myron Marston
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: Shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mocha
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: myron.marston@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ - LICENSE
44
+ files:
45
+ - CHANGELOG.rdoc
46
+ - README.rdoc
47
+ - VERSION.yml
48
+ - lib/factory_data_preloader/core_ext.rb
49
+ - lib/factory_data_preloader/factory_data.rb
50
+ - lib/factory_data_preloader/preloaded_data_hash.rb
51
+ - lib/factory_data_preloader/preloader.rb
52
+ - lib/factory_data_preloader/preloader_collection.rb
53
+ - lib/factory_data_preloader/rails_core_ext.rb
54
+ - lib/factory_data_preloader.rb
55
+ - test/factory_data_test.rb
56
+ - test/lib/models.rb
57
+ - test/lib/schema.rb
58
+ - test/preloaded_data_hash_test.rb
59
+ - test/preloader_test.rb
60
+ - test/test_helper.rb
61
+ - LICENSE
62
+ has_rdoc: true
63
+ homepage: http://github.com/myronmarston/factory_data_preloader
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --inline-source
69
+ - --charset=UTF-8
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.5
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: A library for preloading test data in rails applications.
91
+ test_files: []
92
+