Skip to content

Instantly share code, notes, and snippets.

@jneen
Created November 30, 2014 06:51

Revisions

  1. Jeanine Adkisson created this gist Nov 30, 2014.
    100 changes: 100 additions & 0 deletions variant.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,100 @@
    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