module Footnotes class BeforeFilter # Method called to start the notes # It's a before filter prepend in the controller def self.filter(controller) Footnotes::Filter.start!(controller) end end class AfterFilter # Method that calls Footnotes to attach its contents def self.filter(controller) filter = Footnotes::Filter.new(controller) filter.add_footnotes! filter.close!(controller) end end class Filter @@no_style = false @@multiple_notes = false @@klasses = [] # Default link prefix is textmate @@prefix = 'txmt://open?url=file://%s&line=%d&column=%d' # Edit notes @@notes = [ :controller, :view, :layout, :partials, :stylesheets, :javascripts ] # Show notes @@notes += [ :assigns, :session, :cookies, :params, :filters, :routes, :env, :queries, :log, :general ] # Change queries for rpm note when available # if defined?(NewRelic) # @@notes.delete(:queries) # @@notes << :rpm # end # :no_style => If you don't want the style to be appended to your pages # :notes => Class variable that holds the notes to be processed # :prefix => Prefix appended to FootnotesLinks # :multiple_notes => Set to true if you want to open several notes at the same time cattr_accessor :no_style, :notes, :prefix, :multiple_notes class << self # Calls the class method start! in each note # Sometimes notes need to set variables or clean the environment to work properly # This method allows this kind of setup # def start!(controller) self.each_with_rescue(Footnotes.before_hooks) {|hook| hook.call(controller, self)} @@klasses = [] self.each_with_rescue(@@notes.flatten) do |note| klass = "Footnotes::Notes::#{note.to_s.camelize}Note".constantize klass.start!(controller) if klass.respond_to?(:start!) @@klasses << klass end end # Process notes, discarding only the note if any problem occurs # def each_with_rescue(collection) delete_me = [] collection.each do |item| begin yield item rescue Exception => e # Discard item if it has a problem log_error("Footnotes #{item.to_s.camelize} Exception", e) delete_me << item next end end delete_me.each { |item| collection.delete(item) } return collection end # Logs the error using specified title and format # def log_error(title, exception) Rails.logger.error "#{title}: #{exception}\n#{exception.backtrace.join("\n")}" end # If none argument is sent, simply return the prefix. # Otherwise, replace the args in the prefix. # def prefix(*args) if args.empty? @@prefix else format(@@prefix, *args) end end end def initialize(controller) @controller = controller @template = controller.instance_variable_get(:@template) @body = controller.response.body @notes = [] end def add_footnotes! add_footnotes_without_validation! if valid? rescue Exception => e # Discard footnotes if there are any problems self.class.log_error("Footnotes Exception", e) end # Calls the class method close! in each note # Sometimes notes need to finish their work even after being read # This method allows this kind of work # def close!(controller) self.each_with_rescue(@@klasses) {|klass| klass.close!(controller)} self.each_with_rescue(Footnotes.after_hooks) {|hook| hook.call(controller, self)} end protected def valid? performed_render? && valid_format? && valid_content_type? && @body.is_a?(String) && !component_request? && !xhr? && !footnotes_disabled? end def add_footnotes_without_validation! initialize_notes! insert_styles unless @@no_style insert_footnotes end def initialize_notes! each_with_rescue(@@klasses) do |klass| note = klass.new(@controller) @notes << note if note.respond_to?(:valid?) && note.valid? end end def performed_render? @controller.instance_variable_get(:@performed_render) || # rails 2.x (@controller.respond_to?(:performed?) && @controller.performed?) # rails3, will break on redirect?? end def valid_format? if @template # Rails 2.x [:html,:rhtml,:xhtml,:rxhtml].include?(@template.send(template_format_method.to_sym).to_sym) else # Rails 3 @controller.response.content_type == 'text/html' end end def template_format_method if @template.respond_to?(:template_format) return 'template_format' else return 'format' end end def valid_content_type? c = @controller.response.headers['Content-Type'].to_s (c.empty? || c =~ /html/) end def component_request? @controller.instance_variable_get(:@parent_controller) end def xhr? @controller.request.xhr? end def footnotes_disabled? @controller.params[:footnotes] == "false" end # # Insertion methods # def insert_styles #TODO More customizable(reset.css, from file etc.) insert_text :before, /<\/head>/i, <<-HTML HTML end def insert_footnotes # Fieldsets method should be called first content = fieldsets footnotes_html = <<-HTML