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.
Files changed (177) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +2 -0
  5. data/.travis.yml +2 -0
  6. data/.vimrc +27 -0
  7. data/.yard_templates/default/layout/html/footer.erb +0 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +54 -0
  10. data/Gemfile.lock +197 -0
  11. data/Guardfile +21 -0
  12. data/Guardfile.tmux +28 -0
  13. data/README.markdown +67 -0
  14. data/Rakefile +16 -0
  15. data/config/database.yml +16 -0
  16. data/doc/Array.html +116 -0
  17. data/doc/Array/ArrayLoggerPersistor.html +152 -0
  18. data/doc/Lims.html +114 -0
  19. data/doc/Lims/Core.html +178 -0
  20. data/doc/Lims/Core/Action.html +91 -0
  21. data/doc/Lims/Core/Actions.html +116 -0
  22. data/doc/Lims/Core/Actions/Action.html +216 -0
  23. data/doc/Lims/Core/Actions/Action/AfterEval.html +853 -0
  24. data/doc/Lims/Core/Actions/Action/InvalidParameters.html +268 -0
  25. data/doc/Lims/Core/Actions/ActionGroup.html +196 -0
  26. data/doc/Lims/Core/Actions/ActionGroup/AfterEval.html +315 -0
  27. data/doc/Lims/Core/Actions/BulkAction.html +224 -0
  28. data/doc/Lims/Core/Actions/BulkAction/AfterEval.html +253 -0
  29. data/doc/Lims/Core/Actions/TestActionGroup.html +101 -0
  30. data/doc/Lims/Core/Actions/TestActionGroup/Action.html +133 -0
  31. data/doc/Lims/Core/Actions/TestActionGroup/ActionGroup.html +127 -0
  32. data/doc/Lims/Core/Base.html +287 -0
  33. data/doc/Lims/Core/Base/AccessibleViaSuper.html +252 -0
  34. data/doc/Lims/Core/Base/ClassMethod.html +177 -0
  35. data/doc/Lims/Core/Base/HashString.html +177 -0
  36. data/doc/Lims/Core/Base/IsArrayOf.html +606 -0
  37. data/doc/Lims/Core/Base/State.html +130 -0
  38. data/doc/Lims/Core/Organization.html +113 -0
  39. data/doc/Lims/Core/Organization/Batch.html +106 -0
  40. data/doc/Lims/Core/Persistence.html +267 -0
  41. data/doc/Lims/Core/Persistence/ComparisonFilter.html +318 -0
  42. data/doc/Lims/Core/Persistence/Filter.html +252 -0
  43. data/doc/Lims/Core/Persistence/IdentityMap.html +409 -0
  44. data/doc/Lims/Core/Persistence/IdentityMap/Class.html +144 -0
  45. data/doc/Lims/Core/Persistence/IdentityMap/DuplicateError.html +126 -0
  46. data/doc/Lims/Core/Persistence/IdentityMap/DuplicateIdError.html +136 -0
  47. data/doc/Lims/Core/Persistence/IdentityMap/DuplicateObjectError.html +136 -0
  48. data/doc/Lims/Core/Persistence/IdentityMapClass.html +133 -0
  49. data/doc/Lims/Core/Persistence/Logger.html +105 -0
  50. data/doc/Lims/Core/Persistence/Logger/Persistor.html +334 -0
  51. data/doc/Lims/Core/Persistence/Logger/Session.html +452 -0
  52. data/doc/Lims/Core/Persistence/Logger/Store.html +470 -0
  53. data/doc/Lims/Core/Persistence/MessageBus.html +871 -0
  54. data/doc/Lims/Core/Persistence/MessageBus/ConnectionError.html +123 -0
  55. data/doc/Lims/Core/Persistence/MessageBus/InvalidSettingsError.html +122 -0
  56. data/doc/Lims/Core/Persistence/MultiCriteriaFilter.html +293 -0
  57. data/doc/Lims/Core/Persistence/PersistAssociationTrait.html +91 -0
  58. data/doc/Lims/Core/Persistence/PersistableTrait.html +91 -0
  59. data/doc/Lims/Core/Persistence/Persistor.html +3072 -0
  60. data/doc/Lims/Core/Persistence/Persistor/DuplicateError.html +205 -0
  61. data/doc/Lims/Core/Persistence/Persistor/DuplicateIdError.html +147 -0
  62. data/doc/Lims/Core/Persistence/Persistor/DuplicateObjectError.html +147 -0
  63. data/doc/Lims/Core/Persistence/PersistorTrait.html +91 -0
  64. data/doc/Lims/Core/Persistence/ResourceState.html +1738 -0
  65. data/doc/Lims/Core/Persistence/Search.html +269 -0
  66. data/doc/Lims/Core/Persistence/Search/CreateSearch.html +251 -0
  67. data/doc/Lims/Core/Persistence/Search/SearchPersistor.html +240 -0
  68. data/doc/Lims/Core/Persistence/Search/SearchSequelPersistor.html +396 -0
  69. data/doc/Lims/Core/Persistence/Sequel.html +117 -0
  70. data/doc/Lims/Core/Persistence/Sequel/Filters.html +462 -0
  71. data/doc/Lims/Core/Persistence/Sequel/ForTest.html +101 -0
  72. data/doc/Lims/Core/Persistence/Sequel/ForTest/Name.html +137 -0
  73. data/doc/Lims/Core/Persistence/Sequel/ForTest/Name/NamePersitor.html +143 -0
  74. data/doc/Lims/Core/Persistence/Sequel/Migrations.html +266 -0
  75. data/doc/Lims/Core/Persistence/Sequel/Persistor.html +665 -0
  76. data/doc/Lims/Core/Persistence/Sequel/Session.html +501 -0
  77. data/doc/Lims/Core/Persistence/Sequel/Store.html +417 -0
  78. data/doc/Lims/Core/Persistence/Session.html +2751 -0
  79. data/doc/Lims/Core/Persistence/Session/UnmanagedObjectError.html +111 -0
  80. data/doc/Lims/Core/Persistence/StateGroup.html +696 -0
  81. data/doc/Lims/Core/Persistence/StateList.html +498 -0
  82. data/doc/Lims/Core/Persistence/Store.html +695 -0
  83. data/doc/Lims/Core/Persistence/UuidResource.html +1044 -0
  84. data/doc/Lims/Core/Persistence/UuidResource/InvalidUuidError.html +111 -0
  85. data/doc/Lims/Core/Persistence/UuidResource/UuidResourcePersistor.html +337 -0
  86. data/doc/Lims/Core/Persistence/Uuidable.html +799 -0
  87. data/doc/Lims/Core/Persistor.html +320 -0
  88. data/doc/Lims/Core/Resource.html +165 -0
  89. data/doc/Object.html +228 -0
  90. data/doc/SessionSpec.html +101 -0
  91. data/doc/SessionSpec/Model.html +279 -0
  92. data/doc/SessionSpec/Model/ModelPersistor.html +327 -0
  93. data/doc/_index.html +732 -0
  94. data/doc/class_list.html +47 -0
  95. data/doc/css/common.css +1 -0
  96. data/doc/css/full_list.css +55 -0
  97. data/doc/css/style.css +322 -0
  98. data/doc/file.README.html +127 -0
  99. data/doc/file_list.html +49 -0
  100. data/doc/frames.html +13 -0
  101. data/doc/index.html +127 -0
  102. data/doc/js/app.js +205 -0
  103. data/doc/js/full_list.js +167 -0
  104. data/doc/js/jquery.js +16 -0
  105. data/doc/method_list.html +1894 -0
  106. data/doc/top-level-namespace.html +100 -0
  107. data/lib/common.rb +18 -0
  108. data/lib/lims-core.rb +29 -0
  109. data/lib/lims-core/actions.rb +10 -0
  110. data/lib/lims-core/actions/action.rb +185 -0
  111. data/lib/lims-core/actions/action_group.rb +54 -0
  112. data/lib/lims-core/actions/bulk_action.rb +65 -0
  113. data/lib/lims-core/base.rb +132 -0
  114. data/lib/lims-core/helpers.rb +41 -0
  115. data/lib/lims-core/persistence.rb +15 -0
  116. data/lib/lims-core/persistence/comparison_filter.rb +54 -0
  117. data/lib/lims-core/persistence/filter.rb +23 -0
  118. data/lib/lims-core/persistence/identity_map.rb +55 -0
  119. data/lib/lims-core/persistence/logger/all.rb +5 -0
  120. data/lib/lims-core/persistence/logger/persistor.rb +35 -0
  121. data/lib/lims-core/persistence/logger/session.rb +30 -0
  122. data/lib/lims-core/persistence/logger/store.rb +37 -0
  123. data/lib/lims-core/persistence/message_bus.rb +131 -0
  124. data/lib/lims-core/persistence/multi_criteria_filter.rb +50 -0
  125. data/lib/lims-core/persistence/persist_association_trait.rb +96 -0
  126. data/lib/lims-core/persistence/persistable_trait.rb +150 -0
  127. data/lib/lims-core/persistence/persistor.rb +495 -0
  128. data/lib/lims-core/persistence/resource_state.rb +157 -0
  129. data/lib/lims-core/persistence/search.rb +3 -0
  130. data/lib/lims-core/persistence/search/all.rb +3 -0
  131. data/lib/lims-core/persistence/search/create_search.rb +55 -0
  132. data/lib/lims-core/persistence/search/search_persistor.rb +45 -0
  133. data/lib/lims-core/persistence/search/search_sequel_persistor.rb +40 -0
  134. data/lib/lims-core/persistence/sequel.rb +14 -0
  135. data/lib/lims-core/persistence/sequel/filters.rb +106 -0
  136. data/lib/lims-core/persistence/sequel/migrations.rb +14 -0
  137. data/lib/lims-core/persistence/sequel/migrations/add_audit_tables.rb +147 -0
  138. data/lib/lims-core/persistence/sequel/migrations/initial.rb +156 -0
  139. data/lib/lims-core/persistence/sequel/persistor.rb +200 -0
  140. data/lib/lims-core/persistence/sequel/session.rb +136 -0
  141. data/lib/lims-core/persistence/sequel/store.rb +37 -0
  142. data/lib/lims-core/persistence/session.rb +409 -0
  143. data/lib/lims-core/persistence/state_group.rb +97 -0
  144. data/lib/lims-core/persistence/state_list.rb +56 -0
  145. data/lib/lims-core/persistence/store.rb +73 -0
  146. data/lib/lims-core/persistence/uuid_resource.rb +115 -0
  147. data/lib/lims-core/persistence/uuid_resource_persistor.rb +43 -0
  148. data/lib/lims-core/persistence/uuidable.rb +107 -0
  149. data/lib/lims-core/resource.rb +21 -0
  150. data/lib/lims-core/subclass_tracker.rb +30 -0
  151. data/lib/lims-core/version.rb +5 -0
  152. data/lims-core.gemspec +40 -0
  153. data/makefile +52 -0
  154. data/showoff/core-2012-06-11/core/01_slide.md +237 -0
  155. data/showoff/core-2012-06-11/core/02_slide.md +110 -0
  156. data/showoff/core-2012-06-11/custom.css +44 -0
  157. data/showoff/core-2012-06-11/main/01_slide.md +53 -0
  158. data/showoff/core-2012-06-11/showoff.json +10 -0
  159. data/showoff/core-2012-06-11/tp1.tpl +1 -0
  160. data/spec/actions/action_group_spec.rb +39 -0
  161. data/spec/actions/spec_helper.rb +1 -0
  162. data/spec/persistence/identity_map_spec.rb +55 -0
  163. data/spec/persistence/logger/spec_helper.rb +7 -0
  164. data/spec/persistence/logger/store_spec.rb +48 -0
  165. data/spec/persistence/message_bus_spec.rb +76 -0
  166. data/spec/persistence/sequel/session_spec.rb +125 -0
  167. data/spec/persistence/sequel/spec_helper.rb +39 -0
  168. data/spec/persistence/sequel/store_shared.rb +25 -0
  169. data/spec/persistence/sequel/store_spec.rb +22 -0
  170. data/spec/persistence/session_spec.rb +199 -0
  171. data/spec/persistence/spec_helper.rb +2 -0
  172. data/spec/persistence/uuid_resource_spec.rb +80 -0
  173. data/spec/spec_helper.rb +10 -0
  174. data/spec/subclass_tracker_sperc.rb +62 -0
  175. data/utils/constant_tree.rb +29 -0
  176. data/utils/stack.rb +48 -0
  177. 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,5 @@
1
+ require('lims-core/persistence/logger/persistor')
2
+ require('lims-core/persistence/logger/session')
3
+ require('lims-core/persistence/logger/store')
4
+ require('lims-core/persistence/message_bus')
5
+ require('lims-core/persistence/persistor')
@@ -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