Class: Dialekt::Model::MapProperty

Inherits:
BasicProperty show all
Defined in:
lib/dialekt/model/map_property.rb

Overview

Base class for DSL map accessors

Defined Under Namespace

Classes: Entry

Instance Attribute Summary

Attributes inherited from BasicProperty

#name, #type

Instance Method Summary collapse

Methods inherited from BasicProperty

#access_value, #factory, #factory=, #get_value, #set_value, #transformer, #transformer=

Constructor Details

#initialize(name:, key_type: nil, value_type: nil, type: Hash, factory: -> { {} }, transformer: ->(value:) { value&.to_h }) ⇒ MapProperty

Returns a new instance of MapProperty.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/dialekt/model/map_property.rb', line 37

def initialize(
  name:,
  key_type: nil,
  value_type: nil,
  type: Hash,
  factory: -> { {} },
  transformer: ->(value:) { value&.to_h }
)
  super(
    name: name,
    type: type,
    factory: factory,
    transformer: transformer
  )

  @key_type = key_type
  @key_transformer = nil

  @value_type = value_type
  @value_transformer = nil
  @value_factory = nil

  @entries = {}
end

Instance Method Details

#access_entry(entry:, target:, key:, value: EMPTY, &block) ⇒ Object



141
142
143
144
145
146
147
148
149
150
# File 'lib/dialekt/model/map_property.rb', line 141

def access_entry(entry:, target:, key:, value: EMPTY, &block)
  value = if value == EMPTY
    get_entry(entry: entry, target: target, key: key)
  else
    set_entry(entry: entry, target: target, key: key, value: value)
  end

  Docile.dsl_eval(value, &block) if !value.nil? && block
  value
end

#entriesObject



62
63
64
# File 'lib/dialekt/model/map_property.rb', line 62

def entries
  @entries.dup.freeze
end

#entries=(entries) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/dialekt/model/map_property.rb', line 66

def entries=(entries)
  case entries
  when Hash
    @entries = {}

    entries.each do |name, entry|
      if name != entry.name
        raise ArgumentError, "Entry key '#{name}' does not match entry name for '#{entry.name}'"
      end

      define_entry(entry)
    end
  when Enumerable
    @entries = {}
    entries.each { |entry| define_entry(entry) }
  else
    raise ArgumentError, "Entries must be an Enumerable or a Hash"
  end
end

#entry(name, key_type: nil, key_transformer: nil, value_type: nil, value_transformer: nil, value_factory: nil) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/dialekt/model/map_property.rb', line 86

def entry(name, key_type: nil, key_transformer: nil, value_type: nil, value_transformer: nil, value_factory: nil)
  entry = Entry.new(
    name: name.to_sym,
    key_type: key_type || @key_type,
    key_transformer: key_transformer || @key_transformer,
    value_type: value_type || @value_type,
    value_factory: value_factory || @value_factory,
    value_transformer: value_transformer || @value_transformer
  )

  define_entry(entry)
end

#get_entry(entry:, target:, key:) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/dialekt/model/map_property.rb', line 152

def get_entry(entry:, target:, key:)
  map = get_value(shape: map_shape, target: target)

  if entry.key_transformer
    begin
      key = entry.key_transformer.call(object: target, key: key)
    rescue StandardError
      raise ArgumentError, "Cannot transform key '#{key}' for property '#{@name}' (#{entry.name})"
    end
  end

  if entry.value_factory
    map.fetch(key) do
      value = begin
        entry.value_factory.call(object: target, key: key)
      rescue StandardError
        raise StandardError, "Cannot create entry for '#{key}' for property '#{@name}' (#{entry.name})"
      end

      map[key] = value
    end
  else
    map.fetch(key) do
      raise KeyError, "No value for key '#{key}' for property '#{@name}' (#{entry.name})"
    end
  end
end

#key_transformer(transformer = EMPTY) ⇒ Object



215
216
217
# File 'lib/dialekt/model/map_property.rb', line 215

def key_transformer(transformer = EMPTY)
  transformer == EMPTY ? @key_transformer : (@key_transformer = transformer&.call_adapter)
end

#key_type(type = EMPTY) ⇒ Object



211
212
213
# File 'lib/dialekt/model/map_property.rb', line 211

def key_type(type = EMPTY)
  type == EMPTY ? @key_type : (@key_type = type)
end

#map_shapeObject



132
133
134
135
136
137
138
139
# File 'lib/dialekt/model/map_property.rb', line 132

def map_shape
  @map_shape ||= BasicProperty::Shape.new(
    name: @name,
    type: @type,
    factory: @factory,
    transformer: @transformer
  )
end

#set_entry(entry:, target:, key:, value:) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/dialekt/model/map_property.rb', line 180

def set_entry(entry:, target:, key:, value:)
  map = get_value(shape: map_shape, target: target)
  type_checker = target.class.dialekt_type_checker

  if entry.key_transformer
    begin
      key = entry.key_transformer.call(object: target, key: key)
    rescue StandardError
      raise ArgumentError, "Cannot transform key '#{key}' for property '#{@name}' (#{entry.name})"
    end
  end

  unless type_checker.valid?(type: entry.key_type, value: key)
    raise TypeError, "Illegal key type '#{key.class}' for property '#{@name}' (#{entry.name})"
  end

  if entry.value_transformer
    begin
      value = entry.value_transformer.call(object: target, key: key, value: value)
    rescue StandardError
      raise ArgumentError, "Cannot transform value '#{value}' for property '#{@name}' (#{entry.name})"
    end
  end

  unless type_checker.valid?(type: entry.value_type, value: value)
    raise TypeError, "Illegal value type '#{value.class}' for property '#{@name}' (#{entry.name})"
  end

  map.store(key, value)
end

#setup(owner:) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/dialekt/model/map_property.rb', line 99

def setup(owner:)
  super

  property = self

  if @entries.empty?
    raise StandardError, "Please specify a key type or entries for property '#{@name}'" if @key_type.nil?
    raise StandardError, "Please specify a value type or entries for property '#{@name}'" if @value_type.nil?

    define_entry(Entry.new(name: owner.dialekt_inflector.singularize(@name), key_type: @key_type, value_type: @value_type))
  end

  type_checker = owner.class.dialekt_type_checker

  @key_type ||= type_checker.union_type(types: @entries.values.map(&:key_type))
  @value_type ||= type_checker.union_type(types: @entries.values.map(&:value_type))

  owner.define_method(@name) do |value = EMPTY, &block|
    value = property.access_value(shape: property.map_shape, target: self, value: value, &block)
    value.dup.freeze
  end

  owner.define_method(:"#{@name}=") do |value|
    property.set_value(shape: property.map_shape, target: self, value: value)
  end

  @entries.each_value do |entry|
    owner.define_method(entry.name) do |key, value = EMPTY, &block|
      property.access_entry(entry: entry, target: self, key: key, value: value, &block)
    end
  end
end

#to_sObject



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/dialekt/model/map_property.rb', line 231

def to_s
  result = StringIO.new

  result << @name << " (" << self.class.base_name << ") {"
  result << "type: " << @type
  result << ", key_type: " << @key_type
  result << ", value_type: " << @value_type
  result << ", factory: " << @factory.source_info if @factory
  result << ", transformer: " << @transformer.source_info if @transformer
  result << ", entries: [" << @entries.values.map(&:name).join(", ") << "]"
  result << "}"

  result.string
end

#value_factory(factory = EMPTY) ⇒ Object



227
228
229
# File 'lib/dialekt/model/map_property.rb', line 227

def value_factory(factory = EMPTY)
  factory == EMPTY ? @value_factory : (@value_factory = factory&.call_adapter)
end

#value_transformer(transformer = EMPTY) ⇒ Object



223
224
225
# File 'lib/dialekt/model/map_property.rb', line 223

def value_transformer(transformer = EMPTY)
  transformer == EMPTY ? @value_transformer : (@value_transformer = transformer&.call_adapter)
end

#value_type(type = EMPTY) ⇒ Object



219
220
221
# File 'lib/dialekt/model/map_property.rb', line 219

def value_type(type = EMPTY)
  type == EMPTY ? @value_type : (@value_type = type)
end