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,147 @@
1
+ module Lims::Core::Persistence::Sequel::Migrations
2
+ module AddAuditTables
3
+ def self.migration(exclude_tables={}, additional_tables_to_update=[])
4
+ [:schema_info, :sessions, :primary_keys].each do |table|
5
+ exclude_tables[table] = true
6
+ end
7
+ this = self
8
+ Proc.new do
9
+ next unless defined?(DB)
10
+ table_names = []
11
+ change do
12
+ if additional_tables_to_update.size == 0
13
+ # Create session table
14
+ create_table :sessions do
15
+ primary_key :id
16
+ String :user
17
+ String :backend_application_id
18
+ String :parameters, :text => true
19
+ boolean :success
20
+ timestamp :start_time
21
+ DateTime :end_time
22
+ end
23
+ end
24
+
25
+ #create migration session
26
+ self << <<-EOS
27
+ INSERT INTO sessions(user, backend_application_id)
28
+ VALUES('admin', 'lims-core');
29
+ EOS
30
+
31
+ session_id = DB[:sessions].order(:id).last[:id]
32
+
33
+ tables_to_update = additional_tables_to_update.size == 0 ? DB.tables : additional_tables_to_update
34
+ tables_to_update.each do |table_name|
35
+ next if exclude_tables.include?(table_name)
36
+ table_names << table_name
37
+ table = DB[table_name]
38
+
39
+ # extend all tables with revision_id
40
+ if DB[table].columns.include?(:revision) == false
41
+ alter_table table_name do
42
+ add_column :revision, Integer, :default => 1
43
+ end
44
+ end
45
+
46
+
47
+ # create history table
48
+ revision_table = "#{table_name}_revision"
49
+ self << <<-EOS
50
+ CREATE TABLE #{revision_table} AS
51
+ SELECT *, 'initial' AS `action`, #{session_id} as session_id
52
+ FROM #{table_name}
53
+ EOS
54
+
55
+ puts "adding key to #{revision_table}"
56
+ alter_table revision_table do
57
+ add_primary_key :internal_id
58
+ add_index [:id, :revision], :unique => true
59
+ add_index [:id, :session_id], :unique => true
60
+ add_foreign_key [:session_id], :sessions, :key => :id
61
+ end
62
+
63
+
64
+ # Create trigger
65
+ trigger_name = "maintain_#{table_name}_on_insert"
66
+ self << "DROP TRIGGER IF EXISTS #{trigger_name};"
67
+ trigger_code = <<-EOT
68
+
69
+ CREATE TRIGGER #{trigger_name} AFTER INSERT ON #{table_name}
70
+ FOR EACH ROW
71
+ BEGIN
72
+ #{this.insert_into_revision(table, revision_table, :insert)}
73
+ EOT
74
+
75
+ puts trigger_code
76
+ self << trigger_code
77
+
78
+ trigger_name = "maintain_#{table_name}_on_update"
79
+ self << "DROP TRIGGER IF EXISTS #{trigger_name};"
80
+ trigger_code = <<-EOT
81
+
82
+ CREATE TRIGGER #{trigger_name} BEFORE UPDATE ON #{table_name}
83
+ FOR EACH ROW
84
+ BEGIN
85
+ # Update the revision number
86
+ SET NEW.revision = OLD.revision+1;
87
+ # Update the revision table
88
+ #{this.insert_into_revision(table, revision_table, :update)}
89
+ EOT
90
+
91
+ puts trigger_code
92
+ self << trigger_code
93
+
94
+ trigger_name = "maintain_#{table_name}_on_delete"
95
+ self << "DROP TRIGGER IF EXISTS #{trigger_name};"
96
+ trigger_code = <<-EOT
97
+
98
+ CREATE TRIGGER #{trigger_name} BEFORE DELETE ON #{table_name}
99
+ FOR EACH ROW
100
+ BEGIN
101
+ #{this.insert_into_revision(table, revision_table, :delete)}
102
+ EOT
103
+
104
+ puts trigger_code
105
+ self << trigger_code
106
+ end
107
+
108
+ if additional_tables_to_update.size > 0
109
+ self << "DROP VIEW IF EXISTS revisions"
110
+ end
111
+
112
+ revision_tables = DB.tables.map { |table| table unless table.match(/revision/) }.compact
113
+ revision_tables -= exclude_tables.keys
114
+
115
+ view_code = "CREATE VIEW revisions AS " + revision_tables.map do |table_name|
116
+ revision_table = "#{table_name}_revision"
117
+ %Q{ SELECT '#{table_name}' AS revision_table,
118
+ id,
119
+ action,
120
+ session_id
121
+ FROM #{revision_table}
122
+
123
+ }
124
+ end.join(' UNION ')
125
+
126
+ puts view_code
127
+ self << view_code
128
+ end
129
+ end
130
+ end
131
+
132
+ def self.insert_into_revision(table, revision_table, type)
133
+ %Q{ INSERT INTO #{revision_table}
134
+ SET #{
135
+ if type == :delete
136
+ 'id = OLD.id, revision = OLD.revision+1'
137
+ else
138
+ table.columns.map { |c| "`#{c}` = NEW.#{c}" }.join(', ')
139
+ end
140
+ },
141
+ `action` = '#{type}',
142
+ `session_id` = @current_session_id;
143
+ END;
144
+ }
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,156 @@
1
+ # This module should not be require on it's own.
2
+ # Require migrations instead.
3
+
4
+ module Lims::Core::Persistence::Sequel::Migrations
5
+ Initial = Proc.new do
6
+ change do
7
+ create_table :samples do
8
+ primary_key :id
9
+ String :name
10
+ end
11
+
12
+ create_table :oligos do
13
+ primary_key :id
14
+ String :sequence
15
+ end
16
+
17
+ create_table :aliquots do
18
+ primary_key :id
19
+ foreign_key :sample_id, :samples, :key => :id
20
+ foreign_key :tag_id, :oligos, :key => :id
21
+ Integer :quantity
22
+ String :type
23
+ end
24
+
25
+ create_table :flowcells do
26
+ primary_key :id
27
+ Integer :number_of_lanes
28
+ end
29
+
30
+ create_table :lanes do
31
+ #primary_key :flowcell_id, :position
32
+ primary_key :id
33
+ foreign_key :flowcell_id, :flowcells, :key => :id
34
+ Integer :position
35
+ foreign_key :aliquot_id, :aliquots, :key => :id
36
+ end
37
+
38
+ create_table :tubes do
39
+ primary_key :id
40
+ end
41
+
42
+ create_table :tube_aliquots do
43
+ primary_key :id
44
+ Integer :tube_id
45
+ foreign_key :aliquot_id, :aliquots, :key => :id
46
+ end
47
+
48
+ create_table :spin_columns do
49
+ primary_key :id
50
+ end
51
+
52
+ create_table :spin_column_aliquots do
53
+ primary_key :id
54
+ Integer :spin_column_id
55
+ foreign_key :aliquot_id, :aliquots, :key => :id
56
+ end
57
+
58
+ create_table :plates do
59
+ primary_key :id
60
+ Integer :number_of_rows
61
+ Integer :number_of_columns
62
+ end
63
+
64
+ create_table :wells do
65
+ primary_key :id
66
+ foreign_key :plate_id, :plates, :key => :id
67
+ Integer :position
68
+ foreign_key :aliquot_id, :aliquots, :key => :id
69
+ end
70
+
71
+ create_table :tube_racks do
72
+ primary_key :id
73
+ Integer :number_of_rows
74
+ Integer :number_of_columns
75
+ end
76
+
77
+ create_table :tube_rack_slots do
78
+ primary_key :id
79
+ foreign_key :tube_rack_id, :tube_racks, :key => :id
80
+ Integer :position
81
+ foreign_key :tube_id, :tubes, :key=> :id
82
+ end
83
+
84
+ create_table :tag_groups do
85
+ primary_key :id
86
+ String :name
87
+ end
88
+
89
+ create_table :tag_group_associations do
90
+ primary_key :id
91
+ foreign_key :tag_group_id, :tag_groups, :key => :id
92
+ Integer :position
93
+ foreign_key :oligo_id, :oligos, :key => :id
94
+ end
95
+
96
+ create_table :uuid_resources do
97
+ primary_key :id
98
+ String :uuid, :fixed => true, :size => 64
99
+ String :model_class
100
+ Integer :key
101
+ end
102
+
103
+ create_table :users do
104
+ primary_key :id
105
+ end
106
+
107
+ create_table :studies do
108
+ primary_key :id
109
+ end
110
+
111
+ create_table :orders do
112
+ primary_key :id
113
+ foreign_key :creator_id, :users, :key => :id
114
+
115
+ String :pipeline
116
+ String :parameters
117
+ String :status
118
+ blob :state
119
+ foreign_key :study_id, :studies, :key => :id
120
+ String :cost_code
121
+ end
122
+
123
+ create_table :items do
124
+ primary_key :id
125
+ foreign_key :order_id, :orders, :key => :id
126
+ String :role
127
+ foreign_key :resource_id, :uuid_resources, :key => :id
128
+ String :uuid, :fixed => true, :size => 64
129
+ String :status
130
+ Integer :iteration, :default => 0
131
+ end
132
+
133
+ create_table :searches do
134
+ primary_key :id
135
+ String :description
136
+ String :filter_type
137
+ String :model
138
+ blob :filter_parameters
139
+ end
140
+
141
+ create_table :labellables do
142
+ primary_key :id
143
+ String :name
144
+ String :type
145
+ end
146
+
147
+ create_table :labels do
148
+ primary_key :id
149
+ foreign_key :labellable_id, :labellables, :key => :id
150
+ String :type
151
+ String :position
152
+ String :value
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,200 @@
1
+ # vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
2
+
3
+ require 'lims-core/persistence/identity_map'
4
+ require 'active_support/inflector'
5
+ require 'lims-core/persistence/sequel/filters'
6
+
7
+ module Lims::Core
8
+ module Persistence
9
+ module Sequel
10
+ # Mixin giving extended the persistor classes with
11
+ # the Sequel (load/save) behavior.
12
+ module Persistor
13
+
14
+ include Filters
15
+
16
+ def self.included(klass)
17
+ klass.class_eval do
18
+ # @return [String] the name of SQL table.
19
+ def self.table_name
20
+ @table_name ||= parent_scope.name.split('::').last.pluralize.snakecase.to_sym
21
+ end
22
+ # The Sequel::Dataset.
23
+ # Corresponds to table.
24
+ # @param [Sequel::Session] session
25
+ # @return [::Sequel::Dataset]
26
+ def self.dataset(session)
27
+ session.database[self.table_name]
28
+ end
29
+ end
30
+ end
31
+
32
+ def initialize(session_or_persistor, dataset=nil, *args, &block )
33
+ id_to_state, object_to_state = [nil, nil]
34
+ case session_or_persistor
35
+ when Sequel::Persistor
36
+ # We link the session and the identity map variables,
37
+ # so that object loaded via this persistor can be found (and their id)
38
+ # through the origial persistor.
39
+ # Hack to get those private variables.
40
+ session, identity_map_parameters = session_or_persistor.instance_eval do
41
+ [@session, [@id_to_state, @object_to_state]]
42
+ end
43
+ super(session, *args, &block)
44
+ @id_to_state , @object_to_state = identity_map_parameters
45
+ else Session
46
+ super(session_or_persistor, *args, &block)
47
+ end
48
+
49
+ @dataset = dataset || self.class.dataset(@session)
50
+ end
51
+
52
+ # @return [String] the name of the table
53
+ def table_name
54
+ self.class.table_name
55
+ end
56
+
57
+
58
+ # The Sequel::Dataset.
59
+ # Corresponds to a table.
60
+ # @return [::Sequel::Dataset]
61
+ def dataset
62
+ @dataset
63
+ end
64
+
65
+
66
+ # Returns the number of object in the store
67
+ # @return [Fixnum]
68
+ def count
69
+ dataset.count
70
+ end
71
+
72
+ protected
73
+ # load a slice.
74
+ def for_each_in_slice(start, length)
75
+ return if length == 0
76
+ dataset.order(primary_key).limit(length, start).each do |h|
77
+ yield(h)
78
+ end
79
+ end
80
+
81
+ # The primary key
82
+ # @return [Symbol]
83
+ def primary_key()
84
+ :id
85
+ end
86
+
87
+ def qualified_key
88
+ @qualified_key ||= ::Sequel.qualify(self.class.table_name, primary_key)
89
+ end
90
+
91
+ def bulk_load(ids, *params, &block)
92
+ dataset.filter(qualified_key => ids.map(&:id)).all(&block)
93
+ end
94
+ public :bulk_load
95
+
96
+ def ids_for(criteria)
97
+ dataset.select(qualified_key).filter(criteria).map { |h| h[primary_key] }
98
+
99
+ end
100
+
101
+ # Save a raw object, i.e. the object
102
+ # attributes excluding any associations.
103
+ # @param [Resource] object the object
104
+ # @return [Fixnum, nil] the Id if save successful
105
+ def insert(state, *params)
106
+ # use prepared statement for everything
107
+ # We only need it at the moment as a workaround for saving the UUID
108
+ # So we might in the future either move it to a UuidResourcePersistor
109
+ # or cached it by attributes
110
+ # @todo benchmark against normal insert
111
+ attributes = filter_attributes_on_save(state.resource.attributes, *params)
112
+ dataset.insert(attributes).tap { |id| state.id = id }
113
+ end
114
+
115
+ def bulk_insert(states, *params)
116
+ #super(states, *params)
117
+ bulk_insert_multi(states, *params)
118
+ #bulk_insert_prepared(states, *params)
119
+ end
120
+ public :bulk_insert
121
+
122
+ def bulk_insert_prepared(states, *params)
123
+ # use prepared statement for everything
124
+ # We only need it at the moment as a workaround for saving the UUID
125
+ # So we might in the future either move it to a UuidResourcePersistor
126
+ # or cached it by attributes
127
+ # @todo benchmark against normal insert
128
+ attributes = filter_attributes_on_save(states.first.resource.attributes, *params)
129
+ statement_name = :"#{table_name}__save_raw"
130
+ dataset.prepare(:insert, statement_name, attributes.keys.mash { |k| [k, :"$#{k}"] })
131
+
132
+ states.each do |state|
133
+ attributes = filter_attributes_on_save(state.resource.attributes, *params)
134
+ @session.database.call(statement_name, attributes)
135
+ end
136
+ end
137
+
138
+ # @todo
139
+ def bulk_insert_multi(states, *params)
140
+ free_ids = get_next_available_ids(states.size)
141
+ states.inject(0) { |i,s| s.id = free_ids[i]; i+1 }
142
+ attributes = states.map { |state| filter_attributes_on_save(state.resource.attributes, *params).merge(primary_key => state.id) }
143
+ dataset.multi_insert(attributes)
144
+ end
145
+
146
+ def bulk_update_raw_attributes(attributes, *params)
147
+ return dataset.on_duplicate_key_update.multi_insert(attributes) if dataset.respond_to? :on_duplicate_key_update
148
+ attributes.each { |att| dataset.filter(primary_key => att.delete(primary_key)).update(att) }
149
+ end
150
+
151
+ def bulk_delete_raw(ids, *params)
152
+ dataset.filter(primary_key => ids).delete
153
+ end
154
+
155
+ def delete_raw(objec, id, *params)
156
+ id.tap do
157
+ dataset.filter(primary_key => id).delete
158
+ end
159
+ end
160
+
161
+ # Return a sequence of free ids, ready to be inserted.
162
+ # The last used id corresponding to each table is store in a special table.
163
+ # We need to lock the table to avoid to thread or process to 'use' the same ids.
164
+ # @param [Integer] quantity
165
+ # @return [Array<Integer>]
166
+ def get_next_available_ids(quantity = 1)
167
+ @session.lock(dataset.from(:primary_keys), true) do |primary_keys|
168
+ current_key_row = primary_keys.first(:table_name => table_name.to_s)
169
+ if current_key_row
170
+ current_key = current_key_row[:current_key]
171
+ else
172
+ # We lock again primary keys and the dataset, because doing a new MySQL lock
173
+ # unlock the previous locked tables.
174
+ # Also we need to check again at this point if the primary_keys table has not
175
+ # been updated by another request. In fact, doing the lock below unlock first
176
+ # primary_keys table, and then lock it again with dataset table. If during this laps
177
+ # of time (between unlock and lock), another request try to initialize primary_keys table,
178
+ # we could have duplicate rows.
179
+ current_key = @session.lock([primary_keys, dataset]) do |primary_keys_dataset, resource_dataset|
180
+ primary_key_row = primary_keys_dataset.first(:table_name => table_name.to_s)
181
+ if primary_key_row
182
+ primary_key_row[:current_key]
183
+ else
184
+ last_id = resource_dataset.max(primary_key) || 0
185
+ primary_keys_dataset.insert(:table_name => table_name.to_s, :current_key => last_id)
186
+ last_id
187
+ end
188
+ end
189
+ end
190
+
191
+ new_current_key = current_key + quantity
192
+ primary_keys.where(:table_name => table_name.to_s).update(:current_key => new_current_key)
193
+ (current_key+1..new_current_key).to_a
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ require 'lims-core/persistence/sequel/filters'