Skip to content

Instantly share code, notes, and snippets.

@inopinatus
Created December 14, 2020 22:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inopinatus/1b4f55cf06a53aba66f7fd0b2bbb0f0b to your computer and use it in GitHub Desktop.
Save inopinatus/1b4f55cf06a53aba66f7fd0b2bbb0f0b to your computer and use it in GitHub Desktop.
Value object that replaced an enum
# app/models/organisation.rb
# schema like:
#
# create_table "organisations", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
# t.string "transponder", default: "my_laps", null: false
#
class Organisation < ApplicationRecord
attribute :transponder, TransponderType.new
# app/lib/transponder.rb
module Transponder
class << self
def find(transponder_name)
transponders[transponder_name.to_sym] || Transponder::None
end
def register_transponder(transponder_name, klass)
transponders[transponder_name.to_sym] = klass
end
def known
transponders.keys
end
private
def transponders
@_transponders ||= {}
end
end
class Base
include Comparable
def self.identifier
name.demodulize.underscore.to_sym
end
def inspect
"#<Transponder:#{self.class.identifier}>"
end
def description
raise NotImplementedError
end
def format_description
raise NotImplementedError
end
def input_pattern
raise NotImplementedError
end
def regex
raise NotImplementedError
end
def normalize(value)
return nil if value.blank?
value.strip.upcase
end
def valid?(value)
regex.match?(value)
end
def <=>(other)
self.class <=> other.class
end
def to_s
self.class.identifier.to_s
end
def to_partial_path
"transponders/#{self}"
end
def blank?
false
end
private
def self.inherited(subclass)
Transponder.register_transponder(subclass.identifier, subclass)
end
end
class None < Base
def description
"None"
end
def regex
//
end
def format_description
"(blank)"
end
def blank?
true
end
def to_s
""
end
def input_pattern
""
end
def to_partial_path
"transponders/none"
end
end
class MyLaps < Base
def description
"MyLaps"
end
def format_description
"AA-12345"
end
def input_pattern
'^[a-zA-Z]{2}-\d{5}$'
end
def regex
/\A[a-zA-Z]{2}-[0-9]{3,8}\z/
end
end
class HexQuad < Base
def description
"Hex Quad"
end
def format_description
"four hex digits"
end
def input_pattern
'^[0-9A-Fa-f]{4}$'
end
def regex
/\A[0-9A-F]{4}\z/
end
end
end
# app/types/transponder_type.rb
class TransponderType < ActiveModel::Type::Value
def cast(value)
case value
when String, Symbol then Transponder.find(value).new
when Transponder::Base then value
when nil then Transponder::None
else fail TypeError, "can't make Transponder from #{value.inspect}"
end
end
def serialize(value)
value.to_s
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment