From b7c79e8f54ca5e42937452ea1cf48ae1cc9329c9 Mon Sep 17 00:00:00 2001
From: Henk <henk.van.der.veen@gmail.com>
Date: Tue, 30 Mar 2021 23:51:15 +0200
Subject: [PATCH] add stripped_string type

---
 README.md                               | 23 +++++++++++++++++
 lib/roqua/type.rb                       |  7 +++++
 lib/roqua/type/stripped_string.rb       | 34 +++++++++++++++++++++++++
 spec/roqua/type/stripped_string_spec.rb | 34 +++++++++++++++++++++++++
 4 files changed, 98 insertions(+)
 create mode 100644 lib/roqua/type.rb
 create mode 100644 lib/roqua/type/stripped_string.rb
 create mode 100644 spec/roqua/type/stripped_string_spec.rb

diff --git a/README.md b/README.md
index 4d640f7..f4e3e0c 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,29 @@ class ApiAreaController < ApplicationController
   ...
 ```
 
+### Types
+
+In initializer:
+
+```ruby
+require 'roqua/type'
+```
+
+In models:
+
+```ruby
+# strip whitespace and nil if blank.
+attribute :firstname, :stripped_string
+# strip, but don't nil if blank.
+attribute :firstname, :stripped_string, allow_empty: true 
+```
+
+#### option 2
+
+`require 'roqua/type/stripped_string'`
+
+`attribute :firstname, Roqua::Type::StrippedString.new(allow_empty: true)`
+
 # Delayed Job activity monitoring
 
 To add monitoring of whether Delayed Job keeps picking up jobs or checking for new jobs, add the following line to an initializer:
diff --git a/lib/roqua/type.rb b/lib/roqua/type.rb
new file mode 100644
index 0000000..f2407de
--- /dev/null
+++ b/lib/roqua/type.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require "active_model/type"
+
+require_relative "./type/stripped_string"
+
+ActiveRecord::Type.register(:stripped_string, Roqua::Type::StrippedString)
diff --git a/lib/roqua/type/stripped_string.rb b/lib/roqua/type/stripped_string.rb
new file mode 100644
index 0000000..ef072a9
--- /dev/null
+++ b/lib/roqua/type/stripped_string.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# Strips whitespace and make nil when string becomes empty.
+module Roqua
+  module Type
+    class StrippedString < ActiveModel::Type::ImmutableString
+      # taken from strip_attributes gem
+      # Unicode invisible and whitespace characters. The POSIX character class
+      # [:space:] corresponds to the Unicode class Z ("separator"). We also
+      # include the following characters from Unicode class C ("control"), which
+      # are spaces or invisible characters that make no sense at the start or end
+      # of a string:
+      #   U+180E MONGOLIAN VOWEL SEPARATOR
+      #   U+200B ZERO WIDTH SPACE
+      #   U+200C ZERO WIDTH NON-JOINER
+      #   U+200D ZERO WIDTH JOINER
+      #   U+2060 WORD JOINER
+      #   U+FEFF ZERO WIDTH NO-BREAK SPACE
+      MULTIBYTE_WHITE = "\u180E\u200B\u200C\u200D\u2060\uFEFF"
+      MULTIBYTE_SPACE = /[[:space:]#{MULTIBYTE_WHITE}]/.freeze
+
+      def initialize(allow_empty: false)
+        super()
+        @allow_empty = allow_empty
+      end
+
+      def cast(value)
+        return unless value
+        value = super(value).gsub(/\A#{MULTIBYTE_SPACE}+|#{MULTIBYTE_SPACE}+\z/, "").freeze
+        value.blank? && !@allow_empty ? nil : value
+      end
+    end
+  end
+end
diff --git a/spec/roqua/type/stripped_string_spec.rb b/spec/roqua/type/stripped_string_spec.rb
new file mode 100644
index 0000000..2f5bf53
--- /dev/null
+++ b/spec/roqua/type/stripped_string_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'roqua/type/stripped_string'
+
+RSpec.describe Roqua::Type::StrippedString do
+  let(:cls) do
+    Class.new(ActiveRecord::Base) do
+      self.table_name = 'delayed_jobs'
+
+      attribute :locked_by, Roqua::Type::StrippedString.new
+      attribute :queue, Roqua::Type::StrippedString.new(allow_empty: true)
+    end
+  end
+
+  it "strips whitespace from outside and not inside" do
+    expect(described_class.new.cast("   some name   \n  \t \u180E\u200B\u200C\u200D\u2060\uFEFF ")).to eq "some name"
+  end
+
+  it "nils empty values by default" do
+    expect(described_class.new.cast(" \n \t ")).to eq nil
+  end
+
+  it "keeps value empty if allow_empty is true" do
+    expect(described_class.new(allow_empty: true).cast(" \n \t ")).to eq ""
+  end
+
+  it "works on active_record" do
+    instance = cls.new(locked_by: "  \n  \t ", queue: "  \n  \t ", cron: "  \n  \t ")
+    expect(instance.locked_by).to be_nil
+    expect(instance.queue).to eq ""
+    expect(instance.cron).to eq "  \n  \t "
+  end
+end
-- 
GitLab