myronmarston-factory_data_preloader 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +11 -6
- data/VERSION.yml +1 -1
- data/lib/factory_data_preloader/factory_data.rb +63 -59
- data/lib/factory_data_preloader/preloader.rb +27 -0
- data/lib/factory_data_preloader.rb +1 -0
- data/test/factory_data_test.rb +10 -10
- data/test/preloader_test.rb +43 -0
- data/test/test_helper.rb +2 -2
- metadata +3 -1
data/README.rdoc
CHANGED
@@ -6,7 +6,7 @@ fixtures don't represent "real data". Fixtures just dump the data you've define
|
|
6
6
|
without going through the model--meaning you don't get any of the validations or before/after save callback
|
7
7
|
behavior defined on your models.
|
8
8
|
|
9
|
-
There are multiple gems and plugins that address this; my personal favorite is factory girl.
|
9
|
+
There are multiple gems and plugins that address this; my personal favorite is {factory girl}[http://github.com/thoughtbot/factory_girl/].
|
10
10
|
|
11
11
|
However, none of the solutions I've tried are as fast as using test fixtures. When you use test fixtures,
|
12
12
|
rails rolls back the database transaction used in each test and allows you to re-use your fixture data.
|
@@ -38,16 +38,18 @@ Define your data like this:
|
|
38
38
|
data[:john] = User.create(:first_name => 'John', :last_name => 'Doe')
|
39
39
|
end
|
40
40
|
|
41
|
-
FactoryData.preload(:posts) do |data|
|
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.
|
42
43
|
data[:tour] = FactoryData.users(:thom).posts.create(:title => 'Tour!', :body => 'Radiohead will tour soon.')
|
43
44
|
end
|
44
45
|
|
45
|
-
FactoryData.preload(:some_other_posts, :model_class => Post) do |data|
|
46
|
+
FactoryData.preload(:some_other_posts, :model_class => Post, :depends_on => :users) do |data|
|
46
47
|
# note the use of the :model_class option when the model class cannot be inferred from the symbol.
|
47
48
|
data[:another_post] = Post.create(:user => FactoryData.users(:john), :title => 'Life is good')
|
48
49
|
end
|
49
50
|
|
50
|
-
FactoryData.preload(:comments) do |data|
|
51
|
+
FactoryData.preload(:comments, :depends_on => [:users, :posts]) do |data|
|
52
|
+
# :depends_on lets you pass an array
|
51
53
|
data[:woohoo] = FactoryData.users(:john).comments.create(:post => FactoryData.posts(:tour), :comment => "I can't wait!")
|
52
54
|
end
|
53
55
|
|
@@ -70,10 +72,13 @@ Finally, use this preloaded data in your tests:
|
|
70
72
|
* FactoryData#preload defines a new method on FactoryData using the same name as the symbol.
|
71
73
|
* This can be mixed-n-matched with fixtures. You may want to keep using fixtures in an existing code base,
|
72
74
|
or migrate over to this slowly.
|
73
|
-
*
|
74
|
-
the
|
75
|
+
* If you have dependencies between your preloaded data, you can use the :depends_on option to force some records to be preloaded
|
76
|
+
before others. Where no dependencies exist, the preloaders are run in the order they are defined.
|
75
77
|
* FactoryData#preload attempts to infer the appropriate model class from the symbol you pass. If your symbol doesn't
|
76
78
|
match the model class, pass the model class using the :model_class option.
|
79
|
+
* The preloader will also delete all records from the database, before any preloading begins. This is done at the same
|
80
|
+
time that rails deletes records for test fixtures. The tables are cleared using the reverse of the order defined by
|
81
|
+
your :depends_on options, so be sure to use :depends_on if you have foreign key constraints.
|
77
82
|
|
78
83
|
== Copyright
|
79
84
|
|
data/VERSION.yml
CHANGED
@@ -1,84 +1,88 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
|
3
|
-
|
4
|
-
class
|
3
|
+
module FactoryDataPreloader
|
4
|
+
class PreloaderAlreadyDefinedError < StandardError; end
|
5
|
+
class PreloadedRecordNotFound < StandardError; end
|
5
6
|
|
6
|
-
class FactoryData
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
class FactoryData
|
8
|
+
@@preloaded_cache = nil
|
9
|
+
@@preloaded_data_deleted = nil
|
10
|
+
@@single_test_cache = {}
|
11
|
+
@@preloaders = []
|
11
12
|
|
12
|
-
|
13
|
+
class << self
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
def preload(model_type, options = {}, &proc)
|
16
|
+
raise PreloaderAlreadyDefinedError.new, "You have already defined the preloader for #{model_type.to_s}" if @@preloaders.map(&:model_type).include?(model_type)
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
model_class = options[:model_class] || model_type.to_s.singularize.classify.constantize
|
19
|
+
depends_on = [options[:depends_on]].compact.flatten
|
20
|
+
@@preloaders << FactoryDataPreloader::Preloader.new(model_type, model_class, proc, @@preloaders.size, depends_on)
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
class << self; self; end.class_eval do
|
23
|
+
define_method model_type do |key|
|
24
|
+
get_record(model_type, model_class, key)
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
25
|
-
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
def delete_preload_data!
|
30
|
+
# make sure this only runs once...
|
31
|
+
return unless @@preloaded_data_deleted.nil?
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
# the preloaders are listed in the parent -> child table order,
|
34
|
+
# so we need to delete them in reverse.
|
35
|
+
@@preloaders.sort.reverse.each do |preloader|
|
36
|
+
preloader.model_class.delete_all
|
37
|
+
end
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
+
@@preloaded_data_deleted = true
|
40
|
+
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
def preload_data!
|
43
|
+
return unless @@preloaded_cache.nil? # make sure the data is only preloaded once.
|
44
|
+
@@preloaded_cache = {}
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
next
|
54
|
-
end
|
46
|
+
@@preloaders.sort.each do |preloader|
|
47
|
+
cache = @@preloaded_cache[preloader.model_type] ||= {}
|
48
|
+
preloader.data.each do |key, record|
|
49
|
+
if record.new_record? && !record.save
|
50
|
+
puts "\nError preloading factory data. #{preloader.model_class.to_s} :#{key.to_s} could not be saved. Errors: "
|
51
|
+
puts pretty_error_messages(record)
|
52
|
+
puts "\n\n"
|
53
|
+
next
|
54
|
+
end
|
55
55
|
|
56
|
-
|
56
|
+
cache[key] = record.id
|
57
|
+
end
|
57
58
|
end
|
58
59
|
end
|
59
|
-
end
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
def reset_cache!
|
62
|
+
@@single_test_cache = {}
|
63
|
+
end
|
64
64
|
|
65
|
-
|
65
|
+
private
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
67
|
+
def get_record(type, model_class, key)
|
68
|
+
@@single_test_cache[type] ||= {}
|
69
|
+
@@single_test_cache[type][key] ||= begin
|
70
|
+
record = model_class.find_by_id(@@preloaded_cache[type][key])
|
71
|
+
raise PreloadedRecordNotFound.new, "Could not find a record for FactoryData.#{type}(:#{key})." unless record
|
72
|
+
record
|
73
|
+
end
|
73
74
|
end
|
74
|
-
end
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
# Borrowed from shoulda: http://github.com/thoughtbot/shoulda/blob/e02228d45a879ff92cb72b84f5fccc6a5f856a65/lib/shoulda/active_record/helpers.rb#L4-9
|
77
|
+
def pretty_error_messages(obj)
|
78
|
+
obj.errors.map do |a, m|
|
79
|
+
msg = "#{a} #{m}"
|
80
|
+
msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
|
81
|
+
end
|
81
82
|
end
|
82
83
|
end
|
83
84
|
end
|
84
|
-
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# alias this class so that apps that use it don't have to use the fully qualified name.
|
88
|
+
FactoryData = FactoryDataPreloader::FactoryData
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module FactoryDataPreloader
|
2
|
+
class Preloader
|
3
|
+
attr_accessor :model_type, :model_class, :proc, :defined_index, :depends_on
|
4
|
+
|
5
|
+
def initialize(model_type, model_class, proc, defined_index, depends_on)
|
6
|
+
@model_type, @model_class, @proc, @defined_index, @depends_on = model_type, model_class, proc, defined_index, depends_on || []
|
7
|
+
end
|
8
|
+
|
9
|
+
def data
|
10
|
+
@data ||= begin
|
11
|
+
data = {}
|
12
|
+
self.proc.try(:call, data)
|
13
|
+
data
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(preloader)
|
18
|
+
if self.depends_on.include?(preloader.model_type)
|
19
|
+
1
|
20
|
+
elsif preloader.depends_on.include?(self.model_type)
|
21
|
+
-1
|
22
|
+
else
|
23
|
+
self.defined_index <=> preloader.defined_index
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/test/factory_data_test.rb
CHANGED
@@ -16,7 +16,7 @@ class FactoryDataTest < Test::Unit::TestCase
|
|
16
16
|
should_change "FactoryData.methods.include?('users')", :from => false, :to => true
|
17
17
|
|
18
18
|
should 'not allow it to be called again' do
|
19
|
-
assert_raise PreloaderAlreadyDefinedError do
|
19
|
+
assert_raise FactoryDataPreloader::PreloaderAlreadyDefinedError do
|
20
20
|
FactoryData.preload(:users)
|
21
21
|
end
|
22
22
|
end
|
@@ -54,7 +54,7 @@ class FactoryDataTest < Test::Unit::TestCase
|
|
54
54
|
end
|
55
55
|
|
56
56
|
should 'raise the appropriate error for a non-existant key' do
|
57
|
-
assert_raise PreloadedRecordNotFound do
|
57
|
+
assert_raise FactoryDataPreloader::PreloadedRecordNotFound do
|
58
58
|
FactoryData.users(:not_a_user)
|
59
59
|
end
|
60
60
|
end
|
@@ -125,25 +125,25 @@ class FactoryDataTest < Test::Unit::TestCase
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
-
context 'Preloading multiple record types' do
|
128
|
+
context 'Preloading multiple record types, with dependencies' do
|
129
129
|
setup do
|
130
|
-
FactoryData.preload(:users) do |data|
|
131
|
-
data[:
|
132
|
-
data[:john] = User.create(:first_name => 'John', :last_name => 'Doe')
|
130
|
+
FactoryData.preload(:comments, :depends_on => [:users, :posts]) do |data|
|
131
|
+
data[:woohoo] = FactoryData.users(:john).comments.create(:post => FactoryData.posts(:tour), :comment => "I can't wait!")
|
133
132
|
end
|
134
133
|
|
135
|
-
FactoryData.preload(:posts) do |data|
|
134
|
+
FactoryData.preload(:posts, :depends_on => :users) do |data|
|
136
135
|
data[:tour] = FactoryData.users(:thom).posts.create(:title => 'Tour!', :body => 'Radiohead will tour soon.')
|
137
136
|
end
|
138
137
|
|
139
|
-
FactoryData.preload(:
|
140
|
-
data[:
|
138
|
+
FactoryData.preload(:users) do |data|
|
139
|
+
data[:thom] = User.create(:first_name => 'Thom', :last_name => 'York')
|
140
|
+
data[:john] = User.create(:first_name => 'John', :last_name => 'Doe')
|
141
141
|
end
|
142
142
|
|
143
143
|
FactoryData.preload_data!
|
144
144
|
end
|
145
145
|
|
146
|
-
should 'preload them in the
|
146
|
+
should 'preload them in the proper order, allowing you to use the dependencies' do
|
147
147
|
assert_equal 'Thom', FactoryData.users(:thom).first_name
|
148
148
|
assert_equal 'John', FactoryData.users(:john).first_name
|
149
149
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class FactoryDataTest < Test::Unit::TestCase
|
4
|
+
context 'A new preloader' do
|
5
|
+
setup do
|
6
|
+
proc = lambda { |data|
|
7
|
+
data[:thom] = User.create(:first_name => 'Thom', :last_name => 'York')
|
8
|
+
data[:john] = User.create(:first_name => 'John', :last_name => 'Doe')
|
9
|
+
}
|
10
|
+
@preloader = FactoryDataPreloader::Preloader.new(:users, User, proc, 0, [])
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'return the preloaded data for #data' do
|
14
|
+
data = @preloader.data
|
15
|
+
assert_equal 'York', data[:thom].last_name
|
16
|
+
assert_equal 'Doe', data[:john].last_name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'Post and user preloaders, where post depends on users' do
|
21
|
+
setup do
|
22
|
+
@posts = FactoryDataPreloader::Preloader.new(:posts, Post, nil, 0, [:users])
|
23
|
+
@users = FactoryDataPreloader::Preloader.new(:users, User, nil, 1, [])
|
24
|
+
end
|
25
|
+
|
26
|
+
should 'return a comparison value indicating that users is less than posts' do
|
27
|
+
assert_equal -1, @users <=> @posts
|
28
|
+
assert_equal 1, @posts <=> @users
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'Post and user preloaders, where neither post depends on the other' do
|
33
|
+
setup do
|
34
|
+
@posts = FactoryDataPreloader::Preloader.new(:posts, Post, nil, 0, [])
|
35
|
+
@users = FactoryDataPreloader::Preloader.new(:users, User, nil, 1, [])
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'return a comparison value based on their defined index' do
|
39
|
+
assert_equal 1, @users <=> @posts
|
40
|
+
assert_equal -1, @posts <=> @users
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -38,7 +38,7 @@ module OutputCapturer
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
class FactoryData
|
41
|
+
class FactoryDataPreloader::FactoryData
|
42
42
|
# helper method to reset the factory data between test runs.
|
43
43
|
def self.reset!
|
44
44
|
@@preloaders.reverse.each do |preloader|
|
@@ -47,7 +47,7 @@ class FactoryData
|
|
47
47
|
end
|
48
48
|
|
49
49
|
unless @@preloaded_cache.nil?
|
50
|
-
preloader.model_class.delete_all(:id => @@preloaded_cache[preloader.model_type].values)
|
50
|
+
preloader.model_class.delete_all(:id => (@@preloaded_cache[preloader.model_type] || {}).values)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: myronmarston-factory_data_preloader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Myron Marston
|
@@ -28,12 +28,14 @@ files:
|
|
28
28
|
- lib/factory_data_preloader
|
29
29
|
- lib/factory_data_preloader/core_ext.rb
|
30
30
|
- lib/factory_data_preloader/factory_data.rb
|
31
|
+
- lib/factory_data_preloader/preloader.rb
|
31
32
|
- lib/factory_data_preloader/rails_core_ext.rb
|
32
33
|
- lib/factory_data_preloader.rb
|
33
34
|
- test/factory_data_test.rb
|
34
35
|
- test/lib
|
35
36
|
- test/lib/models.rb
|
36
37
|
- test/lib/schema.rb
|
38
|
+
- test/preloader_test.rb
|
37
39
|
- test/test_helper.rb
|
38
40
|
- LICENSE
|
39
41
|
has_rdoc: true
|