Skip to content

Instantly share code, notes, and snippets.

@jneen
Created November 30, 2014 06:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jneen/00b3882d3240e959e29c to your computer and use it in GitHub Desktop.
Save jneen/00b3882d3240e959e29c to your computer and use it in GitHub Desktop.
class Variant
class Caser
attr_reader :value
def initialize(variant)
@variant = variant
@invoked = false
end
def try_case(tag, &b)
if @variant.tag == tag
@invoked = true
@value = yield(*@variant.values)
end
end
def invoked?
@invoked
end
end
def self.spec
@spec ||= {}
end
def self.caser
@caser ||= Class.new(Caser)
end
attr_reader :values
def initialize(*values)
@values = values
end
def self.variant(tag, *names)
parent_class = self
klass = spec[tag] = Class.new(self)
klass.class_eval do
names.each_with_index do |name, i|
define_method(name) { @values[i] }
end
define_method(:type) { parent_class }
define_method(:tag) { tag }
end
caser.class_eval do
define_method(tag) { |&b| try_case(tag, &b) }
end
(class << self; self; end).class_eval do
define_method(tag) { |*values| klass.new(*values) }
end
end
def inspect
"#<#{type}.#{tag}(#{values.map(&:inspect).join(', ')})>"
end
def cases(cases={}, &b)
if block_given?
caser = self.class.caser.new(self)
yield caser
if caser.invoked?
caser.value
else
non_exhaustive_cases!
end
else
selection = cases[tag] || cases[:else] || non_exhaustive_cases!
selection.call(*values)
end
end
end
class Order < Variant
variant :delivery, :address
variant :digital, :email
variant :pickup, :store_id
end
# n constructors
# Order.delivery('123 Main St')
# Order.digital('customer@example.com')
# Order.pickup(456)
#
# order.cases(
# delivery: ->(address) { "delivering to #{address}" },
# digital: ->(email) { "emailing to #{email}" },
# pickup: ->(store_id) {
# "picking up from #{Store.find(store_id).address}"
# },
# )
#
# order.cases do |c|
# c.delivery { |address| "delivering to #{address}" }
# c.digital { |email| "emailing to #{email}" }
# c.pickup do |store_id|
# "picking up from #{Store.find(store_id).address}"
# end
# end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment