Class: Appfuel::Repository::Base

Inherits:
Object
  • Object
show all
Includes:
Application::AppContainer
Defined in:
lib/appfuel/storage/repository/base.rb

Overview

The generic repository behavior. This represents repo behavior that is agnostic to any storage system. The following is a definition of this patter by Martin Fowler:

"The repository mediates between the domain and data mapping
 layers using a collection-like interface for accessing domain
 objects."

 "Conceptually, a Repository encapsulates the set of objects persisted
  in a data store and the operations performed over them, providing a
  more object-oriented view of the persistence layer."

 https://martinfowler.com/eaaCatalog/repository.html

While we are not a full repository pattern, we are evolving into it. All repositories have access to the application container. They register themselves into the container, as well as handling the cache from the container.

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Application::AppContainer

#app_container, #feature_name, included, #qualify_container_key

Class Attribute Details

.mapperMapper

Mapper holds specific knowledge of storage to domain mappings

Returns:



57
58
59
# File 'lib/appfuel/storage/repository/base.rb', line 57

def mapper
  @mapper ||= create_mapper
end

Class Method Details

.cacheHash

A cache of already resolved domain objects

Returns:

  • (Hash)


73
74
75
# File 'lib/appfuel/storage/repository/base.rb', line 73

def cache
  app_container[:repository_cache]
end

.container_class_typeString

Used when the concrete class is being registered, to construct the container key as a path.

Examples:

features.membership.repositories.user

global.repositories.user

<feature|global>.<container_class_type>.<class|container_key>

Returns:

  • (String)


37
38
39
# File 'lib/appfuel/storage/repository/base.rb', line 37

def container_class_type
  'repositories'
end

.create_mapper(maps = nil) ⇒ Mapper

Factory method to create a mapper. Each concrete Repository will override this.

Parameters:

  • maps (Hash) (defaults to: nil)

    the domain to storage mappings

Returns:



66
67
68
# File 'lib/appfuel/storage/repository/base.rb', line 66

def create_mapper(maps = nil)
  Mapper.new(container_root_name, maps)
end

.inherited(klass) ⇒ Object

Stage the concrete class that is inheriting this for registration. The reason we have to stage the registration is to give the code enough time to mixin the AppContainer functionality needed for registration. Therefore registration is defered until feature initialization.

Parameters:

  • klass (Class)

    the class inheriting this

Returns:

  • nil



49
50
51
52
# File 'lib/appfuel/storage/repository/base.rb', line 49

def inherited(klass)
  stage_class_for_registration(klass)
  nil
end

Instance Method Details

#apply_query_conditions(_result, _criteria, _settings) ⇒ Object

Query conditions can only be applied by a specific type of repo, like a database or elastic search repo. Because of this we will fail if this is not implemented

Parameters:

  • result (Object)

    some type of query relation

  • criteria (SearchCriteria)
  • settings (Settings)

Returns:

  • A query relation



144
145
146
# File 'lib/appfuel/storage/repository/base.rb', line 144

def apply_query_conditions(_result, _criteria, _settings)
  method_not_implemented_error
end

#build(type:, name:, storage:, **inputs) ⇒ Object



206
207
208
209
# File 'lib/appfuel/storage/repository/base.rb', line 206

def build(type:, name:, storage:, **inputs)
  builder = find_entity_builder(type: type, domain_name: name)
  builder.call(storage, inputs)
end

#build_criteria(criteria, settings = nil) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/appfuel/storage/repository/base.rb', line 169

def build_criteria(criteria, settings = nil)
  settings ||= create_settings

  return criteria if criteria?(criteria)

  if criteria.is_a?(String)
    tree = settings.parser.parse(criteria)
    result = settings.transform.apply(tree)
    return result[:search]
  end

  unless criteria.is_a?(Hash)
    fail "criteria must be a String, Hash, or " +
         "Appfuel::Domain::SearchCriteria"
  end
  Criteria.build(criteria)
end

#build_default_entity(domain_name:, storage:) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/appfuel/storage/repository/base.rb', line 229

def build_default_entity(domain_name:, storage:)
  storage = [storage] unless storage.is_a?(Array)

  storage_attrs = {}
  storage.each do |model|
    storage_attrs.merge!(mapper.model_attributes(model))
  end

  hash = mapper.to_entity_hash(domain_name, storage_attrs)
  key  = qualify_container_key(domain_name, "domains")
  app_container[key].new(hash)
end

#build_domains(_result, _criteria, _settings) ⇒ Object

Domain resolution can only be applied by specific repos. Because of this we fail if is not implmented

Parameters:

  • result (Object)

    some type of query relation

  • criteria (SearchCriteria)
  • settings (Settings)

Returns:

  • A query relation



155
156
157
# File 'lib/appfuel/storage/repository/base.rb', line 155

def build_domains(_result, _criteria, _settings)
  method_not_implemented_error
end

#create_settings(settings = {}) ⇒ Object

Factory method to create repo settings. This holds things like pagination details, parser classes etc..

Returns:

  • Settings



164
165
166
167
# File 'lib/appfuel/storage/repository/base.rb', line 164

def create_settings(settings = {})
  return settings if settings.instance_of?(Settings)
  Settings.new(settings)
end

#criteria?(value) ⇒ Boolean

Returns:

  • (Boolean)


187
188
189
# File 'lib/appfuel/storage/repository/base.rb', line 187

def criteria?(value)
  value.instance_of?(Criteria)
end

#execute_query_method(query_method, criteria, settings) ⇒ Object

Validate the method exists and call it with the criteria and settings objects

Returns:

  • DomainCollection



91
92
93
94
95
96
97
# File 'lib/appfuel/storage/repository/base.rb', line 91

def execute_query_method(query_method, criteria, settings)
  unless respond_to?(query_method)
    fail "Could not execute query method (#{query_method})"
  end

  public_send(query_method, criteria, settings)
end

#exists?(criteria) ⇒ Boolean

Returns:

  • (Boolean)


191
192
193
194
# File 'lib/appfuel/storage/repository/base.rb', line 191

def exists?(criteria)
  expr = criteria.fiilters
  mapper.exists?(expr)
end

#find_entity_builder(domain_name:, type:) ⇒ Object

features.membership.presenters.hash.user global.presenters.user

key => db_model key => db_model



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/appfuel/storage/repository/base.rb', line 216

def find_entity_builder(domain_name:, type:)
  key = qualify_container_key(domain_name, "domain_builders.#{type}")

  container = app_container
  unless container.key?(key)
    return ->(data, inputs = {}) {
      build_default_entity(domain_name: domain_name, storage: data)
    }
  end

  container[key]
end

#generate_uuidObject



242
243
244
# File 'lib/appfuel/storage/repository/base.rb', line 242

def generate_uuid
  SecureRandom.uuid
end

#mapperMapper

Returns:



80
81
82
# File 'lib/appfuel/storage/repository/base.rb', line 80

def mapper
  self.class.mapper
end

#query(criteria, settings = {}) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/appfuel/storage/repository/base.rb', line 114

def query(criteria, settings = {})
  settings = create_settings(settings)
  criteria = build_criteria(criteria, settings)

  if settings.manual_query?
    query_method = settings.manual_query
    return execute_query_method(query_method, criteria, settings)
  end

  begin
    result = query_setup(criteria, settings)
    result = handle_query_conditions(criteria, result, settings)
    build_domains(criteria, result, settings)
  rescue => e
    msg = "query failed for #{criteria.domain_name}: " +
          "#{e.class} #{e.message}"
    error = RuntimeError.new(msg)
    error.set_backtrace(e.backtrace)
    raise error
  end
end

#query_setup(criteria, settings) ⇒ Object

The first method called in the query life cycle. It setups up the query method used to return a query relation for the next method in the life cycle. This query method will return a query relation produced by the concrete repo for that domain. The relation is specific to the type of repo, a db repo will return an ActiveRecordRelation for example.

Parameters:

  • criteria (SearchCriteria)
  • settings (Settings)

Returns:

  • (Object)

    A query relation



109
110
111
112
# File 'lib/appfuel/storage/repository/base.rb', line 109

def query_setup(criteria, settings)
  query_method = "#{criteria.domain_basename}_query"
  execute_query_method(query_method, criteria, settings)
end

#timestampObject



251
252
253
# File 'lib/appfuel/storage/repository/base.rb', line 251

def timestamp
  Time.now.utc.iso8601
end

#to_entity(domain_name, type, storage) ⇒ Object



200
201
202
203
204
# File 'lib/appfuel/storage/repository/base.rb', line 200

def to_entity(domain_name, type, storage)
  key  = qualify_container_key(domain_name, "domains")
  hash = mapper.to_entity_hash(domain_name, type, storage)
  app_container[key].new(hash)
end

#to_storage(entity, type, opts = {}) ⇒ Object



196
197
198
# File 'lib/appfuel/storage/repository/base.rb', line 196

def to_storage(entity, type, opts = {})
  mapper.to_storage(entity, type, opts)
end

#url_token(nbr = 32) ⇒ Object



246
247
248
249
# File 'lib/appfuel/storage/repository/base.rb', line 246

def url_token(nbr = 32)
  nbr = Integer(nbr)
  SecureRandom.urlsafe_base64(nbr)
end