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,97 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'lims-core/persistence/state_list'
|
|
3
|
+
module Lims::Core
|
|
4
|
+
module Persistence
|
|
5
|
+
# A immutable list of {ResourceState}.
|
|
6
|
+
class StateGroup < StateList
|
|
7
|
+
attr_reader :persistor
|
|
8
|
+
def initialize(persistor, states)
|
|
9
|
+
@persistor = persistor
|
|
10
|
+
super(states)
|
|
11
|
+
end
|
|
12
|
+
def groups
|
|
13
|
+
[self]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def new(&block)
|
|
17
|
+
self.class.new(self.persistor, block.call)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def save
|
|
21
|
+
# @todo method for that.
|
|
22
|
+
all_parents = StateList.new
|
|
23
|
+
each do |state|
|
|
24
|
+
next if state.resource == nil or state.to_delete
|
|
25
|
+
state.parents!.andtap do |parents|
|
|
26
|
+
all_parents.merge(parents)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
all_parents.save
|
|
31
|
+
|
|
32
|
+
persistor.purge_invalid_object
|
|
33
|
+
|
|
34
|
+
# split by status
|
|
35
|
+
group_by(&:save_action).tap do |groups|
|
|
36
|
+
groups[:delete].andtap { |group| StateGroup.new(persistor, group).destroy }
|
|
37
|
+
groups[:update].andtap { |group| persistor.bulk_update(group) }
|
|
38
|
+
groups[:insert].andtap { |group| persistor.bulk_insert(group) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
all_children = StateList.new
|
|
42
|
+
each do |state|
|
|
43
|
+
next unless state.resource
|
|
44
|
+
state.body_saved!
|
|
45
|
+
state.children!.andtap do |children|
|
|
46
|
+
all_children.merge(children)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
all_children.save
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @todo doc
|
|
53
|
+
# destroy because delete exists already for a Set
|
|
54
|
+
def destroy
|
|
55
|
+
return self if size == 0
|
|
56
|
+
# mark each item for deletion
|
|
57
|
+
# so the parents are not saved later.
|
|
58
|
+
# children needs to be deleted NOW to avoid
|
|
59
|
+
# foreign key constraint error.
|
|
60
|
+
each_with_object(StateList.new) do |state, list|
|
|
61
|
+
# don't delete children if they've been deleted already
|
|
62
|
+
next if state.children_saved?
|
|
63
|
+
state.mark_for_deletion
|
|
64
|
+
list.merge(persistor.deletable_children_for(state.resource))
|
|
65
|
+
state.children_saved!
|
|
66
|
+
end.destroy
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
persistor.bulk_delete(self.select { |s| !s.body_saved? })
|
|
70
|
+
|
|
71
|
+
each_with_object(StateList.new) do |state, list|
|
|
72
|
+
next if state.parents_saved?
|
|
73
|
+
list.merge(persistor.deletable_parents_for(state.resource))
|
|
74
|
+
state.parents_saved!
|
|
75
|
+
end.destroy
|
|
76
|
+
each { |state| state.body_saved! }
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def load(*params)
|
|
81
|
+
to_load = select(&:to_load?)
|
|
82
|
+
all_parents = StateList.new
|
|
83
|
+
attributes_list = []
|
|
84
|
+
persistor.bulk_load(to_load, *params) do |att|
|
|
85
|
+
all_parents.merge(persistor.parents_for_attributes(att))
|
|
86
|
+
attributes_list << att
|
|
87
|
+
end
|
|
88
|
+
all_parents.load(*params)
|
|
89
|
+
attributes_list.map do |att|
|
|
90
|
+
persistor.new_from_attributes(att)
|
|
91
|
+
end
|
|
92
|
+
persistor.load_children(self, *params)
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'lims-core/persistence/resource_state'
|
|
3
|
+
require 'set'
|
|
4
|
+
module Lims::Core
|
|
5
|
+
module Persistence
|
|
6
|
+
# A immutable list of {ResourceState}.
|
|
7
|
+
class StateList < Set
|
|
8
|
+
# @return [List<StateGroup>]
|
|
9
|
+
def groups
|
|
10
|
+
persistor_order = map { |state| state.persistor}.uniq
|
|
11
|
+
grouped = group_by { |state| state.persistor }
|
|
12
|
+
persistor_order.map do |persistor|
|
|
13
|
+
StateGroup.new(persistor, grouped[persistor])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# We need to redefine some basic array function
|
|
18
|
+
# to keep the current class and not return an array
|
|
19
|
+
%w(select).each do |method|
|
|
20
|
+
define_method(method) do |&block|
|
|
21
|
+
new { super(&block) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def new(&block)
|
|
26
|
+
self.class.new(block.call)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def save
|
|
30
|
+
groups.each do |group|
|
|
31
|
+
group.save
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
groups.each do |group|
|
|
37
|
+
group.destroy
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @todo return object according to initial order ?
|
|
42
|
+
def load
|
|
43
|
+
groups.each do |group|
|
|
44
|
+
group.load
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reset_status
|
|
49
|
+
each { |state|
|
|
50
|
+
state.reset }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
require 'lims-core/persistence/state_group'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'common'
|
|
3
|
+
|
|
4
|
+
require 'lims-core/persistence'
|
|
5
|
+
require 'lims-core/persistence/session'
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
module Persistence
|
|
9
|
+
# A store represents a persistent datastore, where object can be saved and restored.
|
|
10
|
+
# A connection to a database, for example.
|
|
11
|
+
class Store
|
|
12
|
+
def self.const_missing(name)
|
|
13
|
+
super(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# The dirty-attribute strategy decides
|
|
17
|
+
# how object modification is detected
|
|
18
|
+
# to avoid saved unmodified object.
|
|
19
|
+
attr_accessor :dirty_attribute_strategy
|
|
20
|
+
DIRTY_ATTRIBUTE_STRATEGY_DEEP_COPY = 1
|
|
21
|
+
DIRTY_ATTRIBUTE_STRATEGY_SHA1 = 2
|
|
22
|
+
DIRTY_ATTRIBUTE_STRATEGY_MD5 = 3
|
|
23
|
+
DIRTY_ATTRIBUTE_STRATEGY_QUICK_HASH = 4
|
|
24
|
+
attr_accessor :dirty_attribute_strategy
|
|
25
|
+
|
|
26
|
+
# Retrieves the effective module of a class
|
|
27
|
+
# Useful to call "sibling" classes.
|
|
28
|
+
# @example
|
|
29
|
+
# class Sequel::Store < Store
|
|
30
|
+
# def session
|
|
31
|
+
# base_module::Session
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# session will return a Sequel::Session instead of a ::Session.
|
|
35
|
+
#
|
|
36
|
+
# @return [Module]
|
|
37
|
+
def self.base_module
|
|
38
|
+
@base_module ||= begin
|
|
39
|
+
base_name = name.sub(/::\w+$/, '')
|
|
40
|
+
constant(base_name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
def base_module
|
|
44
|
+
self.class.base_module
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Create a session and pass it to the block.
|
|
48
|
+
# This is the only way to get a session.
|
|
49
|
+
# @param [Array]
|
|
50
|
+
# @yieldparam [Session] session the created session.
|
|
51
|
+
# @return the value of the block
|
|
52
|
+
def with_session(*params, &block)
|
|
53
|
+
create_session(*params).with_session(&block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Create a session
|
|
58
|
+
# If a session is given a parameter
|
|
59
|
+
# return in instead of creating a new one.
|
|
60
|
+
def create_session(*params)
|
|
61
|
+
return params.first if(params.size >= 1 && params.first.is_a?(Session))
|
|
62
|
+
base_module::Session.new(self, *params)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Execute given block within a transaction
|
|
66
|
+
# If it make sense.
|
|
67
|
+
def transaction
|
|
68
|
+
yield
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'common'
|
|
3
|
+
require 'uuid'
|
|
4
|
+
|
|
5
|
+
require 'lims-core/resource'
|
|
6
|
+
require 'lims-core/persistence/resource_state'
|
|
7
|
+
|
|
8
|
+
module Lims::Core
|
|
9
|
+
module Persistence
|
|
10
|
+
# Bind a uuid (as a String) to a Resource (a key and a model)
|
|
11
|
+
# The key is a FixNum to find the corresponding resource in the store
|
|
12
|
+
# and the model is the real class of the object (or at least something allowing to find it)
|
|
13
|
+
# the `state` attribute is the {ResourceState} corresponding to linked {Resource}.
|
|
14
|
+
class UuidResource
|
|
15
|
+
include Resource
|
|
16
|
+
attribute :state, ResourceState, :writer => :private, :initializable => true
|
|
17
|
+
attribute :model_class, Class, :writer => :private, :initializable => true
|
|
18
|
+
attribute :uuid, String, :writer => :private, :initializable => true
|
|
19
|
+
|
|
20
|
+
def initialize(*args)
|
|
21
|
+
super(*args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Link the resouce state to the UuidResource itself
|
|
25
|
+
def state=(state_)
|
|
26
|
+
@state = state_
|
|
27
|
+
if state_
|
|
28
|
+
state_.uuid_resource = self
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# for speed
|
|
33
|
+
def attributes
|
|
34
|
+
{state: state,
|
|
35
|
+
model_class: model_class,
|
|
36
|
+
uuid: uuid
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def attributes_for_dirty
|
|
41
|
+
{state: state,
|
|
42
|
+
model_class: model_class,
|
|
43
|
+
}.tap do |att|
|
|
44
|
+
att[:uuid] = @uuid if @uuid
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class InvalidUuidError < RuntimeError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Generator = UUID.new
|
|
52
|
+
Form = [8, 4, 4, 4, 12]
|
|
53
|
+
Length = Form.inject { |m, n| m+n }
|
|
54
|
+
ValidationRegexp = /#{Form.map { |n| "[0-9a-f]{#{n}}" }.join('-')}/i
|
|
55
|
+
SplitRegexp = /#{Form.map { |n| "([0-9a-f]{#{n}})"}.join('')}/
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def key
|
|
59
|
+
@state.andtap { |state| state.id }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.valid?(uuid)
|
|
63
|
+
!!(uuid =~ ValidationRegexp)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.generate_uuid()
|
|
67
|
+
Generator.generate
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Pack a string representation of an uuid to a char(16)
|
|
71
|
+
def self.pack(to_pack)
|
|
72
|
+
[compact(to_pack)].pack("H*")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.unpack(packed)
|
|
76
|
+
expand(packed.unpack("H*").first)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Convert the string representation of an Uuid to a bignum
|
|
80
|
+
# @param [String] s
|
|
81
|
+
# @return [Bignum]
|
|
82
|
+
def self.string_to_bignum(s)
|
|
83
|
+
compact(s).to_i(16)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Convert a bignum to a string representation
|
|
87
|
+
# @param [Bignum] b
|
|
88
|
+
# @return [String]
|
|
89
|
+
def self.bignum_to_string(b)
|
|
90
|
+
l = Form.inject { |m, n| m+n }
|
|
91
|
+
expand("%0.*x" % [l,b])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.expand(s)
|
|
95
|
+
match = SplitRegexp.match(s)
|
|
96
|
+
raise InvalidUuidError.new(s) unless match
|
|
97
|
+
match.captures.join('-')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.compact(s)
|
|
101
|
+
s.tr('-', '')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build_resource(attributes)
|
|
105
|
+
model_class.new(attributes)
|
|
106
|
+
end
|
|
107
|
+
protected :build_resource
|
|
108
|
+
|
|
109
|
+
def uuid
|
|
110
|
+
@uuid ||= UuidResource.generate_uuid
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'common'
|
|
3
|
+
|
|
4
|
+
require 'lims-core/persistence/persistor'
|
|
5
|
+
require 'lims-core/persistence/uuid_resource'
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
module Persistence
|
|
9
|
+
class UuidResource
|
|
10
|
+
class UuidResourcePersistor < Persistence::Persistor
|
|
11
|
+
Model = UuidResource
|
|
12
|
+
|
|
13
|
+
def parents_for(resource)
|
|
14
|
+
resource.state && resource.state.resource ? [resource.state] : []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def filter_attributes_on_save(attributes)
|
|
18
|
+
attributes.mash do |k,v|
|
|
19
|
+
case k
|
|
20
|
+
when :model_class then [ k, @session.model_name_for(v) ]
|
|
21
|
+
when :uuid then [ k, @session.pack_uuid(v) ]
|
|
22
|
+
when :state
|
|
23
|
+
[:key, v && v.id]
|
|
24
|
+
else [k, v]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def filter_attributes_on_load(attributes)
|
|
30
|
+
attributes.mash do |k,v|
|
|
31
|
+
case k
|
|
32
|
+
when :model_class then [ k, @session.class_for(v) ]
|
|
33
|
+
when :uuid then [ k, @session.unpack_uuid(v) ]
|
|
34
|
+
when :key then [:state, @session.persistor_for(attributes[:model_class]).state_for_id(attributes[:key])]
|
|
35
|
+
else [k, v]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
|
|
3
|
+
require 'sequel'
|
|
4
|
+
require 'lims-core/persistence'
|
|
5
|
+
require 'lims-core/persistence/uuid_resource_persistor'
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
module Persistence
|
|
9
|
+
# Add uuid behavior (lookup and creation) to a Session
|
|
10
|
+
module Uuidable
|
|
11
|
+
# lookup one or more objects by uuid or resource_uuid
|
|
12
|
+
# @param [String, Arrary<String>, UUidResource] args
|
|
13
|
+
# @return [Resource, nil, Array<Resource>]
|
|
14
|
+
def [](args)
|
|
15
|
+
case args
|
|
16
|
+
when UuidResource then for_uuid_resource(args)
|
|
17
|
+
when String then for_uuid(args)
|
|
18
|
+
when Array then for_uuids(args)
|
|
19
|
+
else
|
|
20
|
+
super(args)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Retrieve id from an object or a Hash with a uuid key
|
|
25
|
+
# A list of uuids will
|
|
26
|
+
def id_for(object)
|
|
27
|
+
case object
|
|
28
|
+
when Array
|
|
29
|
+
object.map { |o| id_for(o) }
|
|
30
|
+
when Hash
|
|
31
|
+
id_for(object[:uuid] || object["uuid"])
|
|
32
|
+
when String
|
|
33
|
+
# We assume it is an uuid
|
|
34
|
+
self.uuid_resource[:uuid => pack_uuid(object)].andtap { |ur| ur.key }
|
|
35
|
+
else
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
# Compute the name (string) used to be saved in the Uuid table.
|
|
40
|
+
# @param [Class] model_class class of the resource
|
|
41
|
+
# @return [String]
|
|
42
|
+
def model_name_for(model_class)
|
|
43
|
+
persistor_name_for(model_class)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get the class from the class name. Inverse of {#model_name_for}.
|
|
47
|
+
def class_for(model_name)
|
|
48
|
+
persistor_for(model_name).model
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def new_uuid_resource_for(object)
|
|
52
|
+
UuidResource.new(:state => state_for(object), :model_class => object.class).tap do |r|
|
|
53
|
+
self << r
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def uuid_resource_for(object)
|
|
58
|
+
state, object = object.is_a?(ResourceState) ? [object, object.resource] : [state_for(object), object]
|
|
59
|
+
self.uuid_resource[:key => state.id, :model_class => model_name_for(object.class)]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Finds the uuid of an object if it exists
|
|
63
|
+
def uuid_for(object)
|
|
64
|
+
# We need to check if the object is managed and have alreday an id
|
|
65
|
+
raise RuntimeError, "Unmanaged object, #{object.inspect}" unless managed?(object)
|
|
66
|
+
state = state_for(object)
|
|
67
|
+
state.uuid_resource.andtap { |ur| return ur.uuid }
|
|
68
|
+
state.id && uuid_resource_for(state).andtap { |r| r.uuid }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Find or create a uuid for an object
|
|
73
|
+
def uuid_for!(object)
|
|
74
|
+
uuid_for(object) || new_uuid_resource_for(object).uuid
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Delete the underlying resource of a UuidResource
|
|
78
|
+
# @param [UuidResource] uuid_resource
|
|
79
|
+
# @return [Id, nil]
|
|
80
|
+
def delete_resource(uuid_resource)
|
|
81
|
+
delete(for_uuid_resource(uuid_resource))
|
|
82
|
+
uuid_resource.key
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
protected
|
|
87
|
+
# find/load the object referenced by a uuid resource.
|
|
88
|
+
# Don't need to be called explicitely. use session[resource_uuid] instead
|
|
89
|
+
# @param [UuidResource] uuid_resource
|
|
90
|
+
# @return [Resource]
|
|
91
|
+
def for_uuid_resource(uuid_resource)
|
|
92
|
+
persistor_for(uuid_resource.model_class)[uuid_resource.key]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def for_uuid(uuid)
|
|
96
|
+
self.uuid_resource[:uuid => uuid].andtap do |r|
|
|
97
|
+
for_uuid_resource(r)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @todo bulk load
|
|
102
|
+
def for_uuids(uuids)
|
|
103
|
+
uuids.map { |u| for_uuid(u) }.compact
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|