bullet 2.2.0 → 2.3.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.
Files changed (81) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +4 -1
  3. data/Gemfile +3 -2
  4. data/Gemfile.lock +85 -69
  5. data/Guardfile +8 -0
  6. data/README.textile +2 -2
  7. data/lib/bullet/action_controller2.rb +4 -4
  8. data/lib/bullet/active_record2.rb +5 -6
  9. data/lib/bullet/active_record3.rb +2 -3
  10. data/lib/bullet/active_record31.rb +10 -12
  11. data/lib/bullet/detector/association.rb +27 -53
  12. data/lib/bullet/detector/counter.rb +34 -29
  13. data/lib/bullet/detector/n_plus_one_query.rb +47 -28
  14. data/lib/bullet/detector/unused_eager_association.rb +31 -30
  15. data/lib/bullet/notification/base.rb +14 -12
  16. data/lib/bullet/notification/n_plus_one_query.rb +6 -10
  17. data/lib/bullet/notification/unused_eager_loading.rb +1 -2
  18. data/lib/bullet/notification_collector.rb +1 -2
  19. data/lib/bullet/rack.rb +6 -3
  20. data/lib/bullet/registry/association.rb +4 -6
  21. data/lib/bullet/registry/base.rb +10 -7
  22. data/lib/bullet/registry/object.rb +6 -6
  23. data/lib/bullet/version.rb +1 -1
  24. data/lib/bullet.rb +13 -8
  25. data/perf/benchmark.rb +30 -12
  26. data/spec/bullet/detector/association_spec.rb +90 -0
  27. data/spec/bullet/detector/base_spec.rb +14 -0
  28. data/spec/bullet/detector/counter_spec.rb +65 -0
  29. data/spec/bullet/detector/n_plus_one_query_spec.rb +94 -0
  30. data/spec/bullet/detector/unused_eager_association_spec.rb +62 -0
  31. data/spec/bullet/notification/base_spec.rb +67 -0
  32. data/spec/bullet/notification/counter_cache_spec.rb +12 -0
  33. data/spec/bullet/notification/n_plus_one_query_spec.rb +13 -0
  34. data/spec/bullet/notification/unused_eager_loading_spec.rb +12 -0
  35. data/spec/bullet/notification_collector_spec.rb +32 -0
  36. data/spec/bullet/rack_spec.rb +80 -24
  37. data/spec/bullet/registry/association_spec.rb +26 -0
  38. data/spec/bullet/registry/base_spec.rb +44 -0
  39. data/spec/bullet/registry/object_spec.rb +25 -0
  40. data/spec/integration/association_for_chris_spec.rb +37 -0
  41. data/spec/integration/association_for_peschkaj_spec.rb +26 -0
  42. data/spec/{bullet → integration}/association_spec.rb +1 -359
  43. data/spec/integration/counter_spec.rb +37 -0
  44. data/spec/models/address.rb +3 -0
  45. data/spec/models/author.rb +3 -0
  46. data/spec/models/base_user.rb +5 -0
  47. data/spec/models/category.rb +7 -0
  48. data/spec/models/city.rb +3 -0
  49. data/spec/models/client.rb +4 -0
  50. data/spec/models/comment.rb +4 -0
  51. data/spec/models/company.rb +3 -0
  52. data/spec/models/contact.rb +3 -0
  53. data/spec/models/country.rb +3 -0
  54. data/spec/models/deal.rb +4 -0
  55. data/spec/models/document.rb +5 -0
  56. data/spec/models/email.rb +3 -0
  57. data/spec/models/entry.rb +3 -0
  58. data/spec/models/firm.rb +4 -0
  59. data/spec/models/folder.rb +2 -0
  60. data/spec/models/hotel.rb +4 -0
  61. data/spec/models/location.rb +3 -0
  62. data/spec/models/newspaper.rb +3 -0
  63. data/spec/models/page.rb +2 -0
  64. data/spec/models/person.rb +3 -0
  65. data/spec/models/pet.rb +3 -0
  66. data/spec/models/post.rb +11 -0
  67. data/spec/models/relationship.rb +4 -0
  68. data/spec/models/student.rb +3 -0
  69. data/spec/models/submission.rb +4 -0
  70. data/spec/models/teacher.rb +3 -0
  71. data/spec/models/user.rb +4 -0
  72. data/spec/models/writer.rb +2 -0
  73. data/spec/spec_helper.rb +16 -106
  74. data/spec/support/bullet_ext.rb +55 -0
  75. data/spec/support/rack_double.rb +55 -0
  76. data/spec/support/seed.rb +256 -0
  77. metadata +104 -14
  78. data/.watchr +0 -24
  79. data/spec/bullet/association_for_chris_spec.rb +0 -96
  80. data/spec/bullet/association_for_peschkaj_spec.rb +0 -86
  81. data/spec/bullet/counter_spec.rb +0 -136
@@ -1,38 +1,57 @@
1
1
  module Bullet
2
2
  module Detector
3
3
  class NPlusOneQuery < Association
4
- # executed when object.assocations is called.
5
- # first, it keeps this method call for object.association.
6
- # then, it checks if this associations call is unpreload.
7
- # if it is, keeps this unpreload associations and caller.
8
- def self.call_association(object, associations)
9
- @@checked = true
10
- add_call_object_associations(object, associations)
11
-
12
- if conditions_met?(object, associations)
13
- caller_in_project
14
- create_notification object.class, associations
4
+ class <<self
5
+ # executed when object.assocations is called.
6
+ # first, it keeps this method call for object.association.
7
+ # then, it checks if this associations call is unpreload.
8
+ # if it is, keeps this unpreload associations and caller.
9
+ def call_association(object, associations)
10
+ @@checked = true
11
+ add_call_object_associations(object, associations)
12
+
13
+ if conditions_met?(object.ar_key, associations)
14
+ create_notification caller_in_project, object.class.to_s, associations
15
+ end
15
16
  end
16
- end
17
17
 
18
- private
19
- def self.create_notification(klazz, associations)
20
- notice = Bullet::Notification::NPlusOneQuery.new( callers, klazz, associations )
21
- Bullet.notification_collector.add( notice )
22
- end
18
+ private
19
+ def create_notification(callers, klazz, associations)
20
+ notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, associations)
21
+ Bullet.notification_collector.add(notice)
22
+ end
23
23
 
24
- # decide whether the object.associations is unpreloaded or not.
25
- def self.conditions_met?(object, associations)
26
- possible?(object) and
27
- !impossible?(object) and
28
- !association?(object, associations)
29
- end
24
+ # decide whether the object.associations is unpreloaded or not.
25
+ def conditions_met?(object_ar_key, associations)
26
+ possible?(object_ar_key) && !impossible?(object_ar_key) && !association?(object_ar_key, associations)
27
+ end
28
+
29
+ def caller_in_project
30
+ rails_root = Rails.root.to_s
31
+ vendor_root = rails_root + "/vendor"
32
+ caller.select { |c| c.include?(rails_root) && !c.include?(vendor_root) }
33
+ end
34
+
35
+ def possible?(object_ar_key)
36
+ possible_objects.include? object_ar_key
37
+ end
38
+
39
+ def impossible?(object_ar_key)
40
+ impossible_objects.include? object_ar_key
41
+ end
42
+
43
+ # check if object => associations already exists in object_associations.
44
+ def association?(object_ar_key, associations)
45
+ value = object_associations[object_ar_key]
46
+ if value
47
+ value.each do |v|
48
+ result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
49
+ return true if result
50
+ end
51
+ end
30
52
 
31
- def self.caller_in_project
32
- vender_root ||= File.join(Rails.root, 'vendor')
33
- callers << caller.select { |c| c =~ /#{Rails.root}/ }.
34
- reject { |c| c =~ /#{vender_root}/ }
35
- callers.uniq!
53
+ return false
54
+ end
36
55
  end
37
56
  end
38
57
  end
@@ -1,40 +1,41 @@
1
1
  module Bullet
2
2
  module Detector
3
3
  class UnusedEagerAssociation < Association
4
- # check if there are unused preload associations.
5
- # for each object => association
6
- # get related_objects from eager_loadings associated with object and associations
7
- # get call_object_association from associations of call_object_associations whose object is in related_objects
8
- # if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
9
- def self.check_unused_preload_associations
10
- @@checked = true
11
- object_associations.each do |object, association|
12
- object_association_diff = diff_object_association object, association
13
- next if object_association_diff.empty?
4
+ class <<self
5
+ # check if there are unused preload associations.
6
+ # get related_objects from eager_loadings associated with object and associations
7
+ # get call_object_association from associations of call_object_associations whose object is in related_objects
8
+ # if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
9
+ def check_unused_preload_associations
10
+ @@checked = true
11
+ object_associations.each do |object_ar_key, association|
12
+ object_association_diff = diff_object_association object_ar_key, association
13
+ next if object_association_diff.empty?
14
14
 
15
- create_notification object.class, object_association_diff
15
+ create_notification object_ar_key.split(":").first, object_association_diff
16
+ end
16
17
  end
17
- end
18
-
19
- protected
20
- def self.create_notification(klazz, associations)
21
- notice = Bullet::Notification::UnusedEagerLoading.new( klazz, associations )
22
- Bullet.notification_collector.add( notice )
23
- end
24
18
 
25
- def self.call_object_association( object, association )
26
- all = Set.new
27
- eager_loadings.similarly_associated( object, association ).each do |related_object|
28
- coa = call_object_associations[related_object]
29
- next if coa.nil?
30
- all.merge coa
31
- end
32
- all.to_a
33
- end
19
+ private
20
+ def create_notification(klazz, associations)
21
+ notice = Bullet::Notification::UnusedEagerLoading.new(klazz, associations)
22
+ Bullet.notification_collector.add(notice)
23
+ end
24
+
25
+ def call_associations(object_ar_key, association)
26
+ all = Set.new
27
+ eager_loadings.similarly_associated(object_ar_key, association).each do |related_object_ar_key|
28
+ coa = call_object_associations[related_object_ar_key]
29
+ next if coa.nil?
30
+ all.merge coa
31
+ end
32
+ all.to_a
33
+ end
34
34
 
35
- def self.diff_object_association( object, association )
36
- potential_objects = association - call_object_association( object, association )
37
- potential_objects.reject {|a| a.is_a?( Hash ) }
35
+ def diff_object_association(object_ar_key, association)
36
+ potential_objects = association - call_associations(object_ar_key, association)
37
+ potential_objects.reject { |a| a.is_a?(Hash) }
38
+ end
38
39
  end
39
40
  end
40
41
  end
@@ -4,18 +4,20 @@ module Bullet
4
4
  attr_accessor :notifier, :url
5
5
  attr_reader :base_class, :associations, :path
6
6
 
7
- def initialize( base_class, associations, path = nil )
7
+ def initialize(base_class, association_or_associations, path = nil)
8
8
  @base_class = base_class
9
- @associations = associations.is_a?( Array ) ? associations : [ associations ]
9
+ @associations = association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
10
10
  @path = path
11
11
  end
12
12
 
13
13
  def title
14
+ raise NoMethodError.new("no method title defined")
14
15
  end
15
16
 
16
17
  def body
18
+ raise NoMethodError.new("no method body defined")
17
19
  end
18
-
20
+
19
21
  def whoami
20
22
  "user: " << `whoami`.chomp
21
23
  end
@@ -33,14 +35,14 @@ module Bullet
33
35
  end
34
36
 
35
37
  def notify_inline
36
- self.notifier.inline_notify( self.full_notice )
38
+ self.notifier.inline_notify(self.full_notice)
37
39
  end
38
40
 
39
41
  def notify_out_of_channel
40
- self.notifier.out_of_channel_notify( self.full_notice )
42
+ self.notifier.out_of_channel_notify(self.full_notice)
41
43
  end
42
44
 
43
- def eql?( other )
45
+ def eql?(other)
44
46
  klazz_associations_str == other.klazz_associations_str
45
47
  end
46
48
 
@@ -49,13 +51,13 @@ module Bullet
49
51
  end
50
52
 
51
53
  protected
52
- def klazz_associations_str
53
- " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
54
- end
54
+ def klazz_associations_str
55
+ " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
56
+ end
55
57
 
56
- def associations_str
57
- ":include => #{@associations.map{|a| a.to_s.to_sym unless a.is_a? Hash}.inspect}"
58
- end
58
+ def associations_str
59
+ ":include => #{@associations.map{ |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
60
+ end
59
61
  end
60
62
  end
61
63
  end
@@ -1,8 +1,8 @@
1
1
  module Bullet
2
2
  module Notification
3
3
  class NPlusOneQuery < Base
4
- def initialize( callers, base_class, associations, path = nil )
5
- super( base_class, associations, path )
4
+ def initialize(callers, base_class, associations, path = nil)
5
+ super(base_class, associations, path)
6
6
 
7
7
  @callers = callers
8
8
  end
@@ -10,7 +10,7 @@ module Bullet
10
10
  def body_with_caller
11
11
  "#{body}\n#{call_stack_messages}"
12
12
  end
13
-
13
+
14
14
  def body
15
15
  "#{klazz_associations_str}\n Add to your finder: #{associations_str}"
16
16
  end
@@ -20,13 +20,9 @@ module Bullet
20
20
  end
21
21
 
22
22
  protected
23
- def call_stack_messages
24
- @callers.collect do |c|
25
- [ 'N+1 Query method call stack',
26
- c.collect {|line| " #{line}"} ].flatten
27
- end.join( "\n" )
28
- end
29
-
23
+ def call_stack_messages
24
+ @callers.unshift('N+1 Query method call stack').join( "\n " )
25
+ end
30
26
  end
31
27
  end
32
28
  end
@@ -6,9 +6,8 @@ module Bullet
6
6
  end
7
7
 
8
8
  def title
9
- "Unused Eager Loading #{@path ? "in #{@path}" : 'detected'}"
9
+ "Unused Eager Loading #{@path ? "in #{@path}" : 'detected'}"
10
10
  end
11
-
12
11
  end
13
12
  end
14
13
  end
@@ -12,14 +12,13 @@ module Bullet
12
12
  @collection = Set.new
13
13
  end
14
14
 
15
- def add( value )
15
+ def add(value)
16
16
  @collection << value
17
17
  end
18
18
 
19
19
  def notifications_present?
20
20
  !@collection.empty?
21
21
  end
22
-
23
22
  end
24
23
  end
25
24
 
data/lib/bullet/rack.rb CHANGED
@@ -13,7 +13,7 @@ module Bullet
13
13
 
14
14
  response_body = nil
15
15
  if Bullet.notification?
16
- if status == 200 and !response.body.frozen? and check_html?(headers, response)
16
+ if status == 200 && !response.body.frozen? && html_request?(headers, response)
17
17
  response_body = response.body << Bullet.gather_inline_notifications
18
18
  headers['Content-Length'] = response_body.length.to_s
19
19
  end
@@ -31,8 +31,11 @@ module Bullet
31
31
  !response.body.is_a?(String) || response.body.empty?
32
32
  end
33
33
 
34
- def check_html?(headers, response)
35
- headers['Content-Type'] and headers['Content-Type'].include? 'text/html' and response.body =~ %r{<html.*</html>}m
34
+ def html_request?(headers, response)
35
+ headers['Content-Type'] &&
36
+ headers['Content-Type'].include?('text/html') &&
37
+ response.body.include?("<html>") &&
38
+ response.body.include?("</html>")
36
39
  end
37
40
 
38
41
  def no_browser_cache(headers)
@@ -1,14 +1,12 @@
1
1
  module Bullet
2
2
  module Registry
3
3
  class Association < Base
4
- def merge( base, associations )
5
- @registry.merge!( { base => associations } )
4
+ def merge(base, associations)
5
+ @registry.merge!(base => associations)
6
6
  end
7
7
 
8
- def similarly_associated( base, associations )
9
- @registry.select do |key, value|
10
- key.include?( base ) and value == associations
11
- end.collect( &:first ).flatten
8
+ def similarly_associated(base, associations)
9
+ @registry.select { |key, value| key.include?(base) && value == associations }.collect(&:first).flatten
12
10
  end
13
11
  end
14
12
  end
@@ -11,19 +11,19 @@ module Bullet
11
11
  @registry[key]
12
12
  end
13
13
 
14
- def each( &block )
15
- @registry.each( &block )
14
+ def each(&block)
15
+ @registry.each(&block)
16
16
  end
17
17
 
18
- def delete( base )
19
- @registry.delete( base )
18
+ def delete(base)
19
+ @registry.delete(base)
20
20
  end
21
21
 
22
- def select( *args, &block )
23
- @registry.select( *args, &block )
22
+ def select(*args, &block)
23
+ @registry.select(*args, &block)
24
24
  end
25
25
 
26
- def add( key, value )
26
+ def add(key, value)
27
27
  @registry[key] ||= Set.new
28
28
  if value.is_a? Array
29
29
  @registry[key] += value
@@ -32,6 +32,9 @@ module Bullet
32
32
  end
33
33
  end
34
34
 
35
+ def include?(key, value)
36
+ @registry[key] && @registry[key].include?(value)
37
+ end
35
38
  end
36
39
  end
37
40
  end
@@ -1,14 +1,14 @@
1
1
  module Bullet
2
2
  module Registry
3
3
  class Object < Base
4
- def add( object_or_objects )
5
- klazz = object_or_objects.is_a?( Array ) ? object_or_objects.first.class :
6
- object_or_objects.class
7
- super( klazz, object_or_objects )
4
+ def add(object_ar_key)
5
+ klazz = object_ar_key.split(":").first
6
+ super(klazz, object_ar_key)
8
7
  end
9
8
 
10
- def contains?( object )
11
- @registry[object.class] and @registry[object.class].include?( object )
9
+ def include?(object_ar_key)
10
+ klazz = object_ar_key.split(":").first
11
+ super(klazz, object_ar_key)
12
12
  end
13
13
  end
14
14
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module Bullet
3
- VERSION = "2.2.0"
3
+ VERSION = "2.3.0"
4
4
  end
5
5
 
data/lib/bullet.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  require 'set'
2
2
  require 'uniform_notifier'
3
3
 
4
+ class Object
5
+ def ar_key
6
+ "#{self.class}:#{self.id}"
7
+ end
8
+ end
9
+
4
10
  module Bullet
5
- if Rails.version =~ /^3\.0/
11
+ if Rails.version =~ /\A3\.0/
6
12
  autoload :ActiveRecord, 'bullet/active_record3'
7
- elsif Rails.version =~ /^3\.[12]/
13
+ elsif Rails.version =~ /\A3\.[12]/
8
14
  autoload :ActiveRecord, 'bullet/active_record31'
9
15
  else
10
16
  autoload :ActiveRecord, 'bullet/active_record2'
@@ -39,7 +45,7 @@ module Bullet
39
45
  @enable = enable
40
46
  if enable?
41
47
  Bullet::ActiveRecord.enable
42
- if Rails.version =~ /^2./
48
+ if Rails.version =~ /\A2./
43
49
  Bullet::ActionController.enable
44
50
  end
45
51
  end
@@ -51,7 +57,7 @@ module Bullet
51
57
 
52
58
  def bullet_logger=(active)
53
59
  if active
54
- bullet_log_file = File.open( 'log/bullet.log', 'a+' )
60
+ bullet_log_file = File.open('log/bullet.log', 'a+')
55
61
  bullet_log_file.sync
56
62
  UniformNotifier.customized_logger = bullet_log_file
57
63
  end
@@ -59,15 +65,15 @@ module Bullet
59
65
 
60
66
  def start_request
61
67
  notification_collector.reset
62
- DETECTORS.each {|bullet| bullet.start_request}
68
+ DETECTORS.each { |bullet| bullet.start_request }
63
69
  end
64
70
 
65
71
  def end_request
66
- DETECTORS.each {|bullet| bullet.end_request}
72
+ DETECTORS.each { |bullet| bullet.end_request }
67
73
  end
68
74
 
69
75
  def clear
70
- DETECTORS.each {|bullet| bullet.clear}
76
+ DETECTORS.each { |bullet| bullet.clear }
71
77
  end
72
78
 
73
79
  def notification_collector
@@ -104,5 +110,4 @@ module Bullet
104
110
  end
105
111
  end
106
112
  end
107
-
108
113
  end
data/perf/benchmark.rb CHANGED
@@ -83,16 +83,18 @@ PerfTools::CpuProfiler.start(ARGV[0]|| "benchmark_profile") if defined? PerfTool
83
83
 
84
84
  Benchmark.bm(70) do |bm|
85
85
  bm.report("Querying & Iterating #{posts_size} Posts with #{comments_size} Comments and #{users_size} Users") do
86
- Bullet.start_request
87
- Post.select("SQL_NO_CACHE *").includes(:user, :comments => :user).each do |p|
88
- p.title
89
- p.user.name
90
- p.comments.each do |c|
91
- c.body
92
- c.user.name
86
+ 10.times do
87
+ Bullet.start_request
88
+ Post.select("SQL_NO_CACHE *").includes(:user, :comments => :user).each do |p|
89
+ p.title
90
+ p.user.name
91
+ p.comments.each do |c|
92
+ c.body
93
+ c.user.name
94
+ end
93
95
  end
96
+ Bullet.end_request
94
97
  end
95
- Bullet.end_request
96
98
  end
97
99
  end
98
100
 
@@ -100,7 +102,23 @@ PerfTools::CpuProfiler.stop if defined? PerfTools
100
102
  puts "End benchmarking..."
101
103
 
102
104
 
103
- # 2.0.1
104
- # user system total real
105
- # Querying & Iterating 100 Posts with 10000 Comments and 100 Users 2.290000 0.050000 2.340000 ( 2.366174)
106
-
105
+ # Run benchmark with bundler
106
+ #
107
+ # bundle exec ruby perf/benchmark.rb
108
+ #
109
+ # bullet 2.3.0 with rails 3.2.2
110
+ # user system total real
111
+ # Querying & Iterating 1000 Posts with 10000 Comments and 100 Users 16.460000 0.190000 16.650000 ( 16.968246)
112
+ #
113
+ # bullet 2.3.0 with rails 3.1.4
114
+ # user system total real
115
+ # Querying & Iterating 1000 Posts with 10000 Comments and 100 Users 14.600000 0.130000 14.730000 ( 14.937590)
116
+ #
117
+ # bullet 2.3.0 with rails 3.0.12
118
+ # user system total real
119
+ # Querying & Iterating 1000 Posts with 10000 Comments and 100 Users 26.120000 0.430000 26.550000 ( 27.179304)
120
+ #
121
+ #
122
+ # bullet 2.2.1 with rails 3.0.12
123
+ # user system total real
124
+ # Querying & Iterating 1000 Posts with 10000 Comments and 100 Users 29.970000 0.270000 30.240000 ( 30.452083)
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Detector
5
+ describe Association do
6
+ before :all do
7
+ @post1 = Post.first
8
+ @post2 = Post.last
9
+ end
10
+ before(:each) { Association.clear }
11
+
12
+ context ".start_request" do
13
+ it "should set @@checked to false" do
14
+ Association.start_request
15
+ Association.class_variable_get(:@@checked).should be_false
16
+ end
17
+ end
18
+
19
+ context ".clear" do
20
+ it "should clear all class variables" do
21
+ Association.clear
22
+ Association.class_variable_get(:@@object_associations).should be_nil
23
+ Association.class_variable_get(:@@possible_objects).should be_nil
24
+ Association.class_variable_get(:@@impossible_objects).should be_nil
25
+ Association.class_variable_get(:@@call_object_associations).should be_nil
26
+ Association.class_variable_get(:@@eager_loadings).should be_nil
27
+ end
28
+ end
29
+
30
+ context ".add_object_association" do
31
+ it "should add object, associations pair" do
32
+ Association.add_object_associations(@post1, :associations)
33
+ Association.send(:object_associations).should be_include(@post1.ar_key, :associations)
34
+ end
35
+ end
36
+
37
+ context ".add_call_object_associations" do
38
+ it "should add call object, associations pair" do
39
+ Association.add_call_object_associations(@post1, :associations)
40
+ Association.send(:call_object_associations).should be_include(@post1.ar_key, :associations)
41
+ end
42
+ end
43
+
44
+ context ".add_possible_objects" do
45
+ it "should add possible objects" do
46
+ Association.add_possible_objects([@post1, @post2])
47
+ Association.send(:possible_objects).should be_include(@post1.ar_key)
48
+ Association.send(:possible_objects).should be_include(@post2.ar_key)
49
+ end
50
+ end
51
+
52
+ context ".add_impossible_object" do
53
+ it "should add impossible object" do
54
+ Association.add_impossible_object(@post1)
55
+ Association.send(:impossible_objects).should be_include(@post1.ar_key)
56
+ end
57
+ end
58
+
59
+ context ".add_eager_loadings" do
60
+ it "should add objects, associations pair when eager_loadings are empty" do
61
+ Association.add_eager_loadings([@post1, @post2], :associations)
62
+ Association.send(:eager_loadings).should be_include([@post1.ar_key, @post2.ar_key], :associations)
63
+ end
64
+
65
+ it "should add objects, associations pair for existing eager_loadings" do
66
+ Association.add_eager_loadings([@post1, @post2], :association1)
67
+ Association.add_eager_loadings([@post1, @post2], :association2)
68
+ Association.send(:eager_loadings).should be_include([@post1.ar_key, @post2.ar_key], :association1)
69
+ Association.send(:eager_loadings).should be_include([@post1.ar_key, @post2.ar_key], :association2)
70
+ end
71
+
72
+ it "should merge objects, associations pair for existing eager_loadings" do
73
+ Association.add_eager_loadings(@post1, :association1)
74
+ Association.add_eager_loadings([@post1, @post2], :association2)
75
+ Association.send(:eager_loadings).should be_include([@post1.ar_key], :association1)
76
+ Association.send(:eager_loadings).should be_include([@post1.ar_key], :association2)
77
+ Association.send(:eager_loadings).should be_include([@post1.ar_key, @post2.ar_key], :association2)
78
+ end
79
+
80
+ it "should delete objects, associations pair for existing eager_loadings" do
81
+ Association.add_eager_loadings([@post1, @post2], :association1)
82
+ Association.add_eager_loadings(@post1, :association2)
83
+ Association.send(:eager_loadings).should be_include([@post1.ar_key], :association1)
84
+ Association.send(:eager_loadings).should be_include([@post1.ar_key], :association2)
85
+ Association.send(:eager_loadings).should be_include([@post2.ar_key], :association1)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Detector
5
+ describe Base do
6
+ context ".end_request" do
7
+ it "should call clear" do
8
+ Base.should_receive(:clear)
9
+ Base.end_request
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end