Sanity for free
Published about 6 years ago

Just an experiment.

require 'erb'

class ActionView::Base
  def _copy_ivars_from_controller_with_sanity
    variables = _copy_ivars_from_controller_without_sanity
    variables.each do |v|
      ivar = instance_variable_get(v)
      if ivar.is_a?(Array)
        values = ivar.map do |subivar|
          subivar.respond_to?(:to_sanity) ? subivar.to_sanity : subivar
        end
        instance_variable_set(v, values)
      elsif ivar.respond_to?(:to_sanity)
        instance_variable_set(v, ivar.to_sanity)
      end
    end
  end

  alias_method_chain :_copy_ivars_from_controller, :sanity
end

module Sanity
  class Cleaner
    Object.instance_methods.each do |m|
      delegate m, :to => :@model unless m =~ /^__/
    end

    def initialize(model)
      @model = model
    end

    def method_missing(method_id, *arguments, &block)
      if @model.class.sanitizable_columns.include?(method_id)
        value = @model.send(method_id)
        arguments.first == false ? value : ERB::Util.h(value)
      else
        @model.send(method_id, *arguments, &block)
      end
    end
  end

  module Model
    def self.included(base)
      base.send :include, InstanceMethods
      base.send :extend, ClassMethods
    end

    module InstanceMethods
      def to_sanity
        Cleaner.new(self)
      end
    end

    module ClassMethods
      def sanitizable_columns
        @sanitizable_columns ||= content_columns.find_all {|c| c.text? }.map(&:name).map(&:to_sym)
      end
    end
  end
end

ActiveRecord::Base.send :include, Sanity::Model

And then..

<%= @item.name # sanitized name %>
<%= @item.name(false) # unsanitized name  %>

I know, too many methods are missing, edge cases and what not. But hey, it’s just an idea/experiment.