factory_data_preloader 0.5.2

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.
@@ -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
+