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,132 @@
|
|
|
1
|
+
# vi: ts=2 sts=2 et sw=2 spell spelllang=en
|
|
2
|
+
require 'common'
|
|
3
|
+
|
|
4
|
+
module Lims::Core
|
|
5
|
+
module Base
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.class_eval do
|
|
8
|
+
include Virtus
|
|
9
|
+
include Aequitas
|
|
10
|
+
include AccessibleViaSuper
|
|
11
|
+
extend Forwardable
|
|
12
|
+
extend ClassMethod
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module AccessibleViaSuper
|
|
17
|
+
def initialize(*args, &block)
|
|
18
|
+
# readonly attributes are normaly not allowed in constructor
|
|
19
|
+
# by Virtus. We need to call set_attributes explicitely
|
|
20
|
+
options = args.extract_options!
|
|
21
|
+
# we would use `options & [:row ... ]` if we could
|
|
22
|
+
# but Sequel redefine Hash#& ...
|
|
23
|
+
initializables = self.class.attributes.select {|a| a.options[:initializable] == true }
|
|
24
|
+
initial_options = options.subset(initializables.map(&:name))
|
|
25
|
+
set_attributes(initial_options)
|
|
26
|
+
super(*args, options - initial_options, &block).tap {
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Like attributes but allow class to define a version
|
|
31
|
+
# without side effet.
|
|
32
|
+
# For example attribute on a UuidResource will generate an uuid.
|
|
33
|
+
# This is the method called by dirty_key_for
|
|
34
|
+
def attributes_for_dirty
|
|
35
|
+
attributes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
# Compare 2 resources.
|
|
40
|
+
# They are == if they have the same values (attributes),
|
|
41
|
+
# regardless they are the same ruby object or not.
|
|
42
|
+
# @param other
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
def ==(other)
|
|
45
|
+
self.attributes == (other.respond(:attributes) || {} )
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
module ClassMethod
|
|
50
|
+
def is_array_of(child_klass, options = {}, &initializer)
|
|
51
|
+
define_method :initialize_array do |*args|
|
|
52
|
+
@content = initializer ? initializer[self, child_klass] : []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class_eval do
|
|
56
|
+
include Enumerable
|
|
57
|
+
include IsArrayOf
|
|
58
|
+
def_delegators :@content, :each, :size , :each_with_index, :map, :zip, :clear, :empty?, :to_s \
|
|
59
|
+
, :include?, :to_a, :first, :last
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
module IsArrayOf
|
|
68
|
+
|
|
69
|
+
def initialize(*args, &block)
|
|
70
|
+
super(*args, &block)
|
|
71
|
+
initialize_array()
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Add content to compare
|
|
75
|
+
# If classe are not in the same hierarchy we only compare the content
|
|
76
|
+
# @param other to compare with
|
|
77
|
+
# @return [Boolean]
|
|
78
|
+
def ==(other)
|
|
79
|
+
if other.is_a?(self.class) || self.is_a?(other.class)
|
|
80
|
+
super(other)
|
|
81
|
+
else
|
|
82
|
+
!other.nil?
|
|
83
|
+
end && self.to_a == other.to_a
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The underlying array. Use to everything which is not directly delegated
|
|
87
|
+
# @return [Array]
|
|
88
|
+
def content
|
|
89
|
+
@content
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Delegate [] to the underlying array.
|
|
93
|
+
# This is needed because Virtus redefine [] as well
|
|
94
|
+
# @param [Fixnum, ... ] i index
|
|
95
|
+
# @return [Object]
|
|
96
|
+
def [](i)
|
|
97
|
+
case i
|
|
98
|
+
when Fixnum then self.content[i]
|
|
99
|
+
else super(i)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def []=(i, value)
|
|
104
|
+
case i
|
|
105
|
+
when Fixnum then self.content[i]=value
|
|
106
|
+
else super(i, value)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
# iterate only between non empty lanes.
|
|
110
|
+
# @yield [content]
|
|
111
|
+
# @return itself
|
|
112
|
+
def each_content
|
|
113
|
+
@content.each do |content|
|
|
114
|
+
yield content if content
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class HashString < Virtus::Attribute::Object
|
|
120
|
+
primitive Hash
|
|
121
|
+
def coerce(hash)
|
|
122
|
+
hash.rekey {|key| key.to_s }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @todo override state_machine to automatically add
|
|
127
|
+
# attribute
|
|
128
|
+
class State < Virtus::Attribute::Object
|
|
129
|
+
primitive String
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Lims::Core
|
|
2
|
+
module Helpers
|
|
3
|
+
def self.gem_available?(gem_name)
|
|
4
|
+
begin
|
|
5
|
+
Gem::Specification.find_by_name(gem_name)
|
|
6
|
+
rescue Gem::LoadError
|
|
7
|
+
false
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Load the available gem for json
|
|
12
|
+
if gem_available?('jrjackson')
|
|
13
|
+
require 'jrjackson'
|
|
14
|
+
def self.to_json(object)
|
|
15
|
+
JrJackson::Json.dump(object)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.load_json(json)
|
|
19
|
+
JrJackson::Json.load(json)
|
|
20
|
+
end
|
|
21
|
+
elsif gem_available?('oj')
|
|
22
|
+
require 'oj'
|
|
23
|
+
def self.to_json(object)
|
|
24
|
+
Oj.dump(object, :mode => :compat)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.load_json(json)
|
|
28
|
+
Oj.load(json)
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
require 'json'
|
|
32
|
+
def self.to_json(object)
|
|
33
|
+
object.to_json
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.load_json(json)
|
|
37
|
+
JSON.parse(json)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#vi: ts=2 sw=2 et
|
|
2
|
+
require 'common'
|
|
3
|
+
require 'facets/string'
|
|
4
|
+
|
|
5
|
+
require 'lims-core/persistence/session'
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
# Generic persistence layer.
|
|
9
|
+
# The main objects are {Persistence::Session Session} which
|
|
10
|
+
# is in charge of saving and restoring object and {Persistence::Store} via Persistors.
|
|
11
|
+
# Persistors are mixins specific to each persistence types.
|
|
12
|
+
# For example, see the {Sequel::Persistor}.
|
|
13
|
+
module Persistence
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'lims-core/persistence/filter'
|
|
2
|
+
require 'lims-core/resource'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Lims::Core
|
|
6
|
+
module Persistence
|
|
7
|
+
# Filter performing a comparison between the resource field's value
|
|
8
|
+
# and a given value.
|
|
9
|
+
# Key being the name of the resource's field and the value is a Hash.
|
|
10
|
+
# The key of the hash is a comparison operator
|
|
11
|
+
# and the value is the given value the filter do the comparison against.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
#
|
|
15
|
+
# "model": "kit",
|
|
16
|
+
# "criteria": {
|
|
17
|
+
# "comparison": {
|
|
18
|
+
# "expires": {
|
|
19
|
+
# ">=": "2013-04-24"
|
|
20
|
+
# }
|
|
21
|
+
# }
|
|
22
|
+
# }
|
|
23
|
+
#
|
|
24
|
+
# Will look for all kits expires after the given date ("2013-04-24").
|
|
25
|
+
#
|
|
26
|
+
class ComparisonFilter < Filter
|
|
27
|
+
include Resource
|
|
28
|
+
|
|
29
|
+
NOT_IN_ROOT = 1
|
|
30
|
+
|
|
31
|
+
attribute :criteria, Hash, :required => true
|
|
32
|
+
|
|
33
|
+
# For Sequel, keys needs to be a Symbol to be seen as column.
|
|
34
|
+
# String are seen as 'value'
|
|
35
|
+
def initialize(criteria)
|
|
36
|
+
criteria = { :criteria => criteria } unless criteria.include?(:criteria)
|
|
37
|
+
criteria[:criteria].rekey!{ |k| k.to_sym }
|
|
38
|
+
super(criteria)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call(persistor)
|
|
42
|
+
persistor.comparison_filter(criteria[:comparison])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Persistor
|
|
48
|
+
# @param [Hash] criteria a
|
|
49
|
+
# @return [Persistor]
|
|
50
|
+
def comparison_filter(criteria)
|
|
51
|
+
raise NotImplementedError, "comparison_filter methods needs to be implemented for subclass of Persistor"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#vi: ts=2 sw=2 et
|
|
2
|
+
require 'common'
|
|
3
|
+
|
|
4
|
+
module Lims::Core
|
|
5
|
+
module Persistence
|
|
6
|
+
# @abstract Base class of all filters.
|
|
7
|
+
# A filter acts on persistors and can be chained.
|
|
8
|
+
# Note: This class is not really usefull in a *Ruby world* and is mainly
|
|
9
|
+
# here for documentation.
|
|
10
|
+
class Filter
|
|
11
|
+
# Transform a persistor to a "filtered persistor"
|
|
12
|
+
# The filtered persistor loading only the filtered object.
|
|
13
|
+
# Note that the actual implementation of the filter depends on the
|
|
14
|
+
# *type* of the persistor (Sequel for example).
|
|
15
|
+
# @param persistor [Persistence::Persistor]
|
|
16
|
+
# @return [Persistor]
|
|
17
|
+
def call(persistor)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
|
|
3
|
+
require 'common'
|
|
4
|
+
|
|
5
|
+
module Lims::Core
|
|
6
|
+
module Persistence
|
|
7
|
+
# Mixing giving an identity map behavior
|
|
8
|
+
# ie a map (both way) between id and object
|
|
9
|
+
module IdentityMap
|
|
10
|
+
# Raised if there is any duplicate in the identity map
|
|
11
|
+
class DuplicateError < RuntimeError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#Raised if the `id` is already associated to a different `object`
|
|
15
|
+
class DuplicateIdError <DuplicateError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#Raised if the `object` is already associated to a different `id`
|
|
19
|
+
class DuplicateObjectError < DuplicateError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Look for the id associated to an object and yield it to the block
|
|
23
|
+
# if found.
|
|
24
|
+
def id_for(object, &block)
|
|
25
|
+
@object_to_id[object].andtap(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Look for the object associated to an object and yield it to the block
|
|
29
|
+
# if found.
|
|
30
|
+
def object_for(id, &block)
|
|
31
|
+
@id_to_object[id].andtap(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# bound an id to an object
|
|
35
|
+
def map_id_object(id, object)
|
|
36
|
+
return nil unless id && object
|
|
37
|
+
raise DuplicateIdError, id unless @id_to_object.fetch(id, object).equal? object
|
|
38
|
+
raise DuplicateObjectError, object unless @object_to_id.fetch(object, id).equal? id
|
|
39
|
+
@id_to_object[id] = object
|
|
40
|
+
@object_to_id[object] = id
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(*args, &block)
|
|
44
|
+
super(*args, &block)
|
|
45
|
+
@id_to_object = {}
|
|
46
|
+
@object_to_id = {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Class version
|
|
50
|
+
class Class
|
|
51
|
+
include IdentityMap
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Lims::Core
|
|
5
|
+
module Persistence
|
|
6
|
+
module Logger
|
|
7
|
+
# Mixin giving extended the persistor classes with
|
|
8
|
+
# the Logger (save) behavior.
|
|
9
|
+
module Persistor
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Load an object to the underlying logger
|
|
13
|
+
# @param [Resource] object the object
|
|
14
|
+
# @return the Id if save successful
|
|
15
|
+
def save_raw(object, *params)
|
|
16
|
+
case object
|
|
17
|
+
when Resource then @session.log("#{object.class.name}: #{filter_attributes_on_save(object.attributes)}")
|
|
18
|
+
else
|
|
19
|
+
@session.log("#{object.inspect}")
|
|
20
|
+
end
|
|
21
|
+
object
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def save_as_aggregation(source_id, target, *params)
|
|
25
|
+
@session.with_indent("#{params} - ") do
|
|
26
|
+
super(source_id, target)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def save_raw_association(source_id, target_id, *params)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'lims-core/persistence/session'
|
|
5
|
+
|
|
6
|
+
module Lims::Core
|
|
7
|
+
module Persistence
|
|
8
|
+
module Logger
|
|
9
|
+
# Logger specific implementation of a {Persistence::Session Session}.
|
|
10
|
+
class Session < Persistence::Session
|
|
11
|
+
|
|
12
|
+
attr_reader :indent_level
|
|
13
|
+
def initialize(*args, &block)
|
|
14
|
+
@indent_level = ""
|
|
15
|
+
super(*args, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def log(msg)
|
|
19
|
+
@store.log(indent_level+msg)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Execute a block with the specified indent level indicator.
|
|
23
|
+
# @param [String] indent the indent level indicator
|
|
24
|
+
def with_indent(indent=" - ", &block)
|
|
25
|
+
temporarily('@indent_level' => @indent_level+indent, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'lims-core/persistence'
|
|
5
|
+
require 'lims-core/persistence/store'
|
|
6
|
+
require 'lims-core/persistence/logger/session'
|
|
7
|
+
require 'lims-core/persistence/logger/persistor'
|
|
8
|
+
|
|
9
|
+
module Lims::Core
|
|
10
|
+
module Persistence
|
|
11
|
+
module Logger
|
|
12
|
+
# An Logger::Store, a store 'logging' object instead of
|
|
13
|
+
# saving them.
|
|
14
|
+
class Store < Persistence::Store
|
|
15
|
+
attr_reader :logger
|
|
16
|
+
attr_reader :method
|
|
17
|
+
|
|
18
|
+
# Create a store with an underlying logger.
|
|
19
|
+
# @param [Logger, file] logger
|
|
20
|
+
# @param [Symbol, String] method the method call to
|
|
21
|
+
# send information to the logger.
|
|
22
|
+
def initialize(logger, method=:info, *args)
|
|
23
|
+
@logger = case logger
|
|
24
|
+
when ::Logger then logger
|
|
25
|
+
else ::Logger.new(logger)
|
|
26
|
+
end
|
|
27
|
+
@method = method
|
|
28
|
+
super(*args)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def log(msg)
|
|
32
|
+
@logger.send(@method, msg)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require 'bunny'
|
|
2
|
+
require 'common'
|
|
3
|
+
|
|
4
|
+
module Lims
|
|
5
|
+
module Core
|
|
6
|
+
module Persistence
|
|
7
|
+
|
|
8
|
+
# Basic methods to publish messages on the bus
|
|
9
|
+
# Use the bunny gem as RabbitMQ client
|
|
10
|
+
class MessageBus
|
|
11
|
+
|
|
12
|
+
include Virtus
|
|
13
|
+
include Aequitas
|
|
14
|
+
attribute :connection_uri, String, :required => true, :writer => :private
|
|
15
|
+
attribute :exchange_name, String, :required => true, :writer => :private
|
|
16
|
+
# By default, exchange_type is topic
|
|
17
|
+
attribute :exchange_type, String, :required => true, :writer => :private
|
|
18
|
+
attribute :durable, Boolean, :required => true, :writer => :private
|
|
19
|
+
attribute :prefetch_number, Integer, :required => true, :writer => :private
|
|
20
|
+
attribute :heart_beat, Integer, :required => false, :writer => :private
|
|
21
|
+
attribute :backend_application_id, String, :required => true
|
|
22
|
+
|
|
23
|
+
# Exception ConnectionError raised after a failed connection
|
|
24
|
+
# to RabbitMQ server.
|
|
25
|
+
class ConnectionError < StandardError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Exception InvalidSettingsError raised after a setting error
|
|
29
|
+
class InvalidSettingsError < StandardError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Initialize the message bus and check the required options
|
|
33
|
+
# are passed as parameters.
|
|
34
|
+
# @param [Hash] settings
|
|
35
|
+
def initialize(settings = {})
|
|
36
|
+
raise InvalidSettingsError, "MessageBug settings are empty" if settings == nil
|
|
37
|
+
@heart_beat = settings["heart_beat"]
|
|
38
|
+
@connection_uri = settings["url"]
|
|
39
|
+
@exchange_name = settings["exchange_name"]
|
|
40
|
+
@exchange_type = settings["exchange_type"] || "topic"
|
|
41
|
+
@durable = settings["durable"]
|
|
42
|
+
@prefetch_number = settings["prefetch_number"]
|
|
43
|
+
@backend_application_id = settings["backend_application_id"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# sets backend_app_id, but just once,
|
|
47
|
+
# otherwise raise am InvalidSettings error
|
|
48
|
+
def backend_application_id=(backend_application_id)
|
|
49
|
+
raise InvalidSettingsError, "Backend Application ID has been set already." if @backend_application_id
|
|
50
|
+
@backend_application_id = backend_application_id
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Executed after a connection loss
|
|
54
|
+
# The exception should be catched and rollback the actions.
|
|
55
|
+
def connection_failure_handler
|
|
56
|
+
Proc.new do
|
|
57
|
+
raise ConnectionError, "can't connect to RabbitMQ server"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Create a new connection to the broker using
|
|
62
|
+
# the connection settings.
|
|
63
|
+
# Create a channel and setup a new exchange.
|
|
64
|
+
def connect
|
|
65
|
+
begin
|
|
66
|
+
if valid?
|
|
67
|
+
options = @heart_beat ? { :heartbeat => heart_beat } : {}
|
|
68
|
+
@connection = Bunny.new(connection_uri, options)
|
|
69
|
+
@connection.start
|
|
70
|
+
@channel = @connection.create_channel
|
|
71
|
+
set_prefetch_number(prefetch_number)
|
|
72
|
+
set_exchange(exchange_name, :durable => durable)
|
|
73
|
+
else
|
|
74
|
+
raise InvalidSettingsError, "settings are invalid"
|
|
75
|
+
end
|
|
76
|
+
rescue Bunny::TCPConnectionFailed, Bunny::PossibleAuthenticationFailureError => e
|
|
77
|
+
connection_failure_handler.call
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Close the connection
|
|
82
|
+
def close
|
|
83
|
+
@connection.close
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Create (or get if it already exists) a new topic
|
|
87
|
+
# exchange with the given options.
|
|
88
|
+
# Especially, the durable option can be set here to
|
|
89
|
+
# mark the exchange as durable (survive a server restart)
|
|
90
|
+
# @param [String] name
|
|
91
|
+
# @param [Hash] exchange options
|
|
92
|
+
def set_exchange(exchange_name, options = {})
|
|
93
|
+
@exchange = Bunny::Exchange.new(@channel, exchange_type.to_sym, exchange_name , options)
|
|
94
|
+
end
|
|
95
|
+
private :set_exchange
|
|
96
|
+
|
|
97
|
+
# Specifies the number of messages to prefetch.
|
|
98
|
+
# @param [int] number of messages to prefetch
|
|
99
|
+
def set_prefetch_number(number)
|
|
100
|
+
@channel.prefetch(number)
|
|
101
|
+
end
|
|
102
|
+
private :set_prefetch_number
|
|
103
|
+
|
|
104
|
+
# Set the message persistence behaviour.
|
|
105
|
+
# If persistent, the message will be persisted to disk
|
|
106
|
+
# and remain in the queue until it is consumed.
|
|
107
|
+
# Survive a server restart.
|
|
108
|
+
# BUNNY ISSUE: bunny0.9pre4 hardcodes the persistent option.
|
|
109
|
+
# @see lib/bunny/channel.rb:174 :delivery_mode => 2
|
|
110
|
+
# It is set all the time, meaning the messages will survive
|
|
111
|
+
# a server restart, if the queue and the exchange are durable.
|
|
112
|
+
# @param [Bool] persistence
|
|
113
|
+
def set_message_persistence(persistent)
|
|
114
|
+
@message_persistence = persistent
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Publish a message on the bus with the given options
|
|
118
|
+
# The routing key is passed in the options.
|
|
119
|
+
# @param [String] JSON message
|
|
120
|
+
# @param [Hash] publishing options
|
|
121
|
+
def publish(message, options = {})
|
|
122
|
+
raise ConnectionError, "exchange is not reachable" unless @exchange.instance_of?(Bunny::Exchange)
|
|
123
|
+
|
|
124
|
+
options.merge!(:persistent => @message_persistence) unless @message_persistence.nil?
|
|
125
|
+
options.merge!(:app_id => @backend_application_id) if @backend_application_id
|
|
126
|
+
@exchange.publish(message, options)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|