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,100 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.7.3
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ relpath = '';
19
+ if (relpath != '') relpath += '/';
20
+ </script>
21
+
22
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
25
+
26
+
27
+ </head>
28
+ <body>
29
+ <script type="text/javascript" charset="utf-8">
30
+ if (window.top.frames.main) document.body.className = 'frames';
31
+ </script>
32
+
33
+ <div id="header">
34
+ <div id="menu">
35
+
36
+ <a href="_index.html">Index</a> &raquo;
37
+
38
+
39
+ <span class="title">Top Level Namespace</span>
40
+
41
+
42
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
43
+ </div>
44
+
45
+ <div id="search">
46
+
47
+ <a id="class_list_link" href="#">Class List</a>
48
+
49
+ <a id="method_list_link" href="#">Method List</a>
50
+
51
+ <a id="file_list_link" href="#">File List</a>
52
+
53
+ </div>
54
+ <div class="clear"></div>
55
+ </div>
56
+
57
+ <iframe id="search_frame"></iframe>
58
+
59
+ <div id="content"><h1>Top Level Namespace
60
+
61
+
62
+
63
+ </h1>
64
+
65
+ <dl class="box">
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+ </dl>
75
+ <div class="clear"></div>
76
+
77
+ <h2>Defined Under Namespace</h2>
78
+ <p class="children">
79
+
80
+
81
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Lims.html" title="Lims (module)">Lims</a></span>, <span class='object_link'><a href="SessionSpec.html" title="SessionSpec (module)">SessionSpec</a></span>
82
+
83
+
84
+
85
+ <strong class="classes">Classes:</strong> <span class='object_link'><a href="Array.html" title="Array (class)">Array</a></span>, <span class='object_link'><a href="Object.html" title="Object (class)">Object</a></span>
86
+
87
+
88
+ </p>
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+ </div>
97
+
98
+
99
+ </body>
100
+ </html>
data/lib/common.rb ADDED
@@ -0,0 +1,18 @@
1
+ # requirement used by everything
2
+ require 'facets/string'
3
+ require 'facets/kernel'
4
+ require 'facets/hash'
5
+ require 'facets/array'
6
+
7
+ require 'virtus'
8
+ require 'aequitas/virtus_integration'
9
+
10
+ class Object
11
+ def andtap(&block)
12
+ self && (block ? block.call(self) : self)
13
+ end
14
+
15
+ def self.parent_scope()
16
+ @__parent_scope ||= eval self.name.split('::').tap { |_| _.pop }.join('::')
17
+ end
18
+ end
data/lib/lims-core.rb ADDED
@@ -0,0 +1,29 @@
1
+ # vi: spell:spelllang=en ts=2:sts=2:sw=2:et
2
+ require "lims-core/version"
3
+
4
+ require 'lims-core/persistence'
5
+ # Persistence submodules need to be required manually. This is to avoid
6
+ # having to require and install all the store dependency (mysql, postgres) etc ...
7
+
8
+
9
+ # LIMS stands for Laboratory Information Management System.
10
+ # A namespace.
11
+ module Lims
12
+ # The Core of the {Lims LIM S}ystem.
13
+ # Includes the basic classes corresponding to the :
14
+ # 1. Resource base class
15
+ # 2. Persistence Layer
16
+ #
17
+ # The Core is split in the following submodule/namespace :
18
+ # 10. {Actions}
19
+ # High level {Actions::Action actions} that can be performed on things.
20
+ #
21
+ # 12. {Persistence}
22
+ #
23
+ #
24
+ # This partition is more for clarity/documentation purposes and it's not meant to be really tight.
25
+ # However it's more likely than the submodules dependency will be a tree than a graph, (but it's not a necessity).
26
+ module Core
27
+ # Your code goes here...
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ #vi: ts=2 sw=2 et spell spelllang=en
2
+ # true needed to avoid modeline comment to be seen as Lims::Core doc.
3
+ require 'lims-core/actions/action'
4
+
5
+ module Lims::Core
6
+ # Actions are high level end user groups of elementary steps that a user can perform on things .
7
+ # They can be seen as use-case, scenario and will probably each correspond to one call in the API.
8
+ module Actions
9
+ end
10
+ end
@@ -0,0 +1,185 @@
1
+ require 'common'
2
+
3
+ require 'virtus'
4
+ require 'facets/ostruct'
5
+
6
+ require 'lims-core/persistence/store'
7
+
8
+ module Lims::Core
9
+ module Actions
10
+ # This mixin add the Action behavior to a Class.
11
+ # An action can be called and reverted (if possible) within a {Persistence::Session session}.
12
+ # For this, the action must implements the {Action::AfterEval#_call_in_session _call_in_session} and {Action::AfterEval#_revert_in_session _revert_in_session}.
13
+ # Those methods are private and take a session as a parameter.
14
+ # The public equivalent (call/revert) will create a session (using the store) and call the corresponding methods.
15
+
16
+ module Action
17
+ extend SubclassTracker
18
+ class << self
19
+ alias_method :tracker_included, :included
20
+ end
21
+ UnrevertableAction = Class.new(StandardError)
22
+ def self.included(klass)
23
+ klass.class_eval do
24
+ include Base
25
+ attribute :store, Persistence::Store, :required => true
26
+ attribute :user, Object, :required => true, :writer => :private, :initializable => true
27
+ attribute :application, String, :required => true
28
+ attribute :result, Object
29
+ include AfterEval # hack so initialize would be called properly
30
+ end
31
+ tracker_included(klass)
32
+ end
33
+
34
+ class InvalidParameters < RuntimeError
35
+ attr_reader :errors
36
+ def initialize(errors = {})
37
+ @errors = errors
38
+ end
39
+ end
40
+
41
+ module AfterEval
42
+ # Initialize a new actions
43
+ # 'Common' parameters are set as argument
44
+ # whereas specific ones are set on a dummy object via the block.
45
+ # The block is executed within a session allowing to find object form id, etc.
46
+
47
+ def initialize(*args, &initializer)
48
+ @initializer = initializer
49
+ super(*args)
50
+ end
51
+
52
+ # Executes the action.
53
+ # This is a wrapper around _call_in_session,
54
+ # and it shouldn't be overriden.
55
+ # A block can be passed to be evaluated with the session after the save session been saved.
56
+ # This is usefull to get ids of saved object.
57
+ # False will be returned if the action failed (or parameters are invalid)
58
+ # @return the value return by the block
59
+ # @yieldparam [Action] a self
60
+ # @yieldparam [Session] session the current session.
61
+ def call(session=nil, &after_save)
62
+ with_session(session) do |session|
63
+ execute_and_store_result(session, &after_save)
64
+ end.andtap { |block| block.call }
65
+ end
66
+
67
+ def execute_and_store_result(session, &after_save)
68
+ after_save ||= lambda { |a,s| a.result }
69
+ self.result = _call_in_session(session)
70
+ _objects_to_save.each do |a|
71
+ session << a
72
+ end
73
+ lambda { after_save[self, session] }
74
+ end
75
+
76
+ protected :execute_and_store_result
77
+
78
+ # Execute the opposite of the action if possible.
79
+ # This a wrapper around _revert_in_session,
80
+ # and shouldn't be overriden.
81
+ # @raise UnrevertableAction
82
+ def revert()
83
+ with_session { |s| _revert_in_session(s) }
84
+ end
85
+
86
+ # Execute the given block within a new session.
87
+ # Validates the action and fill #errors if needed
88
+ # @return [Object, False]
89
+ def with_session(*args, &block)
90
+ @store.with_session(*args) do |session|
91
+ # initialize action
92
+ if @initializer
93
+ params = OpenStruct.new
94
+
95
+ @initializer[params, session]
96
+
97
+ # We want to catch ALL attributes errors
98
+ # therefore We need to iterate on each attributes
99
+ # and catch the potentiel exception raised by each
100
+ # assignment.
101
+
102
+
103
+ attribute_errors = []
104
+ params.each do |key, value|
105
+ next if %w(user application_id).include?(key.to_s)
106
+ begin
107
+ send("#{key}=", value)
108
+ rescue NoMethodError => e
109
+ attribute_errors << [key, value]
110
+ end
111
+ end
112
+
113
+ unless attribute_errors.empty?
114
+ # An error occured.
115
+ # We need to check if set attributes are valid
116
+ # and add the attributes errors to the general error message.
117
+ valid?
118
+ invalid_parameters = errors_to_hash
119
+
120
+ attribute_errors.each do |key, value|
121
+ invalid_parameters[key] = ["field :#{key} doesn't exist or value '#{value}' is invalid"]
122
+ end
123
+ raise InvalidParameters.new(invalid_parameters)
124
+ end
125
+ @initializer = nil
126
+ end
127
+
128
+ # Note: there is a bug in Aequitas gem on the valid?
129
+ # method call. For an attribute which needs to be required
130
+ # and greater than 0, the greater than 0 is tested first
131
+ # and the required after. So if the parameter is not set,
132
+ # nil >= 0 is evaluated by Aequitas and an exception is
133
+ # raised. We catch it here and raise an InvalidParameters error.
134
+ is_valid = begin
135
+ valid?
136
+ rescue
137
+ raise InvalidParameters.new
138
+ end
139
+
140
+ if is_valid
141
+ block.call(session)
142
+ else
143
+ invalid_parameters = errors_to_hash
144
+ raise InvalidParameters.new(invalid_parameters)
145
+ end
146
+ end
147
+ end
148
+
149
+ def errors_to_hash()
150
+ {}.tap do |hash|
151
+ errors.keys.each do |key|
152
+ hash[key] = [].tap do |array|
153
+ # errors[key] returns an array of Aequitas::Violation
154
+ errors[key].each do |error|
155
+ array << error.message
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ # This is the main method of an action,
163
+ # called to effectively perform an action.
164
+ def _call_in_session(session)
165
+ raise NotImplementedError
166
+ end
167
+
168
+ # how to revert the action,
169
+ # if possible.
170
+ def _revert_in_session(session)
171
+ raise UnrevertableAction(self)
172
+ end
173
+
174
+ # List of objects to save (add to the session).
175
+ # By default get all attributes and the resulth.
176
+ # Override if need (to add a created resource for example).
177
+ # @return a list of object to save
178
+ def _objects_to_save
179
+ [result, *attributes.map { |a| a[1] }].select { |o| o.is_a?(Resource) }
180
+ end
181
+ private :_call_in_session, :_revert_in_session, :_objects_to_save
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,54 @@
1
+ require 'lims-core/actions/action'
2
+
3
+ module Lims::Core
4
+ module Actions
5
+ # This module provide a helper to execute multiple action a time.
6
+ # This module is not intended to be used as a bare class but more
7
+ # as a base to define multiple actions "creator", which will
8
+ # manage the needed parameters and create the actions consequently.
9
+ # This action executes all the action in sequence and the result
10
+ # is an array of the results.
11
+ module ActionGroup
12
+ def self.included(klass)
13
+ klass.class_eval do
14
+ include Action
15
+ include AfterEval
16
+ attribute :actions, Array, :required => true, :writer => :private , :reader => :private, :initializable => true
17
+ end
18
+
19
+ end
20
+
21
+ # This module is needed
22
+ # otherwire the following methogs
23
+ # will be overriden by the mixin called in self.included.
24
+ module AfterEval
25
+
26
+ # We need to override call to process all the after_save
27
+ # once all actions have been executed
28
+ def execute_and_store_result(session, &after_save)
29
+ after_save ||= lambda { |a,s| a.result }
30
+ self.result = _call_in_session(session)
31
+ lambda { after_save[self, session] }
32
+ end
33
+
34
+ def _call_in_session(session)
35
+ actions.map do |action|
36
+ update_action_attribute(action)
37
+ action.with_session(session) do |new_session|
38
+ action.execute_and_store_result(new_session)
39
+ action.result
40
+ end
41
+ end
42
+ end
43
+
44
+ # Set the required attribute of the children action
45
+ # to the parent one.
46
+ def update_action_attribute(action)
47
+ %w(store user application).each do |attribute|
48
+ action[attribute]=self[attribute]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,65 @@
1
+ require 'lims-core/actions/action_group'
2
+
3
+ module Lims::Core
4
+ module Actions
5
+ # *Lift* an action to a bulk one
6
+ # by creating a group of similar actions.
7
+ # The parameters being a array of parameters which will
8
+ # be used to create each individual action.
9
+ module BulkAction
10
+ def self.included(klass)
11
+ klass.instance_eval do
12
+ # @param [String] element_name name of the underlying action. Use to get the result from the subaction element.
13
+ # @param [String] parameters key to get parameters array for each action and return the result.
14
+ def initialize_class(element_name, group_name, action_class)
15
+ define_method :group_name do
16
+ group_name
17
+ end
18
+
19
+ define_method :action_class do
20
+ action_class
21
+ end
22
+
23
+ define_method :element_name do
24
+ element_name
25
+ end
26
+
27
+ self.class_eval do
28
+ include ActionGroup
29
+ attribute group_name, Array, :required => true
30
+ include AfterEval
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ module AfterEval
37
+
38
+ def initialize(*args, &initializer)
39
+ super(*args) do |a,s|
40
+ initializer.call(a,s) if initializer
41
+ parameter_list = a[group_name]
42
+ a.actions = parameter_list.map do |parameters|
43
+ action_class.new do |action, session|
44
+ # We copy the parameters to action class.
45
+ # action, is not the real action but an open struct.
46
+ # We can't just pass the parameters in the constructor
47
+ # in case some parameters are private.
48
+ parameters.each { |k, v| action[k] = v }
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def _call_in_session(session)
55
+ super(session)
56
+ if element_name
57
+ {group_name=> actions.map { |a| a.result[element_name] }}
58
+ else
59
+ {group_name=> actions.map { |a| a.result }}
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end