diff --git a/CHANGELOG b/CHANGELOG index e934764abe..8a9ca0c52c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ === HEAD +* Skip stripping of blob columns in the string_stripper plugin (jeremyevans) (#593) + * Allow Database#get to take an array to return multiple values, similar to map/select_map (jeremyevans) * Default :prefetch_rows to 100 in the Oracle adapter (andrewhr) (#592) diff --git a/lib/sequel/plugins/string_stripper.rb b/lib/sequel/plugins/string_stripper.rb index 154874ec5c..f6806d6b24 100644 --- a/lib/sequel/plugins/string_stripper.rb +++ b/lib/sequel/plugins/string_stripper.rb @@ -1,10 +1,18 @@ module Sequel module Plugins - # StringStripper is a very simple plugin that strips all input strings + # StringStripper is a plugin that strips all input strings # when assigning to the model's values. Example: # # album = Album.new(:name=>' A ') # album.name # => 'A' + # + # SQL::Blob instances and all non-strings are not modified by + # this plugin. Additionally, strings passed to a blob column + # setter are also not modified. You can explicitly set + # other columns to skip the stripping: + # + # Album.skip_string_stripping :foo + # Album.new(:foo=>' A ').foo # => ' A ' # # Usage: # @@ -14,11 +22,53 @@ module Plugins # # Make the Album class strip strings # Album.plugin :string_stripper module StringStripper + # Set blob columns as skipping stripping when plugin is loaded. + def self.configure(model) + model.instance_variable_set(:@skipped_string_stripping_columns, []) + model.send(:set_skipped_string_stripping_columns) + end + + module ClassMethods + # Copy skipped stripping columns from superclass into subclass. + def inherited(subclass) + subclass.instance_variable_set(:@skipped_string_stripping_columns, @skipped_string_stripping_columns.dup) + super + end + + # Set blob columns as skipping stripping when plugin is loaded. + def set_dataset(*) + super + set_skipped_string_stripping_columns + end + + # Skip stripping for the given columns. + def skip_string_stripping(*columns) + @skipped_string_stripping_columns.concat(columns).uniq! + end + + # Return true if the column should not have values stripped. + def skip_string_stripping?(column) + @skipped_string_stripping_columns.include?(column) + end + + private + + # Automatically skip stripping of blob columns + def set_skipped_string_stripping_columns + if @db_schema + blob_columns = @db_schema.map{|k,v| k if v[:type] == :blob}.compact + skip_string_stripping(*blob_columns) + end + end + end + module InstanceMethods - # Strip value if it is a string, before attempting to assign + # Strip value if it is a non-blob string and the model hasn't been set + # to skip stripping for the column, before attempting to assign # it to the model's values. def []=(k, v) - v.is_a?(String) ? super(k, v.strip) : super + v = v.strip if v.is_a?(String) && !v.is_a?(SQL::Blob) && !model.skip_string_stripping?(k) + super(k, v) end end end diff --git a/spec/extensions/string_stripper_spec.rb b/spec/extensions/string_stripper_spec.rb index db9f4e93ff..98b27655b3 100644 --- a/spec/extensions/string_stripper_spec.rb +++ b/spec/extensions/string_stripper_spec.rb @@ -3,9 +3,10 @@ describe "Sequel::Plugins::StringStripper" do before do @db = Sequel::Database.new - @c = Class.new(Sequel::Model(@db)) + @c = Class.new(Sequel::Model(@db[:test])) + @c.columns :name, :b + @c.db_schema[:b][:type] = :blob @c.plugin :string_stripper - @c.columns :name @o = @c.new end @@ -20,4 +21,45 @@ @o.name = Date.today @o.name.should == Date.today end + + it "should not strip strings for blob arguments" do + v = Sequel.blob(' name ') + @o.name = v + @o.name.should equal(v) + end + + it "should not strip strings for blob columns" do + @o.b = ' name ' + @o.b.should be_a_kind_of(Sequel::SQL::Blob) + @o.b.should == Sequel.blob(' name ') + end + + it "should allow skipping of columns using Model.skip_string_stripping" do + @c.skip_string_stripping :name + v = ' name ' + @o.name = v + @o.name.should equal(v) + end + + it "should work correctly in subclasses" do + o = Class.new(@c).new + o.name = ' name ' + o.name.should == 'name' + o.b = ' name ' + o.b.should be_a_kind_of(Sequel::SQL::Blob) + o.b.should == Sequel.blob(' name ') + end + + it "should work correctly for dataset changes" do + c = Class.new(Sequel::Model(@db[:test])) + c.plugin :string_stripper + def @db.schema(*) [[:name, {}], [:b, {:type=>:blob}]] end + c.set_dataset(@db[:test2]) + o = c.new + o.name = ' name ' + o.name.should == 'name' + o.b = ' name ' + o.b.should be_a_kind_of(Sequel::SQL::Blob) + o.b.should == Sequel.blob(' name ') + end end