Completed
Push — master ( 17b1aa...4db615 )
by Marcelo
36s
created

ClassMethods.attribute()   B

Complexity

Conditions 2

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 26
rs 8.8571
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
        end
141
142
        @_attributes_factory.freeze
143
144
        @builder = nil
145
      end
146
147
      def attribute(name, adapter, lambda_arg = nil, &block)
148
        unless @builder
149
          raise Error, 'You must call .attribute inside the .attributes block'
150
        end
151
152
        block = lambda_arg || block
153
        @_attributes_factory.add_attribute(name, adapter, &block)
154
155
        instance_eval do
156
          define_method name do
157
            attributes.send(name)
158
          end
159
160
          define_method "#{name}=" do |value|
161
            attributes.send("#{name}=", value)
162
          end
163
164
          define_method "with_#{name}" do |value|
165
            attributes.send("with_#{name}", value)
166
          end
167
168
          define_method :== do |other|
169
            attributes == other.send(:attributes)
170
          end
171
        end
172
      end
173
174
      def array_of(item_adapter)
175
        adapter = @_attributes_factory.coerce_adapter(item_adapter)
176
        Adapter::ArrayOf.new(adapter)
177
      end
178
179
      def hash_of(key_adapter, value_adapter)
180
        key_adapter = @_attributes_factory.coerce_adapter(key_adapter)
181
        value_adapter = @_attributes_factory.coerce_adapter(value_adapter)
182
        Adapter::HashOf.new(key_adapter, value_adapter)
183
      end
184
    end
185
  end
186
end
187