bullet 2.0.0.beta.2 → 2.1.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 (49) hide show
  1. data/.gitignore +9 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +2 -0
  4. data/.rvmrc.example +2 -0
  5. data/.watchr +24 -0
  6. data/Gemfile +15 -0
  7. data/Gemfile.lock +105 -0
  8. data/Hacking.textile +69 -0
  9. data/MIT-LICENSE +1 -1
  10. data/README.textile +61 -49
  11. data/README_for_rails2.textile +41 -42
  12. data/Rakefile +40 -29
  13. data/bullet.gemspec +17 -60
  14. data/lib/bullet/action_controller2.rb +6 -6
  15. data/lib/bullet/active_record2.rb +17 -16
  16. data/lib/bullet/active_record3.rb +17 -25
  17. data/lib/bullet/active_record31.rb +96 -0
  18. data/lib/bullet/detector/association.rb +144 -0
  19. data/lib/bullet/detector/base.rb +12 -0
  20. data/lib/bullet/detector/counter.rb +43 -0
  21. data/lib/bullet/detector/n_plus_one_query.rb +39 -0
  22. data/lib/bullet/detector/unused_eager_association.rb +41 -0
  23. data/lib/bullet/detector.rb +9 -0
  24. data/lib/bullet/notification/base.rb +61 -0
  25. data/lib/bullet/notification/counter_cache.rb +13 -0
  26. data/lib/bullet/notification/n_plus_one_query.rb +32 -0
  27. data/lib/bullet/notification/unused_eager_loading.rb +14 -0
  28. data/lib/bullet/notification.rb +4 -79
  29. data/lib/bullet/notification_collector.rb +25 -0
  30. data/lib/bullet/rack.rb +44 -0
  31. data/lib/bullet/registry/association.rb +15 -0
  32. data/lib/bullet/registry/base.rb +37 -0
  33. data/lib/bullet/registry/object.rb +15 -0
  34. data/lib/bullet/registry.rb +7 -0
  35. data/lib/bullet/version.rb +5 -0
  36. data/lib/bullet.rb +64 -43
  37. data/perf/benchmark.rb +106 -0
  38. data/spec/bullet/association_for_chris_spec.rb +6 -6
  39. data/spec/bullet/association_for_peschkaj_spec.rb +6 -6
  40. data/spec/bullet/association_spec.rb +140 -294
  41. data/spec/bullet/counter_spec.rb +10 -10
  42. data/spec/spec_helper.rb +51 -17
  43. metadata +72 -57
  44. data/VERSION +0 -1
  45. data/lib/bullet/association.rb +0 -294
  46. data/lib/bullet/counter.rb +0 -101
  47. data/lib/bullet/logger.rb +0 -9
  48. data/lib/bulletware.rb +0 -42
  49. data/spec/spec.opts +0 -3
@@ -0,0 +1,37 @@
1
+ module Bullet
2
+ module Registry
3
+ class Base
4
+ attr_reader :registry
5
+
6
+ def initialize
7
+ @registry = {}
8
+ end
9
+
10
+ def [](key)
11
+ @registry[key]
12
+ end
13
+
14
+ def each( &block )
15
+ @registry.each( &block )
16
+ end
17
+
18
+ def delete( base )
19
+ @registry.delete( base )
20
+ end
21
+
22
+ def select( *args, &block )
23
+ @registry.select( *args, &block )
24
+ end
25
+
26
+ def add( key, value )
27
+ @registry[key] ||= Set.new
28
+ if value.is_a? Array
29
+ @registry[key] += value
30
+ else
31
+ @registry[key] << value
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ module Bullet
2
+ module Registry
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 )
8
+ end
9
+
10
+ def contains?( object )
11
+ @registry[object.class] and @registry[object.class].include?( object )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Bullet
2
+ module Registry
3
+ autoload :Base, 'bullet/registry/base'
4
+ autoload :Object, 'bullet/registry/object'
5
+ autoload :Association, 'bullet/registry/association'
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ module Bullet
3
+ VERSION = "2.1.0"
4
+ end
5
+
data/lib/bullet.rb CHANGED
@@ -1,31 +1,46 @@
1
- require 'bulletware'
1
+ require 'set'
2
+ require 'uniform_notifier'
2
3
 
3
4
  module Bullet
4
- if Rails.version =~ /^3.0/
5
+ if Rails.version =~ /^3\.0/
5
6
  autoload :ActiveRecord, 'bullet/active_record3'
7
+ elsif Rails.version =~ /^3\.1/
8
+ autoload :ActiveRecord, 'bullet/active_record31'
6
9
  else
7
10
  autoload :ActiveRecord, 'bullet/active_record2'
8
11
  autoload :ActionController, 'bullet/action_controller2'
9
12
  end
10
- autoload :Association, 'bullet/association'
11
- autoload :Counter, 'bullet/counter'
13
+ autoload :Rack, 'bullet/rack'
12
14
  autoload :BulletLogger, 'bullet/logger'
13
15
  autoload :Notification, 'bullet/notification'
16
+ autoload :Detector, 'bullet/detector'
17
+ autoload :Registry, 'bullet/registry'
18
+ autoload :NotificationCollector, 'bullet/notification_collector'
19
+
20
+ if defined? Rails::Railtie
21
+ class BulletRailtie < Rails::Railtie
22
+ initializer "bullet.configure_rails_initialization" do |app|
23
+ app.middleware.use Bullet::Rack
24
+ end
25
+ end
26
+ end
14
27
 
15
28
  class <<self
16
- attr_accessor :enable, :alert, :console, :growl, :growl_password, :rails_logger, :bullet_logger, :logger, :logger_file, :disable_browser_cache
29
+ attr_accessor :enable, :disable_browser_cache
30
+ attr_reader :notification_collector
31
+
32
+ delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :to => UniformNotifier
33
+
34
+ DETECTORS = [ Bullet::Detector::NPlusOneQuery,
35
+ Bullet::Detector::UnusedEagerAssociation,
36
+ Bullet::Detector::Counter ]
17
37
 
18
38
  def enable=(enable)
19
39
  @enable = enable
20
- if enable?
40
+ if enable?
21
41
  Bullet::ActiveRecord.enable
22
- if Rails.version =~ /^3.0/
23
- require 'action_controller/metal'
24
- ::ActionController::Metal.middleware_stack.use Bulletware
25
- elsif Rails.version =~/^2.3/
42
+ if Rails.version =~ /^2./
26
43
  Bullet::ActionController.enable
27
- require 'action_controller/dispatcher'
28
- ::ActionController::Dispatcher.middleware.use Bulletware
29
44
  end
30
45
  end
31
46
  end
@@ -34,54 +49,60 @@ module Bullet
34
49
  @enable == true
35
50
  end
36
51
 
37
- def growl=(growl)
38
- if growl
39
- begin
40
- require 'ruby-growl'
41
- growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @growl_password)
42
- growl.notify('Bullet Notification', 'Bullet Notification', 'Bullet Growl notifications have been turned on')
43
- rescue MissingSourceFile
44
- raise NotificationError.new('You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`')
45
- end
46
- end
47
- @growl = growl
48
- end
49
-
50
- def bullet_logger=(bullet_logger)
51
- if @bullet_logger = bullet_logger
52
- @logger_file = File.open(Bullet::BulletLogger::LOG_FILE, 'a+')
53
- @logger = Bullet::BulletLogger.new(@logger_file)
52
+ def bullet_logger=(active)
53
+ if active
54
+ bullet_log_file = File.open( 'log/bullet.log', 'a+' )
55
+ bullet_log_file.sync
56
+ UniformNotifier.customized_logger = bullet_log_file
54
57
  end
55
58
  end
56
59
 
57
- BULLETS = [Bullet::Association, Bullet::Counter]
58
-
59
60
  def start_request
60
- BULLETS.each {|bullet| bullet.start_request}
61
+ notification_collector.reset
62
+ DETECTORS.each {|bullet| bullet.start_request}
61
63
  end
62
64
 
63
65
  def end_request
64
- BULLETS.each {|bullet| bullet.end_request}
66
+ DETECTORS.each {|bullet| bullet.end_request}
65
67
  end
66
-
68
+
67
69
  def clear
68
- BULLETS.each {|bullet| bullet.clear}
70
+ DETECTORS.each {|bullet| bullet.clear}
69
71
  end
70
72
 
71
- def notification?
72
- BULLETS.any? {|bullet| bullet.notification?}
73
+ def notification_collector
74
+ @notification_collector ||= Bullet::NotificationCollector.new
73
75
  end
74
76
 
75
- def javascript_notification
76
- BULLETS.collect {|bullet| bullet.javascript_notification if bullet.notification?}.join("\n")
77
+ def notification?
78
+ Bullet::Detector::UnusedEagerAssociation.check_unused_preload_associations
79
+ notification_collector.notifications_present?
77
80
  end
78
81
 
79
- def growl_notification
80
- BULLETS.each {|bullet| bullet.growl_notification if bullet.notification?}
82
+ def gather_inline_notifications
83
+ responses = []
84
+ for_each_active_notifier_with_notification do |notification|
85
+ responses << notification.notify_inline
86
+ end
87
+ responses.join( "\n" )
81
88
  end
82
89
 
83
- def log_notification(path)
84
- BULLETS.each {|bullet| bullet.log_notification(path) if bullet.notification?}
90
+ def perform_out_of_channel_notifications(env = {})
91
+ for_each_active_notifier_with_notification do |notification|
92
+ notification.url = [env['HTTP_HOST'], env['REQUEST_URI']].compact.join
93
+ notification.notify_out_of_channel
94
+ end
85
95
  end
96
+
97
+ private
98
+ def for_each_active_notifier_with_notification
99
+ UniformNotifier.active_notifiers.each do |notifier|
100
+ notification_collector.collection.each do |notification|
101
+ notification.notifier = notifier
102
+ yield notification
103
+ end
104
+ end
105
+ end
86
106
  end
107
+
87
108
  end
data/perf/benchmark.rb ADDED
@@ -0,0 +1,106 @@
1
+ $: << 'lib'
2
+ require 'benchmark'
3
+ require 'rails'
4
+ require 'active_record'
5
+ require 'activerecord-import'
6
+ require 'bullet'
7
+
8
+ begin
9
+ require 'perftools'
10
+ rescue LoadError
11
+ puts "Could not load perftools.rb, profiling won't be possible"
12
+ end
13
+
14
+ class Post < ActiveRecord::Base
15
+ belongs_to :user
16
+ has_many :comments
17
+ end
18
+
19
+ class Comment < ActiveRecord::Base
20
+ belongs_to :user
21
+ belongs_to :post
22
+ end
23
+
24
+ class User < ActiveRecord::Base
25
+ has_many :posts
26
+ has_many :comments
27
+ end
28
+
29
+ # create database bullet_benchmark;
30
+ ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'bullet_benchmark', :server => '/tmp/mysql.socket', :username => 'root')
31
+
32
+ ActiveRecord::Base.connection.tables.each do |table|
33
+ ActiveRecord::Base.connection.drop_table(table)
34
+ end
35
+
36
+ ActiveRecord::Schema.define(:version => 1) do
37
+ create_table :posts do |t|
38
+ t.column :title, :string
39
+ t.column :body, :string
40
+ t.column :user_id, :integer
41
+ end
42
+
43
+ create_table :comments do |t|
44
+ t.column :body, :string
45
+ t.column :post_id, :integer
46
+ t.column :user_id, :integer
47
+ end
48
+
49
+ create_table :users do |t|
50
+ t.column :name, :string
51
+ end
52
+ end
53
+
54
+ users_size = 100
55
+ posts_size = 1000
56
+ comments_size = 10000
57
+ users = []
58
+ users_size.times do |i|
59
+ users << User.new(:name => "user#{i}")
60
+ end
61
+ User.import users
62
+ users = User.all
63
+
64
+ posts = []
65
+ posts_size.times do |i|
66
+ posts << Post.new(:title => "Title #{i}", :body => "Body #{i}", :user => users[i%100])
67
+ end
68
+ Post.import posts
69
+ posts = Post.all
70
+
71
+ comments = []
72
+ comments_size.times do |i|
73
+ comments << Comment.new(:body => "Comment #{i}", :post => posts[i%1000], :user => users[i%100])
74
+ end
75
+ Comment.import comments
76
+
77
+ puts "Start benchmarking..."
78
+
79
+
80
+ Bullet.enable = true
81
+
82
+ PerfTools::CpuProfiler.start(ARGV[0]|| "benchmark_profile") if defined? PerfTools
83
+
84
+ Benchmark.bm(70) do |bm|
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
93
+ end
94
+ end
95
+ Bullet.end_request
96
+ end
97
+ end
98
+
99
+ PerfTools::CpuProfiler.stop if defined? PerfTools
100
+ puts "End benchmarking..."
101
+
102
+
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
+
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
4
  # This test is just used for http://github.com/flyerhzm/bullet/issues/#issue/14
5
- describe Bullet::Association do
5
+ describe Bullet::Detector::Association do
6
6
 
7
7
  describe "for chris" do
8
8
  def setup_db
@@ -65,32 +65,32 @@ describe Bullet::Association do
65
65
  end
66
66
 
67
67
  before(:each) do
68
- Bullet::Association.start_request
68
+ Bullet.start_request
69
69
  end
70
70
 
71
71
  after(:each) do
72
- Bullet::Association.end_request
72
+ Bullet.end_request
73
73
  end
74
74
 
75
75
  it "should detect unpreload association from deal to hotel" do
76
76
  Deal.all.each do |deal|
77
77
  deal.hotel.location.name
78
78
  end
79
- Bullet::Association.should be_detecting_unpreloaded_association_for(Deal, :hotel)
79
+ Bullet::Detector::Association.should be_detecting_unpreloaded_association_for(Deal, :hotel)
80
80
  end
81
81
 
82
82
  it "should detect unpreload association from hotel to location" do
83
83
  Deal.includes(:hotel).each do |deal|
84
84
  deal.hotel.location.name
85
85
  end
86
- Bullet::Association.should be_detecting_unpreloaded_association_for(Hotel, :location)
86
+ Bullet::Detector::Association.should be_detecting_unpreloaded_association_for(Hotel, :location)
87
87
  end
88
88
 
89
89
  it "should not detect unpreload association" do
90
90
  Deal.includes({:hotel => :location}).each do |deal|
91
91
  deal.hotel.location.name
92
92
  end
93
- Bullet::Association.should_not be_has_unused_preload_associations
93
+ Bullet::Detector::Association.should_not be_has_unused_preload_associations
94
94
  end
95
95
  end
96
96
  end
@@ -2,7 +2,7 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
4
  # This test is just used for http://github.com/flyerhzm/bullet/issues#issue/20
5
- describe Bullet::Association do
5
+ describe Bullet::Detector::Association do
6
6
 
7
7
  describe "for peschkaj" do
8
8
  def setup_db
@@ -65,11 +65,11 @@ describe Bullet::Association do
65
65
  end
66
66
 
67
67
  before(:each) do
68
- Bullet::Association.start_request
68
+ Bullet.start_request
69
69
  end
70
70
 
71
71
  after(:each) do
72
- Bullet::Association.end_request
72
+ Bullet.end_request
73
73
  end
74
74
 
75
75
  it "should not detect unused preload associations" do
@@ -78,9 +78,9 @@ describe Bullet::Association do
78
78
  submission.name
79
79
  submission.user.name
80
80
  end
81
- Bullet::Association.check_unused_preload_associations
82
- Bullet::Association.should_not be_unused_preload_associations_for(Category, :submissions)
83
- Bullet::Association.should_not be_unused_preload_associations_for(Submission, :user)
81
+ Bullet::Detector::UnusedEagerAssociation.check_unused_preload_associations
82
+ Bullet::Detector::Association.should_not be_unused_preload_associations_for(Category, :submissions)
83
+ Bullet::Detector::Association.should_not be_unused_preload_associations_for(Submission, :user)
84
84
  end
85
85
  end
86
86
  end