読者です 読者をやめる 読者になる 読者になる

Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

Ruby | Collecting Inputs | Define conversion functions

概要

Define conversion functions

前提

Confident Rubyではメソッド内の処理を次のように分類しています。
・Collecting Inputs(引数チェック、変換など)
・Performing Work(主処理)
・Delivering Output(戻り値に関わる処理)
・Handling Failure(例外処理)

当記事は上記のうち、Collecting Inputsに関する話です。

詳細

状況

様々な形式で入力を受け付けるPublic APIが欲しい。
しかし、内部ではnormalizeされた一つの方のオブジェクトが欲しい。

概要

どのような入力オブジェクトに対しても適用できる冪等性のある変換関数を定義する。

理由

入力が必要とされる型に変換されれば、
不確かな入力に対して心配する時間を節約できて、ビジネスロジックの記述により多くの時間を使うことができる。

サンプルコード仕様

名前と年齢をフィールドに持つPersonクラスの変換関数を作成する。
受け入れる入力は、Array, String, Hashとする。
Arrayは要素が1つなら1要素目を name に設定。
Arrayは要素が2つなら1要素目を name に設定。2要素目を age に設定。
Stringは文字列内にセミコロンがなければ、最初の要素を name に設定。
Stringは文字列内にセミコロンがあれば、セパレートして最初の要素をnameに設定。最後の要素を age に設定。
Hashはkeyに :name があれば、対応する value を name に設定。
Hashはkeyに :age があれば、対応する value を age に設定。
すべて、設定対象がない場合はデフォルトを適用する。name のデフォルトは「unknown」。age のデフォルトは「20」

サンプルコード

require 'pp'

module TalkablePerson
  module Conversion
    module_function

    def Person(args)
      name = Person::DEFAULT_NAME
      age = Person::DEFAULT_AGE
      case args
      when Array
        name = args[0] if args[0]
        age = args[1] if args[1]
      when String
        if args.include? ':'
          name_age = args.split(':')
          name = name_age.first
          age = name_age.last.to_i
        else
          name = args
        end
      when Hash
        name = args[:name] if args[:name]
        age = args[:age] if args[:age]
      else
        fail 'invalid type'
      end
      Person.new(name, age)
    end
  end

  class Person
    DEFAULT_NAME = "unknown"
    DEFAULT_AGE = 20

    attr_reader :name, :age
    def initialize(name, age)
      @name, @age = name, age
    end

    def say
      puts "私は#{name}#{age}歳です"
    end
  end
end

tanakas = []
tanakas << TalkablePerson::Conversion::Person(["Tanaka Array1", 25])
tanakas << TalkablePerson::Conversion::Person(["Tanaka Array2"])
tanakas << TalkablePerson::Conversion::Person("Tanaka String1:25")
tanakas << TalkablePerson::Conversion::Person("Tanaka String2")
tanakas << TalkablePerson::Conversion::Person(name: "Tanaka Hash1", age: 25)
tanakas << TalkablePerson::Conversion::Person(name: "Tanaka Hash2")
tanakas.each(&:say)

出力

私はTanaka Array1。25歳です
私はTanaka Array2。20歳です
私はTanaka String1。25歳です
私はTanaka String2。20歳です
私はTanaka Hash1。25歳です
私はTanaka Hash2。20歳です