Lots of code below, since it’s usually the details that matter. Bit of a
long one, but I think it’s generic enough to be a good one for the
archives.
Experimenting with how to extend ActiveRecord. So I picked a little
example project of an input scrubber. The purpose for the code is to
automatically sanitize attribute data prior to it being saved. Don’t
focus on the scrubber itself, it’s just a arbitrary task for something
to play with (obviously it’s not a full scale sanitizer).
I have the Peepcode plugins book, and I have checked several blogs. I
have tinkered for hours now, trying simple Ruby-only steps to understand
the various ruby dynamics with extend, include, included, etc. So I’m
generally getting the concepts (and working simple examples), but
getting lost in the details of how to get this particular idea to work.
So let’s start with an implementation hard coded inside one model which
works just fine.
class Example < ActiveRecord::Base
@@scrub_attributes = [:author_name, :title, :content]
def before_save
self.attributes.each do |key,value|
if @@scrub_attributes.include?(key.to_sym)
scrub_value(value) if !value.nil?
end
end
end
def scrub_value(input_value)
input_value.gsub!(/"/,’"’)
input_value.gsub!(/`/,’’’)
input_value.gsub!(/’/,’’’)
input_value.gsub!(/(/,’(’)
input_value.gsub!(/)/,’)’)
input_value.gsub!(/</,’<’)
input_value.gsub!(/>/,’>’)
input_value.gsub!(/j\sa\sv\sa\ss\sc\sr\si\sp\st\s:/i,’’)
end
end
#================================================================
But, of course, what I really want for the model code API is this:
class Example < ActiveRecord::Base
scrub_attributes :author_name, :title, :content
end
So, I started work on a plugin. Below is where I am at so far. I have
boiled (or rather logged) the problem down to two details. I have marked
a few points of reference to talk about. Have a gander, and I’ll meet
you at the end of the code…
#================================================================
Plugin structure:
/plugins
init.rb
/ar_extensions
/lib
/gw
attribute_scrubber.rb
init.rb contains
ActiveRecord::Base.send(:include, GW::AttributeScrubber)
module GW
module AttributeScrubber
def self.included(base_module)
class << base_module
@@attributes_to_scrub = []
end
base_module.extend ClassMethods
base_module.send(:include, InstanceMethods) # <---- (1)
end
module ClassMethods
def scrub_attributes(*attr_names)
@@attributes_to_scrub = attr_names
end
def attributes_to_scrub
return @@attributes_to_scrub
end
end
module InstanceMethods
def scrub_value(input_value) # <---- (2)
input_value.gsub!(/\"/,'"')
input_value.gsub!(/`/,''')
input_value.gsub!(/\'/,''')
input_value.gsub!(/\(/,'(')
input_value.gsub!(/\)/,')')
input_value.gsub!(/</,'<')
input_value.gsub!(/>/,'>')
input_value.gsub!(/j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*\:/i,'')
end
end
def self.append_features(base_module)
base_module.before_save do |model|
model.attributes.each do |key,value|
if @@attributes_to_scrub.include?(key.to_sym) #<--- (3)
scrub_value(value) if !value.nil? # <---- (4)
end
end
end
end
end
end
#================================================================
So, the problem I am having starts with (1). The PeepCode book says this
code should be
def self.included(base_module)
base_module.extend ClassMethods
base_module.include InstanceMethods
end
But that generates an error
included': private method
include’ called
Thus, I’m trying base_module.send(:include, InstanceMethods), but that
doesn’t seem to be working either because down at (4) I get errors that
method scrub_value doesn’t exist.
Before we get to (4) though, it turns out that at (3)
@@attributes_to_scrub, which I am expecting to be a class var for
ActiveRecord doesn’t exist for the before_save method. I discovered that
even though the @@attributes_to_scrub contained in scrub_attributes does
havethe values I expect, at (3) there are no values. Namespace problem?
I hard coded an array in place of the class var at (3), and then I get
the error that the method scrub_value doesn’t exist, so I know the
InstanceMethods are not being included, or at least not in the namespace
I’m expecting. But I can get some simple Ruby-only code to work this way
just fine. So, I’m confused about that.
Other than running into the snags at (3) and (4), it all seems to run.
Clues appreciated. Thanks.
– gw