Class: Lims::Core::Persistence::Session

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/lims-core/persistence/session.rb

Overview

A session is in charge of restoring and saving object throug the persistence layer. A Session can not normally be created by the end user. It has to be in a Store::with_session block, which acts has a transaction and save/update everything at the end of it. It should also provides an identity map. Session information (user, time) are also associated to the modifications of those objects.

Defined Under Namespace

Classes: UnmanagedObjectError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(store, *params) ⇒ Session

param [Store] store the underlying store.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/lims-core/persistence/session.rb', line 40

def initialize(store, *params)
  @store = store
  @object_states = StateList.new
  @in_session = false
  @saved = Set.new
  @persistor_map = {}
  @dirty_attribute_strategy = @store.dirty_attribute_strategy

  
  options = params.extract_options!
  @user ||= options[:user]
  @backend_application_id ||= options[:backend_application_id]
  @parameters ||= options[:parameters]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object



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

def method_missing(name, *args, &block)
  begin
    persistor_for(name)
  rescue NameError
    # No persistor found for the given name
    # Call the normal method_missing
    super(name, *args, &block)
  end
end

Instance Attribute Details

#dirty_attribute_strategyObject

The dirty-attribute strategy decides how object modification is detected to avoid saved unmodified object. The default value comes from the session.



25
26
27
# File 'lib/lims-core/persistence/session.rb', line 25

def dirty_attribute_strategy
  @dirty_attribute_strategy
end

Class Method Details

.find_or_create_persistor_for(model) ⇒ Object (private)



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/lims-core/persistence/session.rb', line 342

def self.find_or_create_persistor_for(model)
  # find the persistor within the class
  # other corresponding to the current session type
  return nil unless model
  session_persistor_class = parent_scope.const_get(:Persistor)
  model.constants(false).each do |name|
    klass = model.const_get(name)
    next unless klass.is_a? Module
    if  klass.ancestors.include?(session_persistor_class)
      # quick hack  to fix JRuby test before refactoring this
      # If we are not in a sequel session, we need to not pick the Seque persistor.
      next if session_persistor_class.name !~ /sequel/i && klass.name =~ /sequel/i
      # found
      return klass
    end
  end
  # not found, we need to create it
  # First we look for the base persistor to inherit from
  #debugger unless superclass.respond_to? :persistor_class_for
  raise "Can't find base persistor for #{model.inspect}"  unless superclass.respond_to? :persistor_class_for
   
  parent_persistor_class = superclass.persistor_class_for(model)

  # if the current persistor (ex Sequel::Persistor) is the same  as the base one
  # there is nothing else to do
  return parent_persistor_class unless parent_scope::const_defined?(:Persistor, false)

  raise  "no Persistor defined for #{model.name}" unless parent_persistor_class
  module_name = parent_scope.name.sub(/.*Persistence::/,'')
  model_name = model.name.split('::').pop
  # the we create a new Persistor class including the Persistor mixin
  # corresponding to the session
  class_declaration = <<-EOV
  class #{model_name}#{module_name}Persistor < #{parent_persistor_class.name}
    include #{parent_scope::Persistor}
  end
  EOV
  model.class_eval class_declaration

end

.model_for(object) ⇒ Symbol (private)

Find the model corresponding to an object Takes many type of input

Parameters:

Returns:

  • (Symbol)


282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/lims-core/persistence/session.rb', line 282

def  self.model_for(object)
  case object
  when nil then nil
  when String then name_to_model(object)
  when Symbol then name_to_model(object)
  when Class then
    # check if the class has been registered
    # IMPORTANT needs to be done before 'when module'
    # because object can class and module at the same time.
    return object if model_to_name(object)

    # if it's already persistor find the associate model
    persistor_class_map.id_for(object) do |model|
      return model
    end



    # check the super class
    model_for(object.superclass).andtap { |model|
      return model
    }

    # Check the owner
    return nil unless object.respond_to? :parent_scope
    model_for(object.parent_scope).andtap { |model|
      return model
    }
  when Module then object
  else
    model_for(object.class)
  end
end

.model_mapObject

The map name <=> model class is shared between all type of session



31
32
33
# File 'lib/lims-core/persistence/session.rb', line 31

def self.model_map()
  @@model_map ||= IdentityMap::Class.new
end

.model_to_name(model) ⇒ Symbol (private)

Find the registered name of a given class @param model

Returns:

  • (Symbol)


261
262
263
# File 'lib/lims-core/persistence/session.rb', line 261

def self.model_to_name(model)
  model_map.id_for(model)
end

.name_to_model(name) ⇒ Class (private)

Find the model class for a registered name registered name are used when doing session.model

Parameters:

  • name (String, Symbol)

Returns:

  • (Class)


254
255
256
# File 'lib/lims-core/persistence/session.rb', line 254

def self.name_to_model(name)
  model_map.object_for(name.to_s)
end

.pack_uuid(uuid) ⇒ Object

Pack if needed an uuid to its store representation This method is need to lookup an uuid by name

Parameters:

  • uuid (String)

Returns:



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

def self.pack_uuid(uuid)
  uuid
end

.persistor_class_for(object) ⇒ Class (private)

Parameters:

Returns:

  • (Class)


327
328
329
330
331
332
333
334
335
336
# File 'lib/lims-core/persistence/session.rb', line 327

def self.persistor_class_for(object)
  model = model_for(object)

  persistor = persistor_class_map.object_for(model)
  unless persistor
  persistor = find_or_create_persistor_for(model)
  persistor_class_map.map_id_object(model, persistor)
  end
  persistor
end

.persistor_class_mapObject (private)



338
339
340
# File 'lib/lims-core/persistence/session.rb', line 338

def self.persistor_class_map()
  @persistor_class_map ||= IdentityMap::Class.new
end

.persistor_name_for(object) ⇒ Object (private)



316
317
318
319
# File 'lib/lims-core/persistence/session.rb', line 316

def self.persistor_name_for(object)
  model = model_for(object)
  model_to_name(model)
end

.register_model(name, model) ⇒ Object (private)

Register a model for a given name. This name will be looked up when calling session.<name> Persistors need to be registered.

Parameters:

  • name (String, Symbol)
  • model (Class)


270
271
272
273
274
275
# File 'lib/lims-core/persistence/session.rb', line 270

def self.register_model(name, model)
  name = name.to_s.snakecase
  # skip if name already registered with the same object
  return if model_map.object_for(name) == model
  model_map.map_id_object(name, model)
end

.unpack_uuid(puuid) ⇒ String

Unpac if needed an uuid from its store representation

Parameters:

Returns:

  • (String)


189
190
191
# File 'lib/lims-core/persistence/session.rb', line 189

def self.unpack_uuid(puuid)
  puuid
end

Instance Method Details

#<<(object) ⇒ Object

Tell the session to be responsible of an object. The object will be saved at the end of the session.

Examples:

store.with_session do |session|
  session << Plate.new
end

Parameters:

  • object (Persistable)

    the object to persist.

Returns:

  • the session, to allow for chaining



104
105
106
107
# File 'lib/lims-core/persistence/session.rb', line 104

def << (object)
  manage_state(state_for(object))
  self
end

#delete(object) ⇒ Object

Mark an object as to be deleted. The corresponding object will be deleted at the end of the session. For most object you don’t need to load it to delete it but some needs (to delete the appropriate children). The real delete is made by calling the #delete_in_real method.



168
169
170
171
172
# File 'lib/lims-core/persistence/session.rb', line 168

def delete(object)
  raise UnmanagedObjectError, "can't delete #{object.inspect}" unless managed?(object)
  state = state_for(object)
  state.mark_for_deletion
end

#dirty_key_for(object) ⇒ Object



206
207
208
209
210
211
212
213
# File 'lib/lims-core/persistence/session.rb', line 206

def dirty_key_for(object)
  case @dirty_attribute_strategy
  when Store::DIRTY_ATTRIBUTE_STRATEGY_DEEP_COPY then object
  when Store::DIRTY_ATTRIBUTE_STRATEGY_SHA1 then Digest::SHA1.hexdigest(Lims::Core::Helpers::to_json(object))
  when Store::DIRTY_ATTRIBUTE_STRATEGY_MD5 then Digest::MD5.hexdigest(Lims::Core::Helpers::to_json(object))
  when Store::DIRTY_ATTRIBUTE_STRATEGY_QUICK_HASH then object.hash
  end
end

#filter(persistor) ⇒ Persistor (private)

Create a new persistor sharing the same internal parameters but with the “context” (datasest) of the new one. This can be used to “reset” a filtered persistor to the current session.

Parameters:

Returns:



239
240
241
242
243
244
245
246
247
248
# File 'lib/lims-core/persistence/session.rb', line 239

def filter(persistor)
  # If the persistor session is the current session, there is nothing to do
  # just return the object as it is.
  return persistor if  persistor.instance_eval {@session} == self

  # we need first to find the original persistor, ie the one  that the user can call via
  # session.model
  original = persistor_for(persistor.class)
  persistor.class.new(original, persistor.dataset)
end

#id_for(object) ⇒ Id?

Returns the id of an object if exists.

Parameters:

Returns:

  • (Id, nil)


127
128
129
130
131
132
# File 'lib/lims-core/persistence/session.rb', line 127

def id_for(object)
  case object
  when Resource then persistor_for(object).id_for(object)
  else object # the object should be already an id
  end
end

#id_for!(object) ⇒ Id

Returns the id of an object and save it if necessary

Parameters:

Returns:

  • (Id)


150
151
152
153
# File 'lib/lims-core/persistence/session.rb', line 150

def id_for!(object)
  return nil unless object
  id_for(object) || save(object)
end

#manage_state(state) ⇒ Object



109
110
111
# File 'lib/lims-core/persistence/session.rb', line 109

def manage_state(state)
  @object_states << state
end

#managed?(object) ⇒ Boolean

Check if the session ‘mananage’ already this object. .i.e if it’s been loaded or meant to be saved

Parameters:

Returns:

  • (Boolean)


159
160
161
# File 'lib/lims-core/persistence/session.rb', line 159

def managed?(object)
  persistor_for(object).state_for?(object)
end

#pack_uuid(uuid) ⇒ Object



182
183
184
# File 'lib/lims-core/persistence/session.rb', line 182

def pack_uuid(uuid)
  self.class.pack_uuid(uuid)
end

#persistor_for(object) ⇒ Persistor?

Get the persistor corresponding to the object class

Parameters:

Returns:



387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/lims-core/persistence/session.rb', line 387

def persistor_for(object)
  if object.is_a?(Persistor)
    return filter(object)
  end

  model = self.class.model_for(object)
  @persistor_map[model]  ||= begin
    persistor_class = self.class.persistor_class_for(model)
    raise NameError, "no persistor defined for #{object.class.name}" unless persistor_class &&  persistor_class.ancestors.include?(Persistor)
    persistor_class.new(self)
  end
end

#persistor_name_for(object) ⇒ Object (private)



321
322
323
# File 'lib/lims-core/persistence/session.rb', line 321

def persistor_name_for(object)
  self.class.persistor_name_for(object)
end

#save_allObject (private)

save all objects which needs to be



217
218
219
220
221
222
223
224
# File 'lib/lims-core/persistence/session.rb', line 217

def save_all()
  transaction do
    @save_in_progress = true # allows saving
    @object_states.reset_status
    @object_states.save
    end
  @save_in_progress = false
end

#serialize(object) ⇒ Object

TODO:

doc



198
199
200
# File 'lib/lims-core/persistence/session.rb', line 198

def serialize(object)
  object
end

#state_for(object) ⇒ ResourceState

Get or creates the ResourceState corresponding to an object.

Parameters:

Returns:



137
138
139
# File 'lib/lims-core/persistence/session.rb', line 137

def state_for(object)
  return persistor_for(object).state_for(object)
end

#states_for(objects) ⇒ Object



141
142
143
# File 'lib/lims-core/persistence/session.rb', line 141

def states_for(objects)
  objects && objects.map { |o| state_for(o) }
end

#transactionObject (private)

Execute the provided block within a transaction Here to be overriden if needed



228
229
230
231
232
# File 'lib/lims-core/persistence/session.rb', line 228

def transaction
   @store.transaction do
    yield
  end
end

#unpack_uuid(uuid) ⇒ Object



193
194
195
# File 'lib/lims-core/persistence/session.rb', line 193

def unpack_uuid(uuid)
  self.class.unpack_uuid(uuid)
end

#unserialize(object) ⇒ Object



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

def unserialize(object)
  object
end

#with_session(*params) {|session| ... } ⇒ Object

Execute a block and save every ‘marked’ object in a transaction at the End.

Yield Parameters:

  • session (Session)

    the created session.

Returns:

  • the value of the block



62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/lims-core/persistence/session.rb', line 62

def with_session(*params, &block)
  return block[self] if @in_session
  begin
    @in_session = true
    to_return = block[self]
    @in_session = false
    save_all
    return to_return
  ensure
    @in_session = false
  end
end

#with_subsession(*params, &block) ⇒ Object

Subsession allow to create a session within a session sharing the same persistor but saving only the object managed by the subsession. The current implementation doesn’t create new session but just push some session attributes. The problem about creating a new Session, we want them to share ResourceState, but a state own a persistor which in turn own a session, so it’s easier if the session is the same.



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/lims-core/persistence/session.rb', line 82

def with_subsession(*params, &block)
  backup = [@object_states, @in_session, @saved]
  @object_states = StateList.new
  @in_session = false
  @saved = Set.new

  return_value = with_session(*params, &block)
  
  @object_states, @in_session, @saved = backup

  return_value
end