LoginSignup
12
12

More than 5 years have passed since last update.

タイプキャスト付のattr_accessorを作った

Posted at

車輪の再発明感が凄いというか、active_attrを使っとけばいいのでは、という説があるのだが、attr_accessorにタイプキャスト機能を追加したgemを作ってみました。

joker1007/attr_typecastable

前に書いた Ruby - ActiveModelを活用するための属性定義拡張系gemについて - Qiita というネタで紹介してるgemは基本的にこの機能を持ってます。
ただ、色々と余計なことやり過ぎてて細かく調整しようとした時に、ソースが読みにくい。
今更新しいgemとして作る程のものでもないんですが、コードベースが軽量で機能を限定したものを自前で用意しておいた方が、自分としては楽だろうと思ったのでgemにしてみました。
多分、active_attr, virtus, attrio等と比べてコードベースはめっちゃ小さくなってます。

その他のgemは色々と情報を保持するために読み取り時にタイプキャストをかけるんですが、単純なattr_accessorをラップするだけ、という作りにするために書き込み時にタイプキャストを行うようにしています。
後、2.0以降専用にしてデフォルト値の書き込みにModule#prependを使ってコードを簡易化してます。

サンプル

require 'attr_typecastable'

# Custome typecaster definition
class CastToMoney < AttrTypecastable::Types::Base
  private

  # must define #do_typecast method
  def do_typecast(value)
    # can use @options
    # @options is hash given to `typed_attr_accessor`
    return value if value.is_a?(Money)

    if value.is_a?(Integer)
      Money.new(value)
    else
      raise AttrTypecastable::Types::CastError, "Cannot convert to Money"
    end
  end
end

class User
  # define typed_attr_accessor and initialize hook
  include AttrTypecastable

  typed_attr_accessor :name, String

  # String typecaster uses #to_s
  typed_attr_accessor :default_name, String, default: "Foo"
  typed_attr_accessor :not_nil_name, String, allow_nil: false, default: ""

  # Date typecaster uses #to_date
  typed_attr_accessor :birthday, Date

  # Time typecaster uses #to_time
  typed_attr_accessor :birthday_time, Time

  # DateTime typecaster uses #to_time and #to_datetime
  typed_attr_accessor :birthday_datetime, DateTime

  # Integer typecaster uses #to_i
  typed_attr_accessor :age, Integer

  # Float typecaster uses #to_f
  typed_attr_accessor :bmi, Float

  # Custom Typecaster
  typed_attr_accessor :property, CastToMoney

  # Boolean typecaster uses `#===` method in order to check whether value is true or false.
  typed_attr_accessor :adult, Boolean # default true_values: ["true", 1], false_values: ["false", 0]
  typed_attr_accessor :admin, Boolean, true_value: ["yes"]
  typed_attr_accessor :active, Boolean, true_value: [/true/i] # can use Regexp

  typed_attr_accessor :skills, AttrTypecastable::Types::ArrayFactory.build(String), default: []

  def initialize(name: nil, default_name: nil)
    self.name = name
    self.default_name = default_name
  end
end

user = User.new
user.name          # => nil
user.default_name  # => "Foo"

user.name = 1
user.name          # => "1"

user.birthday = "1990-10-1"
user.birthday      # => Mon, 01 Oct 1990

user.adult = "true"
user.adult         # => true

user.adult = "false"
user.adult         # => false

user.adult = "other"
user.adult         # => true

user.admin = "yes"
user.admin         # => true

user.active = "TrUe"
user.active        # => true

user.skills = "ruby"
user.skills        # => ["ruby"]
user.skills = 1
user.skills        # => ["1"]
user.skills = ["ruby", "js", 1, [2]]
user.skills        # => ["ruby", "js", "1", "[2]"]

user2 = User.new(default_name: "joker")
user2.default_name # => "joker"
12
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
12