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.
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/.rvmrc.example +2 -0
- data/.watchr +24 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +105 -0
- data/Hacking.textile +69 -0
- data/MIT-LICENSE +1 -1
- data/README.textile +61 -49
- data/README_for_rails2.textile +41 -42
- data/Rakefile +40 -29
- data/bullet.gemspec +17 -60
- data/lib/bullet/action_controller2.rb +6 -6
- data/lib/bullet/active_record2.rb +17 -16
- data/lib/bullet/active_record3.rb +17 -25
- data/lib/bullet/active_record31.rb +96 -0
- data/lib/bullet/detector/association.rb +144 -0
- data/lib/bullet/detector/base.rb +12 -0
- data/lib/bullet/detector/counter.rb +43 -0
- data/lib/bullet/detector/n_plus_one_query.rb +39 -0
- data/lib/bullet/detector/unused_eager_association.rb +41 -0
- data/lib/bullet/detector.rb +9 -0
- data/lib/bullet/notification/base.rb +61 -0
- data/lib/bullet/notification/counter_cache.rb +13 -0
- data/lib/bullet/notification/n_plus_one_query.rb +32 -0
- data/lib/bullet/notification/unused_eager_loading.rb +14 -0
- data/lib/bullet/notification.rb +4 -79
- data/lib/bullet/notification_collector.rb +25 -0
- data/lib/bullet/rack.rb +44 -0
- data/lib/bullet/registry/association.rb +15 -0
- data/lib/bullet/registry/base.rb +37 -0
- data/lib/bullet/registry/object.rb +15 -0
- data/lib/bullet/registry.rb +7 -0
- data/lib/bullet/version.rb +5 -0
- data/lib/bullet.rb +64 -43
- data/perf/benchmark.rb +106 -0
- data/spec/bullet/association_for_chris_spec.rb +6 -6
- data/spec/bullet/association_for_peschkaj_spec.rb +6 -6
- data/spec/bullet/association_spec.rb +140 -294
- data/spec/bullet/counter_spec.rb +10 -10
- data/spec/spec_helper.rb +51 -17
- metadata +72 -57
- data/VERSION +0 -1
- data/lib/bullet/association.rb +0 -294
- data/lib/bullet/counter.rb +0 -101
- data/lib/bullet/logger.rb +0 -9
- data/lib/bulletware.rb +0 -42
- 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
|
data/lib/bullet.rb
CHANGED
|
@@ -1,31 +1,46 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'uniform_notifier'
|
|
2
3
|
|
|
3
4
|
module Bullet
|
|
4
|
-
if Rails.version =~ /^3
|
|
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 :
|
|
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, :
|
|
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 =~ /^
|
|
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
|
|
38
|
-
if
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
61
|
+
notification_collector.reset
|
|
62
|
+
DETECTORS.each {|bullet| bullet.start_request}
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
def end_request
|
|
64
|
-
|
|
66
|
+
DETECTORS.each {|bullet| bullet.end_request}
|
|
65
67
|
end
|
|
66
|
-
|
|
68
|
+
|
|
67
69
|
def clear
|
|
68
|
-
|
|
70
|
+
DETECTORS.each {|bullet| bullet.clear}
|
|
69
71
|
end
|
|
70
72
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
+
def notification_collector
|
|
74
|
+
@notification_collector ||= Bullet::NotificationCollector.new
|
|
73
75
|
end
|
|
74
76
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
+
def notification?
|
|
78
|
+
Bullet::Detector::UnusedEagerAssociation.check_unused_preload_associations
|
|
79
|
+
notification_collector.notifications_present?
|
|
77
80
|
end
|
|
78
81
|
|
|
79
|
-
def
|
|
80
|
-
|
|
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
|
|
84
|
-
|
|
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
|
|
68
|
+
Bullet.start_request
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
after(:each) do
|
|
72
|
-
Bullet
|
|
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
|
|
68
|
+
Bullet.start_request
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
after(:each) do
|
|
72
|
-
Bullet
|
|
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::
|
|
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
|