Completed
Push — master ( fc6b68...5ba60e )
by Marcelo
44s
created

ClassMethods.array_of()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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