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 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
- * The data gets preloaded in the order the preloaders are defined. Note in the example above that the users defined in
74
- the first preloader get used in the later preloaders.
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,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 1
3
+ :minor: 2
4
4
  :patch: 0
@@ -1,84 +1,88 @@
1
1
  require 'ostruct'
2
2
 
3
- class PreloaderAlreadyDefinedError < StandardError; end
4
- class PreloadedRecordNotFound < StandardError; end
3
+ module FactoryDataPreloader
4
+ class PreloaderAlreadyDefinedError < StandardError; end
5
+ class PreloadedRecordNotFound < StandardError; end
5
6
 
6
- class FactoryData
7
- @@preloaded_cache = nil
8
- @@preloaded_data_deleted = nil
9
- @@single_test_cache = {}
10
- @@preloaders = []
7
+ class FactoryData
8
+ @@preloaded_cache = nil
9
+ @@preloaded_data_deleted = nil
10
+ @@single_test_cache = {}
11
+ @@preloaders = []
11
12
 
12
- class << self
13
+ class << self
13
14
 
14
- def preload(model_type, options = {}, &block)
15
- raise PreloaderAlreadyDefinedError.new, "You have already defined the preloader for #{model_type.to_s}" if @@preloaders.map(&:model_type).include?(model_type)
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
- model_class = options[:model_class] || model_type.to_s.singularize.classify.constantize
18
- @@preloaders << OpenStruct.new(:model_type => model_type, :model_class => model_class, :proc => block)
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
- class << self; self; end.class_eval do
21
- define_method model_type do |key|
22
- get_record(model_type, model_class, key)
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
- def delete_preload_data!
28
- # make sure this only runs once...
29
- return unless @@preloaded_data_deleted.nil?
29
+ def delete_preload_data!
30
+ # make sure this only runs once...
31
+ return unless @@preloaded_data_deleted.nil?
30
32
 
31
- # the preloaders are listed in the parent -> child table order,
32
- # so we need to delete them in reverse.
33
- @@preloaders.reverse.each do |preloader|
34
- preloader.model_class.delete_all
35
- end
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
- @@preloaded_data_deleted = true
38
- end
39
+ @@preloaded_data_deleted = true
40
+ end
39
41
 
40
- def preload_data!
41
- return unless @@preloaded_cache.nil? # make sure the data is only preloaded once.
42
- @@preloaded_cache = {}
42
+ def preload_data!
43
+ return unless @@preloaded_cache.nil? # make sure the data is only preloaded once.
44
+ @@preloaded_cache = {}
43
45
 
44
- @@preloaders.each do |preloader|
45
- cache = @@preloaded_cache[preloader.model_type] ||= {}
46
- data = {}
47
- preloader.proc.try(:call, data)
48
- 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
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
- cache[key] = record.id
56
+ cache[key] = record.id
57
+ end
57
58
  end
58
59
  end
59
- end
60
60
 
61
- def reset_cache!
62
- @@single_test_cache = {}
63
- end
61
+ def reset_cache!
62
+ @@single_test_cache = {}
63
+ end
64
64
 
65
- private
65
+ private
66
66
 
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
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
- # 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
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
@@ -12,5 +12,6 @@ require 'active_record'
12
12
  require 'active_record/fixtures'
13
13
 
14
14
  require 'factory_data_preloader/core_ext'
15
+ require 'factory_data_preloader/preloader'
15
16
  require 'factory_data_preloader/factory_data'
16
17
  require 'factory_data_preloader/rails_core_ext'
@@ -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[:thom] = User.create(:first_name => 'Thom', :last_name => 'York')
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(:comments) do |data|
140
- data[:woohoo] = FactoryData.users(:john).comments.create(:post => FactoryData.posts(:tour), :comment => "I can't wait!")
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 defined order, allowing you to use prior definitions in later definitions' do
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.1.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