From 533abf9ba569566c4342236b675fb303c45ad6db Mon Sep 17 00:00:00 2001 From: lukas Date: Wed, 24 Feb 2010 09:45:45 +0100 Subject: [PATCH] initial import of vacuum cleaner repository --- LICENSE | 20 ++ Rakefile | 92 +++++++++ lib/vacuum_cleaner/normalizations.rb | 62 ++++++ lib/vacuum_cleaner/normalizations/method.rb | 48 +++++ lib/vacuum_cleaner/normalizer.rb | 67 ++++++ test/fixtures/person.rb | 0 test/test_helper.rb | 10 + test/unit/normalo_test_x.rb | 76 +++++++ .../normalizations/method_test.rb | 33 +++ .../vacuum_cleaner/normalizations_test.rb | 194 ++++++++++++++++++ test/unit/vacuum_cleaner/normalizer_test.rb | 47 +++++ 11 files changed, 649 insertions(+) create mode 100644 LICENSE create mode 100644 Rakefile create mode 100644 lib/vacuum_cleaner/normalizations.rb create mode 100644 lib/vacuum_cleaner/normalizations/method.rb create mode 100644 lib/vacuum_cleaner/normalizer.rb create mode 100644 test/fixtures/person.rb create mode 100644 test/test_helper.rb create mode 100644 test/unit/normalo_test_x.rb create mode 100644 test/unit/vacuum_cleaner/normalizations/method_test.rb create mode 100644 test/unit/vacuum_cleaner/normalizations_test.rb create mode 100644 test/unit/vacuum_cleaner/normalizer_test.rb diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7dfaa3d --- /dev/null +++ b/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. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..020c394 --- /dev/null +++ b/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 diff --git a/lib/vacuum_cleaner/normalizations.rb b/lib/vacuum_cleaner/normalizations.rb new file mode 100644 index 0000000..2bbfae0 --- /dev/null +++ b/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 value.to_s.camelize if it responds to :camelize, 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 \ No newline at end of file diff --git a/lib/vacuum_cleaner/normalizations/method.rb b/lib/vacuum_cleaner/normalizations/method.rb new file mode 100644 index 0000000..eaa3138 --- /dev/null +++ b/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 :method 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 nil 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 \ No newline at end of file diff --git a/lib/vacuum_cleaner/normalizer.rb b/lib/vacuum_cleaner/normalizer.rb new file mode 100644 index 0000000..df2bb05 --- /dev/null +++ b/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 normalize_value which accepts the value to normalize. Furthermore + # the value returned by normalize is used as the new value for + # the attribute. + # + # To reuse the behaviour as defined by the default normalizer (strip & empty), + # just use super. + # + # 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 object or attribute name + # is required, else override +normalize_value+, makes life much simpler :) + # + # Default behaviour just calls normalize_value(value). + 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 empty?. + 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 \ No newline at end of file diff --git a/test/fixtures/person.rb b/test/fixtures/person.rb new file mode 100644 index 0000000..e69de29 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..f694f49 --- /dev/null +++ b/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 } \ No newline at end of file diff --git a/test/unit/normalo_test_x.rb b/test/unit/normalo_test_x.rb new file mode 100644 index 0000000..55b15e0 --- /dev/null +++ b/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 _without_normalization= if = 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 => " 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 \ No newline at end of file diff --git a/test/unit/vacuum_cleaner/normalizations/method_test.rb b/test/unit/vacuum_cleaner/normalizations/method_test.rb new file mode 100644 index 0000000..6ba1c36 --- /dev/null +++ b/test/unit/vacuum_cleaner/normalizations/method_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' +require 'vacuum_cleaner/normalizer' +require 'vacuum_cleaner/normalizations/method' + +class VacuumCleaner::Normalizations::MethodNormalizerTest < Test::Unit::TestCase + include VacuumCleaner::Normalizations + + context "MethodNormalizer" do + should "accept method name as initialization argument" do + downcaser = MethodNormalizer.new(:downcase) + assert_equal "elliot\n", downcaser.normalize_value("Elliot\n") + assert_equal " \t", downcaser.normalize_value(" \t") + end + + should "accept hash with :method key as initializer" do + upcaser = MethodNormalizer.new(:method => :upcase) + assert_equal "ELLIOT\n", upcaser.normalize_value("Elliot\n") + assert_equal " \t", upcaser.normalize_value(" \t") + end + end + + context "DowncaseNormalizer#normalize_value" do + should "downcase input" do + assert_equal "elliot", DowncaseNormalizer.new.normalize_value("Elliot") + end + end + + context "UpcaseNormalizer#normalize_value" do + should "upcase input" do + assert_equal "J.D.", UpcaseNormalizer.new.normalize_value("j.d.") + end + end +end \ No newline at end of file diff --git a/test/unit/vacuum_cleaner/normalizations_test.rb b/test/unit/vacuum_cleaner/normalizations_test.rb new file mode 100644 index 0000000..2824f81 --- /dev/null +++ b/test/unit/vacuum_cleaner/normalizations_test.rb @@ -0,0 +1,194 @@ +require 'test_helper' + +require 'vacuum_cleaner/normalizer' +require 'vacuum_cleaner/normalizations' + +require 'fixtures/person' + +class PrefixDoctorNormalizer < VacuumCleaner::Normalizer + def normalize_value(value) + "Dr. #{value}" + end +end + +class Person + include VacuumCleaner::Normalizations + + attr_accessor :last_name, :first_name + + normalizes :last_name + normalizes :first_name +end + +class Doctor + include VacuumCleaner::Normalizations + attr_accessor :name + + class GirlifyNormalizer < VacuumCleaner::Normalizer + def normalize_value(value) + value == "J.D." ? "Maria" : value + end + end + + normalizes :name, :girlify => true +end + +class VacuumCleaner::NormalizationsTest < Test::Unit::TestCase + context "VacuumCleaner::Normalizations" do + context "ClassMethods#normalizes" do + should "throw ArgumentError if no attributes are passed in" do + assert_raise ArgumentError do + klass = Class.new do + include VacuumCleaner::Normalizations + normalizes + end + end + end + + should "take a symbol as argument" do + assert_respond_to Class.new { include VacuumCleaner::Normalizations; normalizes(:name) }, :normalizes + end + + should "take multiple symbols as argument" do + klass = Class.new { include VacuumCleaner::Normalizations; normalizes(:name, :first_name) } + assert_respond_to klass, :normalizes + 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 { include VacuumCleaner::Normalizations; normalizes(:name) }.new + assert_respond_to obj, :name= + end + + should "set the instance variable using the setter" do + obj = Class.new { include VacuumCleaner::Normalizations; normalizes(:name) }.new + obj.name = "J.D." + assert_equal "J.D.", obj.instance_variable_get(:@name) + end + + should "alias method to _without_normalization= if = already defined" do + klass = Class.new do + include VacuumCleaner::Normalizations + def name=(name); @foo = name end + normalizes :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 => " 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 + include VacuumCleaner::Normalizations + attr_accessor :name + normalizes :name do |value| + value ? value.upcase : value + end + end + obj = klass.new + obj.name = "Turk" + assert_equal "TURK", obj.name + end + + should "accept custom options hash to define other normalizers to run" do + klass = Class.new do + include VacuumCleaner::Normalizations + attr_accessor :email + normalizes :email, :downcase => true + end + obj = klass.new + obj.email = "\nJ.D.Dorian@Sacred-Heart.com " + assert_equal "j.d.dorian@sacred-heart.com", obj.email + end + + should "raise ArgumentError if invalid/unknown normalizer is called" do + assert_raise ArgumentError do + Class.new do + include VacuumCleaner::Normalizations + normalizes :foo, :invalid_unknown_normalizer => true + end + end + end + + should "ignore default split/empty? normalizer if :default => false" do + klass = Class.new do + include VacuumCleaner::Normalizations + attr_accessor :name + normalizes :name, :default => false, :upcase => true + end + obj = klass.new + obj.name = "Dr. Dorian\n\t" + assert_equal "DR. DORIAN\n\t", obj.name + end + + should "be able to use normalizers from the global namespace" do + klass = Class.new do + include VacuumCleaner::Normalizations + attr_accessor :name + normalizes :name, :prefix_doctor => true + end + obj = klass.new + obj.name = "Elliot Reid" + assert_equal "Dr. Elliot Reid", obj.name + end + + should "be able to use normalizers from within the class itself" do + obj = Doctor.new + obj.name = "Elliot Reid" + assert_equal "Elliot Reid", obj.name + obj = Doctor.new + obj.name = "J.D." + assert_equal "Maria", obj.name + end + + should "be able to combine normalizers and custom blocks" do + klass = Class.new do + include VacuumCleaner::Normalizations + attr_accessor :name, :first_name + normalizes(:name, :first_name, :downcase => true) do |value| + value.nil? ? value : "#{value.to_s[0,1].upcase}#{value.to_s[1..-1]}" + end + end + + obj = klass.new + obj.name = "REID" + obj.first_name = "ELLIOT" + assert_equal "Reid", obj.name + assert_equal "Elliot", obj.first_name + end + + should "provide block with all values if asking for them!" do + klass = Class.new do + include VacuumCleaner::Normalizations + attr_accessor :name + normalizes(:name) do |object, attribute, value| + "#{object.object_id}-#{attribute}-#{value}" + end + end + + obj = klass.new + obj.name = "Carla" + assert_equal "#{obj.object_id}-name-Carla", obj.name + end + end + end +end \ No newline at end of file diff --git a/test/unit/vacuum_cleaner/normalizer_test.rb b/test/unit/vacuum_cleaner/normalizer_test.rb new file mode 100644 index 0000000..0a7035a --- /dev/null +++ b/test/unit/vacuum_cleaner/normalizer_test.rb @@ -0,0 +1,47 @@ +require 'test_helper' +require 'vacuum_cleaner/normalizer' + +class VacuumCleaner::NormalizerTest < Test::Unit::TestCase + context "VacuumCleaner::Normalizer" do + should "take an optional hash as argument during #initialize and expose that hash when calling #options" do + expected = { :doctor => "Dr. Dorian", :nurse => "Carla" } + normalizer = VacuumCleaner::Normalizer.new({ :doctor => "Dr. Dorian", :nurse => "Carla" }) + assert_equal "Dr. Dorian", normalizer.options[:doctor] + assert_equal expected, normalizer.options + end + + context "#normalize_value" do + should "leave " do + assert_nil VacuumCleaner::Normalizer.new.normalize_value(nil) + end + + should "convert empty string to " do + assert_nil VacuumCleaner::Normalizer.new.normalize_value('') + end + + should "convert string with only space characters to " do + assert_nil VacuumCleaner::Normalizer.new.normalize_value(" ") + assert_nil VacuumCleaner::Normalizer.new.normalize_value(" \n\t ") + end + + should "strip leading and trailing whitespace" do + assert_equal "Dr. Reid", VacuumCleaner::Normalizer.new.normalize_value(" \nDr. Reid\t ") + assert_equal "Dr. Dorian", VacuumCleaner::Normalizer.new.normalize_value("Dr. Dorian\t \r") + end + end + + context "#normalize" do + should "return always the same as #normalize_value and ignore object and attribute parameters" do + normalizer = VacuumCleaner::Normalizer.new + object = Object.new + attribute = :name + + [["Dr. Reid", "\t Dr. Reid"], ["Dr. Dorian", "Dr. Dorian\n "], [nil, nil], [nil, "\n "], [nil, ""]].each do |tests| + expected, value = *tests + assert_equal expected, normalizer.normalize_value(value) + assert_equal expected, normalizer.normalize(object, attribute, value) + end + end + end + end +end