lims-core 3.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.rvmrc +2 -0
- data/.travis.yml +2 -0
- data/.vimrc +27 -0
- data/.yard_templates/default/layout/html/footer.erb +0 -0
- data/.yardopts +1 -0
- data/Gemfile +54 -0
- data/Gemfile.lock +197 -0
- data/Guardfile +21 -0
- data/Guardfile.tmux +28 -0
- data/README.markdown +67 -0
- data/Rakefile +16 -0
- data/config/database.yml +16 -0
- data/doc/Array.html +116 -0
- data/doc/Array/ArrayLoggerPersistor.html +152 -0
- data/doc/Lims.html +114 -0
- data/doc/Lims/Core.html +178 -0
- data/doc/Lims/Core/Action.html +91 -0
- data/doc/Lims/Core/Actions.html +116 -0
- data/doc/Lims/Core/Actions/Action.html +216 -0
- data/doc/Lims/Core/Actions/Action/AfterEval.html +853 -0
- data/doc/Lims/Core/Actions/Action/InvalidParameters.html +268 -0
- data/doc/Lims/Core/Actions/ActionGroup.html +196 -0
- data/doc/Lims/Core/Actions/ActionGroup/AfterEval.html +315 -0
- data/doc/Lims/Core/Actions/BulkAction.html +224 -0
- data/doc/Lims/Core/Actions/BulkAction/AfterEval.html +253 -0
- data/doc/Lims/Core/Actions/TestActionGroup.html +101 -0
- data/doc/Lims/Core/Actions/TestActionGroup/Action.html +133 -0
- data/doc/Lims/Core/Actions/TestActionGroup/ActionGroup.html +127 -0
- data/doc/Lims/Core/Base.html +287 -0
- data/doc/Lims/Core/Base/AccessibleViaSuper.html +252 -0
- data/doc/Lims/Core/Base/ClassMethod.html +177 -0
- data/doc/Lims/Core/Base/HashString.html +177 -0
- data/doc/Lims/Core/Base/IsArrayOf.html +606 -0
- data/doc/Lims/Core/Base/State.html +130 -0
- data/doc/Lims/Core/Organization.html +113 -0
- data/doc/Lims/Core/Organization/Batch.html +106 -0
- data/doc/Lims/Core/Persistence.html +267 -0
- data/doc/Lims/Core/Persistence/ComparisonFilter.html +318 -0
- data/doc/Lims/Core/Persistence/Filter.html +252 -0
- data/doc/Lims/Core/Persistence/IdentityMap.html +409 -0
- data/doc/Lims/Core/Persistence/IdentityMap/Class.html +144 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateError.html +126 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateIdError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateObjectError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMapClass.html +133 -0
- data/doc/Lims/Core/Persistence/Logger.html +105 -0
- data/doc/Lims/Core/Persistence/Logger/Persistor.html +334 -0
- data/doc/Lims/Core/Persistence/Logger/Session.html +452 -0
- data/doc/Lims/Core/Persistence/Logger/Store.html +470 -0
- data/doc/Lims/Core/Persistence/MessageBus.html +871 -0
- data/doc/Lims/Core/Persistence/MessageBus/ConnectionError.html +123 -0
- data/doc/Lims/Core/Persistence/MessageBus/InvalidSettingsError.html +122 -0
- data/doc/Lims/Core/Persistence/MultiCriteriaFilter.html +293 -0
- data/doc/Lims/Core/Persistence/PersistAssociationTrait.html +91 -0
- data/doc/Lims/Core/Persistence/PersistableTrait.html +91 -0
- data/doc/Lims/Core/Persistence/Persistor.html +3072 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateError.html +205 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateIdError.html +147 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateObjectError.html +147 -0
- data/doc/Lims/Core/Persistence/PersistorTrait.html +91 -0
- data/doc/Lims/Core/Persistence/ResourceState.html +1738 -0
- data/doc/Lims/Core/Persistence/Search.html +269 -0
- data/doc/Lims/Core/Persistence/Search/CreateSearch.html +251 -0
- data/doc/Lims/Core/Persistence/Search/SearchPersistor.html +240 -0
- data/doc/Lims/Core/Persistence/Search/SearchSequelPersistor.html +396 -0
- data/doc/Lims/Core/Persistence/Sequel.html +117 -0
- data/doc/Lims/Core/Persistence/Sequel/Filters.html +462 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest.html +101 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name.html +137 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name/NamePersitor.html +143 -0
- data/doc/Lims/Core/Persistence/Sequel/Migrations.html +266 -0
- data/doc/Lims/Core/Persistence/Sequel/Persistor.html +665 -0
- data/doc/Lims/Core/Persistence/Sequel/Session.html +501 -0
- data/doc/Lims/Core/Persistence/Sequel/Store.html +417 -0
- data/doc/Lims/Core/Persistence/Session.html +2751 -0
- data/doc/Lims/Core/Persistence/Session/UnmanagedObjectError.html +111 -0
- data/doc/Lims/Core/Persistence/StateGroup.html +696 -0
- data/doc/Lims/Core/Persistence/StateList.html +498 -0
- data/doc/Lims/Core/Persistence/Store.html +695 -0
- data/doc/Lims/Core/Persistence/UuidResource.html +1044 -0
- data/doc/Lims/Core/Persistence/UuidResource/InvalidUuidError.html +111 -0
- data/doc/Lims/Core/Persistence/UuidResource/UuidResourcePersistor.html +337 -0
- data/doc/Lims/Core/Persistence/Uuidable.html +799 -0
- data/doc/Lims/Core/Persistor.html +320 -0
- data/doc/Lims/Core/Resource.html +165 -0
- data/doc/Object.html +228 -0
- data/doc/SessionSpec.html +101 -0
- data/doc/SessionSpec/Model.html +279 -0
- data/doc/SessionSpec/Model/ModelPersistor.html +327 -0
- data/doc/_index.html +732 -0
- data/doc/class_list.html +47 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +55 -0
- data/doc/css/style.css +322 -0
- data/doc/file.README.html +127 -0
- data/doc/file_list.html +49 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +127 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +167 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +1894 -0
- data/doc/top-level-namespace.html +100 -0
- data/lib/common.rb +18 -0
- data/lib/lims-core.rb +29 -0
- data/lib/lims-core/actions.rb +10 -0
- data/lib/lims-core/actions/action.rb +185 -0
- data/lib/lims-core/actions/action_group.rb +54 -0
- data/lib/lims-core/actions/bulk_action.rb +65 -0
- data/lib/lims-core/base.rb +132 -0
- data/lib/lims-core/helpers.rb +41 -0
- data/lib/lims-core/persistence.rb +15 -0
- data/lib/lims-core/persistence/comparison_filter.rb +54 -0
- data/lib/lims-core/persistence/filter.rb +23 -0
- data/lib/lims-core/persistence/identity_map.rb +55 -0
- data/lib/lims-core/persistence/logger/all.rb +5 -0
- data/lib/lims-core/persistence/logger/persistor.rb +35 -0
- data/lib/lims-core/persistence/logger/session.rb +30 -0
- data/lib/lims-core/persistence/logger/store.rb +37 -0
- data/lib/lims-core/persistence/message_bus.rb +131 -0
- data/lib/lims-core/persistence/multi_criteria_filter.rb +50 -0
- data/lib/lims-core/persistence/persist_association_trait.rb +96 -0
- data/lib/lims-core/persistence/persistable_trait.rb +150 -0
- data/lib/lims-core/persistence/persistor.rb +495 -0
- data/lib/lims-core/persistence/resource_state.rb +157 -0
- data/lib/lims-core/persistence/search.rb +3 -0
- data/lib/lims-core/persistence/search/all.rb +3 -0
- data/lib/lims-core/persistence/search/create_search.rb +55 -0
- data/lib/lims-core/persistence/search/search_persistor.rb +45 -0
- data/lib/lims-core/persistence/search/search_sequel_persistor.rb +40 -0
- data/lib/lims-core/persistence/sequel.rb +14 -0
- data/lib/lims-core/persistence/sequel/filters.rb +106 -0
- data/lib/lims-core/persistence/sequel/migrations.rb +14 -0
- data/lib/lims-core/persistence/sequel/migrations/add_audit_tables.rb +147 -0
- data/lib/lims-core/persistence/sequel/migrations/initial.rb +156 -0
- data/lib/lims-core/persistence/sequel/persistor.rb +200 -0
- data/lib/lims-core/persistence/sequel/session.rb +136 -0
- data/lib/lims-core/persistence/sequel/store.rb +37 -0
- data/lib/lims-core/persistence/session.rb +409 -0
- data/lib/lims-core/persistence/state_group.rb +97 -0
- data/lib/lims-core/persistence/state_list.rb +56 -0
- data/lib/lims-core/persistence/store.rb +73 -0
- data/lib/lims-core/persistence/uuid_resource.rb +115 -0
- data/lib/lims-core/persistence/uuid_resource_persistor.rb +43 -0
- data/lib/lims-core/persistence/uuidable.rb +107 -0
- data/lib/lims-core/resource.rb +21 -0
- data/lib/lims-core/subclass_tracker.rb +30 -0
- data/lib/lims-core/version.rb +5 -0
- data/lims-core.gemspec +40 -0
- data/makefile +52 -0
- data/showoff/core-2012-06-11/core/01_slide.md +237 -0
- data/showoff/core-2012-06-11/core/02_slide.md +110 -0
- data/showoff/core-2012-06-11/custom.css +44 -0
- data/showoff/core-2012-06-11/main/01_slide.md +53 -0
- data/showoff/core-2012-06-11/showoff.json +10 -0
- data/showoff/core-2012-06-11/tp1.tpl +1 -0
- data/spec/actions/action_group_spec.rb +39 -0
- data/spec/actions/spec_helper.rb +1 -0
- data/spec/persistence/identity_map_spec.rb +55 -0
- data/spec/persistence/logger/spec_helper.rb +7 -0
- data/spec/persistence/logger/store_spec.rb +48 -0
- data/spec/persistence/message_bus_spec.rb +76 -0
- data/spec/persistence/sequel/session_spec.rb +125 -0
- data/spec/persistence/sequel/spec_helper.rb +39 -0
- data/spec/persistence/sequel/store_shared.rb +25 -0
- data/spec/persistence/sequel/store_spec.rb +22 -0
- data/spec/persistence/session_spec.rb +199 -0
- data/spec/persistence/spec_helper.rb +2 -0
- data/spec/persistence/uuid_resource_spec.rb +80 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/subclass_tracker_sperc.rb +62 -0
- data/utils/constant_tree.rb +29 -0
- data/utils/stack.rb +48 -0
- metadata +402 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
|
|
3
|
+
require 'sequel'
|
|
4
|
+
require 'common'
|
|
5
|
+
require 'lims-core/persistence'
|
|
6
|
+
require 'lims-core/persistence/uuidable'
|
|
7
|
+
require 'lims-core/persistence/session'
|
|
8
|
+
require 'lims-core/helpers'
|
|
9
|
+
|
|
10
|
+
module Lims::Core
|
|
11
|
+
module Persistence
|
|
12
|
+
module Sequel
|
|
13
|
+
# Sequel specific implementation of a {Persistence::Session Session}.
|
|
14
|
+
class Session < Persistence::Session
|
|
15
|
+
include Uuidable
|
|
16
|
+
# Pack if needed an uuid to its store representation
|
|
17
|
+
# @param [String] uuid
|
|
18
|
+
# @return [Object]
|
|
19
|
+
def self.pack_uuid(uuid)
|
|
20
|
+
# Normal behavior shoulb be pack to binary data
|
|
21
|
+
UuidResource::pack(uuid)
|
|
22
|
+
#For now, we just compact it.
|
|
23
|
+
UuidResource::compact(uuid)
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Unpac if needed an uuid from its store representation
|
|
28
|
+
# @param [Object] puuid
|
|
29
|
+
# @return [String]
|
|
30
|
+
def self.unpack_uuid(puuid)
|
|
31
|
+
#UuidResource::unpack(puuid)
|
|
32
|
+
UuidResource::expand(puuid)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def serialize(object)
|
|
36
|
+
Lims::Core::Helpers::to_json(object)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def unserialize(object)
|
|
40
|
+
Lims::Core::Helpers::load_json(object)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def lock(datasets, unlock=false, &block)
|
|
44
|
+
datasets = [datasets] unless datasets.is_a?(Array)
|
|
45
|
+
db = datasets.first.db
|
|
46
|
+
|
|
47
|
+
# sqlite3 handles lock differently.
|
|
48
|
+
# @TODO create Session Subclass for each database type.
|
|
49
|
+
return lock_for_update(datasets, &block) if db.database_type == :sqlite
|
|
50
|
+
|
|
51
|
+
db.run("LOCK TABLES #{datasets.map { |d| "#{d.first_source} WRITE"}.join(",")}")
|
|
52
|
+
block.call(*datasets).tap { db.run("UNLOCK TABLES") if unlock }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# this method is to be used when the SQL store
|
|
56
|
+
# doesn't support LOCK, which is the case for SQLITE
|
|
57
|
+
# It can be used to redefine lock if needed.
|
|
58
|
+
def lock_for_update(datasets, &block)
|
|
59
|
+
datasets.first.db.transaction do
|
|
60
|
+
block.call(*datasets.map(&:for_update))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Return the parameters needed for the creation
|
|
65
|
+
# of a session object. It use session attributes
|
|
66
|
+
# which have been set at contruction time.
|
|
67
|
+
# This allow the same session to be reopen multiple times
|
|
68
|
+
# and create each time a new session with the same parameters.
|
|
69
|
+
# @return [Hash]
|
|
70
|
+
def session_object_parameters
|
|
71
|
+
{:user => @user ,
|
|
72
|
+
:backend_application_id => @backend_application_id,
|
|
73
|
+
:parameters => serialize(@parameters) || nil
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Override with_session to create a session object
|
|
78
|
+
# needed by the database to update revision.
|
|
79
|
+
# session object are create from the parameters
|
|
80
|
+
# If the session can't be created due to the lack of parameters.
|
|
81
|
+
# Nothing is created.
|
|
82
|
+
def with_session(*params, &block)
|
|
83
|
+
create_session = true
|
|
84
|
+
success = false
|
|
85
|
+
|
|
86
|
+
# @todo Subclass Session for Sql adapter
|
|
87
|
+
if database.database_type == :sqlite
|
|
88
|
+
create_session = false
|
|
89
|
+
else
|
|
90
|
+
previous_session_id = database.fetch("SELECT @current_session_id AS id").first[:id]
|
|
91
|
+
create_session = false if previous_session_id
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if create_session
|
|
95
|
+
session_id = database[:sessions].insert(session_object_parameters)
|
|
96
|
+
set_current_session(session_id)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
begin
|
|
100
|
+
result = super(*params, &block)
|
|
101
|
+
success = true
|
|
102
|
+
ensure
|
|
103
|
+
if create_session
|
|
104
|
+
# mark it as finished
|
|
105
|
+
database[:sessions].where(:id => session_id).update(:end_time => DateTime.now, :success => success)
|
|
106
|
+
set_current_session(nil)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
return result
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_current_session
|
|
114
|
+
return if database.database_type == :sqlite
|
|
115
|
+
database.fetch("SELECT @current_session_id AS id").first[:id]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def set_current_session(current_session_id=@current_session_id)
|
|
119
|
+
return if database.database_type == :sqlite
|
|
120
|
+
database.run "SET @current_session_id = #{current_session_id ? current_session_id : "NULL"};"
|
|
121
|
+
@current_session_id = current_session_id
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def transaction
|
|
125
|
+
super do
|
|
126
|
+
# Set the current_session_id again
|
|
127
|
+
# in case it's been overriden by another thread.
|
|
128
|
+
# Solves bug #64570338
|
|
129
|
+
set_current_session
|
|
130
|
+
yield
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2
|
|
2
|
+
|
|
3
|
+
require 'lims-core/persistence/store'
|
|
4
|
+
require 'lims-core/persistence/sequel/session'
|
|
5
|
+
require 'sequel'
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
module Persistence
|
|
9
|
+
module Sequel
|
|
10
|
+
# An Sequel::Store, ie an wrapper around a database
|
|
11
|
+
# using the sequel gem
|
|
12
|
+
class Store < Persistence::Store
|
|
13
|
+
attr_reader :database
|
|
14
|
+
# @attribute
|
|
15
|
+
|
|
16
|
+
# Create a store with a Sequel::Database
|
|
17
|
+
# We don't wrap for now the creation of the database
|
|
18
|
+
# @param [Sequel::Database] type underlying database
|
|
19
|
+
def initialize(database, *args)
|
|
20
|
+
raise RuntimeError unless database.is_a?(::Sequel::Database)
|
|
21
|
+
@database = database
|
|
22
|
+
super(*args)
|
|
23
|
+
@dirty_attribute_strategy = DIRTY_ATTRIBUTE_STRATEGY_SHA1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Execute given block within a transaction
|
|
27
|
+
# and create a session object needed to update
|
|
28
|
+
# revisionned table.
|
|
29
|
+
def transaction
|
|
30
|
+
database.transaction do
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2
|
|
2
|
+
|
|
3
|
+
require 'common'
|
|
4
|
+
require 'forwardable'
|
|
5
|
+
require 'digest/md5'
|
|
6
|
+
|
|
7
|
+
require 'lims-core/persistence/filter'
|
|
8
|
+
require 'lims-core/persistence/identity_map'
|
|
9
|
+
require 'lims-core/persistence/state_list'
|
|
10
|
+
require 'lims-core/helpers'
|
|
11
|
+
|
|
12
|
+
module Lims::Core
|
|
13
|
+
module Persistence
|
|
14
|
+
# A session is in charge of restoring and saving object throug the persistence layer.
|
|
15
|
+
# A Session can not normally be created by the end user. It has to be in a Store::with_session
|
|
16
|
+
# block, which acts has a transaction and save/update everything at the end of it.
|
|
17
|
+
# It should also provides an identity map.
|
|
18
|
+
# Session information (user, time) are also associated to the modifications of those objects.
|
|
19
|
+
class Session
|
|
20
|
+
|
|
21
|
+
# The dirty-attribute strategy decides
|
|
22
|
+
# how object modification is detected
|
|
23
|
+
# to avoid saved unmodified object.
|
|
24
|
+
# The default value comes from the session.
|
|
25
|
+
attr_accessor :dirty_attribute_strategy
|
|
26
|
+
class UnmanagedObjectError < RuntimeError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The map name <=> model class is shared between all type of session
|
|
30
|
+
#
|
|
31
|
+
def self.model_map()
|
|
32
|
+
@@model_map ||= IdentityMap::Class.new
|
|
33
|
+
end
|
|
34
|
+
# The map of peristor classes depends of the session type (sequel, log, etc ..)
|
|
35
|
+
# As they will be different classes
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
extend Forwardable
|
|
39
|
+
# param [Store] store the underlying store.
|
|
40
|
+
def initialize(store, *params)
|
|
41
|
+
@store = store
|
|
42
|
+
@object_states = StateList.new
|
|
43
|
+
@in_session = false
|
|
44
|
+
@saved = Set.new
|
|
45
|
+
@persistor_map = {}
|
|
46
|
+
@dirty_attribute_strategy = @store.dirty_attribute_strategy
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
options = params.extract_options!
|
|
50
|
+
@user ||= options[:user]
|
|
51
|
+
@backend_application_id ||= options[:backend_application_id]
|
|
52
|
+
@parameters ||= options[:parameters]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def_delegators :@store, :database
|
|
57
|
+
|
|
58
|
+
# Execute a block and save every 'marked' object
|
|
59
|
+
# in a transaction at the End.
|
|
60
|
+
# @yieldparam [Session] session the created session.
|
|
61
|
+
# @return the value of the block
|
|
62
|
+
def with_session(*params, &block)
|
|
63
|
+
return block[self] if @in_session
|
|
64
|
+
begin
|
|
65
|
+
@in_session = true
|
|
66
|
+
to_return = block[self]
|
|
67
|
+
@in_session = false
|
|
68
|
+
save_all
|
|
69
|
+
return to_return
|
|
70
|
+
ensure
|
|
71
|
+
@in_session = false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
# Subsession allow to create a session
|
|
75
|
+
# within a session sharing the same persistor
|
|
76
|
+
# but saving only the object managed by the subsession.
|
|
77
|
+
# The current implementation doesn't create new session
|
|
78
|
+
# but just push some session attributes.
|
|
79
|
+
# The problem about creating a new Session, we want them
|
|
80
|
+
# to share ResourceState, but a state own a persistor which in
|
|
81
|
+
# turn own a session, so it's easier if the session is the same.
|
|
82
|
+
def with_subsession(*params, &block)
|
|
83
|
+
backup = [@object_states, @in_session, @saved]
|
|
84
|
+
@object_states = StateList.new
|
|
85
|
+
@in_session = false
|
|
86
|
+
@saved = Set.new
|
|
87
|
+
|
|
88
|
+
return_value = with_session(*params, &block)
|
|
89
|
+
|
|
90
|
+
@object_states, @in_session, @saved = backup
|
|
91
|
+
|
|
92
|
+
return_value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Tell the session to be responsible of an object.
|
|
97
|
+
# The object will be saved at the end of the session.
|
|
98
|
+
# @example
|
|
99
|
+
# store.with_session do |session|
|
|
100
|
+
# session << Plate.new
|
|
101
|
+
# end
|
|
102
|
+
# @param [Persistable] object the object to persist.
|
|
103
|
+
# @return the session, to allow for chaining
|
|
104
|
+
def << (object)
|
|
105
|
+
manage_state(state_for(object))
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def manage_state(state)
|
|
110
|
+
@object_states << state
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def method_missing(name, *args, &block)
|
|
114
|
+
begin
|
|
115
|
+
persistor_for(name)
|
|
116
|
+
rescue NameError
|
|
117
|
+
# No persistor found for the given name
|
|
118
|
+
# Call the normal method_missing
|
|
119
|
+
super(name, *args, &block)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Returns the id of an object if exists.
|
|
125
|
+
# @param [Resource, Id] object or id.
|
|
126
|
+
# @return [Id, nil]
|
|
127
|
+
def id_for(object)
|
|
128
|
+
case object
|
|
129
|
+
when Resource then persistor_for(object).id_for(object)
|
|
130
|
+
else object # the object should be already an id
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get or creates the ResourceState corresponding to an object.
|
|
135
|
+
# @param [Resource] object
|
|
136
|
+
# @return [ResourceState]
|
|
137
|
+
def state_for(object)
|
|
138
|
+
return persistor_for(object).state_for(object)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def states_for(objects)
|
|
142
|
+
objects && objects.map { |o| state_for(o) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# Returns the id of an object and save it if necessary
|
|
148
|
+
# @param [Resource, Id] object or id.
|
|
149
|
+
# @return [Id]
|
|
150
|
+
def id_for!(object)
|
|
151
|
+
return nil unless object
|
|
152
|
+
id_for(object) || save(object)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Check if the session 'mananage' already this object.
|
|
156
|
+
# .i.e if it's been loaded or meant to be saved
|
|
157
|
+
# @param [Resource] object
|
|
158
|
+
# @return [Boolean]
|
|
159
|
+
def managed?(object)
|
|
160
|
+
persistor_for(object).state_for?(object)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Mark an object as to be deleted.
|
|
164
|
+
# The corresponding object will be deleted at the end of the session.
|
|
165
|
+
# For most object you don't need to load it to delete it
|
|
166
|
+
# but some needs (to delete the appropriate children).
|
|
167
|
+
# The real delete is made by calling the {#delete_in_real} method.
|
|
168
|
+
def delete(object)
|
|
169
|
+
raise UnmanagedObjectError, "can't delete #{object.inspect}" unless managed?(object)
|
|
170
|
+
state = state_for(object)
|
|
171
|
+
state.mark_for_deletion
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Pack if needed an uuid to its store representation
|
|
175
|
+
# This method is need to lookup an uuid by name
|
|
176
|
+
# @param [String] uuid
|
|
177
|
+
# @return [Object]
|
|
178
|
+
def self.pack_uuid(uuid)
|
|
179
|
+
uuid
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def pack_uuid(uuid)
|
|
183
|
+
self.class.pack_uuid(uuid)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Unpac if needed an uuid from its store representation
|
|
187
|
+
# @param [Object] puuid
|
|
188
|
+
# @return [String]
|
|
189
|
+
def self.unpack_uuid(puuid)
|
|
190
|
+
puuid
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def unpack_uuid(uuid)
|
|
194
|
+
self.class.unpack_uuid(uuid)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @todo doc
|
|
198
|
+
def serialize(object)
|
|
199
|
+
object
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def unserialize(object)
|
|
203
|
+
object
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def dirty_key_for(object)
|
|
207
|
+
case @dirty_attribute_strategy
|
|
208
|
+
when Store::DIRTY_ATTRIBUTE_STRATEGY_DEEP_COPY then object
|
|
209
|
+
when Store::DIRTY_ATTRIBUTE_STRATEGY_SHA1 then Digest::SHA1.hexdigest(Lims::Core::Helpers::to_json(object))
|
|
210
|
+
when Store::DIRTY_ATTRIBUTE_STRATEGY_MD5 then Digest::MD5.hexdigest(Lims::Core::Helpers::to_json(object))
|
|
211
|
+
when Store::DIRTY_ATTRIBUTE_STRATEGY_QUICK_HASH then object.hash
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
# save all objects which needs to be
|
|
217
|
+
def save_all()
|
|
218
|
+
transaction do
|
|
219
|
+
@save_in_progress = true # allows saving
|
|
220
|
+
@object_states.reset_status
|
|
221
|
+
@object_states.save
|
|
222
|
+
end
|
|
223
|
+
@save_in_progress = false
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Execute the provided block within a transaction
|
|
227
|
+
# Here to be overriden if needed
|
|
228
|
+
def transaction
|
|
229
|
+
@store.transaction do
|
|
230
|
+
yield
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Create a new persistor sharing the same internal parameters
|
|
235
|
+
# but with the "context" (datasest) of the new one.
|
|
236
|
+
# This can be used to "reset" a filtered persistor to the current session.
|
|
237
|
+
# @param [Persistor] persistor
|
|
238
|
+
# @return [Persistor]
|
|
239
|
+
def filter(persistor)
|
|
240
|
+
# If the persistor session is the current session, there is nothing to do
|
|
241
|
+
# just return the object as it is.
|
|
242
|
+
return persistor if persistor.instance_eval {@session} == self
|
|
243
|
+
|
|
244
|
+
# we need first to find the original persistor, ie the one that the user can call via
|
|
245
|
+
# session.model
|
|
246
|
+
original = persistor_for(persistor.class)
|
|
247
|
+
persistor.class.new(original, persistor.dataset)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Find the model class for a registered name
|
|
251
|
+
# registered name are used when doing session.model
|
|
252
|
+
# @param [String, Symbol] name
|
|
253
|
+
# @return [Class]
|
|
254
|
+
def self.name_to_model(name)
|
|
255
|
+
model_map.object_for(name.to_s)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Find the registered name of a given class
|
|
259
|
+
# @param[Class] model
|
|
260
|
+
# @return [Symbol]
|
|
261
|
+
def self.model_to_name(model)
|
|
262
|
+
model_map.id_for(model)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Register a model for a given name.
|
|
266
|
+
# This name will be looked up when calling session.<name>
|
|
267
|
+
# Persistors need to be registered.
|
|
268
|
+
# @param [String, Symbol] name
|
|
269
|
+
# @param [Class] model
|
|
270
|
+
def self.register_model(name, model)
|
|
271
|
+
name = name.to_s.snakecase
|
|
272
|
+
# skip if name already registered with the same object
|
|
273
|
+
return if model_map.object_for(name) == model
|
|
274
|
+
model_map.map_id_object(name, model)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Find the model corresponding to an object
|
|
279
|
+
# Takes many type of input
|
|
280
|
+
# @param [String, Symbol, Resource, Persistor] object
|
|
281
|
+
# @return [Symbol]
|
|
282
|
+
def self.model_for(object)
|
|
283
|
+
case object
|
|
284
|
+
when nil then nil
|
|
285
|
+
when String then name_to_model(object)
|
|
286
|
+
when Symbol then name_to_model(object)
|
|
287
|
+
when Class then
|
|
288
|
+
# check if the class has been registered
|
|
289
|
+
# IMPORTANT needs to be done before 'when module'
|
|
290
|
+
# because object can class and module at the same time.
|
|
291
|
+
return object if model_to_name(object)
|
|
292
|
+
|
|
293
|
+
# if it's already persistor find the associate model
|
|
294
|
+
persistor_class_map.id_for(object) do |model|
|
|
295
|
+
return model
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# check the super class
|
|
301
|
+
model_for(object.superclass).andtap { |model|
|
|
302
|
+
return model
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Check the owner
|
|
306
|
+
return nil unless object.respond_to? :parent_scope
|
|
307
|
+
model_for(object.parent_scope).andtap { |model|
|
|
308
|
+
return model
|
|
309
|
+
}
|
|
310
|
+
when Module then object
|
|
311
|
+
else
|
|
312
|
+
model_for(object.class)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def self.persistor_name_for(object)
|
|
317
|
+
model = model_for(object)
|
|
318
|
+
model_to_name(model)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def persistor_name_for(object)
|
|
322
|
+
self.class.persistor_name_for(object)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# @param [String, Symbol, Resource, Persistor] object
|
|
326
|
+
# @return [Class]
|
|
327
|
+
def self.persistor_class_for(object)
|
|
328
|
+
model = model_for(object)
|
|
329
|
+
|
|
330
|
+
persistor = persistor_class_map.object_for(model)
|
|
331
|
+
unless persistor
|
|
332
|
+
persistor = find_or_create_persistor_for(model)
|
|
333
|
+
persistor_class_map.map_id_object(model, persistor)
|
|
334
|
+
end
|
|
335
|
+
persistor
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def self.persistor_class_map()
|
|
339
|
+
@persistor_class_map ||= IdentityMap::Class.new
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def self.find_or_create_persistor_for(model)
|
|
343
|
+
# find the persistor within the class
|
|
344
|
+
# other corresponding to the current session type
|
|
345
|
+
return nil unless model
|
|
346
|
+
session_persistor_class = parent_scope.const_get(:Persistor)
|
|
347
|
+
model.constants(false).each do |name|
|
|
348
|
+
klass = model.const_get(name)
|
|
349
|
+
next unless klass.is_a? Module
|
|
350
|
+
if klass.ancestors.include?(session_persistor_class)
|
|
351
|
+
# quick hack to fix JRuby test before refactoring this
|
|
352
|
+
# If we are not in a sequel session, we need to not pick the Seque persistor.
|
|
353
|
+
next if session_persistor_class.name !~ /sequel/i && klass.name =~ /sequel/i
|
|
354
|
+
# found
|
|
355
|
+
return klass
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
# not found, we need to create it
|
|
359
|
+
# First we look for the base persistor to inherit from
|
|
360
|
+
#debugger unless superclass.respond_to? :persistor_class_for
|
|
361
|
+
raise "Can't find base persistor for #{model.inspect}" unless superclass.respond_to? :persistor_class_for
|
|
362
|
+
|
|
363
|
+
parent_persistor_class = superclass.persistor_class_for(model)
|
|
364
|
+
|
|
365
|
+
# if the current persistor (ex Sequel::Persistor) is the same as the base one
|
|
366
|
+
# there is nothing else to do
|
|
367
|
+
return parent_persistor_class unless parent_scope::const_defined?(:Persistor, false)
|
|
368
|
+
|
|
369
|
+
raise "no Persistor defined for #{model.name}" unless parent_persistor_class
|
|
370
|
+
module_name = parent_scope.name.sub(/.*Persistence::/,'')
|
|
371
|
+
model_name = model.name.split('::').pop
|
|
372
|
+
# the we create a new Persistor class including the Persistor mixin
|
|
373
|
+
# corresponding to the session
|
|
374
|
+
class_declaration = <<-EOV
|
|
375
|
+
class #{model_name}#{module_name}Persistor < #{parent_persistor_class.name}
|
|
376
|
+
include #{parent_scope::Persistor}
|
|
377
|
+
end
|
|
378
|
+
EOV
|
|
379
|
+
model.class_eval class_declaration
|
|
380
|
+
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# Get the persistor corresponding to the object class
|
|
385
|
+
# @param [Resource, String, Symbol, Persistor] object
|
|
386
|
+
# @return [Persistor, nil]
|
|
387
|
+
def persistor_for(object)
|
|
388
|
+
if object.is_a?(Persistor)
|
|
389
|
+
return filter(object)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
model = self.class.model_for(object)
|
|
393
|
+
@persistor_map[model] ||= begin
|
|
394
|
+
persistor_class = self.class.persistor_class_for(model)
|
|
395
|
+
raise NameError, "no persistor defined for #{object.class.name}" unless persistor_class && persistor_class.ancestors.include?(Persistor)
|
|
396
|
+
persistor_class.new(self)
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
public :persistor_for
|
|
402
|
+
# Compute the class name of the persistor corresponding to the argument
|
|
403
|
+
# @param [Resource, String, Symbol] object
|
|
404
|
+
# @return [String]
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
require 'lims-core/persistence/uuid_resource_persistor'
|