Skip to content

Commit

Permalink
initial import of vacuum cleaner repository
Browse files Browse the repository at this point in the history
  • Loading branch information
lwe committed Feb 24, 2010
0 parents commit 533abf9
Show file tree
Hide file tree
Showing 11 changed files with 649 additions and 0 deletions.
20 changes: 20 additions & 0 deletions LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2009 Lukas Westermann

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
92 changes: 92 additions & 0 deletions Rakefile
@@ -0,0 +1,92 @@
require 'rake'
require 'rake/testtask'
require 'yard'

#def gravatarify_version
# @gravatarify_version ||= (tmp = YAML.load(File.read('VERSION.yml'))) && [tmp[:major], tmp[:minor], tmp[:patch]] * '.'
#end

desc 'Default: run unit tests.'
task :default => :test

desc 'Test the vacuum_cleaner plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end

desc 'Generate documentation for vacuum_cleaner. (requires yard)'
YARD::Rake::YardocTask.new(:doc) do |t|
t.files = ['lib/**/*.rb']
t.options = [
"--readme", "README.md",
"--title", "vacuum_cleaner (vBETA) API Documentation"
]
end

begin
require 'jeweler'
Jeweler::Tasks.new do |gemspec|
gemspec.name = "vacuum_cleaner"
gemspec.summary = "TODO"
description = <<-DESC
TODO
DESC
gemspec.description = description.strip
gemspec.email = "lukas.westermann@gmail.com"
gemspec.homepage = "http://github.com/lwe/vacuum_cleaner"
gemspec.authors = ["Lukas Westermann"]
gemspec.licenses = %w{LICENSE}
gemspec.extra_rdoc_files = %w{README.md}

gemspec.add_development_dependency('shoulda', '>= 2.10.2')
gemspec.add_development_dependency('rr', '>= 0.10.5')
gemspec.add_development_dependency('activesupport', '>= 2.3.5')
end
Jeweler::GemcutterTasks.new
rescue LoadError
puts "Jeweler not available. Install it with: sudo gem install jeweler"
end

desc 'Clean all generated files (.yardoc and doc/*)'
task :clean do |t|
FileUtils.rm_rf "doc"
FileUtils.rm_rf "pkg"
FileUtils.rm_rf "*.gemspec"
FileUtils.rm_rf ".yardoc"
end

namespace :metrics do
desc 'Report all metrics, i.e. stats and code coverage.'
task :all => [:stats, :coverage]

desc 'Report code statistics for library and tests to shell.'
task :stats do |t|
require 'code_statistics'
dirs = {
'Libraries' => 'lib',
'Unit tests' => 'test/unit'
}.map { |name,dir| [name, File.join(File.dirname(__FILE__), dir)] }
CodeStatistics.new(*dirs).to_s
end

desc 'Report code coverage to HTML (doc/coverage) and shell (requires rcov).'
task :coverage do |t|
rm_f "doc/coverage"
mkdir_p "doc/coverage"
rcov = %(rcov -Ilib:test --exclude '\/gems\/' -o doc/coverage -T test/unit/*_test.rb )
system rcov
end

desc 'Report the fishy smell of bad code (requires reek)'
task :smelly do |t|
puts
puts "* * * NOTE: reek currently reports several false positives,"
puts " eventhough it's probably good to check once in a while!"
puts
reek = %(reek -s lib)
system reek
end
end
62 changes: 62 additions & 0 deletions lib/vacuum_cleaner/normalizations.rb
@@ -0,0 +1,62 @@
module VacuumCleaner
module Normalizations
WITHOUT_NORMALIZATION_SUFFIX = "_without_normalization"

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def normalizes(*attributes, &block)
metaklass = class << self; self; end

normalizations = attributes.last.is_a?(Hash) ? attributes.pop : {}
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?

normalizers = []
normalizers << Normalizer.new unless normalizations.delete(:default) === false

normalizations.each do |key, options|
begin
#klass = "#{Inflector.camelize(key)}Normalizer"
#klass = const_defined?(klass) ? const_get(klass) : (Object.const_defined?(klass) ? Object.const_get(klass) : eval("VacuumCleaner::Normalizations::#{klass}"))
normalizers << const_get("#{Inflector.camelize(key)}Normalizer").new(options === true ? {} : options)
rescue NameError
raise ArgumentError, "Unknown normalizer: '#{key}'"
end
end

attributes.each do |attribute|
rb_src = unless instance_methods.include?("#{attribute}=")
"@#{attribute} = value"
else
send(:alias_method, "#{attribute}#{VacuumCleaner::Normalizations::WITHOUT_NORMALIZATION_SUFFIX}=", "#{attribute}=")
"send('#{attribute}#{VacuumCleaner::Normalizations::WITHOUT_NORMALIZATION_SUFFIX}=', value)"
end

metaklass.send(:define_method, :"normalize_#{attribute}") do |value|
value = normalizers.inject(value) { |v,n| n.normalize(self, attribute.to_sym, v) }
block_given? ? (block.arity == 1 ? yield(value) : yield(self, attribute.to_sym, value)) : value
end

module_eval "def #{attribute}=(value); value = self.class.send(:'normalize_#{attribute}', value); #{rb_src} end", __FILE__, __LINE__
end
end
end

module Inflector
# Call either <tt>value.to_s.camelize</tt> if it responds to <tt>:camelize</tt>, else
# simple implementation taken directly from http://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L25
# of a default camelize behaviour.
def self.camelize(value)
value = value.to_s
value.respond_to?(:camelize) ? value.camelize : value.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
end
end
end
end

# load standard normalizations
Dir[File.dirname(__FILE__) + "/normalizations/*.rb"].sort.each do |path|
require "vacuum_cleaner/normalizations/#{File.basename(path)}"
end
48 changes: 48 additions & 0 deletions lib/vacuum_cleaner/normalizations/method.rb
@@ -0,0 +1,48 @@
module VacuumCleaner
module Normalizations

# Generic method based normalizer which just calls supplied method
# on value (unless nil).
#
# normalizes :name, :method => :titelize
#
# Custom instances accept a <tt>:method</tt> option.
#
# MethodNormalizer.new(:method => :titelize)
#
# Subclasses of the +MethodNormalizer+ can take advantage of it's
# +normalize_if_respond_to+ method, to easily create custom
# normalizers based on methods availble on the result value.
class MethodNormalizer < Normalizer
# Ensure access to default normalization method
alias_method :default_normalize_value, :normalize_value

# Helper method to "bake" a method normalizer from a method, enabling us to do stuff like.
#
# TitelizeNormalizer = MethodNormalizer.build(:titleize)
#
def self.build(sym)
module_eval "Class.new(MethodNormalizer) do; def initialize(*args); super({ :method => #{sym.inspect}}) end; end", __FILE__, __LINE__
end

# Accept either a hash or symbol name.
def initialize(args = {})
args = { :method => args } unless args.is_a?(Hash)
super(args)
end

# Normalize value by calling the default normalizer (strip + nil if empty)
# and then if not <tt>nil</tt> call the method defined.
def normalize_value(value)
sym = options[:method]
value.respond_to?(sym) ? value.send(sym) : value
end
end

# Downcase value unless nil or empty.
DowncaseNormalizer = MethodNormalizer.build(:downcase)

# Upcases value unless nil or empty.
UpcaseNormalizer = MethodNormalizer.build(:upcase)
end
end
67 changes: 67 additions & 0 deletions lib/vacuum_cleaner/normalizer.rb
@@ -0,0 +1,67 @@
module VacuumCleaner #:nodoc:

# A small base class for implementing custom value normalizers.
# Might seem like a slight overkill, yet makes the library pretty
# reusable and all. Based on Rails 3 validator stuff.
#
#
# class Person
# include VacuumCleaner::Normalizations
# normalizes :name, :titleize => true
# end
#
# class TitleizeNormalizer < VacuumCleaner::Normalizer
# def normalize_value(value)
# value.titelize unless value.blank?
# end
# end
#
# Any class that inherits from +VacuumCleaner::Normalizer+ must implement
# a method called <tt>normalize_value</tt> which accepts the <tt>value</tt> to normalize. Furthermore
# the value returned by <tt>normalize</tt> is used as the new value for
# the attribute.
#
# To reuse the behaviour as defined by the default normalizer (strip & empty),
# just use <tt>super</tt>.
#
# class TitleizeNormalizer < VacuumCleaner::Normalizer
# def normalize_value(value)
# super(value).try(:titelize)
# end
# end
#
# If access to the record or attribute being normalized is required the method
# +normalize+ can be overriden instead.
#
# class FancyNormalizer < VacuumCleaner::Normalizer
# def normalize(object, attribute, value)
# ...
# end
# end
#
# This can be used together with the +normalizes+ method (see
# VacuumCleaner::Normalizers.normalizes for more on this).
class Normalizer
attr_reader :options

# Accepts an array of options, which will be made available through the +options+ reader.
def initialize(options = {})
@options = options
end

# Only override this method if access to the <tt>object</tt> or <tt>attribute</tt> name
# is required, else override +normalize_value+, makes life much simpler :)
#
# Default behaviour just calls <tt>normalize_value(value)</tt>.
def normalize(object, attribute, value); normalize_value(value) end

# Override this method in subclasses to specifiy custom normalization steps and magic.
#
# The standard implementation strips the value of trailing/leading whitespace and then
# either returns that value or +nil+ if it's <tt>empty?</tt>.
def normalize_value(value)
value = value.strip if value.respond_to?(:strip)
value.nil? || (value.respond_to?(:empty?) && value.empty?) ? nil : value
end
end
end
Empty file added test/fixtures/person.rb
Empty file.
10 changes: 10 additions & 0 deletions test/test_helper.rb
@@ -0,0 +1,10 @@
require 'rubygems'
require 'test/unit'
require 'shoulda'
require 'rr'

# require 'normalo'

Test::Unit::TestCase.send(:include, RR::Adapters::TestUnit)

# Dir[File.dirname(__FILE__) + "/fixtures/*.rb"].each { |fixture| require fixture }
76 changes: 76 additions & 0 deletions test/unit/normalo_test_x.rb
@@ -0,0 +1,76 @@
require 'test_helper'

class NormaloTest < Test::Unit::TestCase
context "`extend Normalo`" do
should "provide #normalize to anonymous class" do
assert_respond_to Class.new { extend Normalo::Normalizer }, :normalize
end
end

context "#normalize" do
should "take a symbol as argument" do
assert_respond_to Class.new { extend Normalo::Normalizer; normalize(:name) }, :normalize
end

should "take multiple symbols as argument" do
klass = Class.new { extend Normalo::Normalizer; normalize(:name, :first_name) }
assert_respond_to klass, :normalize
assert_respond_to klass, :normalize_name
assert_respond_to klass, :normalize_first_name
end

should "create a setter for supplied attribute" do
obj = Class.new { extend Normalo::Normalizer; normalize(:name) }.new
assert_respond_to obj, :name=
end

should "set the instance variable using the setter" do
obj = Class.new { extend Normalo::Normalizer; normalize(:name) }.new
obj.name = "J.D."
assert_equal "J.D.", obj.instance_variable_get(:@name)
end

should "alias method to <attr>_without_normalization= if <attr>= already defined" do
klass = Class.new do
extend Normalo::Normalizer
def name=(name); @foo = name end
normalize :name
end
obj = klass.new
obj.name = "Elliot Reid"
assert_respond_to obj, :name_without_normalization=
assert_equal "Elliot Reid", obj.instance_variable_get(:@foo)
assert_nil obj.instance_variable_get(:@name)
end

should "convert any blank input, like empty string, nil etc. to => <nil>" do
obj = Person.new
obj.first_name = " "
obj.last_name = ''
assert_nil obj.first_name
assert_nil obj.last_name
end

should "strip leading and trailing white-space" do
obj = Person.new
obj.first_name = "\nElliot\t "
obj.last_name = nil
assert_nil obj.last_name
assert_equal "Elliot", obj.first_name
end

should "accept a block which overrides the default to_nil_if_empty strategy" do
klass = Class.new do
extend Normalo::Normalizer
attr_accessor :name
normalize :name do |value|
value = value.to_nil_if_empty
value ? value.upcase : value
end
end
obj = klass.new
obj.name = "Turk"
assert_equal "TURK", obj.name
end
end
end

0 comments on commit 533abf9

Please sign in to comment.