lims-core 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|