Class: Lims::Core::Persistence::Persistor Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/lims-core/persistence/persistor.rb

Overview

This class is abstract.

Base class for all the persistors, needs to implements a ‘self.model`

returning the class to persist. A persistor , is used to save and load it’s cousin class. The specific code of a persistor should be extended by writting a persistor class within the class to persist and module corresponding to the store. The common Persistor architecture would be like this (let’s consider we have a Plate class and a Sequel Persistor). module SequelPersistor end Class Plate

Common to all store
class PlatePersistor < Persistence::Persistor
end

class PlateSequelPersistor < PlatePersistor
  include SequelPersistor
end

end if a base persistor exists for a class but not the store specific one (PlatePersistor exists but PlateSequelPersistor not). If there is a store pecific Persistor module (like SequelPersistor). The equivalent of PlateSequelPersistor will be generated on the fly by deriving the base one and including the mixin. Persistor needs to be registered to be accessible form the session. However, if NO_AUTO_REGISTRATION is not enabled persistors will register themselves. In that case, they will need to be defined in class to persist see Persistor.register_model. If a base peristor for exists for a class but there is no Each instance can get an identity map, and or parameter specific to a session/thread.

  • Methods relative to store are

  • insert : a new object to the store

  • delete : remove an object fromt the store

  • update : modify an existing object from the store.

  • retrieve : get an object from the store.

  • bulk_<method> vs <method> refers to method acting on a list of states

instead of an individual object. Althoug only one version needs to be implemted , the bulk version is prefered for performance reason.

  • raw_<method_ refers when exists to the physical action done to the store

without any side effect on the Session or Persistor. They should not normally be called.

  • Methods relative to parents/children

  • parents : resources needed to be saved BEFORE the resource itself.

  • children : resources needed to be save AFTER the resource itself.

  • deletable_children : resources which needs to be deleted BEFORE the resource itself.

  • deletable_parent : resources which needs to be deleted AFTER the resource itself.

Defined Under Namespace

Classes: DuplicateError, DuplicateIdError, DuplicateObjectError

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(session, *args, &block) ⇒ Persistor

Returns a new instance of Persistor.



92
93
94
95
96
97
# File 'lib/lims-core/persistence/persistor.rb', line 92

def initialize (session, *args, &block)
  @session = session
  @id_to_state = Hash.new { |h,k| h[k] = ResourceState.new(nil, self, k) }
  @object_to_state = Hash.new { |h,k| h[k] = ResourceState.new(k, self) }
  super(*args, &block)
end

Class Method Details

.inherited(subclass) ⇒ Object

Performs an autoregistration if needed. Autoregistration can be skipped by defined NO_AUTO_REGISTRATION on the model class. See Persistor::register_model.



69
70
71
# File 'lib/lims-core/persistence/persistor.rb', line 69

def self.inherited(subclass)
  register_model(subclass)
end

.register_model(subclass) ⇒ Object

Register a sub-persistor to the Session. The name used to register the persistor would be either the name of the model (parent) class or if SESSION_NAME is specified on the model : SESSION_NAME

Parameters:

  • subclass (Class)


78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/lims-core/persistence/persistor.rb', line 78

def self.register_model(subclass)
model = subclass.parent_scope
return if model::const_defined? :NO_AUTO_REGISTRATION

name  =\
  if model::const_defined? :SESSION_NAME 
    model::SESSION_NAME
  else
    name = model.name.split('::').pop
  end

  Session::register_model(name, model)
end

Instance Method Details

#[](id, single = true) ⇒ Object?

Load a model by different criteria. Could be either :

  • an Id

  • a Hash

  • a list of Ids

This method will return either a single object or a list of object, depending of the parameter. Note that loaded object are automatically added to the session.

Parameters:

  • id (Fixnum, Hash)

    the id in the database

  • single (Boolean) (defaults to: true)

    or list of object to return

Returns:

  • (Object, nil)

    nil if object not found.



115
116
117
118
119
120
121
# File 'lib/lims-core/persistence/persistor.rb', line 115

def [](id, single=true)
  case id
  when Fixnum then retrieve(id)
  when Hash then find_by(filter_attributes_on_save(id), single)
  when Array, Enumerable then bulk_retrieve(id)
  end
end

#bind_state_to_id(state) ⇒ Object

Updates the cache so id_to_state reflects state.id

Parameters:

Raises:

  • (RuntimeError)


162
163
164
165
166
167
# File 'lib/lims-core/persistence/persistor.rb', line 162

def bind_state_to_id(state)
  raise RuntimeError, 'Invalid state' if state.persistor != self
  raise DuplicateIdError.new(self, state.id)if @id_to_state.include?(state.id)
  on_object_load(state)
  @id_to_state[state.id] = state
end

#bind_state_to_resource(state) ⇒ Object

Update the cache

Raises:

  • (RuntimeError)


178
179
180
181
182
# File 'lib/lims-core/persistence/persistor.rb', line 178

def bind_state_to_resource(state)
  raise RuntimeError, 'Invalobject state' if state.persistor != self
  raise DuplicateIdError.new(self, state.resource) if @object_to_state.include?(state.resource)
  @object_to_state[state.resource] = state
end

#bulk_delete(states, *params) ⇒ Object

Remove object form the underlying store and Manages them. This method only care about the objects themselves not about theirs parents or children.



285
286
287
288
289
290
291
292
293
# File 'lib/lims-core/persistence/persistor.rb', line 285

def bulk_delete(states, *params)
  # delete theme but leave them in cache
  # in case they need to be displayed.
  states.each do |state|
    state.id.andtap { |id| @id_to_state.delete(id) }
    state.resource #.andtap { |object| @object_to_state.delete(object) }
  end
  bulk_delete_raw(states.map(&:id).compact, *params)
end

#bulk_delete_raw(states, *params) ⇒ Object

This method is abstract.

Physically remove objects from a store.

Raises:

  • (NotImplementedError)


297
298
299
# File 'lib/lims-core/persistence/persistor.rb', line 297

def bulk_delete_raw(states, *params)
  raise NotImplementedError
end

#bulk_insert(states, *params) ⇒ Object

Inserts objects in the underlying store AND manages them. This method only care about the objects themselves not about theirs parents or children. The physical insert in the store must be specified for each store.



278
279
280
# File 'lib/lims-core/persistence/persistor.rb', line 278

def bulk_insert(states, *params)
  states.map { |state| insert(state, *params) }
end

#bulk_retrieve(ids, *params) ⇒ Array<Object]

Retreives a list of objects . @param ids

Returns:



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/lims-core/persistence/persistor.rb', line 330

def bulk_retrieve(ids, *params)
  # create a list of states and load them
  states = StateGroup.new(self, ids.map do |id|
    @id_to_state[id]
  end)

  states.load
  return StateList.new(states.map { |state| state.resource })

  # we need to separate object which need to be loaded
  # from the one which are already in cache
  to_load = ids.reject { |id| id == nil || @id_to_state.include?(id) }
  loaded_states = bulk_load_raw_attributes(to_load, *params) do |att|
    id = att.delete(primary_key)
    new_state_for_attribute(id, att).resource
  end

  bulk_retrieve_children(new_states, *params)
  #bulk_retrieve_parent(new_states, *params)


  ids.map { |id| object_for(id) }
end

#bulk_update(states, *params) ⇒ Object

Updates the store and manages object. Doesn’t care of children or parents.

Parameters:



357
358
359
360
361
362
363
364
365
# File 'lib/lims-core/persistence/persistor.rb', line 357

def bulk_update(states, *params)
  attributes = states.map do |state|
    filter_attributes_on_save(state.resource.attributes).merge(primary_key => state.id)
  end
  bulk_update_raw_attributes(attributes, *params)
  states.each do |state|
    state.updated
  end
end

#children(resource) ⇒ Array<Resource>

List of children , i.e, object which need to be saved AFTER it.

Parameters:

Returns:



387
388
389
# File 'lib/lims-core/persistence/persistor.rb', line 387

def children(resource)
  []
end

#countFixnum

This method is abstract.

Returns the number of object in the store

Returns:

  • (Fixnum)

Raises:

  • (NotImplementedError)


246
247
248
# File 'lib/lims-core/persistence/persistor.rb', line 246

def count
  raise NotImplementedError
end

#deletable_children(resource) ⇒ Object

TODO:


392
393
394
# File 'lib/lims-core/persistence/persistor.rb', line 392

def deletable_children(resource)
  []
end

#deletable_parents(resource) ⇒ Object



396
397
398
# File 'lib/lims-core/persistence/persistor.rb', line 396

def deletable_parents(resource)
  []
end

#dirty_key_for(resource) ⇒ Object

Computes “dirty_key” of an object. The dirty key is used to decide if an object has been modified or not.

Parameters:

Returns:



201
202
203
204
205
# File 'lib/lims-core/persistence/persistor.rb', line 201

def dirty_key_for(resource)
    if resource && @session.dirty_attribute_strategy
      @session.dirty_key_for(filter_attributes_on_save(resource.attributes_for_dirty))
    end
end

#for_each_in_slice(start, length) {|key, attributes| ... } ⇒ Object

This method is abstract.

Load a slice. Doesn’t return an object but a hash allowing to build it.

Parameters:

  • start (Fixnum)

    (0 based)

  • length (Fixnum)

Yield Parameters:

  • key (Fixnum)
  • attributes (Hash)

    of the object

Raises:

  • (NotImplementedError)


257
258
259
# File 'lib/lims-core/persistence/persistor.rb', line 257

def for_each_in_slice(start, length)
  raise NotImplementedError
end

#id_for(object) ⇒ Id, Nil

Get the id from an object from the cache.

Parameters:

  • object (Resource)

    object to find the id for.

Returns:

  • (Id, Nil)


126
127
128
# File 'lib/lims-core/persistence/persistor.rb', line 126

def id_for(object)
  state_for(object).andtap { |state| state.id }
end

#ids_for(criteria) ⇒ Array<Id>

compute a list of ids matching the criteria

Parameters:

  • criteria (Hash)

    list of attribute/value pais

Returns:

Raises:

  • (NotImplementedError)


239
240
241
# File 'lib/lims-core/persistence/persistor.rb', line 239

def ids_for(criteria)
  raise NotImplementedError
end

#invalid_resource?(resource) ⇒ Boolean

if a resource is invalid and need to be deleted. For example an association proxy corresponding to an old relation.

Returns:

  • (Boolean)


403
404
405
# File 'lib/lims-core/persistence/persistor.rb', line 403

def invalid_resource?(resource)
  resource.respond_to?(:invalid?) && resource.invalid?
end

#load_children(states, *params) ⇒ Object



436
437
438
# File 'lib/lims-core/persistence/persistor.rb', line 436

def load_children(states, *params)
  []
end

#modelClass

Associate class (without persistence).

Returns:

  • (Class)


101
102
103
# File 'lib/lims-core/persistence/persistor.rb', line 101

def model
  self.class::Model
end

#new_from_attributes(attributes) ⇒ Object



441
442
443
444
445
# File 'lib/lims-core/persistence/persistor.rb', line 441

def new_from_attributes(attributes)
  id = attributes.delete(primary_key)
  resource = block_given? ? yield(attributes) :   model.new(filter_attributes_on_load(attributes))
  state_for_id(id).tap { |state| state.resource = resource }
end

#new_object(id, attributes) ⇒ Resource

Creates a new object from a Hash and associate it to its id

Parameters:

  • id (Id)

    id of the new object

  • attributes (Hash)

    of the new object.

Returns:



188
189
190
191
192
193
194
# File 'lib/lims-core/persistence/persistor.rb', line 188

def new_object(id, attributes)
  id = attributes.delete(primary_key)
  model.new(filter_attributes_on_load(attributes)).tap do |resource|
    state = state_for_id(id)
    state.resource = resource
  end
end

#object_for(id) ⇒ Resourec, Nil

Get the object from a given id.

Parameters:

  • id (Fixnum)

Returns:

  • (Resourec, Nil)


133
134
135
# File 'lib/lims-core/persistence/persistor.rb', line 133

def object_for(id)
  @id_to_state[id].andtap(&:resource)
end

#on_object_load(state) ⇒ Object

Called by Persistor to inform the session about the loading of an object. MUST be called by persistors creating Resources.

Parameters:



173
174
175
# File 'lib/lims-core/persistence/persistor.rb', line 173

def on_object_load(state)
  @session.manage_state(state)
end

#parents(resource) ⇒ Array<Resource>

List of parents of object, i.e. object which need to be saved BEFORE it. Default implementation get all Resource attributes.

Parameters:

Returns:



380
381
382
# File 'lib/lims-core/persistence/persistor.rb', line 380

def parents(resource)
  resource.attributes.values.select  { |v| v.is_a? Resource }
end

#parents_for_attributes(attributes) ⇒ Object



431
432
433
# File 'lib/lims-core/persistence/persistor.rb', line 431

def parents_for_attributes(attributes)
  []
end

#purge_invalid_objectObject

Delete all invalid object loaded by a persistor. Typically invalid object are association which doesn’t exist anymore



209
210
211
212
213
214
215
216
# File 'lib/lims-core/persistence/persistor.rb', line 209

def purge_invalid_object
  to_delete = StateGroup.new(self, [])
  @object_to_state.each do |object, state|
    to_delete << state if  invalid_resource?(object)
  end

  to_delete.destroy
end

#retrieve(id, *params) ⇒ Object?

Retrieves an object from it’s id. Doesn’t load it if it’s been alreday loaded.

Parameters:

  • id (Id)

Returns:



320
321
322
323
324
325
# File 'lib/lims-core/persistence/persistor.rb', line 320

def retrieve(id, *params)
  object_for(id).andtap { |o| return o }
  objects = bulk_retrieve([id], *params)
  return objects.first if objects && objects.size == 1

end

#slice(start, length) ⇒ Enumerable<Hash>

Get a slice of object by offset, length. start here is an offset (starting at 0) not an Id.

Parameters:

  • start (Fixnum)

    (0 based)

  • length (Fixnum)

Returns:

  • (Enumerable<Hash>)


266
267
268
269
270
271
272
# File 'lib/lims-core/persistence/persistor.rb', line 266

def slice(start, length)
  to_load = StateGroup.new(self, [])
    for_each_in_slice(start, length) do |att|
      to_load << new_from_attributes(att)
    end
  to_load.load.map(&:resource)
end

#state_for(object) ⇒ ResourceState

Returns the state proxy of an object. Creates it if needed.

Parameters:

Returns:



142
143
144
# File 'lib/lims-core/persistence/persistor.rb', line 142

def state_for(object)
  @object_to_state[object]
end

#state_for?(object) ⇒ Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/lims-core/persistence/persistor.rb', line 147

def state_for?(object)
  @object_to_state.include?(object)
end

#state_for_id(id) ⇒ ResourceState

Returns the state proxy of an object fromt its id (in cache). Creates the state if needed.

Parameters:

  • object (Id)

Returns:



155
156
157
# File 'lib/lims-core/persistence/persistor.rb', line 155

def state_for_id(id)
    @id_to_state[id]
end