Permalink
Browse files

[fixes #28076359] Add Persistence::Logger

[fixes #28291495] Refactor persistence

Add Logger::Store and Session.
Refactor as well the normal persistence by adding a Persistor class.
This is needed to dry up the code between the Sequel and the Logger
persistor.
  • Loading branch information...
1 parent 816dde2 commit a810da5128663b7dc528f91cac0f2bb60b1f8e8c @maxigit maxigit committed Apr 22, 2012
@@ -1,4 +1,5 @@
#vi: ts=2 sw=2 et
+require 'facets/string'
module Lims::Core
@@ -8,5 +9,42 @@ module Lims::Core
# Persistors are mixins specific to each persistence types.
# For example, see the {Sequel::Persistor}.
module Persistence
+
+ # Creates all the missing for a submodule.
+ # Needs to be called once for each submodule
+ def self.finalize_submodule(mod)
+ require_all("#{mod.name.pathize.sub('lims/core/',"")}/*")
+ generate_missing_classes(self, mod, mod::Persistor)
+ end
+
+ # Generate all the 'missing' persistors (.i.e. those
+ # existing in the main Persistence module but which
+ # haven't been subclassed.
+ # @param mod the module to add class into.
+ def self.generate_missing_classes(base, mod, persistor)
+ (base.constants(false)-mod.constants(false)).map { |c| base.const_get(c) }.each do |klass|
+ case
+ when klass == Persistor then next
+ when klass.ancestors.include?(Persistor)
+ generate_persistor(klass, mod, persistor)
+ end
+ end
+ end
+
+ # Generate a persistor inheriting from klass
+ # and extended with the 'local' persistor.
+ def self.generate_persistor(klass, mod, persistor)
+ class_name = klass.name.split("::").last
+ generated_class = mod.class_eval %Q{
+ class #{class_name} < ::#{klass.name}
+ include ::#{persistor.name}
+ end
+ }
+
+ # generate nested classes, ex Plate::Well
+ generate_missing_classes(klass, generated_class, persistor)
+ end
end
end
+
+require_all('persistence/*.rb')
@@ -1,6 +1,7 @@
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
+require 'lims/core/persistence/persistor'
require 'lims/core/laboratory/aliquot'
module Lims::Core
@@ -9,7 +10,7 @@ module Persistence
# Base for all Aliquot persistor.
# Real implementation classes (e.g. Sequel::Aliquot) should
# include the suitable persistor.
- class Aliquot
+ class Aliquot < Persistor
Model = Laboratory::Aliquot
end
end
@@ -1,6 +1,7 @@
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
+require 'lims/core/persistence/persistor'
require 'lims/core/laboratory/flowcell'
module Lims::Core
@@ -9,15 +10,21 @@ module Persistence
# Base for all Flowcell persistor.
# Real implementation classes (e.g. Sequel::Flowcell) should
# include the suitable persistor.
- class Flowcell
+ class Flowcell < Persistor
Model = Laboratory::Flowcell
# @abstract
# Base for all Lane persistor.
# Real implementation classes (e.g. Sequel::Lane) should
# include the suitable persistor.
- class Lane
+ class Lane < Persistor
Model = Laboratory::Flowcell::Lane
+ def save(lane, flowcell_id, position)
+ #todo bulk save if needed
+ lane.each do |aliquot|
+ save_as_aggregation(flowcell_id, aliquot, position)
+ end
+ end
end
end
end
@@ -0,0 +1,52 @@
+# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
+
+module Lims::Core
+ module Persistence
+ module Logger
+ # Mixin giving extended the persistor classes with
+ # the Logger (save) behavior.
+ module Persistor
+ private
+
+ # Load an object to the underlying logger
+ # @param [Resource] object the object
+ # @return the Id if save successful
+ def save_raw(object, *params)
+ case object
+ when Resource then @session.log("#{object.class.name}: #{object.attributes}")
+ else
+ @session.log("#{object.inspect}")
+ end
+ object
+ end
+
+ # Overriden the default save new to add indentation
+ # around children
+ def save_new(object, *params)
+ save_raw(object, *params).tap do |id|
+ @session.with_indent("- ") { save_children(id, object) }
+ end
+ end
+
+ # Upate a raw object, i.e. the object attributes
+ # excluding any associations.
+ # @param [Resource] object the object
+ # @param [Fixnum] id the Id of the object
+ # @return [Fixnum, nil] the id
+ def update_raw(object, id, *params)
+ id.tap do
+ save_raw(object, *params)
+ end
+ end
+
+ def save_as_aggregation(source_id, target, *params)
+ @session.with_indent("#{params} - ") do
+ super(source_id, target)
+ end
+ end
+ def save_raw_association(source_id, target_id, *params)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,30 @@
+# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
+
+require 'logger'
+require 'lims/core/persistence/session'
+
+module Lims::Core
+ module Persistence
+ module Logger
+ # Logger specific implementation of a {Persistence::Session Session}.
+ class Session < Persistence::Session
+
+ attr_reader :indent_level
+ def initialize(*args, &block)
+ @indent_level = ""
+ super(*args, &block)
+ end
+
+ def log(msg)
+ @store.log(indent_level+msg)
+ end
+
+ # Execute a block with the specified indent level indicator.
+ # @param [String] indent the indent level indicator
+ def with_indent(indent=" - ", &block)
+ temporarily('@indent_level' => @indent_level+indent, &block)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,36 @@
+# vi: ts=2:sts=2:et:sw=2
+
+require 'logger'
+require 'lims/core/persistence'
+require 'lims/core/persistence/store'
+
+module Lims::Core
+ module Persistence
+ module Logger
+ # An Logger::Store, a store 'logging' object instead of
+ # saving them.
+ class Store < Persistence::Store
+ attr_reader :logger
+ attr_reader :method
+
+ # Create a store with an underlying logger.
+ # @param [Logger, file] logger
+ # @param [Symbol, String] method the method call to
+ # send information to the logger.
+ def initialize(logger, method=:info, *args)
+ @logger = case logger
+ when ::Logger then logger
+ else ::Logger.new(logger)
+ end
+ @method = method
+ super(*args)
+ end
+
+ def log(msg)
+ @logger.send(@method, msg)
+ end
+ end
+ end
+ finalize_submodule(Logger)
+ end
+end
@@ -0,0 +1,155 @@
+# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
+
+require 'lims/core/persistence/identity_map'
+
+
+module Lims::Core
+ module Persistence
+ # @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 module in the sub-persistence module. This module will be
+ # automatically included to generated class. See {Persistence::finalize_module}.
+ # Each instance can get an identity map, and or parameter
+ # specific to a session/thread.
+ class Persistor
+ include IdentityMap
+
+ def initialize (session, *args, &block)
+ @session = session
+ super(*args, &block)
+ end
+
+ # Associate class (without persistence).
+ # @return [Class]
+ def model
+ self.class::Model
+ end
+
+ # Load a model by id
+ # Note that loaded object are automatically _added_ to the session.
+ # @param [Fixnum] id the id in the database
+ # @return [Object,nil] nil if object not found.
+ def [](id)
+ case id
+ when Fixnum then load_single_model(id)
+ end
+ end
+
+ # save an object and return is id or nil if failure
+ # @return [Fixnum, nil]
+ def save(object, *params)
+ id_for(object) { |id| update(object, id, *params) } ||
+ map_id_object(save_new(object, *params) , object)
+ end
+
+ # save an association
+ # Association doesn't have necessarily and id
+ # therefore we don't use the indentity map.
+ # Save objects if needed.
+ # @param [Resource, Id] source the source of the association.
+ # @param [Resource, Id] target the target of the association.
+ # @param [Hash] params specific to the Store.
+ def save_as_association(source, target, *params)
+ save_raw_association(@session.id_for!(source), @session.id_for!(target), *params)
+ end
+
+
+ # save an aggregation
+ # Aggregation doesn't have necessarily and id
+ # therefore we don't use the indentity map.
+ # Save objects if needed.
+ # Aggregation differs from association in the fact that the 'parent' is already saved
+ # and the children have to be saved.
+ # @param [id] source_id
+ # @param [Resource] target will be saved.
+ # @param [Hash] params specific to the Store.
+ def save_as_aggregation(source_id, target, *params)
+ save_raw_association(source_id, @session.save(target), *params)
+ end
+
+ # Load a model object (and its children) from its database id.
+ # @param [Id] id in the database
+ # @return [Resource] the model object.
+ # @raises error if object doesn't exists.
+ def load_single_model(id)
+ object_for(id) || load_raw_object(id).tap do |m|
+ map_id_object(id, m)
+ load_children(id, m)
+ @session.on_object_load(m)
+ end
+ end
+
+
+ private
+ # The primary key
+ # @return [Symbol]
+ def primary_key()
+ :id
+ end
+
+ # load the object without any dependency
+ # @param id identifier of the object
+ # @return the loaded object
+ def load_raw_object(id)
+ raise NotImplementedError
+ end
+
+ # Called to save a new object, i.e. which is not
+ # already in the database.
+ # @param [Resource] object the object
+ # @return [Fixnum, nil] the Id if save successful
+ def save_new(object, *params)
+ save_raw(object, *params).tap do |id|
+ save_children(id, object)
+ end
+ end
+
+ # @param object the object to save
+ def save_raw(object, *params)
+ raise NotImplementedError
+ end
+
+ # Save a object already in the database
+ # @param [Resource] object the object
+ # @param [Fixum] id id in the database
+ # @return [Fixnum, nil] the Id if save successful.
+ def update(object, id, *params)
+ # naive version , update everything.
+ # Probably quicker than trying to guess what has changed
+ id.tap do
+ update_raw(object, id, *params)
+ dataset[primary_key => id].update(object.attributes)
+ update_children(id, object)
+ end
+ end
+
+ # save children of a newly created object.
+ # @param [Fixum] id id in the database
+ # @param [Resource] object the object
+ def save_children(id, object)
+
+ end
+
+ # save children of an existing object.
+ # @param [Fixum] id id in the database
+ # @param [Resource] object the object
+ def update_children(id, object)
+ delete_children(id, object)
+ save_children(id, object)
+ end
+
+ def delete_children(id, object)
+ raise NotImplementedError
+ end
+
+ # Loads children from the database and set the to model object.
+ # @param id primary key of the model object in the database.
+ # @param m instance of model to load
+ def load_children(id, m)
+ raise NotImplementedError
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit a810da5

Please sign in to comment.