Completed
Pull Request — master (#10)
by Marcelo
35s
created

ClassMethods.attributes()   B

Complexity

Conditions 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
require 'koine/attributes/version'
2
require 'koine/attributes/adapter/base'
3
4
# provides the following API
5
#
6
# @example using attributes
7
#   class Person
8
#     include Koine::Attributes
9
#
10
#     attributes do
11
#       attribute :name, :string
12
#       attribute :birthday, :date
13
#
14
#       # or
15
#       attribute :birthday, Koine::Attributes::Adapter::Date.new
16
#     end
17
#   end
18
#
19
#   peson = Person.new
20
#   person.name = 'John Doe'
21
#   person.birtday = '2001-02-31' # Date Object can also be given
22
#
23
#   person.name # => 'John Doe'
24
#   person.birtday # => #<Date 2001-02-31>
25
#
26
# @example custom attribute options
27
#
28
#     attributes do
29
#       attribute :name, Koine::Attributes::Adapters::Date.new.with_default('guest')
30
#
31
#       # or
32
#       attribute :name, :string, ->(adapter) { adapter.with_default('guest') }
33
#     end
34
#
35
# @example Constructor for attributes
36
#
37
#   class Person
38
#     include Koine::Attributes
39
#
40
#     attributes initializer: true do
41
#       attribute :name, :string
42
#       attribute :birthday, :date
43
#     end
44
#   end
45
#
46
#   person = Person.new(name: 'John Doe', birthday: '2001-01-31')
47
#
48
#   # foo: attribute will raise error with strict mode
49
#   person = Person.new(name: 'John Doe', birthday: '2001-01-31', foo: :bar)
50
#
51
# @example Constructor for attributes withouth strict mode
52
#
53
#   class Person
54
#     include Koine::Attributes
55
#
56
#     attributes initializer: { strict: false } do
57
#       attribute :name, :string
58
#       attribute :birthday, :date
59
#     end
60
#   end
61
#
62
#   # foo will be ignored
63
#   person = Person.new(name: 'John Doe', birthday: '2001-01-31', foo: :bar)
64
#
65
# @example Override constructor
66
#
67
#   class Person
68
#     include Koine::Attributes
69
#
70
#     attr_reader :foo
71
#
72
#     attributes initializer: true do
73
#       attribute :name, :string
74
#       attribute :birthday, :date
75
#     end
76
#
77
#     def initialize(attributes = {})
78
#       @foo = attributes.delete(:foo)
79
#       self.attributes.set_values(attributes)
80
#     end
81
#   end
82
#
83
#   person = Person.new(name: 'John Doe', birthday: '2001-01-31', foo: :bar)
84
#   person.foo # => :bar
85
#
86
# @example
87
#  class Location
88
#    include Koine::Attributes
89
#
90
#    attributes initializer: { freeze: true } do
91
#      attribute :lat, :float
92
#      attribute :lon, :float
93
#    end
94
#  end
95
#
96
#  location = Location.new(lat: 1, lon: 2)
97
#  new_location = location.with_lon(3)
98
#
99
module Koine
100
  module Attributes
101
    autoload :Attributes, 'koine/attributes/attributes'
102
    autoload :AttributesFactory, 'koine/attributes/attributes_factory'
103
104
    module Adapter
105
      autoload :Any, 'koine/attributes/adapter/any'
106
      autoload :ArrayOf, 'koine/attributes/adapter/array_of'
107
      autoload :Boolean, 'koine/attributes/adapter/boolean'
108
      autoload :Date, 'koine/attributes/adapter/date'
109
      autoload :Float, 'koine/attributes/adapter/float'
110
      autoload :HashOf, 'koine/attributes/adapter/hash_of'
111
      autoload :Integer, 'koine/attributes/adapter/integer'
112
      autoload :String, 'koine/attributes/adapter/string'
113
      autoload :Symbol, 'koine/attributes/adapter/symbol'
114
      autoload :Time, 'koine/attributes/adapter/time'
115
    end
116
117
    Error = Class.new(StandardError)
118
119
    def self.included(base)
120
      base.extend(Forwardable)
121
      base.extend(ClassMethods)
122
    end
123
124
    module ClassMethods
125
      private
126
127
      def attributes(options = {}, &block)
128
        @builder = true
129
        @_attributes_factory ||= AttributesFactory.new(options)
130
        class_eval(&block)
131
132
        instance_eval do
133
          define_method :attributes do
134
            @_attributes ||= self.class.instance_variable_get(:@_attributes_factory).create(self)
135
          end
136
137
          private :attributes
138
139
          define_method(:initialize) { |*args| attributes.initialize_values(*args) }
140
141
          define_method(:inspect) do
142
            hex_id = '%x' % (object_id << 1)
143
            "#<#{self.class}:0x00#{hex_id} @attributes=#{attributes.to_h.inspect}>"
144
          end
145
        end
146
147
        @_attributes_factory.freeze
148
149
        @builder = nil
150
      end
151
152
      def attribute(name, adapter, lambda_arg = nil, &block)
153
        unless @builder
154
          raise Error, 'You must call .attribute inside the .attributes block'
155
        end
156
157
        block = lambda_arg || block
158
        @_attributes_factory.add_attribute(name, adapter, &block)
159
160
        instance_eval do
161
          define_method name do
162
            attributes.send(name)
163
          end
164
165
          define_method "#{name}=" do |value|
166
            attributes.send("#{name}=", value)
167
          end
168
169
          define_method "with_#{name}" do |value|
170
            attributes.send("with_#{name}", value)
171
          end
172
173
          define_method :== do |other|
174
            attributes == other.send(:attributes)
175
          end
176
        end
177
      end
178
179
      def array_of(item_adapter)
180
        adapter = @_attributes_factory.coerce_adapter(item_adapter)
181
        Adapter::ArrayOf.new(adapter)
182
      end
183
184
      def hash_of(key_adapter, value_adapter)
185
        key_adapter = @_attributes_factory.coerce_adapter(key_adapter)
186
        value_adapter = @_attributes_factory.coerce_adapter(value_adapter)
187
        Adapter::HashOf.new(key_adapter, value_adapter)
188
      end
189
    end
190
  end
191
end
192