ActiveRecord::Enum으로 빠지기 쉬운 실수를 피하십시오

ActiveRecord::Enum이라는 기능이 Rails4.1부터 시작되었습니다.
부담없이 사용할 수 있으므로, 나누기와 적극적으로 사용하고 있습니다만 그 안인 문제가 발생했습니다.

ActiveRecord::Enum의 제공해 주는 표준으로 제공해 주는 인터페이스만으로는 *1, where의 인수로 지정하는 값은 실제로 SQL의 WHERE구로 지정되는 것이 건네주기 때문에 본래는 캐릭터 라인을 사용할 수 없다 하지만 왠지 사용할 수 있기 때문에 전달해 보면 값이 0으로 동작한다는 문제입니다

다음은 예입니다. NotifyEndpoint라는 Push 알림의 대상을 영속화하는 테이블에 대해 생각한 경우 platform으로 iPhone 용 APNS와 Android 용 GCM 등의 플랫폼을 enum으로 지정해 보겠습니다.
class NotifyEndpoint
  enum platform: [:apns, :gcm]
end
# OK: newやcreateでは渡せる
endpoint = NotifyEndpoint.new(platform: :apns)

# OK: platform=メソッドにも渡せる
endpoint.platform = :gcm

# OK: apnsというスコープは定義されている
endpoints = NotifyEndpoint.apns

# NG: whereに文字列やシンボルを渡しても正しく動作しない
#     しかしエラーが出ずに0としてSELECTされる!!
endpoints = NotifyEndpoint.where(platform: :gcm)
#=> SELECT * FROM notify_endoints WHERE platform = 0;

# OK: NotifyEndpoint.platformsから、enumの序数を取得してwhereで指定する
endpoints = NotifyEndpoint.where(platform: NotifyEndpoint.platforms[:gcm])
#=> SELECT * FROM notify_endoints WHERE platform = 1;

와 같이, 기본적으로는 캐릭터 라인 심볼로 enum를 취급할 수 있습니다만, 표준으로 정의되는 스코프 이외로, SELECT 하고 싶을 때는 요주의입니다. where에는 enum의 요소의 이름을 받아들이지 않습니다만 에러가 나오지 않기 때문에 어렵습니다. 정상 케이스의 테스트가 1개째의 요소 밖에 테스트하고 있지 않으면 보통으로 다니므로 실수로 버그를 넣어 버립니다

그렇다고 해서 매회 NotifyEndpoint.where(platform: NotifyEndpoint.platforms[:gcm])와 같은 기술 방법을 하는 것은 클래스명 2회 써야 하고, enum의 서수에 대해 일일이 의식하지 않으면 안 되는 것은 귀찮습니다.

↑의 문제를 회피하면서, 번거롭지 않은 기법을 할 수 있도록 enum명으로 scope를 살려주는 패치를 썼습니다 lib/active_record/enum/scoping.rb 등에 두고 사용하면 사용할 수 있다고 생각합니다.
if defined?(ActiveRecord) &&  defined?(::ActiveRecord::Enum)
  module ActiveRecord
    module Enum
      module Scoping
        def self.extended(base)
          ::ActiveRecord::Enum.alias_method_chain(:enum, :scoping)
        end
      end

      def enum_with_scoping(definitions)
        enum_without_scoping(definitions)
        define_scoping_method(definitions)
      end

      private
      def define_scoping_method(definitions)
        definitions.each do |name, values|
          scoping_method_name =  "#{name}_as".to_sym
          scope(scoping_method_name, - > (item) { enum_scope(name, item) }})
        end
      end
    end

    def enum_scope(enum_name, item_name)
      query_hash =  {}
      query_hash[enum_name.to_sym] =  send(enum_name.to_s.pluralize)[item_name]
      where(query_hash)
    end
  end

  ::ActiveRecord::Base.extend(::ActiveRecord::Enum::Scoping)
end

이것을 도입하면 이하와 같이, <enum_name>_as 라고 하는 스코프가 정의되므로, 캐릭터 라인·심볼을 건네주어도 동작하게 되었고, 일일이 클래스 메소드로 값을 변환하거나, 스스로 매회 범위를 정의하지 않고 끝내기
endpoints = NotifyEndpoint.platform_as(:gcm)
#=> SELECT * FROM notify_endpoints WHERE platform = 1;

수요가 있으면 gem으로 해봅시다.
  • *1(실장은 그리 길지 않으므로 읽어 보는 것을 추천합니다
    htps : // 기주 b. 코 m/라이 ls/라이 ls/bぉb/마s테 r/아 c ゔぇ레코 rd/ぃb/아c ゔぇ_레코 rd/에누 m. rb )
  • 좋은 웹페이지 즐겨찾기