├── .rspec.example ├── lib ├── rails-footnotes │ ├── version.rb │ ├── notes │ │ ├── all.rb │ │ ├── javascripts_note.rb │ │ ├── stylesheets_note.rb │ │ ├── general_note.rb │ │ ├── params_note.rb │ │ ├── cookies_note.rb │ │ ├── layout_note.rb │ │ ├── session_note.rb │ │ ├── env_note.rb │ │ ├── view_note.rb │ │ ├── files_note.rb │ │ ├── log_note.rb │ │ ├── controller_note.rb │ │ ├── filters_note.rb │ │ ├── assigns_note.rb │ │ ├── partials_note.rb │ │ ├── routes_note.rb │ │ └── queries_note.rb │ ├── backtracer.rb │ ├── abstract_note.rb │ └── footnotes.rb └── rails-footnotes.rb ├── .gitignore ├── spec ├── notes │ ├── partials_notes_spec.rb │ ├── javascripts_note_spec.rb │ ├── stylesheets_note_spec.rb │ └── assigns_note_spec.rb ├── spec_helper.rb ├── abstract_note_spec.rb └── footnotes_spec.rb ├── Gemfile ├── .watchr.example ├── Rakefile ├── MIT-LICENSE ├── rails-footnotes.gemspec ├── Gemfile.lock ├── CHANGELOG └── README.rdoc /.rspec.example: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /lib/rails-footnotes/version.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | VERSION = "3.7.5.rc1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/all.rb: -------------------------------------------------------------------------------- 1 | Dir[File.join(File.dirname(__FILE__), '*_note.rb')].each {|note| require note} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.gitmodules 4 | !*.example 5 | *.gem 6 | .bundle 7 | Gemfile.lock 8 | pkg/* 9 | coverage 10 | tags 11 | doc 12 | -------------------------------------------------------------------------------- /spec/notes/partials_notes_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "rails-footnotes/notes/partials_note" 3 | 4 | describe Footnotes::Notes::PartialsNote do 5 | pending 6 | end 7 | -------------------------------------------------------------------------------- /spec/notes/javascripts_note_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require "rails-footnotes/notes/javascripts_note" 3 | 4 | describe Footnotes::Notes::JavascriptsNote do 5 | pending 6 | end 7 | -------------------------------------------------------------------------------- /spec/notes/stylesheets_note_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require "rails-footnotes/notes/stylesheets_note" 3 | 4 | describe Footnotes::Notes::StylesheetsNote do 5 | pending 6 | end 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in mcutter.gemspec 4 | gemspec 5 | 6 | # gem "rails", ">= 3.0.5" 7 | # 8 | if RUBY_PLATFORM =~ /darwin/ 9 | group :test do 10 | gem 'simplecov', '>= 0.4.0', :require => false 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/javascripts_note.rb: -------------------------------------------------------------------------------- 1 | require "rails-footnotes/notes/files_note" 2 | 3 | module Footnotes 4 | module Notes 5 | class JavascriptsNote < FilesNote 6 | def title 7 | "Javascripts (#{@files.length})" 8 | end 9 | 10 | protected 11 | def scan_text(text) 12 | text.scan(/]+src\s*=\s*['"]([^>?'"]+\.js)/im).flatten 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/stylesheets_note.rb: -------------------------------------------------------------------------------- 1 | require "rails-footnotes/notes/files_note" 2 | 3 | module Footnotes 4 | module Notes 5 | class StylesheetsNote < FilesNote 6 | def title 7 | "Stylesheets (#{@files.length})" 8 | end 9 | 10 | protected 11 | def scan_text(text) 12 | text.scan(/]+href\s*=\s*['"]([^>?'"]+\.css)/im).flatten 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/general_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class GeneralNote < AbstractNote 4 | def title 5 | 'General Debug' 6 | end 7 | 8 | def legend 9 | 'General (id="general_debug_info")' 10 | end 11 | 12 | def content 13 | 'You can use this tab to debug other parts of your application, for example Javascript.' 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | require 'rubygems' 5 | 6 | ENV['RAILS_ENV'] = 'test' 7 | 8 | require 'active_support' 9 | require 'active_support/all' unless Class.respond_to?(:cattr_accessor) 10 | require 'rails-footnotes/footnotes' 11 | require 'rails-footnotes/abstract_note' 12 | require "rails-footnotes" 13 | 14 | class Rails 15 | def self.logger; end 16 | end 17 | 18 | RSpec.configure do |config| 19 | end 20 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/params_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class ParamsNote < AbstractNote 4 | def initialize(controller) 5 | @params = controller.params.symbolize_keys 6 | end 7 | 8 | def title 9 | "Params (#{@params.length})" 10 | end 11 | 12 | def content 13 | mount_table_for_hash(@params, :summary => "Debug information for #{title}") 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /.watchr.example: -------------------------------------------------------------------------------- 1 | ROOT_PATH = File.dirname(__FILE__) 2 | 3 | def run_spec(file) 4 | system "rspec #{file}" if File.exist?(File.join(ROOT_PATH, file)) 5 | end 6 | 7 | watch('spec/.*_spec\.rb') {|m| run_spec m[0] } 8 | watch('spec/notes/.*_spec\.rb') {|m| run_spec m[0] } 9 | 10 | watch('lib/rails-footnotes/(.*)\.rb') {|m| run_spec("spec/#{m[1]}_spec.rb") } 11 | watch('lib/rails-footnotes/notes/.*_note\.rb') {|m| run_spec("spec/notes/#{m[1]}") } 12 | 13 | watch('Gemfile') {|m| run_spec("spec") } 14 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/cookies_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class CookiesNote < AbstractNote 4 | def initialize(controller) 5 | @cookies = controller.request.env["rack.request.cookie_hash"].nil? ? {} : controller.request.env["rack.request.cookie_hash"].dup 6 | end 7 | 8 | def title 9 | "Cookies (#{@cookies.length})" 10 | end 11 | 12 | def content 13 | mount_table_for_hash(@cookies, :summary => "Debug information for #{title}") 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | require "rspec/core/rake_task" 4 | require 'rake/rdoctask' 5 | 6 | desc 'Default: run tests' 7 | task :default => :spec 8 | 9 | desc 'Run tests for Footnotes.' 10 | RSpec::Core::RakeTask.new(:spec) do |t| 11 | t.pattern = 'spec/**/*_spec.rb' 12 | t.rcov = false 13 | end 14 | 15 | desc 'Generate documentation for Footnotes.' 16 | Rake::RDocTask.new(:rdoc) do |rdoc| 17 | rdoc.rdoc_dir = 'rdoc' 18 | rdoc.title = 'Footnotes' 19 | rdoc.options << '--line-numbers' << '--inline-source' 20 | rdoc.rdoc_files.include('README') 21 | rdoc.rdoc_files.include('MIT-LICENSE') 22 | rdoc.rdoc_files.include('lib/**/*.rb') 23 | end 24 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/layout_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class LayoutNote < AbstractNote 4 | def initialize(controller) 5 | @controller = controller 6 | end 7 | 8 | def row 9 | :edit 10 | end 11 | 12 | def link 13 | escape(Footnotes::Filter.prefix(filename, 1, 1)) 14 | end 15 | 16 | def valid? 17 | prefix? && nil#@controller.active_layout TODO doesn't work with Rails 3 18 | end 19 | 20 | protected 21 | def filename 22 | File.join(File.expand_path(Rails.root), 'app', 'layouts', "#{@controller.active_layout.to_s.underscore}").sub('/layouts/layouts/', '/views/layouts/') 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/session_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class SessionNote < AbstractNote 4 | def initialize(controller) 5 | session = controller.session 6 | if session 7 | if session.respond_to? :to_hash 8 | # rails >= 2.3 9 | session = session.to_hash 10 | else 11 | #rails < 2.3 12 | session = session.data 13 | end 14 | end 15 | @session = (session || {}).symbolize_keys 16 | end 17 | 18 | def title 19 | "Session (#{@session.length})" 20 | end 21 | 22 | def content 23 | mount_table_for_hash(@session, :summary => "Debug information for #{title}") 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/env_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class EnvNote < AbstractNote 4 | def initialize(controller) 5 | @env = controller.request.env.dup 6 | end 7 | 8 | def content 9 | env_data = @env.to_a.sort.unshift([:key, :value]).map do |k,v| 10 | case k 11 | when 'HTTP_COOKIE' 12 | # Replace HTTP_COOKIE for a link 13 | [k, 'See cookies on its tab'] 14 | else 15 | [k, escape(v.to_s)] 16 | end 17 | end 18 | 19 | # Create the env table 20 | mount_table(env_data) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/view_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class ViewNote < AbstractNote 4 | def initialize(controller) 5 | @controller = controller 6 | @template = controller.instance_variable_get(:@template) 7 | end 8 | 9 | def row 10 | :edit 11 | end 12 | 13 | def link 14 | escape(Footnotes::Filter.prefix(filename, 1, 1)) 15 | end 16 | 17 | def valid? 18 | prefix? && first_render? 19 | end 20 | 21 | protected 22 | 23 | def first_render? 24 | @template.instance_variable_get(:@_first_render) 25 | end 26 | 27 | def filename 28 | @filename ||= @template.instance_variable_get(:@_first_render).filename 29 | end 30 | 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/notes/assigns_note_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require 'action_controller' 3 | require "rails-footnotes/notes/assigns_note" 4 | 5 | class FootnotesController < ActionController::Base 6 | end 7 | 8 | describe Footnotes::Notes::AssignsNote do 9 | let(:note) do 10 | @controller = FootnotesController.new 11 | Footnotes::Notes::AssignsNote.new(@controller) 12 | end 13 | subject {note} 14 | 15 | before(:each) {Footnotes::Notes::AssignsNote.ignored_assigns = []} 16 | it {should be_valid} 17 | its(:title) {should eql 'Assigns (3)'} 18 | specify {note.send(:assigns).should eql [:@action_has_layout, :@view_context_class, :@_status] } 19 | 20 | describe "Ignored Assigns" do 21 | before(:each) {Footnotes::Notes::AssignsNote.ignored_assigns = [:@_status]} 22 | it {note.send(:assigns).should_not include :@_status} 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/files_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class FilesNote < AbstractNote 4 | def initialize(controller) 5 | @files = scan_text(controller.response.body) 6 | parse_files! 7 | end 8 | 9 | def row 10 | :edit 11 | end 12 | 13 | def content 14 | if @files.empty? 15 | "" 16 | else 17 | "" 18 | end 19 | end 20 | 21 | def valid? 22 | prefix? 23 | end 24 | 25 | protected 26 | def scan_text(text) 27 | [] 28 | end 29 | 30 | def parse_files! 31 | @files.collect! do |filename| 32 | if filename =~ %r{^/} 33 | full_filename = File.join(File.expand_path(Rails.root), 'public', filename) 34 | %[#{filename}] 35 | else 36 | %[#{filename}] 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/log_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class LogNote < AbstractNote 4 | @@log = [] 5 | 6 | def self.log(message) 7 | @@log << message 8 | end 9 | 10 | def initialize(controller) 11 | @controller = controller 12 | end 13 | 14 | def title 15 | "Log (#{log.count("\n")})" 16 | end 17 | 18 | def content 19 | escape(log.gsub(/\e\[.+?m/, '')).gsub("\n", '
') 20 | end 21 | 22 | def log 23 | unless @log 24 | @log = @@log.join('') 25 | @@log = [] 26 | if rindex = @log.rindex('Processing '+@controller.class.name+'#'+@controller.action_name) 27 | @log = @log[rindex..-1] 28 | end 29 | end 30 | @log 31 | end 32 | 33 | module LoggingExtensions 34 | def add(*args, &block) 35 | logged_message = super 36 | Footnotes::Notes::LogNote.log(logged_message) 37 | logged_message 38 | end 39 | end 40 | 41 | Rails.logger.extend LoggingExtensions 42 | end 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /lib/rails-footnotes/backtracer.rb: -------------------------------------------------------------------------------- 1 | require 'rails/backtrace_cleaner' 2 | class Rails::BacktraceCleaner 3 | def replace_filter(index, &block) 4 | @filters[index] = block 5 | end 6 | 7 | def clean(backtrace, kind = :silent) 8 | safe = super.map {|l| l.html_safe} 9 | def safe.join(*args) 10 | (joined = super).html_safe 11 | end 12 | safe 13 | end 14 | end 15 | 16 | backtrace_cleaner = Rails.backtrace_cleaner 17 | backtrace_cleaner.replace_filter(0) do |line| 18 | if match = line.match(/^(.+):(\d+):in/) || match = line.match(/^(.+):(\d+)\s*$/) 19 | file, line_number = match[1], match[2] 20 | %[#{line.sub("#{Rails.root.to_s}/",'').html_safe}].html_safe 21 | else 22 | line 23 | end 24 | end 25 | 26 | gems_paths = (Gem.path + [Gem.default_dir]).uniq.map!{ |p| Regexp.escape(p) } 27 | unless gems_paths.empty? 28 | gems_regexp = %r{\>#{gems_paths.join('|')}/gems/} 29 | backtrace_cleaner.replace_filter(3) {|line| line.sub(gems_regexp, '>') } 30 | end 31 | backtrace_cleaner.remove_silencers! 32 | backtrace_cleaner.add_silencer { |line| line !~ /\>\/?(app|config|lib|test)/ } 33 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 Coda Hale 2 | Copyright (c) 2008 José Valim (jose.valim at gmail dot com) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/rails-footnotes.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | mattr_accessor :before_hooks 3 | @@before_hooks = [] 4 | 5 | mattr_accessor :after_hooks 6 | @@after_hooks = [] 7 | 8 | def self.before(&block) 9 | @@before_hooks << block 10 | end 11 | 12 | def self.after(&block) 13 | @@after_hooks << block 14 | end 15 | 16 | # The footnotes are applied by default to all actions. You can change this 17 | # behavior commenting the after_filter line below and putting it in Your 18 | # application. Then you can cherrypick in which actions it will appear. 19 | # 20 | module RailsFootnotesExtension 21 | def self.included(base) 22 | base.prepend_before_filter Footnotes::BeforeFilter 23 | base.after_filter Footnotes::AfterFilter 24 | end 25 | end 26 | 27 | def self.run! 28 | require 'rails-footnotes/footnotes' 29 | require 'rails-footnotes/backtracer' 30 | require 'rails-footnotes/abstract_note' 31 | require 'rails-footnotes/notes/all' 32 | 33 | ActionController::Base.send(:include, RailsFootnotesExtension) 34 | 35 | load Rails.root.join('.footnotes') if Rails.root.join('.footnotes').exist? 36 | end 37 | 38 | def self.setup 39 | yield self 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /rails-footnotes.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "rails-footnotes/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "rails-footnotes" 7 | s.version = Footnotes::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Keenan Brock"] 10 | s.email = ["keenan@thebrocks.net"] 11 | s.homepage = "http://github.com/josevalim/rails-footnotes" 12 | s.summary = %q{Every Rails page has footnotes that gives information about your application and links back to your editor.} 13 | s.description = %q{Every Rails page has footnotes that gives information about your application and links back to your editor.} 14 | 15 | s.rubyforge_project = "rails-footnotes" 16 | 17 | s.add_dependency "rails", ">= 3.0.0" 18 | 19 | s.add_development_dependency "rails", ">= 3.0.0" 20 | s.add_development_dependency "rspec" 21 | s.add_development_dependency "watchr" 22 | 23 | s.files = `git ls-files`.split("\n") 24 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 25 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 26 | s.require_paths = ["lib"] 27 | end 28 | 29 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/controller_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class ControllerNote < AbstractNote 4 | def initialize(controller) 5 | @controller = controller 6 | end 7 | 8 | def row 9 | :edit 10 | end 11 | 12 | def link 13 | Footnotes::Filter.prefix(controller_filename, controller_line_number + 1, 3) 14 | end 15 | 16 | def valid? 17 | prefix? && File.exists?(self.controller_filename) 18 | end 19 | 20 | protected 21 | def controller_path 22 | @controller_path = @controller.class.name.underscore 23 | end 24 | 25 | def controller_filename 26 | @controller_filename ||= Gem.find_files(self.controller_path).first # tnx https://github.com/MasterLambaster 27 | end 28 | 29 | def controller_text 30 | @controller_text ||= IO.read(controller_filename) 31 | end 32 | 33 | def action_index 34 | (controller_text =~ /def\s+#{@controller.action_name}[\s\(]/) 35 | end 36 | 37 | def controller_line_number 38 | lines_from_index(controller_text, action_index) || 0 39 | end 40 | 41 | def lines_from_index(string, index) 42 | return nil if string.blank? || index.blank? 43 | 44 | lines = string.respond_to?(:to_a) ? string.to_a : string.lines.to_a 45 | running_length = 0 46 | lines.each_with_index do |line, i| 47 | running_length += line.length 48 | if running_length > index 49 | return i 50 | end 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/filters_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class FiltersNote < AbstractNote 4 | def initialize(controller) 5 | @controller = controller 6 | @parsed_filters = parse_filters 7 | end 8 | 9 | def legend 10 | "Filter chain for #{@controller.class.to_s}" 11 | end 12 | 13 | def content 14 | mount_table(@parsed_filters.unshift([:name, :type, :actions]), :summary => "Debug information for #{title}") 15 | end 16 | 17 | protected 18 | # Get controller filter chain 19 | # 20 | def parse_filters 21 | return [] # TODO @controller.class.filter_chain.collect do |filter| 22 | # [parse_method(filter.method), filter.type.inspect, controller_filtered_actions(filter).inspect] 23 | # end 24 | end 25 | 26 | # This receives a filter, creates a mock controller and check in which 27 | # actions the filter is performed 28 | # 29 | def controller_filtered_actions(filter) 30 | mock_controller = Footnotes::Extensions::MockController.new 31 | 32 | return @controller.class.action_methods.select { |action| 33 | mock_controller.action_name = action 34 | 35 | #remove conditions (this would call a Proc on the mock_controller) 36 | filter.options.merge!(:if => nil, :unless => nil) 37 | 38 | filter.__send__(:should_run_callback?, mock_controller) 39 | }.map(&:to_sym) 40 | end 41 | 42 | def parse_method(method = '') 43 | escape(method.inspect.gsub(RAILS_ROOT, '')) 44 | end 45 | end 46 | end 47 | 48 | module Extensions 49 | class MockController < Struct.new(:action_name); end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/assigns_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class AssignsNote < AbstractNote 4 | @@ignored_assigns = [ 5 | :@real_format, 6 | :@before_filter_chain_aborted, 7 | :@performed_redirect, 8 | :@performed_render, 9 | :@_params, 10 | :@_response, 11 | :@url, 12 | :@template, 13 | :@_request, 14 | :@db_rt_before_render, 15 | :@db_rt_after_render, 16 | :@view_runtime 17 | ] 18 | cattr_accessor :ignored_assigns, :instance_writter => false 19 | 20 | def initialize(controller) 21 | @controller = controller 22 | end 23 | 24 | def title 25 | "Assigns (#{assigns.size})" 26 | end 27 | 28 | def valid? 29 | assigns 30 | end 31 | 32 | def content 33 | rows = [] 34 | assigns.each do |key| 35 | rows << [ key, escape(assigned_value(key)) ] 36 | end 37 | mount_table(rows.unshift(['Name', 'Value']), :class => 'name_values', :summary => "Debug information for #{title}") 38 | end 39 | 40 | protected 41 | 42 | def assigns 43 | assign = [] 44 | ignored = @@ignored_assigns 45 | 46 | @controller.instance_variables.each {|x| assign << x.intern } 47 | @controller.protected_instance_variables.each {|x| ignored << x.intern } if @controller.respond_to? :protected_instance_variables 48 | 49 | assign -= ignored 50 | return assign 51 | end 52 | 53 | def assigned_value(key) 54 | @controller.instance_variable_get(key).inspect 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/partials_note.rb: -------------------------------------------------------------------------------- 1 | require 'rails-footnotes/notes/log_note' 2 | 3 | module Footnotes 4 | module Notes 5 | class PartialsNote < LogNote 6 | def initialize(controller) 7 | super 8 | @controller = controller 9 | end 10 | def row 11 | :edit 12 | end 13 | def title 14 | "Partials (#{partials.size})" 15 | end 16 | def content 17 | rows = partials.map do |filename| 18 | href = Footnotes::Filter.prefix(filename,1,1) 19 | shortened_name=filename.gsub(File.join(Rails.root,"app/views/"),"") 20 | [%{#{shortened_name}},"#{@partial_times[filename].sum}ms",@partial_counts[filename]] 21 | end 22 | mount_table(rows.unshift(%w(Partial Time Count)), :summary => "Partials for #{title}") 23 | end 24 | 25 | 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.to_s, "#{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 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/routes_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class RoutesNote < AbstractNote 4 | def initialize(controller) 5 | @controller = controller 6 | @parsed_routes = parse_routes 7 | end 8 | 9 | def legend 10 | "Routes for #{@controller.class.to_s}" 11 | end 12 | 13 | def content 14 | mount_table(@parsed_routes.unshift([:path, :name, :options, :requirements]), :summary => "Debug information for #{title}") 15 | end 16 | 17 | protected 18 | def parse_routes 19 | routes_with_name = Rails.application.routes.named_routes.to_a.flatten 20 | 21 | return Rails.application.routes.filtered_routes(:controller => @controller.controller_path).collect do |route| 22 | # Catch routes name if exists 23 | i = routes_with_name.index(route) 24 | name = i ? routes_with_name[i-1].to_s : '' 25 | 26 | # Catch segments requirements 27 | req = {} 28 | if Rails.version < '3.0' 29 | route.segments.each do |segment| 30 | next unless segment.is_a?(ActionController::Routing::DynamicSegment) && segment.regexp 31 | req[segment.key.to_sym] = segment.regexp 32 | end 33 | [escape(name), route.segments.join, route.requirements.reject{|key,value| key == :controller}.inspect, req.inspect] 34 | else 35 | req = route.conditions 36 | [escape(name), route.conditions.keys.join, route.requirements.reject{|key,value| key == :controller}.inspect, req.inspect] 37 | end 38 | end 39 | end 40 | end 41 | end 42 | 43 | module Extensions 44 | module Routes 45 | # Filter routes according to the filter sent 46 | # 47 | def filtered_routes(filter = {}) 48 | return [] unless filter.is_a?(Hash) 49 | return routes.reject do |r| 50 | filter_diff = filter.diff(r.requirements) 51 | route_diff = r.requirements.diff(filter) 52 | (filter_diff == filter) || (filter_diff != route_diff) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | 59 | if Footnotes::Notes::RoutesNote.included? 60 | ActionController::Routing::RouteSet.send :include, Footnotes::Extensions::Routes 61 | end 62 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rails-footnotes (3.7.2) 5 | rails (>= 3.0.0) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | abstract (1.0.0) 11 | actionmailer (3.0.5) 12 | actionpack (= 3.0.5) 13 | mail (~> 2.2.15) 14 | actionpack (3.0.5) 15 | activemodel (= 3.0.5) 16 | activesupport (= 3.0.5) 17 | builder (~> 2.1.2) 18 | erubis (~> 2.6.6) 19 | i18n (~> 0.4) 20 | rack (~> 1.2.1) 21 | rack-mount (~> 0.6.13) 22 | rack-test (~> 0.5.7) 23 | tzinfo (~> 0.3.23) 24 | activemodel (3.0.5) 25 | activesupport (= 3.0.5) 26 | builder (~> 2.1.2) 27 | i18n (~> 0.4) 28 | activerecord (3.0.5) 29 | activemodel (= 3.0.5) 30 | activesupport (= 3.0.5) 31 | arel (~> 2.0.2) 32 | tzinfo (~> 0.3.23) 33 | activeresource (3.0.5) 34 | activemodel (= 3.0.5) 35 | activesupport (= 3.0.5) 36 | activesupport (3.0.5) 37 | arel (2.0.9) 38 | builder (2.1.2) 39 | diff-lcs (1.1.2) 40 | erubis (2.6.6) 41 | abstract (>= 1.0.0) 42 | i18n (0.5.0) 43 | mail (2.2.15) 44 | activesupport (>= 2.3.6) 45 | i18n (>= 0.4.0) 46 | mime-types (~> 1.16) 47 | treetop (~> 1.4.8) 48 | mime-types (1.16) 49 | polyglot (0.3.1) 50 | rack (1.2.2) 51 | rack-mount (0.6.13) 52 | rack (>= 1.0.0) 53 | rack-test (0.5.7) 54 | rack (>= 1.0) 55 | rails (3.0.5) 56 | actionmailer (= 3.0.5) 57 | actionpack (= 3.0.5) 58 | activerecord (= 3.0.5) 59 | activeresource (= 3.0.5) 60 | activesupport (= 3.0.5) 61 | bundler (~> 1.0) 62 | railties (= 3.0.5) 63 | railties (3.0.5) 64 | actionpack (= 3.0.5) 65 | activesupport (= 3.0.5) 66 | rake (>= 0.8.7) 67 | thor (~> 0.14.4) 68 | rake (0.8.7) 69 | rspec (2.5.0) 70 | rspec-core (~> 2.5.0) 71 | rspec-expectations (~> 2.5.0) 72 | rspec-mocks (~> 2.5.0) 73 | rspec-core (2.5.2) 74 | rspec-expectations (2.5.0) 75 | diff-lcs (~> 1.1.2) 76 | rspec-mocks (2.5.0) 77 | simplecov (0.4.2) 78 | simplecov-html (~> 0.4.4) 79 | simplecov-html (0.4.5) 80 | thor (0.14.6) 81 | treetop (1.4.9) 82 | polyglot (>= 0.3.1) 83 | tzinfo (0.3.25) 84 | watchr (0.7) 85 | 86 | PLATFORMS 87 | ruby 88 | 89 | DEPENDENCIES 90 | rails (>= 3.0.0) 91 | rails-footnotes! 92 | rspec 93 | simplecov (>= 0.4.0) 94 | watchr 95 | -------------------------------------------------------------------------------- /spec/abstract_note_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Footnotes::Notes::AbstractNote do 4 | before do 5 | @note = Footnotes::Notes::AbstractNote.new 6 | Footnotes::Filter.notes = [:abstract] 7 | end 8 | 9 | it {described_class.should respond_to :start!} 10 | it {described_class.should respond_to :close!} 11 | it {described_class.should respond_to :title} 12 | 13 | it {should respond_to :to_sym} 14 | its(:to_sym) {should eql :abstract} 15 | 16 | it { described_class.should be_included } 17 | specify do 18 | Footnotes::Filter.notes = [] 19 | described_class.should_not be_included 20 | end 21 | 22 | it { should respond_to :row } 23 | it { should respond_to :legend } 24 | it { should respond_to :link } 25 | it { should respond_to :onclick } 26 | it { should respond_to :stylesheet } 27 | it { should respond_to :javascript } 28 | 29 | it { should respond_to :valid? } 30 | it { should be_valid } 31 | 32 | it { should respond_to :has_fieldset? } 33 | it { should_not have_fieldset } 34 | 35 | specify { Footnotes::Filter.prefix = ''; should_not be_prefix } 36 | specify do 37 | Footnotes::Filter.prefix = 'txmt://open?url=file://%s&line=%d&column=%d' 38 | should be_prefix 39 | end 40 | 41 | #TODO should be moved to builder 42 | #helpers 43 | specify { subject.escape('<').should eql '<' } 44 | specify { subject.escape('&').should eql '&' } 45 | specify { subject.escape('>').should eql '>' } 46 | 47 | specify { subject.mount_table([]).should be_blank } 48 | specify { subject.mount_table([['h1', 'h2', 'h3']], :class => 'table').should be_blank } 49 | 50 | specify { 51 | tab = <<-TABLE 52 | 53 | 54 | 55 |
H1
r1c1
56 | TABLE 57 | 58 | subject.mount_table([['h1'],['r1c1']], :class => 'table').should eql tab 59 | } 60 | 61 | specify { 62 | tab = <<-TABLE 63 | 64 | 65 | 66 |
H1H2H3
r1c1r1c2r1c3
67 | TABLE 68 | subject.mount_table([['h1', 'h2', 'h3'],['r1c1', 'r1c2', 'r1c3']]).should eql tab 69 | } 70 | 71 | specify { 72 | tab = <<-TABLE 73 | 74 | 75 | 76 |
H1H2H3
r1c1r1c2r1c3
r2c1r2c2r2c3
77 | TABLE 78 | subject.mount_table([['h1', 'h2', 'h3'], ['r1c1', 'r1c2', 'r1c3'], ['r2c1', 'r2c2', 'r2c3']]) 79 | } 80 | end 81 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == Footnotes v3.7.0 2 | * Migrate to Bundler 3 | * Support only Rails3 4 | 5 | == Footnotes v3.6.7 6 | * fixed log_note error - long overdue (thanks to many including Moritz Heidkamp) 7 | * 1.9: ignore more assigns (thanks to justin case) 8 | * 1.9 fixed test mocking (thanks to lsylvester) 9 | * sort notes includes (thanks Alexey Smolianiov) 10 | * handle controller paths in gems / others (thanks ubilabs) 11 | * more graceful fallback when controller not found 12 | * fixes for table entries being double encoded 13 | * moved rpm exclude sql explain logic to config option 14 | 15 | == Footnotes v3.6.6 16 | * fix for ruby 1.9 compat (thanks to ivanoats) 17 | * fix for log note (thanks to tobias) 18 | * pre rails 2.3 support fixes (thanks to tobias) 19 | * better disabling of query notes (thanks to tobias) 20 | * fixed variable assignment escaping (thanks to gdelvino) 21 | * fixed cookie value escaping (thanks to indirect) 22 | * Turn off footnotes with a parameter footnotes=false (thanks to indirect) 23 | 24 | == Footnotes v3.6 25 | * Cookies, sessions and params notes now use table; 26 | * Old components note removed; 27 | * Added assigns notes (thanks to scorpio); 28 | * Improve query note, count query time and display it with several configurable 29 | options (:alert_db_time and :alert_sql_number) (thanks to scorpio); 30 | * Fixed bugs of layout link and route note related (thanks to scorpio). 31 | 32 | == Footnotes v3.5 33 | * Added NewRelic RPM footnote (thanks to kbrock); 34 | * Several bug fixes (thanks to kbrock, ivanoats and kristopher). 35 | 36 | == Footnotes v3.4 37 | * Rails 2.3 compatible. 38 | 39 | == Footnotes v3.3.1 40 | * Changed prefix to support %s and %d; 41 | * Created gemspec; 42 | * Some code refactoring (called eval just once instead of three times). 43 | 44 | == Footnotes v3.3 45 | * Rails Edge (aka 2.2) compatibility; 46 | * Initial Ruby 1.9 compatibility. 47 | 48 | == Footnotes v3.2.2 49 | * Added trace to QueriesNote; 50 | * Fixed incompatibility with Ultrasphinx (thanks to mhartl); 51 | * Added W3C compatibility (thanks to tapajos); 52 | * Added support to other log mechanisms in LogNote. 53 | 54 | == Footnotes v3.2.1 55 | * Added some tests; 56 | * Redefined Footnotes CSS and Javascripts to use concise names. 57 | 58 | == Footnotes v3.2 59 | * Now you can easily add your own notes; 60 | * Added numbers to tabs; 61 | * Added Queries note; 62 | * Added MySQL Query Analyzer. 63 | 64 | == Footnotes v3.1 65 | * Code refactoring (using modules, except backtracer); 66 | * Ability to cherry pick notes; 67 | * Working on Rails 2.1. 68 | 69 | == Footnotes v3.0 70 | * Some code refactoring; 71 | * Stylesheets bug fixed: was showing not only css in Stylesheets div; 72 | * Logtail fix: working with Rails 2.0 logger and in all OS, since it's Regexp based; 73 | * Rails 1.2 (except backtrace) and 2.0 compatible; 74 | * RoutingNavigator (based on Rick Olson plugin); 75 | * FilterChain. 76 | 77 | == Textmate Footnotes v2.0 78 | Copyright (c) 2006 InquiryLabs, Inc. 79 | http://inquirylabs.com/ 80 | 81 | Description: 82 | Creates clickable footnotes on each rendered page, as well as clickable 83 | links in the backtrace should an error occur in your Rails app. Links take 84 | you to the right place inside TextMate. 85 | Enable only the TextMate on Macs in development mode 86 | -------------------------------------------------------------------------------- /lib/rails-footnotes/notes/queries_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | class QueriesNote < AbstractNote 4 | cattr_accessor :alert_db_time, :alert_sql_number, :orm, :ignored_regexps, :instance_writter => false 5 | @@alert_db_time = 16.0 6 | @@alert_sql_number = 8 7 | @@query_subscriber = nil 8 | @@orm = [:active_record, :data_mapper] 9 | @@ignored_regexps = [%r{(pg_table|pg_attribute|show\stables)}i] 10 | 11 | def self.start!(controller) 12 | self.query_subscriber.reset! 13 | end 14 | 15 | def self.query_subscriber 16 | @@query_subscriber ||= Footnotes::Notes::QuerySubscriber.new(self.orm) 17 | end 18 | 19 | def events 20 | self.class.query_subscriber.events 21 | end 22 | 23 | def title 24 | queries = self.events.count 25 | total_time = self.events.map(&:duration).sum / 1000.0 26 | query_color = generate_red_color(self.events.count, alert_sql_number) 27 | db_color = generate_red_color(total_time, alert_db_time) 28 | 29 | <<-TITLE 30 | Queries (#{queries}) 31 | DB (#{"%.3f" % total_time}ms) 32 | TITLE 33 | end 34 | 35 | def content 36 | html = '' 37 | self.events.each_with_index do |event, index| 38 | sql_links = [] 39 | sql_links << "trace" 40 | 41 | html << <<-HTML 42 | #{escape(event.type.to_s.upcase)} (#{sql_links.join(' | ')})
43 | #{print_query(event.payload[:sql])}
44 | #{print_name_and_time(event.payload[:name], event.duration / 1000.0)}  45 |
46 | HTML 47 | end 48 | return html 49 | end 50 | 51 | protected 52 | def print_name_and_time(name, time) 53 | "#{escape(name || 'SQL')} (#{'%.3fms' % time})" 54 | end 55 | 56 | def print_query(query) 57 | escape(query.to_s.gsub(/(\s)+/, ' ').gsub('`', '')) 58 | end 59 | 60 | def generate_red_color(value, alert) 61 | c = ((value.to_f/alert).to_i - 1) * 16 62 | c = 0 if c < 0; c = 15 if c > 15; c = (15-c).to_s(16) 63 | "#ff#{c*4}" 64 | end 65 | 66 | def alert_ratio 67 | alert_db_time / alert_sql_number 68 | end 69 | 70 | def parse_trace(trace) 71 | trace.map do |t| 72 | s = t.split(':') 73 | %[#{escape(t)}
] 74 | end.join 75 | end 76 | end 77 | 78 | class QuerySubscriberNotifactionEvent 79 | attr_reader :event, :trace, :query 80 | delegate :name, :payload, :duration, :time, :type, :to => :event 81 | 82 | def initialize(event, ctrace) 83 | @event, @ctrace, @query = event, ctrace, event.payload[:sql] 84 | end 85 | 86 | def trace 87 | @trace ||= @ctrace.collect(&:strip).select{|i| i.gsub!(/^#{Rails.root.to_s}\//, '') } || [] 88 | end 89 | 90 | def type 91 | @type ||= self.query.match(/^(\s*)(select|insert|update|delete|alter)\b/im) || 'Unknown' 92 | end 93 | end 94 | 95 | class QuerySubscriber < ActiveSupport::LogSubscriber 96 | attr_accessor :events, :ignore_regexps 97 | 98 | def initialize(orm) 99 | @events = [] 100 | orm.each {|adapter| ActiveSupport::LogSubscriber.attach_to adapter, self} 101 | end 102 | 103 | def reset! 104 | self.events.clear 105 | end 106 | 107 | def sql(event) 108 | unless QueriesNote.ignored_regexps.any? {|rex| event.payload[:sql] =~ rex } 109 | @events << QuerySubscriberNotifactionEvent.new(event.dup, caller) 110 | end 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = Rails Footnotes 2 | 3 | If you are developing in Rails you should know the plugin! It displays 4 | footnotes in your application for easy debugging, such as sessions, 5 | request parameters, cookies, filter chain, routes, queries, etc. 6 | 7 | Even more, it contains links to open files directly in your editor including 8 | your backtrace lines. 9 | 10 | == Installation 11 | 12 | NOTE: Since this branch aims Rails 3 support, if you want to use it with Rails 2.3 you should check this branch: 13 | 14 | https://github.com/josevalim/rails-footnotes/tree/rails2 15 | 16 | Install Rails Footnotes is very easy. 17 | 18 | === Rails 3.x 19 | 20 | gem 'rails-footnotes', '>= 3.7', :group => :development 21 | 22 | === Rails 2.x 23 | 24 | In RAILS_ROOT/config/environments/development.rb (yes, you want it only in development): 25 | 26 | gem "rails-footnotes", '< 3.7.0', :group => :development 27 | 28 | == Configuration 29 | 30 | For version greater then 3.7.0 31 | 32 | If you want to add alternate logic to enable or disable footnotes, 33 | add something like this to config/initializers/footnotes.rb: 34 | 35 | if defined?(Footnotes) && Rails.env.development? 36 | Footnotes.run! # first of all 37 | 38 | # ... other init code 39 | end 40 | 41 | === Post initialization 42 | 43 | If you want to add alternate logic to config footnotes without commit it to SCM, add your code to .footnotes: 44 | 45 | Footnotes::Filter.notes = [] 46 | 47 | this code temporarily disables notes for all controllers 48 | 49 | === Hooks 50 | 51 | Footnotes.setup do |config| 52 | config.before {|controller, filter| filter.notes = controller.class.name =~ /Message/ && \ 53 | controller.action_name == 'index' ? [:assigns] : []} 54 | config.before {|controller, filter| filter.notes |= [:params] if controller.class.name =~ /Profile/ && \ 55 | controller.action_name == 'edit' } 56 | end 57 | 58 | If you are not using Textmate as text editor, in your environment.rb or 59 | in an initializer do: 60 | 61 | Footnotes::Filter.prefix = 'mvim://open?url=file://%s&line=%d&column=%d' 62 | 63 | for MacVim 64 | 65 | Where you are going to choose a prefix compatible with your text editor. The %s is 66 | replaced by the name of the file, the first %d is replaced by the line number and 67 | the second %d is replaced by the column. 68 | 69 | By default, footnotes are appended at the end of the page with default stylesheet. If you want 70 | to change their position, you can define a div with id "footnotes_holder" or define your own stylesheet 71 | by turning footnotes stylesheet off: 72 | 73 | Footnotes::Filter.no_style = true 74 | 75 | Another option is to allow multiple notes to be opened at the same time: 76 | 77 | Footnotes::Filter.multiple_notes = true 78 | 79 | Finally, you can control which notes you want to show. The default are: 80 | 81 | Footnotes::Filter.notes = [:session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general] 82 | 83 | == Creating your own notes 84 | 85 | Create your notes to integrate with Footnotes is easy. 86 | 87 | 1. Create a Footnotes::Notes::YourExampleNote class 88 | 2. Implement the necessary methods (check abstract_note.rb file in lib/notes) 89 | 3. Append your example note in Footnotes::Filter.notes array (usually at the end of your environment file or in an initializer): 90 | 91 | For example, to create a note that shows info about the user logged in your application you just have to do: 92 | 93 | module Footnotes 94 | module Notes 95 | class CurrentUserNote < AbstractNote 96 | # This method always receives a controller 97 | # 98 | def initialize(controller) 99 | @current_user = controller.instance_variable_get("@current_user") 100 | end 101 | 102 | # Returns the title that represents this note. 103 | # 104 | def title 105 | "Current user: #{@current_user.name}" 106 | end 107 | 108 | # This Note is only valid if we actually found an user 109 | # If it's not valid, it won't be displayed 110 | # 111 | def valid? 112 | @current_user 113 | end 114 | 115 | # The fieldset content 116 | # 117 | def content 118 | escape(@current_user.inspect) 119 | end 120 | end 121 | end 122 | end 123 | 124 | Then put in your environment: 125 | 126 | Footnotes::Filter.notes += [:current_user] 127 | 128 | == Colaborators 129 | 130 | * Leon Li - http://github.com/scorpio 131 | * Keenan Brock - http://github.com/kbrock 132 | * Ivan Storck - http://github.com/ivanoats 133 | * Kris Chamber - http://github.com/kristopher 134 | 135 | == Bugs and Feedback 136 | 137 | If you discover any bugs, please send an e-mail to keenan@thebrocks.net 138 | If you just want to give some positive feedback or drop a line, that's fine too! 139 | 140 | === Version 3.x 141 | 142 | This plugin was maintained until version 3.6 by José Valim 143 | 144 | Copyright (c) 2009 José Valim (jose@plataformatec.com.br) 145 | http://blog.plataformatec.com.br 146 | 147 | === Version 2.0 148 | 149 | This plugin was created and maintained until version 2.0 by Duane Johnson: 150 | 151 | Copyright (c) 2006 InquiryLabs, Inc. 152 | http://blog.inquirylabs.com 153 | -------------------------------------------------------------------------------- /lib/rails-footnotes/abstract_note.rb: -------------------------------------------------------------------------------- 1 | module Footnotes 2 | module Notes 3 | # This is the abstract class for notes. 4 | # You can overwrite all instance public methods to create your notes. 5 | # 6 | class AbstractNote 7 | 8 | # Class methods. Do NOT overwrite them. 9 | # 10 | class << self 11 | # Returns the symbol that represents this note. 12 | # It's the name of the class, underscored and without _note. 13 | # 14 | # For example, for ControllerNote it will return :controller. 15 | # 16 | def to_sym 17 | @note_sym ||= self.title.underscore.to_sym 18 | end 19 | 20 | # Returns the title that represents this note. 21 | # It's the name of the class without Note. 22 | # 23 | # For example, for ControllerNote it will return Controller. 24 | # 25 | def title 26 | @note_title ||= self.name.match(/^Footnotes::Notes::(\w+)Note$/)[1] 27 | end 28 | 29 | # Return true if Note is included in notes array. 30 | # 31 | def included? 32 | Footnotes::Filter.notes.include?(self.to_sym) 33 | end 34 | 35 | # Action to be called to start the Note. 36 | # This is applied as a before_filter. 37 | # 38 | def start!(controller = nil) 39 | end 40 | 41 | # Action to be called after the Note was used. 42 | # This is applied as an after_filter. 43 | # 44 | def close!(controller = nil) 45 | end 46 | end 47 | 48 | # Initialize notes. 49 | # Always receives a controller. 50 | # 51 | def initialize(controller = nil) 52 | end 53 | 54 | # Returns the symbol that represents this note. 55 | # 56 | def to_sym 57 | self.class.to_sym 58 | end 59 | 60 | # Specifies in which row should appear the title. 61 | # The default is :show. 62 | # 63 | def row 64 | :show 65 | end 66 | 67 | # Returns the title to be used as link. 68 | # The default is the note title. 69 | # 70 | def title 71 | self.class.title 72 | end 73 | 74 | # If has_fieldset? is true, create a fieldset with the value returned as legend. 75 | # By default, returns the title of the class (defined above). 76 | # 77 | def legend 78 | self.class.title 79 | end 80 | 81 | # If content is defined, has_fieldset? returns true and the value of content 82 | # is displayed when the Note is clicked. See has_fieldset? below for more info. 83 | # 84 | # def content 85 | # end 86 | 87 | # Set href field for Footnotes links. 88 | # If it's nil, Footnotes will use '#'. 89 | # 90 | def link 91 | end 92 | 93 | # Set onclick field for Footnotes links. 94 | # If it's nil, Footnotes will make it open the fieldset. 95 | # 96 | def onclick 97 | end 98 | 99 | # Insert here any additional stylesheet. 100 | # This is directly inserted into a 196 | 197 | HTML 198 | end 199 | 200 | def insert_footnotes 201 | # Fieldsets method should be called first 202 | content = fieldsets 203 | 204 | footnotes_html = <<-HTML 205 | 206 |
207 |
208 | #{links} 209 | #{content} 210 | 251 |
252 | 253 | HTML 254 | 255 | placeholder = /]+id=['"]footnotes_holder['"][^>]*>/i 256 | if @controller.response.body =~ placeholder 257 | insert_text :after, placeholder, footnotes_html 258 | else 259 | insert_text :before, /<\/body>/i, footnotes_html 260 | end 261 | end 262 | 263 | # Process notes to gets their links in their equivalent row 264 | # 265 | def links 266 | links = Hash.new([]) 267 | order = [] 268 | each_with_rescue(@notes) do |note| 269 | order << note.row 270 | links[note.row] += [link_helper(note)] 271 | end 272 | 273 | html = '' 274 | order.uniq! 275 | order.each do |row| 276 | html << "#{row.is_a?(String) ? row : row.to_s.camelize}: #{links[row].join(" | \n")}
" 277 | end 278 | html 279 | end 280 | 281 | # Process notes to get their content 282 | # 283 | def fieldsets 284 | content = '' 285 | each_with_rescue(@notes) do |note| 286 | next unless note.has_fieldset? 287 | content << <<-HTML 288 | 292 | HTML 293 | end 294 | content 295 | end 296 | 297 | # Process notes to get javascript code to close them. 298 | # This method is only used when multiple_notes is false. 299 | # 300 | def close 301 | javascript = '' 302 | each_with_rescue(@notes) do |note| 303 | next unless note.has_fieldset? 304 | javascript << close_helper(note) 305 | end 306 | javascript 307 | end 308 | 309 | # 310 | # Helpers 311 | # 312 | 313 | # Helper that creates the javascript code to close the note 314 | # 315 | def close_helper(note) 316 | "Footnotes.hide(document.getElementById('#{note.to_sym}_debug_info'));\n" 317 | end 318 | 319 | # Helper that creates the link and javascript code when note is clicked 320 | # 321 | def link_helper(note) 322 | onclick = note.onclick 323 | unless href = note.link 324 | href = '#' 325 | onclick ||= "Footnotes.hideAllAndToggle('#{note.to_sym}_debug_info');return false;" if note.has_fieldset? 326 | end 327 | 328 | "#{note.title}" 329 | end 330 | 331 | # Inserts text in to the body of the document 332 | # +pattern+ is a Regular expression which, when matched, will cause +new_text+ 333 | # to be inserted before or after the match. If no match is found, +new_text+ is appended 334 | # to the body instead. +position+ may be either :before or :after 335 | # 336 | def insert_text(position, pattern, new_text) 337 | index = case pattern 338 | when Regexp 339 | if match = @controller.response.body.match(pattern) 340 | match.offset(0)[position == :before ? 0 : 1] 341 | else 342 | @controller.response.body.size 343 | end 344 | else 345 | pattern 346 | end 347 | newbody = @controller.response.body 348 | newbody.insert index, new_text 349 | @controller.response.body = newbody 350 | end 351 | 352 | # Instance each_with_rescue method 353 | # 354 | def each_with_rescue(*args, &block) 355 | self.class.each_with_rescue(*args, &block) 356 | end 357 | 358 | end 359 | end 360 | --------------------------------------------------------------------------------