bullet 5.5.0 → 6.1.5
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.
- checksums.yaml +5 -5
- data/.github/workflows/main.yml +66 -0
- data/CHANGELOG.md +83 -1
- data/Gemfile +6 -1
- data/Gemfile.mongoid-7.0 +15 -0
- data/Gemfile.rails-4.0 +1 -1
- data/Gemfile.rails-4.1 +1 -1
- data/Gemfile.rails-4.2 +1 -1
- data/Gemfile.rails-5.0 +2 -2
- data/Gemfile.rails-5.1 +15 -0
- data/Gemfile.rails-5.2 +15 -0
- data/Gemfile.rails-6.0 +15 -0
- data/Gemfile.rails-6.1 +15 -0
- data/Guardfile +2 -2
- data/Hacking.md +1 -1
- data/README.md +58 -37
- data/Rakefile +20 -21
- data/bullet.gemspec +21 -15
- data/lib/bullet/active_job.rb +13 -0
- data/lib/bullet/active_record4.rb +15 -34
- data/lib/bullet/active_record41.rb +14 -31
- data/lib/bullet/active_record42.rb +23 -36
- data/lib/bullet/active_record5.rb +178 -158
- data/lib/bullet/active_record52.rb +251 -0
- data/lib/bullet/active_record60.rb +278 -0
- data/lib/bullet/active_record61.rb +278 -0
- data/lib/bullet/bullet_xhr.js +64 -0
- data/lib/bullet/dependency.rb +60 -32
- data/lib/bullet/detector/association.rb +41 -32
- data/lib/bullet/detector/base.rb +2 -0
- data/lib/bullet/detector/counter_cache.rb +25 -17
- data/lib/bullet/detector/n_plus_one_query.rb +38 -24
- data/lib/bullet/detector/unused_eager_loading.rb +35 -26
- data/lib/bullet/detector.rb +2 -0
- data/lib/bullet/ext/object.rb +9 -5
- data/lib/bullet/ext/string.rb +3 -1
- data/lib/bullet/mongoid4x.rb +9 -8
- data/lib/bullet/mongoid5x.rb +9 -8
- data/lib/bullet/mongoid6x.rb +13 -12
- data/lib/bullet/mongoid7x.rb +57 -0
- data/lib/bullet/notification/base.rb +28 -25
- data/lib/bullet/notification/counter_cache.rb +3 -1
- data/lib/bullet/notification/n_plus_one_query.rb +8 -7
- data/lib/bullet/notification/unused_eager_loading.rb +8 -7
- data/lib/bullet/notification.rb +4 -1
- data/lib/bullet/notification_collector.rb +2 -1
- data/lib/bullet/rack.rb +66 -35
- data/lib/bullet/registry/association.rb +2 -0
- data/lib/bullet/registry/base.rb +2 -0
- data/lib/bullet/registry/object.rb +2 -0
- data/lib/bullet/registry.rb +2 -0
- data/lib/bullet/stack_trace_filter.rb +50 -20
- data/lib/bullet/version.rb +3 -2
- data/lib/bullet.rb +136 -55
- data/lib/generators/bullet/install_generator.rb +48 -0
- data/perf/benchmark.rb +19 -22
- data/rails/init.rb +2 -0
- data/spec/bullet/detector/association_spec.rb +6 -4
- data/spec/bullet/detector/base_spec.rb +2 -0
- data/spec/bullet/detector/counter_cache_spec.rb +17 -15
- data/spec/bullet/detector/n_plus_one_query_spec.rb +75 -46
- data/spec/bullet/detector/unused_eager_loading_spec.rb +45 -26
- data/spec/bullet/ext/object_spec.rb +18 -11
- data/spec/bullet/ext/string_spec.rb +7 -5
- data/spec/bullet/notification/base_spec.rb +38 -39
- data/spec/bullet/notification/counter_cache_spec.rb +5 -3
- data/spec/bullet/notification/n_plus_one_query_spec.rb +20 -5
- data/spec/bullet/notification/unused_eager_loading_spec.rb +9 -3
- data/spec/bullet/notification_collector_spec.rb +12 -10
- data/spec/bullet/rack_spec.rb +200 -48
- data/spec/bullet/registry/association_spec.rb +12 -10
- data/spec/bullet/registry/base_spec.rb +22 -20
- data/spec/bullet/registry/object_spec.rb +6 -4
- data/spec/bullet_spec.rb +64 -33
- data/spec/integration/{active_record4 → active_record}/association_spec.rb +238 -223
- data/spec/integration/counter_cache_spec.rb +26 -44
- data/spec/integration/mongoid/association_spec.rb +53 -65
- data/spec/models/address.rb +2 -0
- data/spec/models/attachment.rb +5 -0
- data/spec/models/author.rb +2 -0
- data/spec/models/base_user.rb +2 -0
- data/spec/models/category.rb +2 -0
- data/spec/models/city.rb +2 -0
- data/spec/models/client.rb +4 -0
- data/spec/models/comment.rb +3 -1
- data/spec/models/company.rb +2 -0
- data/spec/models/country.rb +2 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/document.rb +4 -2
- data/spec/models/entry.rb +2 -0
- data/spec/models/firm.rb +3 -0
- data/spec/models/folder.rb +2 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/mongoid/address.rb +3 -1
- data/spec/models/mongoid/category.rb +4 -2
- data/spec/models/mongoid/comment.rb +3 -1
- data/spec/models/mongoid/company.rb +3 -1
- data/spec/models/mongoid/entry.rb +3 -1
- data/spec/models/mongoid/post.rb +6 -4
- data/spec/models/mongoid/user.rb +2 -0
- data/spec/models/newspaper.rb +3 -1
- data/spec/models/page.rb +2 -0
- data/spec/models/person.rb +2 -0
- data/spec/models/pet.rb +2 -0
- data/spec/models/post.rb +19 -0
- data/spec/models/relationship.rb +2 -0
- data/spec/models/reply.rb +2 -0
- data/spec/models/student.rb +2 -0
- data/spec/models/submission.rb +3 -0
- data/spec/models/teacher.rb +2 -0
- data/spec/models/user.rb +3 -0
- data/spec/models/writer.rb +2 -0
- data/spec/spec_helper.rb +19 -23
- data/spec/support/bullet_ext.rb +10 -9
- data/spec/support/mongo_seed.rb +38 -57
- data/spec/support/rack_double.rb +13 -19
- data/spec/support/sqlite_seed.rb +104 -76
- data/tasks/bullet_tasks.rake +4 -2
- data/test.sh +5 -0
- data/update.sh +3 -0
- metadata +34 -17
- data/.travis.yml +0 -16
- data/spec/integration/active_record5/association_spec.rb +0 -768
data/spec/models/post.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Post < ActiveRecord::Base
|
|
2
4
|
belongs_to :category, inverse_of: :posts
|
|
3
5
|
belongs_to :writer
|
|
4
6
|
has_many :comments, inverse_of: :post
|
|
7
|
+
has_and_belongs_to_many :deals
|
|
5
8
|
|
|
6
9
|
validates :category, presence: true
|
|
7
10
|
|
|
@@ -12,4 +15,20 @@ class Post < ActiveRecord::Base
|
|
|
12
15
|
def link=(*)
|
|
13
16
|
comments.new
|
|
14
17
|
end
|
|
18
|
+
|
|
19
|
+
# see association_spec.rb 'should not detect newly assigned object in an after_save'
|
|
20
|
+
attr_accessor :trigger_after_save
|
|
21
|
+
after_save do
|
|
22
|
+
next unless trigger_after_save
|
|
23
|
+
|
|
24
|
+
temp_comment = Comment.new(post: self)
|
|
25
|
+
|
|
26
|
+
# this triggers self to be "possible", even though it's
|
|
27
|
+
# not saved yet
|
|
28
|
+
temp_comment.post
|
|
29
|
+
|
|
30
|
+
# category should NOT whine about not being pre-loaded, because
|
|
31
|
+
# it's obviously attached to a new object
|
|
32
|
+
category
|
|
33
|
+
end
|
|
15
34
|
end
|
data/spec/models/relationship.rb
CHANGED
data/spec/models/reply.rb
CHANGED
data/spec/models/student.rb
CHANGED
data/spec/models/submission.rb
CHANGED
data/spec/models/teacher.rb
CHANGED
data/spec/models/user.rb
CHANGED
data/spec/models/writer.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rspec'
|
|
2
4
|
begin
|
|
3
5
|
require 'active_record'
|
|
@@ -9,31 +11,31 @@ rescue LoadError
|
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
module Rails
|
|
12
|
-
class <<self
|
|
14
|
+
class << self
|
|
13
15
|
def root
|
|
14
16
|
File.expand_path(__FILE__).split('/')[0..-3].join('/')
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def env
|
|
18
|
-
|
|
20
|
+
'test'
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
|
|
23
|
-
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) +
|
|
25
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
|
|
24
26
|
require 'bullet'
|
|
25
27
|
extend Bullet::Dependency
|
|
26
28
|
Bullet.enable = true
|
|
27
29
|
|
|
28
|
-
MODELS = File.join(File.dirname(__FILE__),
|
|
30
|
+
MODELS = File.join(File.dirname(__FILE__), 'models')
|
|
29
31
|
$LOAD_PATH.unshift(MODELS)
|
|
30
|
-
SUPPORT = File.join(File.dirname(__FILE__),
|
|
31
|
-
Dir[
|
|
32
|
+
SUPPORT = File.join(File.dirname(__FILE__), 'support')
|
|
33
|
+
Dir[File.join(SUPPORT, '*.rb')].reject { |filename| filename =~ /_seed.rb$/ }.sort.each { |file| require file }
|
|
32
34
|
|
|
33
35
|
RSpec.configure do |config|
|
|
34
36
|
config.extend Bullet::Dependency
|
|
35
37
|
|
|
36
|
-
config.filter_run :
|
|
38
|
+
config.filter_run focus: true
|
|
37
39
|
config.run_all_when_everything_filtered = true
|
|
38
40
|
end
|
|
39
41
|
|
|
@@ -41,11 +43,11 @@ if active_record?
|
|
|
41
43
|
ActiveRecord::Migration.verbose = false
|
|
42
44
|
|
|
43
45
|
# Autoload every active_record model for the test suite that sits in spec/models.
|
|
44
|
-
Dir[
|
|
45
|
-
name = File.basename(filename,
|
|
46
|
+
Dir[File.join(MODELS, '*.rb')].sort.each do |filename|
|
|
47
|
+
name = File.basename(filename, '.rb')
|
|
46
48
|
autoload name.camelize.to_sym, name
|
|
47
49
|
end
|
|
48
|
-
require File.join(SUPPORT,
|
|
50
|
+
require File.join(SUPPORT, 'sqlite_seed.rb')
|
|
49
51
|
|
|
50
52
|
RSpec.configure do |config|
|
|
51
53
|
config.before(:suite) do
|
|
@@ -58,12 +60,10 @@ if active_record?
|
|
|
58
60
|
Bullet.enable = true
|
|
59
61
|
end
|
|
60
62
|
|
|
61
|
-
config.after(:example)
|
|
62
|
-
Bullet.end_request
|
|
63
|
-
end
|
|
63
|
+
config.after(:example) { Bullet.end_request }
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
if ENV[
|
|
66
|
+
if ENV['BULLET_LOG']
|
|
67
67
|
require 'logger'
|
|
68
68
|
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
69
69
|
end
|
|
@@ -71,8 +71,8 @@ end
|
|
|
71
71
|
|
|
72
72
|
if mongoid?
|
|
73
73
|
# Autoload every mongoid model for the test suite that sits in spec/models.
|
|
74
|
-
Dir[
|
|
75
|
-
require File.join(SUPPORT,
|
|
74
|
+
Dir[File.join(MODELS, 'mongoid', '*.rb')].sort.each { |file| require file }
|
|
75
|
+
require File.join(SUPPORT, 'mongo_seed.rb')
|
|
76
76
|
|
|
77
77
|
RSpec.configure do |config|
|
|
78
78
|
config.before(:suite) do
|
|
@@ -85,16 +85,12 @@ if mongoid?
|
|
|
85
85
|
Support::MongoSeed.teardown_db
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
config.before(:each)
|
|
89
|
-
Bullet.start_request
|
|
90
|
-
end
|
|
88
|
+
config.before(:each) { Bullet.start_request }
|
|
91
89
|
|
|
92
|
-
config.after(:each)
|
|
93
|
-
Bullet.end_request
|
|
94
|
-
end
|
|
90
|
+
config.after(:each) { Bullet.end_request }
|
|
95
91
|
end
|
|
96
92
|
|
|
97
|
-
if ENV[
|
|
93
|
+
if ENV['BULLET_LOG']
|
|
98
94
|
Mongoid.logger = Logger.new(STDOUT)
|
|
99
95
|
Moped.logger = Logger.new(STDOUT)
|
|
100
96
|
end
|
data/spec/support/bullet_ext.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
def self.collected_notifications_of_class(notification_class)
|
|
3
|
-
Bullet.notification_collector.collection.select
|
|
4
|
-
notification.is_a? notification_class
|
|
5
|
-
end
|
|
5
|
+
Bullet.notification_collector.collection.select { |notification| notification.is_a? notification_class }
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def self.collected_counter_cache_notifications
|
|
@@ -21,7 +21,7 @@ end
|
|
|
21
21
|
module Bullet
|
|
22
22
|
module Detector
|
|
23
23
|
class Association
|
|
24
|
-
class <<self
|
|
24
|
+
class << self
|
|
25
25
|
# returns true if all associations are preloaded
|
|
26
26
|
def completely_preloading_associations?
|
|
27
27
|
Bullet.collected_n_plus_one_query_notifications.empty?
|
|
@@ -33,21 +33,22 @@ module Bullet
|
|
|
33
33
|
|
|
34
34
|
# returns true if a given object has a specific association
|
|
35
35
|
def creating_object_association_for?(object, association)
|
|
36
|
-
object_associations[object.bullet_key].present? &&
|
|
36
|
+
object_associations[object.bullet_key].present? &&
|
|
37
|
+
object_associations[object.bullet_key].include?(association)
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# returns true if a given class includes the specific unpreloaded association
|
|
40
41
|
def detecting_unpreloaded_association_for?(klass, association)
|
|
41
|
-
Bullet.collected_n_plus_one_query_notifications.select
|
|
42
|
+
Bullet.collected_n_plus_one_query_notifications.select do |notification|
|
|
42
43
|
notification.base_class == klass.to_s && notification.associations.include?(association)
|
|
43
|
-
|
|
44
|
+
end.present?
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
# returns true if the given class includes the specific unused preloaded association
|
|
47
48
|
def unused_preload_associations_for?(klass, association)
|
|
48
|
-
Bullet.collected_unused_eager_association_notifications.select
|
|
49
|
+
Bullet.collected_unused_eager_association_notifications.select do |notification|
|
|
49
50
|
notification.base_class == klass.to_s && notification.associations.include?(association)
|
|
50
|
-
|
|
51
|
+
end.present?
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
end
|
data/spec/support/mongo_seed.rb
CHANGED
|
@@ -1,68 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Support
|
|
2
4
|
module MongoSeed
|
|
3
|
-
|
|
4
|
-
category1 = Mongoid::Category.create(:name => 'first')
|
|
5
|
-
category2 = Mongoid::Category.create(:name => 'second')
|
|
6
|
-
|
|
7
|
-
post1 = category1.posts.create(:name => 'first')
|
|
8
|
-
post1a = category1.posts.create(:name => 'like first')
|
|
9
|
-
post2 = category2.posts.create(:name => 'second')
|
|
10
|
-
|
|
11
|
-
post1.users << Mongoid::User.create(:name => 'first')
|
|
12
|
-
post1.users << Mongoid::User.create(:name => 'another')
|
|
13
|
-
post2.users << Mongoid::User.create(:name => 'second')
|
|
14
|
-
|
|
15
|
-
comment1 = post1.comments.create(:name => 'first')
|
|
16
|
-
comment2 = post1.comments.create(:name => 'first2')
|
|
17
|
-
comment3 = post1.comments.create(:name => 'first3')
|
|
18
|
-
comment4 = post1.comments.create(:name => 'second')
|
|
19
|
-
comment8 = post1a.comments.create(:name => "like first 1")
|
|
20
|
-
comment9 = post1a.comments.create(:name => "like first 2")
|
|
21
|
-
comment5 = post2.comments.create(:name => 'third')
|
|
22
|
-
comment6 = post2.comments.create(:name => 'fourth')
|
|
23
|
-
comment7 = post2.comments.create(:name => 'fourth')
|
|
24
|
-
|
|
25
|
-
entry1 = category1.entries.create(:name => 'first')
|
|
26
|
-
entry2 = category1.entries.create(:name => 'second')
|
|
27
|
-
|
|
28
|
-
company1 = Mongoid::Company.create(:name => 'first')
|
|
29
|
-
company2 = Mongoid::Company.create(:name => 'second')
|
|
5
|
+
module_function
|
|
30
6
|
|
|
31
|
-
|
|
32
|
-
Mongoid::
|
|
7
|
+
def seed_db
|
|
8
|
+
category1 = Mongoid::Category.create(name: 'first')
|
|
9
|
+
category2 = Mongoid::Category.create(name: 'second')
|
|
10
|
+
|
|
11
|
+
post1 = category1.posts.create(name: 'first')
|
|
12
|
+
post1a = category1.posts.create(name: 'like first')
|
|
13
|
+
post2 = category2.posts.create(name: 'second')
|
|
14
|
+
|
|
15
|
+
post1.users << Mongoid::User.create(name: 'first')
|
|
16
|
+
post1.users << Mongoid::User.create(name: 'another')
|
|
17
|
+
post2.users << Mongoid::User.create(name: 'second')
|
|
18
|
+
|
|
19
|
+
comment1 = post1.comments.create(name: 'first')
|
|
20
|
+
comment2 = post1.comments.create(name: 'first2')
|
|
21
|
+
comment3 = post1.comments.create(name: 'first3')
|
|
22
|
+
comment4 = post1.comments.create(name: 'second')
|
|
23
|
+
comment8 = post1a.comments.create(name: 'like first 1')
|
|
24
|
+
comment9 = post1a.comments.create(name: 'like first 2')
|
|
25
|
+
comment5 = post2.comments.create(name: 'third')
|
|
26
|
+
comment6 = post2.comments.create(name: 'fourth')
|
|
27
|
+
comment7 = post2.comments.create(name: 'fourth')
|
|
28
|
+
|
|
29
|
+
entry1 = category1.entries.create(name: 'first')
|
|
30
|
+
entry2 = category1.entries.create(name: 'second')
|
|
31
|
+
|
|
32
|
+
company1 = Mongoid::Company.create(name: 'first')
|
|
33
|
+
company2 = Mongoid::Company.create(name: 'second')
|
|
34
|
+
|
|
35
|
+
Mongoid::Address.create(name: 'first', company: company1)
|
|
36
|
+
Mongoid::Address.create(name: 'second', company: company2)
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def setup_db
|
|
36
|
-
if Mongoid::VERSION =~ /\
|
|
37
|
-
Mongoid.configure do |config|
|
|
38
|
-
config.master = Mongo::Connection.new.db("bullet")
|
|
39
|
-
end
|
|
40
|
-
elsif Mongoid::VERSION =~ /\A3/
|
|
40
|
+
if Mongoid::VERSION =~ /\A4/
|
|
41
41
|
Mongoid.configure do |config|
|
|
42
|
-
config.
|
|
42
|
+
config.load_configuration(sessions: { default: { database: 'bullet', hosts: %w[localhost:27017] } })
|
|
43
43
|
end
|
|
44
|
-
|
|
44
|
+
else
|
|
45
45
|
Mongoid.configure do |config|
|
|
46
|
-
config.load_configuration(
|
|
47
|
-
sessions: {
|
|
48
|
-
default: {
|
|
49
|
-
database: "bullet",
|
|
50
|
-
hosts: [ "localhost:27017" ]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
)
|
|
54
|
-
end
|
|
55
|
-
elsif Mongoid::VERSION =~ /\A5/
|
|
56
|
-
Mongoid.configure do |config|
|
|
57
|
-
config.load_configuration(
|
|
58
|
-
clients: {
|
|
59
|
-
default: {
|
|
60
|
-
database: "bullet",
|
|
61
|
-
hosts: [ "localhost:27017" ]
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
)
|
|
46
|
+
config.load_configuration(clients: { default: { database: 'bullet', hosts: %w[localhost:27017] } })
|
|
65
47
|
end
|
|
48
|
+
|
|
66
49
|
# Increase the level from DEBUG in order to avoid excessive logging to the screen
|
|
67
50
|
Mongo::Logger.logger.level = Logger::WARN
|
|
68
51
|
end
|
|
@@ -72,7 +55,5 @@ module Support
|
|
|
72
55
|
Mongoid.purge!
|
|
73
56
|
Mongoid::IdentityMap.clear if Mongoid.const_defined?(:IdentityMap)
|
|
74
57
|
end
|
|
75
|
-
|
|
76
|
-
extend self
|
|
77
58
|
end
|
|
78
59
|
end
|
data/spec/support/rack_double.rb
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Support
|
|
2
4
|
class AppDouble
|
|
3
|
-
def call
|
|
5
|
+
def call(_env)
|
|
4
6
|
env = @env
|
|
5
|
-
[
|
|
7
|
+
[status, headers, response]
|
|
6
8
|
end
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
@status = status
|
|
10
|
-
end
|
|
10
|
+
attr_writer :status
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
@headers = headers
|
|
14
|
-
end
|
|
12
|
+
attr_writer :headers
|
|
15
13
|
|
|
16
14
|
def headers
|
|
17
|
-
@headers ||= {
|
|
15
|
+
@headers ||= { 'Content-Type' => 'text/html' }
|
|
18
16
|
@headers
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
@response = response
|
|
23
|
-
end
|
|
19
|
+
attr_writer :response
|
|
24
20
|
|
|
25
21
|
private
|
|
22
|
+
|
|
26
23
|
def status
|
|
27
24
|
@status || 200
|
|
28
25
|
end
|
|
@@ -33,23 +30,20 @@ module Support
|
|
|
33
30
|
end
|
|
34
31
|
|
|
35
32
|
class ResponseDouble
|
|
36
|
-
def initialize
|
|
33
|
+
def initialize(actual_body = nil)
|
|
37
34
|
@actual_body = actual_body
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
def body
|
|
41
|
-
@body ||=
|
|
38
|
+
@body ||= '<html><head></head><body></body></html>'
|
|
42
39
|
end
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
@body = body
|
|
46
|
-
end
|
|
41
|
+
attr_writer :body
|
|
47
42
|
|
|
48
43
|
def each
|
|
49
44
|
yield body
|
|
50
45
|
end
|
|
51
46
|
|
|
52
|
-
def close
|
|
53
|
-
end
|
|
47
|
+
def close; end
|
|
54
48
|
end
|
|
55
49
|
end
|