Class: Foobara::Model

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

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.



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.130/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.130/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.130/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.130/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



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

def abstract
  @is_abstract = true
end

.abstract?Boolean

Returns:

  • (Boolean)


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

def abstract?
  @is_abstract
end

.attribute_namesObject



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

def attribute_names
  attributes_type.element_types.keys
end

.closest_namespace_moduleObject



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'foobara-0.0.130/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



19
20
21
22
23
24
25
26
27
28
29
30
# File 'foobara-0.0.130/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



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.130/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



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

def domain_name
  domain.foobara_domain_name
end

.foobara_model_nameObject



113
114
115
116
117
118
119
# File 'foobara-0.0.130/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



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

def foobara_name
  foobara_model_name
end

.full_model_nameObject



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

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

.install!Object



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.130/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



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

def organization_name
  domain.foobara_organization_name
end

.possible_errors(mutable: true) ⇒ Object



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.130/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



30
31
32
# File 'foobara-0.0.130/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



158
159
160
161
162
163
164
165
166
167
# File 'foobara-0.0.130/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)


103
104
105
# File 'foobara-0.0.130/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



107
108
109
110
111
# File 'foobara-0.0.130/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



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

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

#attributesObject



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

def attributes
  @attributes ||= {}
end

#attributes_with_delegatesObject



241
242
243
244
245
246
247
# File 'foobara-0.0.130/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



288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'foobara-0.0.130/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



302
303
304
305
306
307
308
# File 'foobara-0.0.130/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)


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

def eql?(other)
  self == other
end

#hashObject



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

def hash
  attributes.hash
end

#read_attribute(attribute_name) ⇒ Object



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

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

#read_attribute!(attribute_name) ⇒ Object



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

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

#to_hObject



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

def to_h
  attributes
end

#to_json(*_args) ⇒ Object



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

def to_json(*_args)
  to_h.to_json
end

#valid?Boolean

Returns:

  • (Boolean)


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

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

#validate!Object



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

def validate!
  attributes_type.process_value!(attributes)
end

#validation_errorsObject



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

def validation_errors
  attributes_type.process_value(attributes).errors
end

#write_attribute(attribute_name, value) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
# File 'foobara-0.0.130/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



262
263
264
265
# File 'foobara-0.0.130/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



267
268
269
270
271
# File 'foobara-0.0.130/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



273
274
275
276
277
# File 'foobara-0.0.130/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