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, included, #qualify_container_key

Class Attribute Details

.mapperMapper

Mapper holds specific knowledge of storage to domain mappings

Returns:



55
56
57
# File 'lib/appfuel/storage/repository/base.rb', line 55

def mapper
  @mapper ||= create_mapper
end

Class Method Details

.cacheHash

A cache of already resolved domain objects

Returns:

  • (Hash)


71
72
73
# File 'lib/appfuel/storage/repository/base.rb', line 71

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)


35
36
37
# File 'lib/appfuel/storage/repository/base.rb', line 35

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:



64
65
66
# File 'lib/appfuel/storage/repository/base.rb', line 64

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



47
48
49
50
# File 'lib/appfuel/storage/repository/base.rb', line 47

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



142
143
144
# File 'lib/appfuel/storage/repository/base.rb', line 142

def apply_query_conditions(_result, _criteria, _settings)
  method_not_implemented_error
end

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



204
205
206
207
# File 'lib/appfuel/storage/repository/base.rb', line 204

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



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

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



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

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



153
154
155
# File 'lib/appfuel/storage/repository/base.rb', line 153

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



162
163
164
165
# File 'lib/appfuel/storage/repository/base.rb', line 162

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

#criteria?(value) ⇒ Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/appfuel/storage/repository/base.rb', line 185

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



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

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)


189
190
191
192
# File 'lib/appfuel/storage/repository/base.rb', line 189

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



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

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

#mapperMapper

Returns:



78
79
80
# File 'lib/appfuel/storage/repository/base.rb', line 78

def mapper
  self.class.mapper
end

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



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

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



107
108
109
110
# File 'lib/appfuel/storage/repository/base.rb', line 107

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

#to_entity(domain_name, storage) ⇒ Object



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

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

#to_storage(entity, exclude = []) ⇒ Object



194
195
196
# File 'lib/appfuel/storage/repository/base.rb', line 194

def to_storage(entity, exclude = [])
  mapper.to_storage(entity, exclude)
end