Module: Mangrove::Enum

Extended by:
T::Helpers, T::Sig
Defined in:
lib/mangrove/enum.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(receiver) ⇒ void

This method returns an undefined value.

Parameters:

  • receiver (T::Class[T.anything])


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/mangrove/enum.rb', line 120

def self.extended(receiver)
  code = <<~RUBY
    extend T::Sig
    extend T::Helpers

    abstract!

    def self.deserialize(hash)
      klass = const_get(hash[:type] || hash["type"])
      value = hash[:value] || hash["value"]

      begin
        klass.new(value)
      rescue TypeError
        klass.new(klass.deserialize(value))
      end
    end
  RUBY

  receiver.class_eval(code)
end

Instance Method Details

#variants(&block) ⇒ void

This method returns an undefined value.

Parameters:

  • block (T.proc.bind(EnumVariantsScope).void)


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/mangrove/enum.rb', line 35

def variants(&block)
  receiver = block.send(:binding).receiver

  outer_code = <<~RUBY
    def self.const_missing(id)
      code = <<~NESTED_RUBY
        class \#{id} < \#{caller.send(:binding).receiver.name}
          def initialize(inner)
            @inner = T.let(inner, self.class.instance_variable_get(:@__mangrove__enum_inner_type))
          end

          def inner
            @inner
          end

          def as_super
            T.cast(self, \#{caller.send(:binding).receiver.name})
          end

          def serialize(inner_serialization_methods: [:serialize, :to_h])
            serialized_inner = begin
              serialized_klass = [
                Hash,
                String,
                Numeric,
                TrueClass,
                FalseClass,
                NilClass,
                Array
              ]

              if serialized_klass.any? { |klass| @inner.is_a?(klass) }
                @inner
              else
                inner_serialization_methods.find do |method_name|
                  if @inner.respond_to?(method_name, true)
                    maybe_hash = @inner.send(method_name)

                    if maybe_hash.is_a?(Hash)
                      break maybe_hash
                    end
                  end
                end
              end
            end

            { type: self.class.name, value: serialized_inner }
          end

          def ==(other)
            other.is_a?(self.class) && other.inner == @inner
          end
        end
      NESTED_RUBY

      class_eval(code, __FILE__, __LINE__ + 1)
      class_eval(id.to_s, __FILE__, __LINE__ + 1)
    end
  RUBY

  original_const_missing = receiver.method(:const_missing)

  receiver.class_eval(outer_code, __FILE__, __LINE__ + 1)
  EnumVariantsScope.new(receiver).call(&block)

  # Mark as sealed hear because we can not define classes in const_missing after marking as sealed
  receiver.send(:eval, "sealed!", nil, __FILE__, __LINE__)

  variants = constants.filter_map { |variant_name|
    maybe_variant = receiver.const_get(variant_name, false)

    if maybe_variant.instance_variable_defined?(:@__mangrove__enum_inner_type)
      maybe_variant
    end
  }

  receiver.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
    @sorbet_sealed_module_all_subclasses = #{variants} # @sorbet_sealed_module_all_subclasses = #{variants}
  RUBY

  # Bring back the original const_missing
  receiver.define_singleton_method(:const_missing, original_const_missing)
end