ActiveRecord의 attributes API를 사용하여 새로운 유형을 만들고 싶습니다.

하고 싶은 일


  • ActiveRecord attributes API를 사용하여 integer/string/date/time과 같은 기본 유형을 기반으로 새 유형을 만들고 싶습니다.
  • 기사중의 예로서는, Date 를 베이스로 한 Birthday (생일형)를 작성해, 거기에 age (연령) 메소드를 생긴다.
  • 검증 버전은 Rails 5.2.0

  • 배경


    # users テーブルには birthday という date 型の属性を持っていることを前提としている.
    #
    class User < ApplicationRecord
      # ユーザは誕生日の属性を持っている。そして、誕生日から導出可能な年齢を知っている。
      def age(today: Date.current)
        return nil unless birthday
    
        result = today.year - birthday.year
        result -= 1 if today < birthday + result.years
        result
      end
    end
    

    이 코드의 문제점으로는, 예를 들면 User 외에 Administrator 라고 하는 비슷한 모델이 있어, 그 쪽에도 생일이 있었다고 했을 때에 비슷한 (그대로) 코드가 복수 개소에 존재하게 되기 어려워 라는 것.
    # users と同様に、 administrators テーブルには birthday という date 型の属性を持っていることを前提としています.
    #
    class Administrator < ApplicationRecord
      # 同じことを書いてる。。。。。。。。。
      def age(today: Date.current)
        return nil unless birthday
    
        result = today.year - birthday.year
        result -= 1 if today < birthday + result.years
        result
      end
    end
    

    or 모듈로 작성?

    app/models/concerns/age_calculable.rb
    module AgeCalculable
      def age(today: Date.current)
        return nil unless birthday
    
        result = today.year - birthday.year
        result -= 1 if today < birthday + result.years
        result
      end
    end
    
    User.include AgeCalculable
    Administrator.include AgeCalculable
    

    or 생일부터 연령을 도출하는 클래스 정의를 만들어 거기에 위양하는 손은 있다.
    class AgeCalculator
      def initialize(birthday)
        @birthday = birthday
      end
    
      def age(today: Date.current)
        return nil unless birthday
    
        result = today.year - birthday.year
        result -= 1 if today < birthday + result.years
        result
      end
    
      private
    
      attr_reader :birthday
    end
    
    class User < ApplicationRecord
      delegate :age, to: :age_calculator
    
      private
    
      def age_calculator
        AgeCalculator.new(birthday)
      end
    end
    

    가능하면 이 케이스의 이상으로서는 생일 클래스는 연령을 알고 있는 것이 좋다(※)라고 생각된다.

    이런 코드로 쓰고 싶다


    User 모델의 birthday 속성은 Birthday 클래스의 인스턴스입니다.
    Birthday 클래스는, 메소드 age 를 말할 수가 있다.
    user = User.new(birthday: Date.new(2000, 1, 1))
    
    # birthday が age を知っている
    user.birthday.age  #=> 19
    
    # 上記前提で age メッセージの応答を birthday に委譲するパターン
    class User
      delegate :age, to: :birthday, allow_nil: true
    end
    user = User.new(birthday: Date.new(2000, 1, 1))
    user.age  #=> 19
    

    구현



    Birthday 및 Birthday::Type



    우선은 Birthday 클래스를 정의해, ActiveRecord 레이어에서의 시리얼라이즈 및 디시리얼라이즈 방법을 가지는 클래스도 정의한다.
    Date 나 Integer 등, ActiveRecord 의 속성에 매핑 된 클래스에 위양하는 클래스를 만들면 비교적 간단. 그렇지 않은 경우는 ActiveModel::Type::Value 클래스 구현 (을)를 씹어, 이것을 상속해 덧쓰기해야 할 메소드 정의를 기술하게 된다.

    app/values/birthday.rb
    require 'delegate'
    
    class Birthday < DelegateClass(Date)
      # Birthday クラス独自のメソッド記述
      def age(today: Date.current)
        result = today.year - year
        result -= 1 if today < self + result.years
        result
      end
    
      class Type < ActiveRecord::Type::Date
        def type
          :birthday
        end
    
        private
    
        def cast_value(value)
          # 自身の型であるなら dup を返す. そうでない場合は基底クラスに任せたあとその初期化結果をもとに自身のインスタンスを作る.
          case value
          when Birthday then value.dup
          else super.then { |ret| Birthday.new(ret) if ret }
          end
        end
      end
    end
    

    ActiveRecord::Type.register로 형식 등록



    config/initializers/active_record.rb
    require 'active_record/type'
    
    Rails.application.config.to_prepare do
      ActiveModel::Type.register(:birthday, Birthday::Type)
      ActiveRecord::Type.register(:birthday, Birthday::Type)
    end
    

    사용


    # 今回の場合、実際のデータベースの型は基底の date 型で
    bundle exec rails g model User birthday:date
    

    app/models/user.rb
    class User < ApplicationRecord
      # attribute :birthday, :date
      attribute :birthday, :birthday  # date 型改め birthday 型
    
      delegate :age, to: :birthday, allow_nil: true
    end
    

    할 수 있었다


    user = User.new(birthday: Date.new(2000, 1, 1))
    user.age  #=> 19
    user = User.new(birthday: '2010-12-31')
    user.age  #=> 8
    

    요약


  • ActiveRecord (ActiveModel) attributes API를 사용하여 고유 한 유형의 속성을 만들 수 있습니다
  • 기존의 형태를 확장한 형태를 만들 경우는 기존의 형태에의 위양을 전제로 하면 간단


  • ※ 프레임워크에 밀접하게 의존하지 않는 편이 좋다는 관점이 있습니다. 하지만 여기서는 일단 그렇게하기로 결정하십시오

    좋은 웹페이지 즐겨찾기