lims-core 3.2.3

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