├── .rspec ├── lib ├── taiwan_validator │ ├── version.rb │ ├── address_validator.rb │ ├── phone_number_validator.rb │ ├── ubn_validator.rb │ └── id_validator.rb └── taiwan_validator.rb ├── Rakefile ├── bin ├── setup └── console ├── .gitignore ├── Gemfile ├── .travis.yml ├── spec ├── address_validator_spec.rb ├── id_validator_spec.rb ├── phone_number_validator_spec.rb ├── ubn_validator_spec.rb └── spec_helper.rb ├── LICENSE.txt ├── taiwan_validator.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /lib/taiwan_validator/version.rb: -------------------------------------------------------------------------------- 1 | module TaiwanValidator 2 | VERSION = "1.3.3" 3 | end 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | 11 | .DS_Store 12 | spec/examples.txt 13 | coverage 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in taiwan_validator.gemspec 4 | gemspec 5 | 6 | # CodeClimate Test Coverage 7 | group :test do 8 | gem 'simplecov', require: false, group: :test 9 | end 10 | -------------------------------------------------------------------------------- /lib/taiwan_validator.rb: -------------------------------------------------------------------------------- 1 | require "taiwan_validator/version" 2 | require "active_model" 3 | require "active_support/hash_with_indifferent_access" 4 | require "taiwan_validator/ubn_validator" 5 | require "taiwan_validator/id_validator" 6 | require "taiwan_validator/address_validator" 7 | require "taiwan_validator/phone_number_validator" 8 | 9 | module TaiwanValidator 10 | end 11 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "taiwan_validator" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /lib/taiwan_validator/address_validator.rb: -------------------------------------------------------------------------------- 1 | class TaiwanValidator::AddressValidator < ActiveModel::EachValidator 2 | class << self 3 | def valid?(address) 4 | /(\A\d{3,5})?\s?(\D+[縣市])(\D+[鄉鎮市區])?(.+)/.match(address) 5 | end 6 | end 7 | 8 | def validate_each(record, attribute, value) 9 | unless self.class.valid?(value) 10 | record.errors.add(attribute, options[:message] || :invalid) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/taiwan_validator/phone_number_validator.rb: -------------------------------------------------------------------------------- 1 | class TaiwanValidator::PhoneNumberValidator < ActiveModel::EachValidator 2 | class << self 3 | def valid?(phone_number) 4 | phone_number.gsub(/[\+\-\s]/, "") =~ /\A(886|0)[2-9]([0-9]{6,8})\z/ 5 | end 6 | end 7 | 8 | def validate_each(record, attribute, value) 9 | unless self.class.valid?(value) 10 | record.errors.add(attribute, options[:message] || :invalid) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - CC_TEST_REPORTER_ID=85915b296b49680c2c4fd50d2be8f76cc094409297c63b37cf15c145968f1e91 4 | language: ruby 5 | rvm: 6 | - 2.4.6 7 | - 2.5.5 8 | - 2.6.3 9 | - 2.7.0 10 | before_script: 11 | - gem install bundler 12 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 13 | - chmod +x ./cc-test-reporter 14 | - ./cc-test-reporter before-build 15 | script: 16 | - bundle exec rspec 17 | after_script: 18 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 19 | -------------------------------------------------------------------------------- /spec/address_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe TaiwanValidator::AddressValidator do 4 | subject { TaiwanValidator::AddressValidator } 5 | 6 | describe ".valid?" do 7 | it "accepts zipcode as a start" do 8 | expect(subject.valid?("10048 臺北市中正區重慶南路1段122號")).to be_truthy 9 | end 10 | 11 | it "accepts zipcode as a start or not" do 12 | expect(subject.valid?("臺北市中正區重慶南路1段122號")).to be_truthy 13 | end 14 | 15 | it "doesn't accepts address without city" do 16 | expect(subject.valid?("中正區重慶南路1段122號")).to be_falsey 17 | end 18 | 19 | it "accepts address without district" do 20 | expect(subject.valid?("臺北市中正區重慶南路1段122號")).to be_truthy 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/id_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe TaiwanValidator::IdValidator do 4 | subject { TaiwanValidator::IdValidator } 5 | 6 | describe ".valid?" do 7 | it "returns false when id size != 10" do 8 | expect(subject.valid?("A" + "1" * 8)).to be_falsey 9 | end 10 | 11 | it "returns false when id not start with valid first letter" do 12 | expect(subject.valid?("1" * 10)).to be_falsey 13 | end 14 | 15 | it "returns false when id not with tailing 9 number digits" do 16 | expect(subject.valid?("A" * 10)).to be_falsey 17 | end 18 | 19 | it "returns true with correct example" do 20 | expect(subject.valid?("B120863158")).to be_truthy 21 | end 22 | end 23 | 24 | describe "#validate_each" do 25 | pending 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Yun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /taiwan_validator.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'taiwan_validator/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "taiwan_validator" 8 | spec.version = TaiwanValidator::VERSION 9 | spec.authors = ["David Yun"] 10 | spec.email = ["abookyun@gmail.com"] 11 | 12 | spec.summary = %q{Collection of useful custom validators in Taiwan for Rails applications} 13 | spec.description = %q{taiwan_validator provides a set of commonly used validators in Taiwan for Rails applications} 14 | spec.homepage = "https://github.com/abookyun/taiwan_validator" 15 | spec.license = "MIT" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.require_paths = ["lib"] 19 | 20 | spec.add_dependency "activemodel", ">= 4.2" 21 | spec.add_dependency "activesupport", ">= 4.2" 22 | 23 | spec.add_development_dependency "bundler", "> 1.17" 24 | spec.add_development_dependency "rake", "~> 13.0" 25 | spec.add_development_dependency "rspec", "~> 3.3" 26 | end 27 | -------------------------------------------------------------------------------- /lib/taiwan_validator/ubn_validator.rb: -------------------------------------------------------------------------------- 1 | class TaiwanValidator::UbnValidator < ActiveModel::EachValidator 2 | MULTIPLIER = [1,2,1,2,1,2,4,1].freeze 3 | 4 | class << self 5 | def valid?(ubn) 6 | ubn = ubn.to_s 7 | return false unless ubn.match?(/\A\d{8}\z/) 8 | 9 | digits = ubn.chars.map(&:to_i) 10 | results = digits.zip(MULTIPLIER).map do |op1, op2| 11 | digit = op1 * op2 12 | next digit if digit < 10 13 | 14 | digit = (digit / 10) + (digit % 10) 15 | next digit if digit < 10 16 | 17 | [digit / 10, digit % 10] 18 | end 19 | 20 | known_answers = results.select{ |x| x.is_a?(Numeric) }.inject(&:+) 21 | possible_answers = results.reject{ |x| x.is_a?(Numeric) }.flatten 22 | 23 | if possible_answers.empty? 24 | return known_answers % 10 == 0 25 | end 26 | 27 | possible_answers.any? do |possible_answer| 28 | (possible_answer + known_answers) % 10 == 0 29 | end 30 | end 31 | end 32 | 33 | def validate_each(record, attribute, value) 34 | unless self.class.valid?(value) 35 | record.errors.add(attribute, options[:message] || :invalid) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/taiwan_validator/id_validator.rb: -------------------------------------------------------------------------------- 1 | class TaiwanValidator::IdValidator < ActiveModel::EachValidator 2 | MULTIPLIER = [1,9,8,7,6,5,4,3,2,1,1].freeze 3 | FIRST_LETTER = HashWithIndifferentAccess.new( 4 | A: 10, B: 11, C: 12, D: 13, E: 14, 5 | F: 15, G: 16, H: 17, I: 34, J: 18, 6 | K: 19, M: 21, N: 22, O: 35, P: 23, 7 | Q: 24, T: 27, U: 28, V: 29, W: 32, 8 | X: 30, Z: 33 9 | ).freeze 10 | DEPRECATED_FIRST_LETTER = HashWithIndifferentAccess.new( 11 | L: 20, R: 25, S: 26, Y: 31 12 | ).freeze 13 | VALID_FIRST_LETTER = FIRST_LETTER.merge(DEPRECATED_FIRST_LETTER).freeze 14 | 15 | class << self 16 | def valid?(id) 17 | id = id.to_s 18 | return false if id.size != 10 || !(VALID_FIRST_LETTER.keys.include?(id[0])) || (id[1..9] =~ /\A\d+\Z/).nil? 19 | 20 | digits = (VALID_FIRST_LETTER[id[0]].to_s.chars + id[1..9].to_s.chars).map(&:to_i) 21 | results = digits.zip(MULTIPLIER).map { |r| r.inject(&:*) }.inject(&:+) 22 | 23 | results % 10 == 0 24 | end 25 | end 26 | 27 | def validate_each(record, attribute, value) 28 | unless self.class.valid?(value) 29 | record.errors.add(attribute, options[:message] || :invalid) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/phone_number_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe TaiwanValidator::PhoneNumberValidator do 4 | subject { TaiwanValidator::PhoneNumberValidator } 5 | 6 | describe ".valid?" do 7 | it "accepts +886 as start digits" do 8 | expect(subject.valid?("+886912123123")).to be_truthy 9 | end 10 | 11 | it "accepts 886 as start digits" do 12 | expect(subject.valid?("886912123123")).to be_truthy 13 | end 14 | 15 | it "accepts 0 as start digits" do 16 | expect(subject.valid?("0912123123")).to be_truthy 17 | end 18 | 19 | it "doesn't accepts without 886|0 as start digits" do 20 | expect(subject.valid?("912123123")).to be_falsey 21 | end 22 | 23 | it "accepts 2-9 as local distric or mobile digits" do 24 | expect(subject.valid?("0212123123")).to be_truthy 25 | end 26 | 27 | it "doesn't accepts 1 as local distric or mobile digits" do 28 | expect(subject.valid?("0112123123")).to be_falsey 29 | end 30 | 31 | it "accepts 6 digits long as number digits" do 32 | expect(subject.valid?("02123456")).to be_truthy 33 | end 34 | 35 | it "accepts 8 digits long as number digits" do 36 | expect(subject.valid?("0212345678")).to be_truthy 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/ubn_validator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe TaiwanValidator::UbnValidator do 4 | subject { TaiwanValidator::UbnValidator } 5 | 6 | describe ".valid?" do 7 | it "accepts interger as well" do 8 | expect(subject.valid?(10458575)).to be_truthy 9 | end 10 | 11 | it "returns false when ubn size != 8" do 12 | expect(subject.valid?("1" * 7)).to be_falsey 13 | end 14 | 15 | it "returns false when ubn contains non-number digits" do 16 | expect(subject.valid?("s" * 8)).to be_falsey 17 | end 18 | 19 | it "returns true when results % 10 == 0" do 20 | expect(subject.valid?("0" * 8)).to be_truthy 21 | end 22 | 23 | it "returns true with correct example(a)" do 24 | expect(subject.valid?("04595257")).to be_truthy 25 | end 26 | 27 | it "returns true with correct example(b)" do 28 | expect(subject.valid?("10458575")).to be_truthy 29 | end 30 | 31 | it "returns true with correct example(c)" do 32 | expect(subject.valid?("16894172")).to be_truthy 33 | end 34 | 35 | it "returns true with correct example(d)" do 36 | expect(subject.valid?("10458575")).to be_truthy 37 | end 38 | end 39 | 40 | describe "#validate_each" do 41 | pending 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TaiwanValidator 2 | 3 | [![Build Status](https://travis-ci.org/abookyun/taiwan_validator.svg)](https://travis-ci.org/abookyun/taiwan_validator) 4 | [![Code Climate](https://codeclimate.com/github/abookyun/taiwan_validator/badges/gpa.svg)](https://codeclimate.com/github/abookyun/taiwan_validator) 5 | [![Test Coverage](https://codeclimate.com/github/abookyun/taiwan_validator/badges/coverage.svg)](https://codeclimate.com/github/abookyun/taiwan_validator/coverage) 6 | 7 | TaiwanValidator provides set of commonly used validators in Taiwan for Rails applications. 8 | 9 | ## Installation 10 | 11 | Add this line to your application's Gemfile: 12 | 13 | ```ruby 14 | gem 'taiwan_validator' 15 | ``` 16 | 17 | And then execute: 18 | 19 | $ bundle 20 | 21 | Or install it yourself as: 22 | 23 | $ gem install taiwan_validator 24 | 25 | ## Usage 26 | 27 | ```ruby 28 | include TaiwanValidator 29 | 30 | validates :ubn, ubn: true 31 | validates :id, id: true 32 | validates :address, address: true 33 | validates :tel, phone_number: true 34 | ``` 35 | 36 | ## Development 37 | 38 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 39 | 40 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 41 | 42 | ## Contributing 43 | 44 | Bug reports and pull requests are welcome on GitHub at https://github.com/abookyun/taiwan_validator. 45 | 46 | 47 | ## License 48 | 49 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 50 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "simplecov" 2 | SimpleCov.start 3 | 4 | require "active_model" 5 | require "active_support/hash_with_indifferent_access" 6 | require "taiwan_validator" 7 | 8 | # This file was generated by the `rspec --init` command. Conventionally, all 9 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 10 | # The generated `.rspec` file contains `--require spec_helper` which will cause 11 | # this file to always be loaded, without a need to explicitly require it in any 12 | # files. 13 | # 14 | # Given that it is always loaded, you are encouraged to keep this file as 15 | # light-weight as possible. Requiring heavyweight dependencies from this file 16 | # will add to the boot time of your test suite on EVERY test run, even for an 17 | # individual file that may not need all of that loaded. Instead, consider making 18 | # a separate helper file that requires the additional dependencies and performs 19 | # the additional setup, and require it from the spec files that actually need 20 | # it. 21 | # 22 | # The `.rspec` file also contains a few flags that are not defaults but that 23 | # users commonly want. 24 | # 25 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 26 | RSpec.configure do |config| 27 | # rspec-expectations config goes here. You can use an alternate 28 | # assertion/expectation library such as wrong or the stdlib/minitest 29 | # assertions if you prefer. 30 | config.expect_with :rspec do |expectations| 31 | # This option will default to `true` in RSpec 4. It makes the `description` 32 | # and `failure_message` of custom matchers include text for helper methods 33 | # defined using `chain`, e.g.: 34 | # be_bigger_than(2).and_smaller_than(4).description 35 | # # => "be bigger than 2 and smaller than 4" 36 | # ...rather than: 37 | # # => "be bigger than 2" 38 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 39 | end 40 | 41 | # rspec-mocks config goes here. You can use an alternate test double 42 | # library (such as bogus or mocha) by changing the `mock_with` option here. 43 | config.mock_with :rspec do |mocks| 44 | # Prevents you from mocking or stubbing a method that does not exist on 45 | # a real object. This is generally recommended, and will default to 46 | # `true` in RSpec 4. 47 | mocks.verify_partial_doubles = true 48 | end 49 | 50 | # These two settings work together to allow you to limit a spec run 51 | # to individual examples or groups you care about by tagging them with 52 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 53 | # get run. 54 | config.filter_run :focus 55 | config.run_all_when_everything_filtered = true 56 | 57 | # Allows RSpec to persist some state between runs in order to support 58 | # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # you configure your source control system to ignore this file. 60 | config.example_status_persistence_file_path = "spec/examples.txt" 61 | 62 | # Limits the available syntax to the non-monkey patched syntax that is 63 | # recommended. For more details, see: 64 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 65 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 67 | config.disable_monkey_patching! 68 | 69 | # This setting enables warnings. It's recommended, but in some cases may 70 | # be too noisy due to issues in dependencies. 71 | config.warnings = true 72 | 73 | # Many RSpec users commonly either run the entire suite or an individual 74 | # file, and it's useful to allow more verbose output when running an 75 | # individual spec file. 76 | if config.files_to_run.one? 77 | # Use the documentation formatter for detailed output, 78 | # unless a formatter has already been configured 79 | # (e.g. via a command-line flag). 80 | config.default_formatter = 'doc' 81 | end 82 | 83 | # Print the 10 slowest examples and example groups at the 84 | # end of the spec run, to help surface which specs are running 85 | # particularly slow. 86 | config.profile_examples = 10 87 | 88 | # Run specs in random order to surface order dependencies. If you find an 89 | # order dependency and want to debug it, you can fix the order by providing 90 | # the seed, which is printed after each run. 91 | # --seed 1234 92 | config.order = :random 93 | 94 | # Seed global randomization in this process using the `--seed` CLI option. 95 | # Setting this allows you to use `--seed` to deterministically reproduce 96 | # test failures related to randomization by passing the same `--seed` value 97 | # as the one that triggered the failure. 98 | Kernel.srand config.seed 99 | end 100 | --------------------------------------------------------------------------------