Class: Foobara::Model

Inherits:
Object
  • Object
show all
Includes:
Concerns::Aliases, Concerns::Classes, Concerns::Reflection, Concerns::Types
Defined in:
foobara-0.0.125/projects/model/src/model.rb,
foobara-0.0.125/projects/model/lib/foobara/model.rb,
foobara-0.0.125/projects/model/src/concerns/types.rb,
foobara-0.0.125/projects/model/src/concerns/aliases.rb,
foobara-0.0.125/projects/model/src/concerns/classes.rb,
foobara-0.0.125/projects/model/src/concerns/reflection.rb,
foobara-0.0.125/projects/model/src/sensitive_type_removers/model.rb,
foobara-0.0.125/projects/model/src/sensitive_value_removers/model.rb,
foobara-0.0.125/projects/model/src/sensitive_type_removers/extended_model.rb
more...

Overview

TODO: either make this an abstract base class of ValueModel and Entity or rename it to ValueModel and have Entity inherit from it… TODO: also, why is this at the root level instead of in a project??

Defined Under Namespace

Modules: Concerns, SensitiveTypeRemovers, SensitiveValueRemovers Classes: AttributeIsImmutableError, NoSuchAttributeError

Constant Summary collapse

ALLOWED_OPTIONS =
[:validate, :mutable, :ignore_unexpected_attributes, :skip_validations].freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Concern

foobara_class_methods_module_for, foobara_concern?, included

Constructor Details

#initialize(attributes = nil, options = {}) ⇒ Model

Returns a new instance of Model.

[View source]

176
177
178
179
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'foobara-0.0.125/projects/model/src/model.rb', line 176

def initialize(attributes = nil, options = {})
  invalid_options = options.keys - ALLOWED_OPTIONS

  unless invalid_options.empty?
    # :nocov:
    raise ArgumentError, "Invalid options #{invalid_options} expected only #{ALLOWED_OPTIONS}"
    # :nocov:
  end

  self.skip_validations = options[:skip_validations]

  if options[:ignore_unexpected_attributes]
    Thread.with_inheritable_thread_local_var(:foobara_ignore_unexpected_attributes, true) do
      initialize(attributes, options.except(:ignore_unexpected_attributes))
      return
    end
  end

  validate = options[:validate]

  if attributes.nil?
    if validate
      # :nocov:
      raise ArgumentError, "Cannot use validate option without attributes"
      # :nocov:
    end
  else
    if Thread.inheritable_thread_local_var_get(:foobara_ignore_unexpected_attributes)
      outcome = attributes_type.process_value(attributes)

      if outcome.success?
        attributes = outcome.result
      end
    end

    self.mutable = true
    attributes.each_pair do |attribute_name, value|
      write_attribute(attribute_name, value)
    end
  end

  mutable = if options.key?(:mutable)
              options[:mutable]
            elsif self.class.model_type.declaration_data.key?(:mutable)
              self.class.model_type.declaration_data[:mutable]
            else
              # why do we default to true here but false in the transformers?
              true
            end

  self.mutable = if mutable.is_a?(::Array)
                   mutable.map(&:to_sym)
                 else
                   mutable
                 end

  validate! if validate # TODO: test this code path
end

Class Attribute Details

.is_abstractObject

Returns the value of attribute is_abstract.


17
18
19
# File 'foobara-0.0.125/projects/model/src/model.rb', line 17

def is_abstract
  @is_abstract
end

Instance Attribute Details

#mutableObject

Returns the value of attribute mutable.


172
173
174
# File 'foobara-0.0.125/projects/model/src/model.rb', line 172

def mutable
  @mutable
end

#skip_validationsObject

Returns the value of attribute skip_validations.


172
173
174
# File 'foobara-0.0.125/projects/model/src/model.rb', line 172

def skip_validations
  @skip_validations
end

Class Method Details

.abstractObject

TODO: would be nice to make this a universal concept via a concern

[View source]

41
42
43
# File 'foobara-0.0.125/projects/model/src/model.rb', line 41

def abstract
  @is_abstract = true
end

.abstract?Boolean

Returns:

  • (Boolean)
[View source]

45
46
47
# File 'foobara-0.0.125/projects/model/src/model.rb', line 45

def abstract?
  @is_abstract
end

.attribute_namesObject

[View source]

99
100
101
# File 'foobara-0.0.125/projects/model/src/model.rb', line 99

def attribute_names
  attributes_type.element_types.keys
end

.closest_namespace_moduleObject

[View source]

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'foobara-0.0.125/projects/model/src/model.rb', line 49

def closest_namespace_module
  # TODO: Feels like we should use the autoset_namespace helpers here
  mod = Util.module_for(self)

  while mod
    if mod.is_a?(Namespace::IsNamespace)
      namespace = mod
      break
    end

    mod = Util.module_for(mod)
  end

  if mod.nil? || mod == GlobalOrganization || mod == Foobara
    GlobalDomain
  else
    namespace
  end
end

.description(*args) ⇒ Object

[View source]

19
20
21
22
23
24
25
26
27
28
29
30
# File 'foobara-0.0.125/projects/model/src/model.rb', line 19

def description(*args)
  case args.size
  when 0
    @description
  when 1
    @description = args.first
  else
    # :nocov:
    raise ArgumentError, "expected 0 or 1 argument, got #{args.size}"
    # :nocov:
  end
end

.domainObject

[View source]

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
# File 'foobara-0.0.125/projects/model/src/model.rb', line 69

def domain
  if model_type
    domain = model_type.foobara_domain

    if domain == GlobalDomain
      module_name = model_type.declaration_data[:model_module]

      begin
        Domain.to_domain(module_name)
      rescue Domain::NoSuchDomain
        module_to_check = module_name

        loop do
          domain = if Object.const_defined?(module_to_check)
                     return Domain.domain_through_modules(Object.const_get(module_to_check))
                   elsif module_to_check.include?("::")
                     module_to_check = module_to_check.split("::")[..-2].join("::")
                   else
                     return GlobalDomain
                   end
        end
      end
    else
      domain
    end
  else
    Domain.domain_through_modules(self)
  end
end

.domain_nameObject

[View source]

36
37
38
# File 'foobara-0.0.125/projects/model/src/model.rb', line 36

def domain_name
  domain.foobara_domain_name
end

.foobara_model_nameObject

[View source]

113
114
115
116
117
118
119
# File 'foobara-0.0.125/projects/model/src/model.rb', line 113

def foobara_model_name
  if foobara_type&.scoped_path_set?
    foobara_type.scoped_name
  else
    Util.non_full_name(self) || model_name&.split("::")&.last
  end
end

.foobara_nameObject

[View source]

121
122
123
# File 'foobara-0.0.125/projects/model/src/model.rb', line 121

def foobara_name
  foobara_model_name
end

.full_model_nameObject

[View source]

125
126
127
# File 'foobara-0.0.125/projects/model/src/model.rb', line 125

def full_model_name
  [*model_type&.scoped_full_name, model_name].max_by(&:size)
end

.install!Object

[View source]

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'foobara-0.0.125/projects/model/lib/foobara/model.rb', line 8

def install!
  model_handler = TypeDeclarations::Handlers::ExtendModelTypeDeclaration.new
  TypeDeclarations.register_type_declaration(model_handler)
  extended_model_handler = TypeDeclarations::Handlers::ExtendRegisteredModelTypeDeclaration.new
  TypeDeclarations.register_type_declaration(extended_model_handler)

  TypeDeclarations.register_sensitive_type_remover(SensitiveTypeRemovers::Model.new(model_handler))
  TypeDeclarations.register_sensitive_value_remover(model_handler, SensitiveValueRemovers::Model)
  TypeDeclarations.register_sensitive_type_remover(
    SensitiveTypeRemovers::ExtendedModel.new(extended_model_handler)
  )
  # TypeDeclarations.register_sensitive_value_remover(
  #   extended_model_handler,
  #   SensitiveValueRemovers::ExtendedModel
  # )

  atomic_duck = Namespace.global.foobara_lookup_type!(:atomic_duck)
  BuiltinTypes.build_and_register!(:model, atomic_duck, nil)
  # address = build_and_register!(:address, model)
  # us_address = build_and_register!(:us_address, model)
end

.organization_nameObject

[View source]

32
33
34
# File 'foobara-0.0.125/projects/model/src/model.rb', line 32

def organization_name
  domain.foobara_organization_name
end

.possible_errors(mutable: true) ⇒ Object

[View source]

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'foobara-0.0.125/projects/model/src/model.rb', line 129

def possible_errors(mutable: true)
  if mutable == true
    attributes_type.possible_errors
  elsif mutable
    element_types = attributes_type.element_types

    p = []

    Util.array(mutable).each do |attribute_name|
      attribute_name = attribute_name.to_sym

      # TODO: this doesn't feel quite right... we should be excluding errors so that we don't
      # miss any that are on attributes_type unrelated to the elements.
      element_types[attribute_name].possible_errors.each do |possible_error|
        possible_error = possible_error.dup
        possible_error.prepend_path!(attribute_name)
        p << possible_error
      end
    end

    p
  else
    # Hmmm, can't there still be errors even if it's immutable?
    []
  end
end

.reset_allObject

[View source]

30
31
32
# File 'foobara-0.0.125/projects/model/lib/foobara/model.rb', line 30

def reset_all
  install!
end

.subclass(name:) ⇒ Object

will create an anonymous subclass TODO: change to a normal parameter since it’s just name

[View source]

158
159
160
161
162
163
164
165
166
167
# File 'foobara-0.0.125/projects/model/src/model.rb', line 158

def subclass(name:)
  name = name.to_s if name.is_a?(::Symbol)

  # TODO: How are we going to set the domain and organization?
  Class.new(self) do
    singleton_class.define_method :model_name do
      name
    end
  end
end

.valid_attribute_name?(attribute_name) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

103
104
105
# File 'foobara-0.0.125/projects/model/src/model.rb', line 103

def valid_attribute_name?(attribute_name)
  attribute_names.include?(attribute_name.to_sym)
end

.validate_attribute_name!(attribute_name) ⇒ Object

[View source]

107
108
109
110
111
# File 'foobara-0.0.125/projects/model/src/model.rb', line 107

def validate_attribute_name!(attribute_name)
  unless valid_attribute_name?(attribute_name)
    raise NoSuchAttributeError, "No such attribute #{attribute_name} expected one of #{attribute_names}"
  end
end

Instance Method Details

#==(other) ⇒ Object

[View source]

322
323
324
# File 'foobara-0.0.125/projects/model/src/model.rb', line 322

def ==(other)
  self.class == other.class && attributes == other.attributes
end

#attributesObject

[View source]

237
238
239
# File 'foobara-0.0.125/projects/model/src/model.rb', line 237

def attributes
  @attributes ||= {}
end

#attributes_with_delegatesObject

[View source]

241
242
243
244
245
246
247
# File 'foobara-0.0.125/projects/model/src/model.rb', line 241

def attributes_with_delegates
  h = self.class.delegates.keys.to_h do |delegated_attribute_name|
    [delegated_attribute_name, send(delegated_attribute_name)]
  end

  attributes.merge(h)
end

#cast_attribute(attribute_name, value) ⇒ Object

[View source]

288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'foobara-0.0.125/projects/model/src/model.rb', line 288

def cast_attribute(attribute_name, value)
  attribute_type = attributes_type.element_types[attribute_name]

  return Outcome.success(value) unless attribute_type

  attribute_type.process_value(value).tap do |outcome|
    unless outcome.success?
      outcome.errors.each do |error|
        error.prepend_path!(attribute_name)
      end
    end
  end
end

#cast_attribute!(attribute_name, value) ⇒ Object

[View source]

302
303
304
305
306
307
308
# File 'foobara-0.0.125/projects/model/src/model.rb', line 302

def cast_attribute!(attribute_name, value)
  validate_attribute_name!(attribute_name)

  outcome = cast_attribute(attribute_name, value)
  outcome.raise!
  outcome.result
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

326
327
328
# File 'foobara-0.0.125/projects/model/src/model.rb', line 326

def eql?(other)
  self == other
end

#hashObject

[View source]

330
331
332
# File 'foobara-0.0.125/projects/model/src/model.rb', line 330

def hash
  attributes.hash
end

#read_attribute(attribute_name) ⇒ Object

[View source]

279
280
281
# File 'foobara-0.0.125/projects/model/src/model.rb', line 279

def read_attribute(attribute_name)
  attributes[attribute_name&.to_sym]
end

#read_attribute!(attribute_name) ⇒ Object

[View source]

283
284
285
286
# File 'foobara-0.0.125/projects/model/src/model.rb', line 283

def read_attribute!(attribute_name)
  validate_attribute_name!(attribute_name)
  read_attribute(attribute_name)
end

#to_hObject

[View source]

334
335
336
# File 'foobara-0.0.125/projects/model/src/model.rb', line 334

def to_h
  attributes
end

#to_json(*_args) ⇒ Object

[View source]

338
339
340
# File 'foobara-0.0.125/projects/model/src/model.rb', line 338

def to_json(*_args)
  to_h.to_json
end

#valid?Boolean

Returns:

  • (Boolean)
[View source]

310
311
312
# File 'foobara-0.0.125/projects/model/src/model.rb', line 310

def valid?
  attributes_type.process_value(attributes).success?
end

#validate!Object

[View source]

318
319
320
# File 'foobara-0.0.125/projects/model/src/model.rb', line 318

def validate!
  attributes_type.process_value!(attributes)
end

#validation_errorsObject

[View source]

314
315
316
# File 'foobara-0.0.125/projects/model/src/model.rb', line 314

def validation_errors
  attributes_type.process_value(attributes).errors
end

#write_attribute(attribute_name, value) ⇒ Object

[View source]

249
250
251
252
253
254
255
256
257
258
259
260
# File 'foobara-0.0.125/projects/model/src/model.rb', line 249

def write_attribute(attribute_name, value)
  attribute_name = attribute_name.to_sym

  if mutable == true || (mutable != false && mutable&.include?(attribute_name))
    outcome = cast_attribute(attribute_name, value)
    attributes[attribute_name] = outcome.success? ? outcome.result : value
  else
    # :nocov:
    raise AttributeIsImmutableError, "Cannot write attribute #{attribute_name} because it is not mutable"
    # :nocov:
  end
end

#write_attribute!(attribute_name, value) ⇒ Object

[View source]

262
263
264
265
# File 'foobara-0.0.125/projects/model/src/model.rb', line 262

def write_attribute!(attribute_name, value)
  attribute_name = attribute_name.to_sym
  attributes[attribute_name] = cast_attribute!(attribute_name, value)
end

#write_attributes(attributes) ⇒ Object

[View source]

267
268
269
270
271
# File 'foobara-0.0.125/projects/model/src/model.rb', line 267

def write_attributes(attributes)
  attributes.each_pair do |attribute_name, value|
    write_attribute(attribute_name, value)
  end
end

#write_attributes!(attributes) ⇒ Object

[View source]

273
274
275
276
277
# File 'foobara-0.0.125/projects/model/src/model.rb', line 273

def write_attributes!(attributes)
  attributes.each_pair do |attribute_name, value|
    write_attribute!(attribute_name, value)
  end
end