rails3-footnotes 4.0.0.pre.2 → 4.0.0.pre.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ == Footnotes v4.0.0.pre.3 (March 30, 2011)
2
+ * Merge in contributions from irjudson and jackkinsella
3
+ * fixes for partials note
4
+ * fixes for views note
5
+ * fixes for queries note
6
+ * fix a warning on routes note
7
+
1
8
  == Footnotes v4.0.0.pre.2 (February 21, 2011)
2
9
  * add a Railtie so the plugin will load in Rails 3 apps
3
10
  * remove some internal rails3 ivars from the assigns note
data/README.md CHANGED
@@ -56,7 +56,7 @@ If you have New Relic RPM installed, you may want to turn off query explains
56
56
 
57
57
  Finally, you can control which notes you want to show. The default are:
58
58
 
59
- Footnotes::Filter.notes = [:session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general]
59
+ Footnotes::Filter.notes = [:session, :cookies, :params, :filters, :routes, :env, :queries, :log]
60
60
 
61
61
 
62
62
  ## Creating your own notes
data/Rakefile CHANGED
@@ -2,6 +2,9 @@ require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
 
5
+ require 'bundler'
6
+ Bundler::GemHelper.install_tasks
7
+
5
8
  desc 'Run tests for Footnotes.'
6
9
  Rake::TestTask.new(:test) do |t|
7
10
  t.libs << 'test'
@@ -27,7 +27,7 @@ module Footnotes
27
27
  # Edit notes
28
28
  @@notes = [ :controller, :view, :layout, :partials, :stylesheets, :javascripts ]
29
29
  # Show notes
30
- @@notes += [ :assigns, :session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general ]
30
+ @@notes += [ :assigns, :session, :cookies, :params, :filters, :routes, :env, :queries, :log ]
31
31
 
32
32
  # Change queries for rpm note when available
33
33
  # if defined?(NewRelic)
@@ -38,7 +38,7 @@ module Footnotes
38
38
  else
39
39
  @controller_filename=File.join(File.expand_path(Rails.root), 'app', 'controllers', "#{controller_name}.rb").sub('/controllers/controllers/', '/controllers/')
40
40
  end
41
- @controller_filename
41
+ @controller_filename if File.exist?(@controller_filename)
42
42
  end
43
43
 
44
44
  def controller_text
@@ -3,54 +3,70 @@ require "#{File.dirname(__FILE__)}/log_note"
3
3
  module Footnotes
4
4
  module Notes
5
5
  class PartialsNote < LogNote
6
+ @@partial_subscriber = nil
7
+ cattr_accessor :partial_subscriber
8
+
6
9
  def initialize(controller)
7
10
  super
8
11
  @controller = controller
9
12
  end
13
+
14
+ def self.start!(controller)
15
+ @@partial_subscriber = Footnotes::Notes::PartialSubscriber.new
16
+ ActiveSupport::LogSubscriber.attach_to(:action_view, @@partial_subscriber)
17
+ end
18
+
10
19
  def row
11
20
  :edit
12
21
  end
22
+
13
23
  def title
14
24
  "Partials (#{partials.size})"
15
25
  end
26
+
16
27
  def content
17
28
  rows = partials.map do |filename|
18
29
  href = Footnotes::Filter.prefix(filename,1,1)
19
30
  shortened_name=filename.gsub(File.join(Rails.root,"app/views/"),"")
20
- [%{<a href="#{href}">#{shortened_name}</a>},"#{@partial_times[filename].sum}ms",@partial_counts[filename]]
31
+ [%{<a href="#{href}">#{shortened_name}</a>},"#{@partial_times[filename].sum}ms", @partial_counts[filename]]
21
32
  end
22
33
  mount_table(rows.unshift(%w(Partial Time Count)), :summary => "Partials for #{title}")
23
34
  end
24
35
 
36
+ def self.load
37
+ self.loaded = true unless loaded
38
+ end
39
+
25
40
  protected
26
- #Generate a list of partials that were rendered, also build up render times and counts.
27
- #This is memoized so we can use its information in the title easily.
28
- def partials
29
- @partials ||= begin
30
- partials = []
31
- @partial_counts = {}
32
- @partial_times = {}
33
- log_lines = log
34
- log_lines.split("\n").each do |line|
35
- if line =~ /Rendered (\S*) \(([\d\.]+)\S*?\)/
36
- partial = $1
37
- @controller.view_paths.each do |view_path|
38
- path = File.join(view_path, "#{partial}*")
39
- files = Dir.glob(path)
40
- for file in files
41
- #TODO figure out what format got rendered if theres multiple
42
- @partial_times[file] ||= []
43
- @partial_times[file] << $2.to_f
44
- @partial_counts[file] ||= 0
45
- @partial_counts[file] += 1
46
- partials << file unless partials.include?(file)
47
- end
48
- end
49
- end
50
- end
51
- partials.reverse
41
+ #Generate a list of partials that were rendered, also build up render times and counts.
42
+ #This is memoized so we can use its information in the title easily.
43
+ def partials
44
+ @partials ||= begin
45
+ partials = []
46
+ @partial_counts = {}
47
+ @partial_times = {}
48
+ @@partial_subscriber.events.each do |event|
49
+ partial = event.payload[:identifier]
50
+ @partial_times[partial] ||= []
51
+ @partial_times[partial] << event.duration
52
+ @partial_counts[partial] ||= 0
53
+ @partial_counts[partial] += 1
54
+ partials << partial unless partials.include?(partial)
52
55
  end
56
+ partials.reverse
53
57
  end
54
- end
58
+ end
59
+ end
60
+ class PartialSubscriber < ActiveSupport::LogSubscriber
61
+ attr_accessor :events
62
+ def initialize
63
+ @events = Array.new
64
+ super
65
+ end
66
+
67
+ def render_partial(event)
68
+ @events << event.dup
69
+ end
70
+ end
55
71
  end
56
72
  end
@@ -3,22 +3,26 @@ require "#{File.dirname(__FILE__)}/abstract_note"
3
3
  module Footnotes
4
4
  module Notes
5
5
  class QueriesNote < AbstractNote
6
- @@alert_db_time = 0.16
6
+ @@alert_db_time = 16.0
7
7
  @@alert_sql_number = 8
8
8
  @@sql = []
9
9
  @@include_when_new_relic_installed = false
10
10
  @@loaded = false
11
-
11
+ @@query_subscriber = nil
12
12
  cattr_accessor :sql, :alert_db_time, :alert_sql_number, :alert_explain, :loaded, :sql_explain, :instance_writter => false
13
13
  cattr_reader :include_when_new_relic_installed
14
-
14
+
15
15
  def self.include_when_new_relic_installed=(include_me)
16
16
  @@include_when_new_relic_installed = include_me
17
17
  load if include_me
18
18
  end
19
-
19
+
20
20
  def self.start!(controller)
21
21
  @@sql = []
22
+ @@query_subscriber = Footnotes::Notes::QuerySubscriber.new
23
+ # There appears to be nothing wrong with registering with a non-existant notifier, so we just attach to both :-).
24
+ ActiveSupport::LogSubscriber.attach_to(:data_mapper, @@query_subscriber)
25
+ ActiveSupport::LogSubscriber.attach_to(:active_record, @@query_subscriber)
22
26
  end
23
27
 
24
28
  def self.to_sym
@@ -26,161 +30,92 @@ module Footnotes
26
30
  end
27
31
 
28
32
  def title
29
- db_time = @@sql.inject(0){|sum, item| sum += item.time }
30
- query_color = generate_red_color(@@sql.length, alert_sql_number)
31
- db_color = generate_red_color(db_time, alert_db_time)
33
+ queries = @@query_subscriber.events.length
34
+ total_time = @@query_subscriber.events.inject(0){|sum, item| sum += item.payload[:duration]} / 1000.0
35
+ query_color = generate_red_color(@@query_subscriber.events.length, alert_sql_number)
36
+ db_color = generate_red_color(total_time, alert_db_time)
32
37
 
33
38
  <<-TITLE
34
- <span style="background-color:#{query_color}">Queries (#{@@sql.length})</span>
35
- <span style="background-color:#{db_color}">DB (#{"%.6f" % db_time}s)</span>
39
+ <span style="background-color:#{query_color}">Queries (#{queries})</span>
40
+ <span style="background-color:#{db_color}">DB (#{"%.3f" % total_time}ms)</span>
36
41
  TITLE
37
42
  end
38
43
 
39
44
  def stylesheet
40
45
  <<-STYLESHEET
41
- #queries_debug_info table td, #queries_debug_info table th{border:1px solid #A00; padding:0 3px; text-align:center;}
42
- #queries_debug_info table thead, #queries_debug_info table tbody {color:#A00;}
43
- #queries_debug_info p {background-color:#F3F3FF; border:1px solid #CCC; margin:12px; padding:4px 6px;}
44
- #queries_debug_info a:hover {text-decoration:underline;}
46
+ #queries_debug_info table td, #queries_debug_info table th{border:1px solid #A00; padding:0 3px; text-align:center;}
47
+ #queries_debug_info table thead, #queries_debug_info table tbody {color:#A00;}
48
+ #queries_debug_info p {background-color:#F3F3FF; border:1px solid #CCC; margin:12px; padding:4px 6px;}
49
+ #queries_debug_info a:hover {text-decoration:underline;}
45
50
  STYLESHEET
46
51
  end
47
52
 
48
53
  def content
49
54
  html = ''
50
55
 
51
- @@sql.each_with_index do |item, i|
52
- sql_links = []
53
- sql_links << "<a href=\"javascript:Footnotes.toggle('qtable_#{i}')\" style=\"color:#A00;\">explain</a>" if item.explain
54
- sql_links << "<a href=\"javascript:Footnotes.toggle('qtrace_#{i}')\" style=\"color:#00A;\">trace</a>" if item.trace
55
-
56
+ @@query_subscriber.events.each_with_index do |item, i|
56
57
  html << <<-HTML
57
- <b id="qtitle_#{i}">#{escape(item.type.to_s.upcase)}</b> (#{sql_links.join(' | ')})<br />
58
- #{print_name_and_time(item.name, item.time)}<br />
59
- <span id="explain_#{i}">#{print_query(item.query)}</span><br />
60
- #{print_explain(i, item.explain) if item.explain}
61
- <p id="qtrace_#{i}" style="display:none;">#{parse_trace(item.trace) if item.trace}</p><br />
58
+ #{print_name_and_time(item.payload[:name], item.payload[:duration] / 1000.0)}&nbsp;
59
+ <span id="explain_#{i}">#{print_query(item.payload[:sql])}</span><br />
62
60
  HTML
63
61
  end
64
62
 
65
63
  return html
66
64
  end
67
-
65
+
68
66
  def self.load
69
- #only include when NewRelic is installed if configured to do so
70
- if !loaded and included? and defined?(ActiveRecord)
71
- ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Footnotes::Extensions::AbstractAdapter
72
- ActiveRecord::ConnectionAdapters.local_constants.each do |adapter|
73
- next unless adapter =~ /.*[^Abstract]Adapter$/
74
- next if adapter =~ /(SQLite|Salesforce)Adapter$/
75
- eval("ActiveRecord::ConnectionAdapters::#{adapter}").send :include, Footnotes::Extensions::QueryAnalyzer
76
- self.loaded = true
77
- end
78
- end
67
+ self.loaded = true unless loaded
79
68
  end
80
-
81
- protected
82
- def parse_explain(results)
83
- table = []
84
- table << results.fetch_fields.map(&:name)
85
- results.each do |row|
86
- table << row
87
- end
88
- table
89
- end
90
-
91
- def parse_trace(trace)
92
- trace.map do |t|
93
- s = t.split(':')
94
- %[<a href="#{escape(Footnotes::Filter.prefix("#{Rails.root.to_s}/#{s[0]}", s[1].to_i, 1))}">#{escape(t)}</a><br />]
95
- end.join
96
- end
97
-
98
- def print_name_and_time(name, time)
99
- "<span style='background-color:#{generate_red_color(time, alert_ratio)}'>#{escape(name || 'SQL')} (#{sprintf('%f', time)}s)</span>"
100
- end
101
69
 
102
- def print_query(query)
103
- escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
104
- end
70
+ protected
71
+ def print_name_and_time(name, time)
72
+ "<span style='background-color:#{generate_red_color(time, alert_ratio)}'>#{escape(name || 'SQL')} (#{'%.3fms' % time})</span>"
73
+ end
105
74
 
106
- def print_explain(i, explain)
107
- mount_table(parse_explain(explain), :id => "qtable_#{i}", :style => 'margin:10px;display:none;', :summary => "Debug information for #{title}")
108
- end
75
+ def print_query(query)
76
+ escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', ''))
77
+ end
109
78
 
110
- def generate_red_color(value, alert)
111
- c = ((value.to_f/alert).to_i - 1) * 16
112
- c = 0 if c < 0
113
- c = 15 if c > 15
79
+ def generate_red_color(value, alert)
80
+ c = ((value.to_f/alert).to_i - 1) * 16
81
+ c = 0 if c < 0
82
+ c = 15 if c > 15
114
83
 
115
- c = (15-c).to_s(16)
116
- "#ff#{c*4}"
117
- end
84
+ c = (15-c).to_s(16)
85
+ "#ff#{c*4}"
86
+ end
118
87
 
119
- def alert_ratio
120
- alert_db_time / alert_sql_number
121
- end
88
+ def alert_ratio
89
+ alert_db_time / alert_sql_number
90
+ end
122
91
 
123
92
  end
124
- end
125
-
126
- module Extensions
127
- class Sql
128
- attr_accessor :type, :name, :time, :query, :explain, :trace
129
93
 
130
- def initialize(type, name, time, query, explain)
131
- @type = type
132
- @name = name
133
- @time = time
134
- @query = query
135
- @explain = explain
94
+ class QuerySubscriber < ActiveSupport::LogSubscriber
95
+ attr_accessor :events
136
96
 
137
- # Strip, select those ones from app and reject first two, because they
138
- # are from the plugin
139
- @trace = Kernel.caller.collect(&:strip).select{|i| i.gsub!(/^#{Rails.root}\//im, '') }[2..-1]
97
+ def self.runtime=(value)
98
+ Thread.current["orm_sql_runtime"] = value
140
99
  end
141
- end
142
100
 
143
- module QueryAnalyzer
144
- def self.included(base)
145
- base.class_eval do
146
- alias_method_chain :execute, :analyzer
147
- end
101
+ def self.runtime
102
+ Thread.current["orm_sql_runtime"] ||= 0
148
103
  end
149
104
 
150
- def execute_with_analyzer(query, name = nil)
151
- query_results = nil
152
- time = Benchmark.realtime { query_results = execute_without_analyzer(query, name) }
153
-
154
- if query =~ /^(select|create|update|delete)\b/i
155
- type = $&.downcase.to_sym
156
- explain = nil
157
-
158
- if adapter_name == 'MySQL' && type == :select && Footnotes::Notes::QueriesNote.sql_explain
159
- log_silence do
160
- explain = execute_without_analyzer("EXPLAIN #{query}", name)
161
- end
162
- end
163
- Footnotes::Notes::QueriesNote.sql << Footnotes::Extensions::Sql.new(type, name, time, query, explain)
164
- end
105
+ def self.reset_runtime
106
+ rt, self.runtime = runtime, 0
107
+ rt
108
+ end
165
109
 
166
- query_results
110
+ def initialize
111
+ @events = Array.new
112
+ super
167
113
  end
168
- end
169
114
 
170
- module AbstractAdapter
171
- def log_silence
172
- result = nil
173
- if @logger
174
- @logger.silence do
175
- result = yield
176
- end
177
- else
178
- result = yield
179
- end
180
- result
115
+ def sql(event)
116
+ @events << event.dup
181
117
  end
182
118
  end
183
-
184
119
  end
185
120
  end
186
121
 
@@ -48,7 +48,7 @@ module Footnotes
48
48
  #
49
49
  def filtered_routes(filter = {})
50
50
  return [] unless filter.is_a?(Hash)
51
- return routes.reject do |r|
51
+ return routes.reject do |r|
52
52
  filter_diff = filter.diff(r.requirements)
53
53
  route_diff = r.requirements.diff(filter)
54
54
  (filter_diff == filter) || (filter_diff != route_diff)
@@ -3,33 +3,73 @@ require "#{File.dirname(__FILE__)}/abstract_note"
3
3
  module Footnotes
4
4
  module Notes
5
5
  class ViewNote < AbstractNote
6
- def initialize(controller)
7
- @controller = controller
8
- @template = controller.instance_variable_get(:@template)
9
- end
6
+ @@alert_time = 500.0
7
+ @@loaded = false
8
+ @@view_subscriber = nil
10
9
 
11
- def row
12
- :edit
13
- end
10
+ def initialize(controller)
11
+ super
12
+ @controller = controller
13
+ end
14
14
 
15
- def link
16
- escape(Footnotes::Filter.prefix(filename, 1, 1))
17
- end
15
+ def self.start!(controller)
16
+ @@view_subscriber = Footnotes::Notes::ViewSubscriber.new
17
+ ActiveSupport::LogSubscriber.attach_to(:action_view, @@view_subscriber)
18
+ end
18
19
 
19
- def valid?
20
- prefix? && first_render?
21
- end
20
+ def self.to_sym
21
+ :views
22
+ end
22
23
 
23
- protected
24
+ def title
25
+ total_time = @@view_subscriber.events.select{ |e| e.name =~ /render_template/ }[0].duration
26
+ "<span style=\"background-color:#{generate_red_color(total_time)}\">View Render (#{"%.3f" % total_time}ms)</span>"
27
+ end
24
28
 
25
- def first_render?
26
- @template.instance_variable_get(:@_first_render)
27
- end
29
+ def content
30
+ html = ''
31
+ page = @@view_subscriber.events.select{ |e| e.name =~ /render_template/ }[0]
32
+ partials = @@view_subscriber.events.select{ |e| e.name =~ /render_partial/ }
33
+ partial_time = partials.inject(0) {|sum, item| sum += item.duration}
34
+
35
+ view = page.payload[:identifier].gsub(File.join(Rails.root,"app/views/"),"")
36
+ layout = page.payload[:layout].gsub(File.join(Rails.root,"app/views/"),"")
28
37
 
29
- def filename
30
- @filename ||= @template.instance_variable_get(:@_first_render).filename
38
+ rows = [["View", "Layout", "View Render (ms)", "Partial(s) Render (ms)", "Total Render (ms)"],
39
+ [escape(view), escape(layout), "#{'%.3f' % (page.duration - partial_time)}",
40
+ "<a href=\"#\" onclick=\"Footnotes.hideAllAndToggle('partials_debug_info');return false;\">#{'%.3f' % partial_time}</a>",
41
+ "#{'%.3f' % page.duration}"]]
42
+
43
+ puts rows.inspect
44
+
45
+ mount_table(rows)
46
+ end
47
+
48
+ def self.load
49
+ self.loaded = true unless loaded
50
+ end
51
+
52
+ def generate_red_color(value)
53
+ if value > @@alert_time
54
+ "#f00"
55
+ else
56
+ "#aaa"
57
+ end
58
+ end
31
59
  end
32
60
 
61
+ class ViewSubscriber < ActiveSupport::LogSubscriber
62
+ attr_accessor :events
63
+ def initialize
64
+ @events = Array.new
65
+ super
66
+ end
67
+
68
+ def render_template(event)
69
+ @events << event.dup
70
+ end
71
+ alias :render_partial :render_template
72
+ alias :render_collection :render_template
33
73
  end
34
74
  end
35
75
  end
@@ -1,3 +1,3 @@
1
1
  module Footnotes
2
- VERSION = "4.0.0.pre.2"
2
+ VERSION = "4.0.0.pre.3"
3
3
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails3-footnotes
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1923831853
4
+ hash: 1923831855
5
5
  prerelease: true
6
6
  segments:
7
7
  - 4
8
8
  - 0
9
9
  - 0
10
10
  - pre
11
- - 2
12
- version: 4.0.0.pre.2
11
+ - 3
12
+ version: 4.0.0.pre.3
13
13
  platform: ruby
14
14
  authors:
15
15
  - "Andr\xC3\xA9 Arko"
@@ -35,6 +35,21 @@ dependencies:
35
35
  version: "3.0"
36
36
  type: :runtime
37
37
  version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: bundler
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 15
47
+ segments:
48
+ - 1
49
+ - 0
50
+ version: "1.0"
51
+ type: :development
52
+ version_requirements: *id002
38
53
  description: Adds footnotes to each page in your Rails app, containing useful development information as well as links to edit the controllers and views in your editor.
39
54
  email: andre@arko.net
40
55
  executables: []
@@ -57,7 +72,6 @@ files:
57
72
  - lib/rails-footnotes/notes/env_note.rb
58
73
  - lib/rails-footnotes/notes/files_note.rb
59
74
  - lib/rails-footnotes/notes/filters_note.rb
60
- - lib/rails-footnotes/notes/general_note.rb
61
75
  - lib/rails-footnotes/notes/javascripts_note.rb
62
76
  - lib/rails-footnotes/notes/layout_note.rb
63
77
  - lib/rails-footnotes/notes/log_note.rb
@@ -1,19 +0,0 @@
1
- require "#{File.dirname(__FILE__)}/abstract_note"
2
-
3
- module Footnotes
4
- module Notes
5
- class GeneralNote < AbstractNote
6
- def title
7
- 'General Debug'
8
- end
9
-
10
- def legend
11
- 'General (id="general_debug_info")'
12
- end
13
-
14
- def content
15
- 'You can use this tab to debug other parts of your application, for example Javascript.'
16
- end
17
- end
18
- end
19
- end