├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── README.md ├── Rakefile ├── auto_strip_attributes.gemspec ├── lib ├── auto_strip_attributes.rb └── auto_strip_attributes │ └── version.rb ├── script ├── package └── release └── test └── auto_strip_attributes_test.rb /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - ruby-version: '2.6.10' 19 | rails-version: '6.0.0' 20 | - ruby-version: '2.6.10' 21 | rails-version: '6.1.0' 22 | - ruby-version: '3.2' 23 | rails-version: '7.0.0' 24 | 25 | env: 26 | RAILS_VERSION: ${{ matrix.rails-version }} 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Set up Ruby ${{ matrix.ruby-version }} 30 | uses: ruby/setup-ruby@v1 31 | with: 32 | bundler-cache: true 33 | ruby-version: ${{ matrix.ruby-version }} 34 | - name: Run tests 35 | run: bundle exec rake 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | 6 | .loadpath 7 | .project 8 | .idea 9 | .redcar 10 | 11 | help -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Master] 2 | 3 | ## [2.6] 4 | 5 | - Support for `array` attributes (thnks to [@sharshenov](https://github.com/holli/auto_strip_attributes/pull/29)) 6 | 7 | ## [2.5] 8 | 9 | - Support for callback options (e.g. if: -> ...) (thnks to [@watsonjon#28](https://github.com/holli/auto_strip_attributes/pull/28)) 10 | 11 | ## [2.4] 12 | 13 | - Using `ActiveSupport.on_load(:active_record)` instead of direct `extend`. ([#26](https://github.com/holli/auto_strip_attributes/commit/02431f07fcd880baaa352fc3e5a47d07c6d3935d)) 14 | - Possibility to pass options to custom filters. ([@nasa42#27](https://github.com/holli/auto_strip_attributes/pull/27)) 15 | - Rewritten to use keyword arguments instead of hash, and other Ruby 2 stuff 16 | 17 | ## [2.3] - 2018-02-06 18 | 19 | - Replacing all utf8 characters in squish (thnks to [@nasa42](https://github.com/holli/auto_strip_attributes/pull/24)) 20 | 21 | ## [2.2] - 2016-07-28 22 | 23 | - Added 'virtual'-option (thnks to [@nasa42](https://github.com/holli/auto_strip_attributes/pull/23)) 24 | 25 | ## [2.1] - 2016-07-28 26 | 27 | - Drop support for ruby 1.9.3 28 | 29 | ## [2.0.6] - 2014-07-29 30 | 31 | - Some file permission issues 32 | 33 | ## [2.0.5] - 2014-06-03 34 | 35 | - Added :convert_non_breaking_spaces filter (https://github.com/holli/auto_strip_attributes/pull/12) 36 | 37 | ## [2.0] - 2011-11-23 38 | 39 | - Includes config for extra filters (thnks to [@dadittoz](https://github.com/holli/auto_strip_attributes/issues/1)) 40 | 41 | ## [1.0] - 2011-08-26 42 | 43 | - Only basic filters 44 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in auto_strip_attributes.gemspec 4 | gemspec 5 | 6 | 7 | # For travis testing 8 | # http://schneems.com/post/50991826838/testing-against-multiple-rails-versions 9 | rails_version = ENV["RAILS_VERSION"] || "default" 10 | 11 | case rails_version 12 | when "default" 13 | gem "rails" 14 | else 15 | gem "rails", "~> #{rails_version}" 16 | end 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AutoStripAttributes 2 | 3 | AutoStripAttributes helps to remove unnecessary whitespaces from ActiveRecord or ActiveModel attributes. 4 | It's good for removing accidental spaces from user inputs (e.g. when user copy/pastes some value to a form and the value has extra spaces at the end). 5 | 6 | It works by adding a before_validation hook to the record. No other methods are added. Gem is kept as simple as possible. 7 | 8 | Gem has option to set empty strings to nil or to remove extra spaces inside the string. 9 | 10 | [![Downloads](https://img.shields.io/gem/dt/auto_strip_attributes)](https://rubygems.org/gems/auto_strip_attributes/) 11 | [![Gem](https://img.shields.io/gem/v/auto_strip_attributes)](https://rubygems.org/gems/auto_strip_attributes/) 12 | 13 | ## Howto / examples 14 | 15 | Include gem in your Gemfile: 16 | 17 | ```ruby 18 | gem "auto_strip_attributes", "~> 2.6" 19 | ``` 20 | 21 | Example ActiveRecord usage: 22 | 23 | ```ruby 24 | class User < ActiveRecord::Base 25 | 26 | # Normal usage where " aaa bbb\t " changes to "aaa bbb" 27 | auto_strip_attributes :nick, :comment 28 | 29 | # Squeezes spaces inside the string: "James Bond " => "James Bond" 30 | auto_strip_attributes :name, squish: true 31 | 32 | # Won't set to null even if string is blank. " " => "" 33 | auto_strip_attributes :email, nullify: false 34 | 35 | # Won't set to null even if array is blank. [" "] => [] 36 | auto_strip_attributes :tags, nullify_array: false 37 | 38 | # Use with attributes that are not mapped to a column 39 | auto_strip_attributes :password, virtual: true 40 | end 41 | ``` 42 | 43 | Example ActiveModel usage: 44 | 45 | ```ruby 46 | class VirtualUser 47 | include ActiveModel::Validations 48 | include ActiveModel::Validations::Callbacks 49 | 50 | extend AutoStripAttributes 51 | 52 | attr_accessor :email 53 | 54 | # Use the `virtual` option because attributes are not mapped to a column 55 | auto_strip_attributes :email, virtual: true 56 | end 57 | 58 | virtual_user = VirtualUser.new 59 | virtual_user.email = " alice@example.com " 60 | 61 | virtual_user.validate 62 | virtual_user.email #=> "alice@example.com" 63 | 64 | ``` 65 | 66 | # Options 67 | ### Default options 68 | 69 | By default the following options are defined (listed in the order of processing): 70 | 71 | - `:strip` (enabled by default) - removes whitespaces from the beginning and the end of string. Works exactly same as `String#strip`, i.e., may not strip non-ASCII whitespaces. 72 | - `:nullify` (enabled by default) - replaces empty strings with nil. 73 | - `:nullify_array` (enabled by default) - replaces empty arrays with nil. 74 | - `:squish` (disabled by default) - replaces all consecutive Unicode whitespace characters (including tabs and new lines) with single space (U+0020). Works exactly same as Rails `String#squish` 75 | - `:delete_whitespaces` (disabled by default) - deletes all spaces (U+0020) and tabs (U+0009). 76 | - `:convert_non_breaking_spaces` (disabled by default) - converts non-breaking spaces (U+00A0) to normal spaces (U+0020). 77 | - `:virtual` (disabled by default) - By default `auto_strip_attributes` doesn't work with non-persistent attributes (e.g., attributes that are created with `attr_accessor`). This is to avoid calling their custom getter/setter methods. Use this option with non-persistent attributes. 78 | - For more filters use custom filters (more examples at https://github.com/holli/auto_strip_attributes/wiki) 79 | 80 | ### Custom Filters 81 | 82 | Gem supports custom filtering methods. Custom methods can be set by calling to set_filter method 83 | inside a block passed to AutoStripAttributes::Config.setup. set_filter method accepts either Symbol or Hash as a 84 | parameter. If parameter is a Hash, the key should be filter name and the value is boolean whether filter is enabled by 85 | default or not. Block should return processed value. See examples of custom filters at https://github.com/holli/auto_strip_attributes/wiki 86 | 87 | This is an example on how to add html tags stripping in Rails 88 | 89 | ```ruby 90 | 91 | E.g. inside config/initializers/auto_strip_attributes.rb 92 | 93 | AutoStripAttributes::Config.setup do 94 | set_filter(strip_html: false) do |value| 95 | ActionController::Base.helpers.strip_tags value 96 | end 97 | end 98 | 99 | 100 | And in the model: 101 | 102 | class User < ActiveRecord::Base 103 | auto_strip_attributes :extra_info, strip_html: true 104 | end 105 | 106 | ``` 107 | 108 | Change the order of filters is done by manipulating filters_order array. You may also enable or disable filter by 109 | default by changing filters_enabled hash. 110 | 111 | Example of making :strip_html filter first and enabling :squish by default 112 | 113 | ```ruby 114 | AutoStripAttributes::Config.setup do 115 | filters_order.delete(:strip_html) 116 | filters_order.insert(0, :strip_html) 117 | filters_enabled[:squish] = true 118 | end 119 | ``` 120 | 121 | AutoStripAttributes::Config.setup accepts following options 122 | 123 | - :clear_previous => true, to clear all filters 124 | - :defaults => true, to set three default filters mentioned above 125 | 126 | 127 | # Versions 128 | 129 | - see https://github.com/holli/auto_strip_attributes/blob/master/CHANGELOG.md 130 | 131 | 132 | # Requirements 133 | 134 | Gem has been tested with newest Ruby & Rails combination and it probably works also with older versions. See test matrix at https://github.com/holli/auto_strip_attributes/blob/master/.github/workflows/ci.yaml 135 | 136 | # Support 137 | 138 | Submit suggestions or feature requests as a GitHub Issue or Pull Request. Remember to update tests. Tests are quite extensive. 139 | 140 | # Other approaches 141 | 142 | This gem works by adding before_validation hook and setting attributes with self[attribute]=stripped_value. See: https://github.com/holli/auto_strip_attributes/blob/master/lib/auto_strip_attributes.rb 143 | 144 | Other approaches could include calling attribute= from before_validation. This would end up calling possible custom setters twice. Might not be desired effect (e.g. if setter does some logging). 145 | 146 | Method chaining attribute= can be also used. But then stripping would be omitted if there is some code that calls model[attribute]= directly. This could happen easily when using hashes in some places. 147 | 148 | ### Similar gems 149 | 150 | There are many similar gems. Most of those don't have :squish or :nullify options. Those gems 151 | might have some extra methods whereas this gem is kept as simple as possible. These gems have a bit 152 | different approaches. See discussion in previous chapter. 153 | 154 | - https://github.com/phatworx/acts_as_strip 155 | - https://github.com/rmm5t/strip_attributes 156 | - https://github.com/thomasfedb/attr_cleaner 157 | - https://github.com/mdeering/attribute_normalizer (Bit hardcore approach, more features and more complex) 158 | 159 | # Licence 160 | 161 | Released under the MIT license (http://www.opensource.org/licenses/mit-license.php) 162 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | require "bundler/gem_tasks" 3 | require "bundler/setup" 4 | 5 | desc "Default: run unit tests." 6 | task :default => :test 7 | 8 | desc "Run tests for gem." 9 | Rake::TestTask.new(:test) do |t| 10 | t.libs << "lib" << "test" 11 | t.pattern = "test/**/*_test.rb" 12 | t.verbose = true 13 | end 14 | -------------------------------------------------------------------------------- /auto_strip_attributes.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "auto_strip_attributes/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "auto_strip_attributes" 7 | s.version = AutoStripAttributes::VERSION 8 | s.authors = ["Olli Huotari"] 9 | s.email = ["olli.huotari@iki.fi"] 10 | s.homepage = "https://github.com/holli/auto_strip_attributes" 11 | s.summary = "Removes unnecessary whitespaces in attributes. Extension to ActiveRecord or ActiveModel." 12 | s.description = "AutoStripAttributes helps to remove unnecessary whitespaces from ActiveRecord or ActiveModel attributes. It's good for removing accidental spaces from user inputs. It works by adding a before_validation hook to the record. It has option to set empty strings to nil or to remove extra spaces inside the string." 13 | s.license = "MIT" 14 | 15 | s.rubyforge_project = "auto_strip_attributes" 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ["lib"] 21 | 22 | 23 | s.add_runtime_dependency "activerecord", ">= 4.0" 24 | 25 | #s.add_development_dependency "activerecord", ">= 3.0" 26 | s.add_development_dependency "minitest", ">= 3.0" 27 | s.add_development_dependency "mocha", "~> 2.0" 28 | s.add_development_dependency 'rake' 29 | # s.add_development_dependency 'pry' 30 | 31 | end 32 | -------------------------------------------------------------------------------- /lib/auto_strip_attributes.rb: -------------------------------------------------------------------------------- 1 | require "auto_strip_attributes/version" 2 | 3 | module AutoStripAttributes 4 | def auto_strip_attributes(*attributes) 5 | options = AutoStripAttributes::Config.filters_enabled 6 | if attributes.last.is_a?(Hash) 7 | options = options.merge(attributes.pop) 8 | end 9 | 10 | # option `:virtual` is needed because we want to guarantee that 11 | # getter/setter methods for an attribute will _not_ be invoked by default 12 | virtual = options.delete(:virtual) 13 | 14 | attributes.each do |attribute| 15 | before_validation(options) do |record| 16 | if virtual 17 | value = record.public_send(attribute) 18 | else 19 | value = record[attribute] 20 | end 21 | AutoStripAttributes::Config.filters_order.each do |filter_name| 22 | next if !options[filter_name] 23 | filter = lambda { |original| AutoStripAttributes::Config.filters[filter_name].call(original, options[filter_name]) } 24 | value = if value.respond_to?(:is_a?) && value.is_a?(Array) 25 | array = value.map { |item| filter.call(item) }.compact 26 | options[:nullify_array] && array.empty? ? nil : array 27 | else 28 | filter.call(value) 29 | end 30 | if virtual 31 | record.public_send("#{attribute}=", value) 32 | else 33 | record[attribute] = value 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | 41 | class AutoStripAttributes::Config 42 | class << self 43 | attr_accessor :filters 44 | attr_accessor :filters_enabled 45 | attr_accessor :filters_order 46 | end 47 | 48 | def self.setup(clear_previous: false, defaults: true, &block) 49 | @filters, @filters_enabled, @filters_order = {}, {}, [] if clear_previous 50 | 51 | @filters ||= {} 52 | @filters_enabled ||= {} 53 | @filters_order ||= [] 54 | 55 | if defaults 56 | set_filter(convert_non_breaking_spaces: false) do |value| 57 | value.respond_to?(:gsub) ? value.gsub("\u00A0", " ") : value 58 | end 59 | set_filter(strip: true) do |value| 60 | value.respond_to?(:strip) ? value.strip : value 61 | end 62 | set_filter(nullify: true) do |value| 63 | # We check for blank? and empty? because rails uses empty? inside blank? 64 | # e.g. MiniTest::Mock.new() only responds to .blank? but not empty?, check tests for more info 65 | # Basically same as value.blank? ? nil : value 66 | (value.respond_to?(:'blank?') and value.respond_to?(:'empty?') and value.blank?) ? nil : value 67 | end 68 | set_filter(nullify_array: true) {|value| value} 69 | set_filter(squish: false) do |value| 70 | value = value.respond_to?(:gsub) ? value.gsub(/[[:space:]]+/, ' ') : value 71 | value.respond_to?(:strip) ? value.strip : value 72 | end 73 | set_filter(delete_whitespaces: false) do |value| 74 | value.respond_to?(:delete) ? value.delete(" \t") : value 75 | end 76 | end 77 | 78 | instance_eval(&block) if block_given? 79 | end 80 | 81 | def self.set_filter(filter, &block) 82 | if filter.is_a?(Hash) 83 | filter_name = filter.keys.first 84 | filter_enabled = filter.values.first 85 | else 86 | filter_name = filter 87 | filter_enabled = false 88 | end 89 | @filters[filter_name] = block 90 | @filters_enabled[filter_name] = filter_enabled 91 | # in case filter is redefined, we probably don't want to change the order 92 | @filters_order << filter_name if !@filters_order.include?(filter_name) 93 | end 94 | end 95 | 96 | #ActiveRecord::Base.send(:extend, AutoStripAttributes) if defined? ActiveRecord 97 | ActiveSupport.on_load(:active_record) do 98 | extend AutoStripAttributes 99 | end 100 | 101 | AutoStripAttributes::Config.setup 102 | -------------------------------------------------------------------------------- /lib/auto_strip_attributes/version.rb: -------------------------------------------------------------------------------- 1 | module AutoStripAttributes 2 | VERSION = "2.6.0" 3 | end 4 | -------------------------------------------------------------------------------- /script/package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: script/package 3 | # Updates the gemspec and builds a new gem in the pkg directory. 4 | 5 | mkdir -p pkg 6 | chmod -R a+r * 7 | gem build *.gemspec 8 | mv *.gem pkg 9 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: script/release 3 | # Build the package, tag a commit, push it to origin, and then release the 4 | # package publicly. 5 | 6 | set -e 7 | 8 | version="$(script/package | grep Version: | awk '{print $2}')" 9 | [ -n "$version" ] || exit 1 10 | 11 | echo $version 12 | git commit --allow-empty -a -m "Release $version" 13 | git tag "v$version" 14 | git push origin 15 | git push origin "v$version" 16 | echo "NOT YET PUSHING GEM AUTOMATICALLY, RUN: gem push pkg/*-${version}.gem" 17 | echo "NOT YET PUSHING GEM AUTOMATICALLY, RUN: gem push pkg/*-${version}.gem" 18 | # gem push pkg/*-${version}.gem 19 | -------------------------------------------------------------------------------- /test/auto_strip_attributes_test.rb: -------------------------------------------------------------------------------- 1 | # to use: 2 | # require "test/test_helper" 3 | # bundle exec ruby test/auto_strip_attributes_test.rb -v --name /test_name/ 4 | 5 | require 'minitest/autorun' 6 | require 'minitest/spec' 7 | require "active_record" 8 | require "auto_strip_attributes" 9 | require 'mocha/minitest' 10 | 11 | # if you need debug, add relevant line to auto_strip_attributes.gemspec 12 | # s.add_development_dependency 'pry' 13 | # and uncomment following line, and then write binding.pry somewhere 14 | # require 'pry' 15 | 16 | 17 | class MockRecordParent 18 | include ActiveModel::Validations 19 | include ActiveModel::Validations::Callbacks 20 | extend AutoStripAttributes 21 | 22 | # Overriding @record[key]=val , that's only found in activerecord, not in ActiveModel 23 | def []=(key, val) 24 | # send("#{key}=", val) # We dont want to call setter again 25 | instance_variable_set(:"@#{key}", val) 26 | end 27 | 28 | def [](key) 29 | k = :"@#{key}" 30 | instance_variable_defined?(k) ? instance_variable_get(k) : nil 31 | end 32 | 33 | end 34 | 35 | describe AutoStripAttributes do 36 | 37 | def setup 38 | @init_params = {:foo => "\tfoo ", :bar => " bar bar "} 39 | end 40 | 41 | def teardown 42 | AutoStripAttributes::Config.filters = {} 43 | AutoStripAttributes::Config.filters_enabled = {} 44 | AutoStripAttributes::Config.filters_order = [] 45 | AutoStripAttributes::Config.setup 46 | end 47 | 48 | it "should have defined AutoStripAttributes" do 49 | assert Object.const_defined?(:AutoStripAttributes) 50 | end 51 | 52 | describe "Basic attribute with default options and conditional evaluation" do 53 | class MockRecordBasic < MockRecordParent 54 | attr_accessor :boo 55 | auto_strip_attributes :boo, if: ->(record) { record[:boo] == " bbb \t" } 56 | end 57 | 58 | it "should not strip when conditional is not met" do 59 | @record = MockRecordBasic.new() 60 | @record.boo = " aaa \t" 61 | @record.valid? 62 | assert_equal @record.boo, " aaa \t" 63 | end 64 | 65 | it "should strip when conditional is met" do 66 | @record = MockRecordBasic.new() 67 | @record.boo = " bbb \t" 68 | @record.valid? 69 | assert_equal @record.boo, "bbb" 70 | end 71 | end 72 | 73 | describe "Basic attribute with default options" do 74 | class MockRecordBasic < MockRecordParent 75 | attr_accessor :foo 76 | auto_strip_attributes :foo 77 | end 78 | 79 | it "should be ok for normal strings" do 80 | @record = MockRecordBasic.new() 81 | @record.foo = " aaa \t" 82 | @record.valid? 83 | assert_equal @record.foo, "aaa" 84 | end 85 | 86 | it "should be ok for strings arrays" do 87 | @record = MockRecordBasic.new() 88 | @record.foo = [" aaa \t", " "] 89 | @record.valid? 90 | assert_equal @record.foo, ["aaa"] 91 | end 92 | 93 | it "should not delete non breaking spaces" do 94 | @record = MockRecordBasic.new() 95 | @record.foo = " aaa \t\u00A0" 96 | @record.valid? 97 | assert_equal @record.foo, "aaa \t\u00A0" 98 | end 99 | 100 | it "should be ok for normal strings and not squish things" do 101 | @record = MockRecordBasic.new() 102 | @record.foo = " aaa bbb " 103 | @record.valid? 104 | assert_equal @record.foo, "aaa bbb" 105 | end 106 | 107 | 108 | it "should set empty strings to nil" do 109 | @record = MockRecordBasic.new() 110 | @record.foo = " " 111 | @record.valid? 112 | assert_nil @record.foo 113 | end 114 | 115 | it "should set empty strings arrays to nil" do 116 | @record = MockRecordBasic.new() 117 | @record.foo = [" "] 118 | @record.valid? 119 | assert_nil @record.foo 120 | end 121 | 122 | it "should call strip method to attribute if possible" do 123 | @record = MockRecordBasic.new() 124 | str_mock = " strippable_str " 125 | str_mock.expects(:strip).returns(@stripped_str="stripped_str_here") 126 | @record.foo = str_mock 127 | @record.valid? 128 | assert_equal @record.foo, @stripped_str 129 | 130 | #str_mock.expect :'nil?', false 131 | #str_mock.expect :strip, (@stripped_str="stripped_str_here") 132 | #@record.foo = str_mock 133 | #@record.valid? 134 | #str_mock.verify 135 | #@record.foo.must_be_same_as @stripped_str 136 | end 137 | 138 | it "should not call strip or nullify method for non strippable attributes" do 139 | @record = MockRecordBasic.new() 140 | 141 | str_mock = MiniTest::Mock.new() # answers false to str_mock.respond_to?(:strip) and respond_to?(:blank) 142 | # Mock.new is problematic in ruby 1.9 because it responds to blank? but doesn't respond to ! 143 | # rails blank? method returns !self if an object doesn't respond to :empty? 144 | # Now we check in the validator also for :empty? so !self is never called. 145 | # Other way could be to mock !self in here 146 | 147 | @record.foo = str_mock 148 | @record.valid? 149 | assert @record.foo === str_mock 150 | str_mock.verify # "Should not call anything on mock when respond_to is false" 151 | end 152 | end 153 | 154 | describe "Attribute with convert non breaking spaces option" do 155 | #class MockRecordWithConvertNBSP < ActiveRecord::Base 156 | class MockRecordWithConvertNBSP < MockRecordParent 157 | #column :foo, :string 158 | attr_accessor :foo 159 | auto_strip_attributes :foo, convert_non_breaking_spaces: true 160 | end 161 | 162 | it "should delete non breaking spaces" do 163 | @record = MockRecordWithConvertNBSP.new() 164 | @record.foo = " aaa \t\u00A0" 165 | @record.valid? 166 | assert_equal @record.foo, "aaa" 167 | end 168 | 169 | end 170 | 171 | describe "Attribute with nullify option" do 172 | #class MockRecordWithNullify < ActiveRecord::Base 173 | class MockRecordWithNullify < MockRecordParent 174 | #column :foo, :string 175 | attr_accessor :foo 176 | auto_strip_attributes :foo, nullify: false 177 | end 178 | 179 | it "should not set blank strings to nil" do 180 | @record = MockRecordWithNullify.new 181 | @record.foo = " " 182 | @record.valid? 183 | assert_equal @record.foo, "" 184 | end 185 | 186 | it "should not set blank strings arrays to nil" do 187 | @record = MockRecordWithNullify.new() 188 | @record.foo = [" "] 189 | @record.valid? 190 | assert_equal @record.foo, [""] 191 | end 192 | end 193 | 194 | describe "Attribute with nullify_array option" do 195 | #class MockRecordWithNullifyArray < ActiveRecord::Base 196 | class MockRecordWithNullifyArray < MockRecordParent 197 | #column :foo, :string 198 | attr_accessor :foo 199 | auto_strip_attributes :foo, nullify_array: false 200 | end 201 | 202 | it "should not set blank strings array to nil" do 203 | @record = MockRecordWithNullifyArray.new 204 | @record.foo = [" "] 205 | @record.valid? 206 | assert_equal @record.foo, [] 207 | end 208 | end 209 | 210 | describe "Attribute with squish option" do 211 | class MockRecordWithSqueeze < MockRecordParent #< ActiveRecord::Base 212 | attr_accessor :foo 213 | # testing also that `:squish => true` implies `:strip => true` 214 | auto_strip_attributes :foo, squish: true, strip: false 215 | end 216 | 217 | it "should squish string also form inside" do 218 | @record = MockRecordWithSqueeze.new 219 | @record.foo = " aaa \u0009 \u000A \u000B \u000C \u000D \u0020 \u0085 \u00A0 \u1680 \u2000 \u2001 \u2002 \u2003 \u2004 \u2005 \u2006 \u2007 \u2008 \u2009 \u200A \u2028 \u2029 \u202F \u205F \u3000 bbb \u00A0 " 220 | @record.valid? 221 | assert_equal @record.foo, "aaa bbb" 222 | end 223 | 224 | it "should do normal nullify with empty string" do 225 | @record = MockRecordWithSqueeze.new 226 | @record.foo = " " 227 | @record.valid? 228 | assert_nil @record.foo 229 | end 230 | end 231 | 232 | describe "Attribute with delete_whitespaces option" do 233 | class MockRecordWithDelete < MockRecordParent 234 | #column :foo, :string 235 | attr_accessor :foo 236 | auto_strip_attributes :foo, delete_whitespaces: true 237 | end 238 | 239 | it "should delete all spaces and tabs" do 240 | @record = MockRecordWithDelete.new 241 | @record.foo = " a \t bbb" 242 | @record.valid? 243 | assert_equal @record.foo, "abbb" 244 | end 245 | end 246 | 247 | describe "Multible attributes with multiple options" do 248 | class MockRecordWithMultipleAttributes < MockRecordParent #< ActiveRecord::Base 249 | #column :foo, :string 250 | #column :bar, :string 251 | #column :baz, :string 252 | #column :qux, :string, array: true 253 | #column :quux, :string, array: true 254 | #column :quuz, :string, array: true 255 | attr_accessor :foo, :bar, :baz, :qux, :quux, :quuz 256 | auto_strip_attributes :foo, :bar, :qux 257 | auto_strip_attributes :baz, :quux, {nullify: false, squish: true} 258 | auto_strip_attributes :quuz, {nullify: true, nullify_array: false} 259 | end 260 | 261 | it "should handle everything ok" do 262 | @record = MockRecordWithMultipleAttributes.new 263 | @record.foo = " foo\tfoo" 264 | @record.bar = " " 265 | @record.baz = " " 266 | @record.qux = ["\n"] 267 | @record.quux = [" foo\tfoo", " "] 268 | @record.quuz = [" "] 269 | @record.valid? 270 | assert_equal @record.foo, "foo\tfoo" 271 | assert_nil @record.bar 272 | assert_equal @record.baz, "" 273 | assert_nil @record.qux 274 | assert_equal @record.quux, ["foo foo", ""] 275 | assert_equal @record.quuz, [] 276 | end 277 | end 278 | 279 | describe "Attribute with custom setter" do 280 | class MockRecordWithCustomSetter < MockRecordParent # < ActiveRecord::Base 281 | #column :foo, :string 282 | attr_accessor :foo 283 | auto_strip_attributes :foo 284 | 285 | def foo=(val) 286 | self[:foo] = "#{val}-#{val}" 287 | end 288 | end 289 | 290 | it "should not call setter again in before_validation" do 291 | @record = MockRecordWithCustomSetter.new 292 | @record.foo = " foo " 293 | assert_equal @record.foo, " foo - foo " 294 | @record.valid? 295 | assert_equal @record.foo, "foo - foo" 296 | end 297 | end 298 | 299 | describe "Virtual attributes" do 300 | class MockVirtualAttribute < MockRecordParent 301 | undef :[]= 302 | undef :[] 303 | 304 | auto_strip_attributes :foo, virtual: true 305 | 306 | def foo 307 | @bar 308 | end 309 | 310 | def foo=(val) 311 | @bar = val 312 | end 313 | end 314 | 315 | it "should handle everything ok" do 316 | @record = MockVirtualAttribute.new 317 | @record.foo = " foo " 318 | assert_equal @record.foo, " foo " 319 | @record.valid? 320 | assert_equal @record.foo, "foo" 321 | end 322 | end 323 | 324 | describe "Configuration tests" do 325 | it "should have defined AutoStripAttributes::Config" do 326 | assert AutoStripAttributes.const_defined?(:Config) 327 | end 328 | 329 | it "should have default filters set in right order" do 330 | AutoStripAttributes::Config.setup(clear_previous: true) 331 | filters_order = AutoStripAttributes::Config.filters_order 332 | assert_equal filters_order, [:convert_non_breaking_spaces, :strip, :nullify, :nullify_array, :squish, :delete_whitespaces] 333 | end 334 | 335 | it "should reset filters to defaults when :clear is true" do 336 | AutoStripAttributes::Config.setup do 337 | set_filter(:test) do 338 | 'test' 339 | end 340 | end 341 | AutoStripAttributes::Config.setup(clear_previous: true) 342 | filters_order = AutoStripAttributes::Config.filters_order 343 | assert_equal filters_order, [:convert_non_breaking_spaces, :strip, :nullify, :nullify_array, :squish, :delete_whitespaces] 344 | end 345 | 346 | it "should remove all filters when :clear is true and :defaults is false" do 347 | AutoStripAttributes::Config.setup do 348 | set_filter(:test) do 349 | 'test' 350 | end 351 | end 352 | AutoStripAttributes::Config.setup(clear_previous: true, defaults: false) 353 | filter_order = AutoStripAttributes::Config.filters_order 354 | assert_equal filter_order, [] 355 | 356 | # returning to original state 357 | AutoStripAttributes::Config.setup(clear_previous: true) 358 | end 359 | 360 | it "should correctly define and process custom filters" do 361 | class MockRecordWithCustomFilter < MockRecordParent #< ActiveRecord::Base 362 | attr_accessor :foo 363 | auto_strip_attributes :foo 364 | end 365 | 366 | AutoStripAttributes::Config.setup do 367 | set_filter(test: true) do |value| 368 | value.downcase 369 | end 370 | end 371 | 372 | filters_block = AutoStripAttributes::Config.filters 373 | filters_order = AutoStripAttributes::Config.filters_order 374 | filters_enabled = AutoStripAttributes::Config.filters_enabled 375 | 376 | assert_equal filters_order, [:convert_non_breaking_spaces, :strip, :nullify, :nullify_array, :squish, :delete_whitespaces, :test] 377 | assert Proc === filters_block[:test] 378 | assert_equal filters_enabled[:test], true 379 | 380 | @record = MockRecordWithCustomFilter.new 381 | @record.foo = " FOO " 382 | @record.valid? 383 | assert_equal @record.foo, "foo" 384 | 385 | # returning to original state 386 | AutoStripAttributes::Config.setup(clear_previous: true) 387 | end 388 | 389 | end 390 | 391 | describe "Using options in custom filters" do 392 | class CustomOptionsMockRecord < MockRecordParent 393 | attr_accessor :foo, :bar_downcase 394 | auto_strip_attributes :foo, truncate_test: {length: 5, separator: " ", omission: "…"} 395 | end 396 | 397 | def setup 398 | AutoStripAttributes::Config.setup do 399 | set_filter(:truncate_test) do |value, options| 400 | !value.blank? && value.respond_to?(:truncate) ? value.truncate(options[:length], omission: options[:omission]) : value 401 | end 402 | end 403 | end 404 | 405 | def teardown 406 | AutoStripAttributes::Config.setup(defaults: true, clear_previous: true) 407 | end 408 | 409 | it "should be able to truncate as asked" do 410 | @record = CustomOptionsMockRecord.new 411 | @record.foo = " abcdefghijklmnopqrstijklmn" 412 | @record.valid? 413 | assert_equal @record.foo, "abcd…" 414 | end 415 | end 416 | 417 | describe "complex usecase with custom config" do 418 | class ComplexFirstMockRecord < MockRecordParent 419 | #column :foo, :string 420 | attr_accessor :foo, :bar_downcase 421 | auto_strip_attributes :foo 422 | auto_strip_attributes :bar_downcase, downcase: true, nullify: false 423 | end 424 | 425 | # before will not work currently: https://github.com/seattlerb/minitest/issues/50 using def setup 426 | #before do 427 | #end 428 | 429 | def setup 430 | AutoStripAttributes::Config.setup do 431 | set_filter(downcase: false) do |value| 432 | value.downcase if value.respond_to?(:downcase) 433 | end 434 | end 435 | end 436 | 437 | def teardown 438 | AutoStripAttributes::Config.setup(defaults: true, clear_previous: true) 439 | end 440 | 441 | it "should not use extra filters when not in setup" do 442 | @record = ComplexFirstMockRecord.new 443 | @record.foo = " FOO " 444 | @record.valid? 445 | assert_equal @record.foo, "FOO" 446 | end 447 | 448 | it "should use extra filters when given" do 449 | @record = ComplexFirstMockRecord.new 450 | @record.bar_downcase = " BAR " 451 | @record.valid? 452 | assert_equal @record.bar_downcase, "bar" 453 | end 454 | 455 | it "should use extra filters when given and also respect given other configs" do 456 | @record = ComplexFirstMockRecord.new 457 | @record.bar_downcase = " " 458 | @record.valid? 459 | assert_equal @record.bar_downcase, "" 460 | end 461 | end 462 | 463 | 464 | end 465 | --------------------------------------------------------------------------------