lims-core 3.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.rvmrc +2 -0
- data/.travis.yml +2 -0
- data/.vimrc +27 -0
- data/.yard_templates/default/layout/html/footer.erb +0 -0
- data/.yardopts +1 -0
- data/Gemfile +54 -0
- data/Gemfile.lock +197 -0
- data/Guardfile +21 -0
- data/Guardfile.tmux +28 -0
- data/README.markdown +67 -0
- data/Rakefile +16 -0
- data/config/database.yml +16 -0
- data/doc/Array.html +116 -0
- data/doc/Array/ArrayLoggerPersistor.html +152 -0
- data/doc/Lims.html +114 -0
- data/doc/Lims/Core.html +178 -0
- data/doc/Lims/Core/Action.html +91 -0
- data/doc/Lims/Core/Actions.html +116 -0
- data/doc/Lims/Core/Actions/Action.html +216 -0
- data/doc/Lims/Core/Actions/Action/AfterEval.html +853 -0
- data/doc/Lims/Core/Actions/Action/InvalidParameters.html +268 -0
- data/doc/Lims/Core/Actions/ActionGroup.html +196 -0
- data/doc/Lims/Core/Actions/ActionGroup/AfterEval.html +315 -0
- data/doc/Lims/Core/Actions/BulkAction.html +224 -0
- data/doc/Lims/Core/Actions/BulkAction/AfterEval.html +253 -0
- data/doc/Lims/Core/Actions/TestActionGroup.html +101 -0
- data/doc/Lims/Core/Actions/TestActionGroup/Action.html +133 -0
- data/doc/Lims/Core/Actions/TestActionGroup/ActionGroup.html +127 -0
- data/doc/Lims/Core/Base.html +287 -0
- data/doc/Lims/Core/Base/AccessibleViaSuper.html +252 -0
- data/doc/Lims/Core/Base/ClassMethod.html +177 -0
- data/doc/Lims/Core/Base/HashString.html +177 -0
- data/doc/Lims/Core/Base/IsArrayOf.html +606 -0
- data/doc/Lims/Core/Base/State.html +130 -0
- data/doc/Lims/Core/Organization.html +113 -0
- data/doc/Lims/Core/Organization/Batch.html +106 -0
- data/doc/Lims/Core/Persistence.html +267 -0
- data/doc/Lims/Core/Persistence/ComparisonFilter.html +318 -0
- data/doc/Lims/Core/Persistence/Filter.html +252 -0
- data/doc/Lims/Core/Persistence/IdentityMap.html +409 -0
- data/doc/Lims/Core/Persistence/IdentityMap/Class.html +144 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateError.html +126 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateIdError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateObjectError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMapClass.html +133 -0
- data/doc/Lims/Core/Persistence/Logger.html +105 -0
- data/doc/Lims/Core/Persistence/Logger/Persistor.html +334 -0
- data/doc/Lims/Core/Persistence/Logger/Session.html +452 -0
- data/doc/Lims/Core/Persistence/Logger/Store.html +470 -0
- data/doc/Lims/Core/Persistence/MessageBus.html +871 -0
- data/doc/Lims/Core/Persistence/MessageBus/ConnectionError.html +123 -0
- data/doc/Lims/Core/Persistence/MessageBus/InvalidSettingsError.html +122 -0
- data/doc/Lims/Core/Persistence/MultiCriteriaFilter.html +293 -0
- data/doc/Lims/Core/Persistence/PersistAssociationTrait.html +91 -0
- data/doc/Lims/Core/Persistence/PersistableTrait.html +91 -0
- data/doc/Lims/Core/Persistence/Persistor.html +3072 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateError.html +205 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateIdError.html +147 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateObjectError.html +147 -0
- data/doc/Lims/Core/Persistence/PersistorTrait.html +91 -0
- data/doc/Lims/Core/Persistence/ResourceState.html +1738 -0
- data/doc/Lims/Core/Persistence/Search.html +269 -0
- data/doc/Lims/Core/Persistence/Search/CreateSearch.html +251 -0
- data/doc/Lims/Core/Persistence/Search/SearchPersistor.html +240 -0
- data/doc/Lims/Core/Persistence/Search/SearchSequelPersistor.html +396 -0
- data/doc/Lims/Core/Persistence/Sequel.html +117 -0
- data/doc/Lims/Core/Persistence/Sequel/Filters.html +462 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest.html +101 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name.html +137 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name/NamePersitor.html +143 -0
- data/doc/Lims/Core/Persistence/Sequel/Migrations.html +266 -0
- data/doc/Lims/Core/Persistence/Sequel/Persistor.html +665 -0
- data/doc/Lims/Core/Persistence/Sequel/Session.html +501 -0
- data/doc/Lims/Core/Persistence/Sequel/Store.html +417 -0
- data/doc/Lims/Core/Persistence/Session.html +2751 -0
- data/doc/Lims/Core/Persistence/Session/UnmanagedObjectError.html +111 -0
- data/doc/Lims/Core/Persistence/StateGroup.html +696 -0
- data/doc/Lims/Core/Persistence/StateList.html +498 -0
- data/doc/Lims/Core/Persistence/Store.html +695 -0
- data/doc/Lims/Core/Persistence/UuidResource.html +1044 -0
- data/doc/Lims/Core/Persistence/UuidResource/InvalidUuidError.html +111 -0
- data/doc/Lims/Core/Persistence/UuidResource/UuidResourcePersistor.html +337 -0
- data/doc/Lims/Core/Persistence/Uuidable.html +799 -0
- data/doc/Lims/Core/Persistor.html +320 -0
- data/doc/Lims/Core/Resource.html +165 -0
- data/doc/Object.html +228 -0
- data/doc/SessionSpec.html +101 -0
- data/doc/SessionSpec/Model.html +279 -0
- data/doc/SessionSpec/Model/ModelPersistor.html +327 -0
- data/doc/_index.html +732 -0
- data/doc/class_list.html +47 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +55 -0
- data/doc/css/style.css +322 -0
- data/doc/file.README.html +127 -0
- data/doc/file_list.html +49 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +127 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +167 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +1894 -0
- data/doc/top-level-namespace.html +100 -0
- data/lib/common.rb +18 -0
- data/lib/lims-core.rb +29 -0
- data/lib/lims-core/actions.rb +10 -0
- data/lib/lims-core/actions/action.rb +185 -0
- data/lib/lims-core/actions/action_group.rb +54 -0
- data/lib/lims-core/actions/bulk_action.rb +65 -0
- data/lib/lims-core/base.rb +132 -0
- data/lib/lims-core/helpers.rb +41 -0
- data/lib/lims-core/persistence.rb +15 -0
- data/lib/lims-core/persistence/comparison_filter.rb +54 -0
- data/lib/lims-core/persistence/filter.rb +23 -0
- data/lib/lims-core/persistence/identity_map.rb +55 -0
- data/lib/lims-core/persistence/logger/all.rb +5 -0
- data/lib/lims-core/persistence/logger/persistor.rb +35 -0
- data/lib/lims-core/persistence/logger/session.rb +30 -0
- data/lib/lims-core/persistence/logger/store.rb +37 -0
- data/lib/lims-core/persistence/message_bus.rb +131 -0
- data/lib/lims-core/persistence/multi_criteria_filter.rb +50 -0
- data/lib/lims-core/persistence/persist_association_trait.rb +96 -0
- data/lib/lims-core/persistence/persistable_trait.rb +150 -0
- data/lib/lims-core/persistence/persistor.rb +495 -0
- data/lib/lims-core/persistence/resource_state.rb +157 -0
- data/lib/lims-core/persistence/search.rb +3 -0
- data/lib/lims-core/persistence/search/all.rb +3 -0
- data/lib/lims-core/persistence/search/create_search.rb +55 -0
- data/lib/lims-core/persistence/search/search_persistor.rb +45 -0
- data/lib/lims-core/persistence/search/search_sequel_persistor.rb +40 -0
- data/lib/lims-core/persistence/sequel.rb +14 -0
- data/lib/lims-core/persistence/sequel/filters.rb +106 -0
- data/lib/lims-core/persistence/sequel/migrations.rb +14 -0
- data/lib/lims-core/persistence/sequel/migrations/add_audit_tables.rb +147 -0
- data/lib/lims-core/persistence/sequel/migrations/initial.rb +156 -0
- data/lib/lims-core/persistence/sequel/persistor.rb +200 -0
- data/lib/lims-core/persistence/sequel/session.rb +136 -0
- data/lib/lims-core/persistence/sequel/store.rb +37 -0
- data/lib/lims-core/persistence/session.rb +409 -0
- data/lib/lims-core/persistence/state_group.rb +97 -0
- data/lib/lims-core/persistence/state_list.rb +56 -0
- data/lib/lims-core/persistence/store.rb +73 -0
- data/lib/lims-core/persistence/uuid_resource.rb +115 -0
- data/lib/lims-core/persistence/uuid_resource_persistor.rb +43 -0
- data/lib/lims-core/persistence/uuidable.rb +107 -0
- data/lib/lims-core/resource.rb +21 -0
- data/lib/lims-core/subclass_tracker.rb +30 -0
- data/lib/lims-core/version.rb +5 -0
- data/lims-core.gemspec +40 -0
- data/makefile +52 -0
- data/showoff/core-2012-06-11/core/01_slide.md +237 -0
- data/showoff/core-2012-06-11/core/02_slide.md +110 -0
- data/showoff/core-2012-06-11/custom.css +44 -0
- data/showoff/core-2012-06-11/main/01_slide.md +53 -0
- data/showoff/core-2012-06-11/showoff.json +10 -0
- data/showoff/core-2012-06-11/tp1.tpl +1 -0
- data/spec/actions/action_group_spec.rb +39 -0
- data/spec/actions/spec_helper.rb +1 -0
- data/spec/persistence/identity_map_spec.rb +55 -0
- data/spec/persistence/logger/spec_helper.rb +7 -0
- data/spec/persistence/logger/store_spec.rb +48 -0
- data/spec/persistence/message_bus_spec.rb +76 -0
- data/spec/persistence/sequel/session_spec.rb +125 -0
- data/spec/persistence/sequel/spec_helper.rb +39 -0
- data/spec/persistence/sequel/store_shared.rb +25 -0
- data/spec/persistence/sequel/store_spec.rb +22 -0
- data/spec/persistence/session_spec.rb +199 -0
- data/spec/persistence/spec_helper.rb +2 -0
- data/spec/persistence/uuid_resource_spec.rb +80 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/subclass_tracker_sperc.rb +62 -0
- data/utils/constant_tree.rb +29 -0
- data/utils/stack.rb +48 -0
- metadata +402 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'virtus'
|
|
3
|
+
|
|
4
|
+
require 'lims-core/resource'
|
|
5
|
+
require 'lims-core/persistence/persistor'
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
module Persistence
|
|
9
|
+
# Hold different information relateed particular resource
|
|
10
|
+
# within a {Session}/{Persistor}.
|
|
11
|
+
# This could be the database id, the current saving state etc ...
|
|
12
|
+
class ResourceState
|
|
13
|
+
include Virtus
|
|
14
|
+
attribute :id, Object
|
|
15
|
+
attribute :uuid_resource, Object
|
|
16
|
+
attribute :resource, Object, :writer => :private, :required => true
|
|
17
|
+
attribute :persistor, Persistor, :writer => :private, :required => true
|
|
18
|
+
attribute :to_delete, Object, :writer => :private
|
|
19
|
+
|
|
20
|
+
def children_saved?
|
|
21
|
+
@children_saved
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def body_saved?
|
|
25
|
+
@body_saved
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(resource, persistor, id=nil)
|
|
29
|
+
@resource=resource
|
|
30
|
+
@persistor=persistor
|
|
31
|
+
self.id=id if id
|
|
32
|
+
@to_delete = false
|
|
33
|
+
update_dirty_key
|
|
34
|
+
reset
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_delete
|
|
39
|
+
@to_delete
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def id
|
|
44
|
+
@id
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def resource
|
|
48
|
+
@resource
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def persistor
|
|
52
|
+
@persistor
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def dirty_key
|
|
56
|
+
@persistor.dirty_key_for(resource)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def update_dirty_key
|
|
60
|
+
@last_dirty_key = resource ? @persistor.dirty_key_for(resource) : nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def new?
|
|
64
|
+
@id == nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_load?
|
|
68
|
+
@id != nil && @resource == nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def dirty?
|
|
73
|
+
!@body_saved && dirty_key != @last_dirty_key
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def id=(new_id)
|
|
77
|
+
return if new_id == @id
|
|
78
|
+
raise RuntimeError, "modifing existing id not allowed. #{self}" if @id
|
|
79
|
+
@id = new_id
|
|
80
|
+
# link the new id in th id_to_state map
|
|
81
|
+
@persistor.bind_state_to_id(self)
|
|
82
|
+
id
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def resource=(new_resource)
|
|
86
|
+
# Hack
|
|
87
|
+
# for some emtpy resources , resource == nil returns true
|
|
88
|
+
# but nil == resource returns false
|
|
89
|
+
# which is why we test both.
|
|
90
|
+
return if resource == new_resource && new_resource == resource
|
|
91
|
+
raise RuntimeError, "modifing existing resource not allowed. #{self}" if @resource
|
|
92
|
+
@resource = new_resource
|
|
93
|
+
# link the new resource in th resource_to_state map
|
|
94
|
+
@persistor.bind_state_to_resource(self)
|
|
95
|
+
update_dirty_key
|
|
96
|
+
resource
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def mark_for_deletion
|
|
100
|
+
@to_delete = true
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @todo use state machine ?
|
|
104
|
+
def save_action
|
|
105
|
+
case
|
|
106
|
+
when @body_saved then nil
|
|
107
|
+
when to_delete && !new? then :delete
|
|
108
|
+
when new? then :insert
|
|
109
|
+
when dirty? then :update
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def inserted(new_id=nil)
|
|
114
|
+
self.id = new_id
|
|
115
|
+
update_dirty_key
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def updated
|
|
119
|
+
update_dirty_key
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def parents_saved!
|
|
123
|
+
@parents_saved = true
|
|
124
|
+
end
|
|
125
|
+
def parents
|
|
126
|
+
!@parents_saved && @persistor.parents_for(resource)
|
|
127
|
+
end
|
|
128
|
+
def parents!
|
|
129
|
+
parents.tap { parents_saved! }
|
|
130
|
+
end
|
|
131
|
+
def children_saved!
|
|
132
|
+
@children_saved = true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def parents_saved?
|
|
136
|
+
@parents_saved
|
|
137
|
+
end
|
|
138
|
+
def children
|
|
139
|
+
return [] if to_delete
|
|
140
|
+
!@children_saved && @persistor.children_for(resource)
|
|
141
|
+
end
|
|
142
|
+
def children!
|
|
143
|
+
children.tap { children_saved! }
|
|
144
|
+
end
|
|
145
|
+
def body_saved!
|
|
146
|
+
@body_saved = true
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def reset
|
|
150
|
+
@parents_saved = nil
|
|
151
|
+
@children_saved = nil
|
|
152
|
+
@body_saved= nil
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
require 'lims-core/actions/action'
|
|
3
|
+
|
|
4
|
+
require 'lims-core/persistence/search/search_persistor'
|
|
5
|
+
require 'lims-core/persistence/multi_criteria_filter'
|
|
6
|
+
require 'lims-core/persistence/comparison_filter'
|
|
7
|
+
|
|
8
|
+
module Lims::Core
|
|
9
|
+
module Persistence
|
|
10
|
+
class Search
|
|
11
|
+
class CreateSearch
|
|
12
|
+
include Actions::Action
|
|
13
|
+
|
|
14
|
+
attribute :description, String, :required => true
|
|
15
|
+
attribute :model, String, :required => true
|
|
16
|
+
attribute :criteria, Hash, :required => true
|
|
17
|
+
|
|
18
|
+
def _call_in_session(session)
|
|
19
|
+
# Use the appropriate filter if needed.
|
|
20
|
+
filter = nil
|
|
21
|
+
keys = criteria.keys
|
|
22
|
+
keys.delete("comparison") if criteria.keys.size > 1
|
|
23
|
+
if keys.size == 1
|
|
24
|
+
keys.first.andtap do |model|
|
|
25
|
+
filter_class_name = "#{model.capitalize.gsub(/_[^_]*/) { |b| b[1..b.size].capitalize }}Filter"
|
|
26
|
+
if Persistence::const_defined? filter_class_name
|
|
27
|
+
filter = Persistence::const_get(filter_class_name).new(:criteria => criteria)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
filter ||= Persistence::MultiCriteriaFilter.new(:criteria => criteria)
|
|
32
|
+
search = Persistence::Search.new(:description => description,
|
|
33
|
+
:model => session.send(model).model,
|
|
34
|
+
:filter => filter)
|
|
35
|
+
if search.valid?
|
|
36
|
+
stored_search = session.search[search.attributes, false].first
|
|
37
|
+
if stored_search.nil?
|
|
38
|
+
session << search
|
|
39
|
+
else
|
|
40
|
+
search = stored_search
|
|
41
|
+
end
|
|
42
|
+
{ :search => search, :uuid => session.uuid_for!(search) }
|
|
43
|
+
else
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
module Persistence
|
|
51
|
+
class Search
|
|
52
|
+
Create = CreateSearch
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#vi: ts=2 sw=2 et
|
|
2
|
+
require 'common'
|
|
3
|
+
|
|
4
|
+
require 'lims-core/resource'
|
|
5
|
+
require 'lims-core/persistence/persistor'
|
|
6
|
+
require 'lims-core/persistence/filter'
|
|
7
|
+
|
|
8
|
+
# We need to load all the possible filter to be able load them by name.
|
|
9
|
+
require_all('*filter')
|
|
10
|
+
|
|
11
|
+
module Lims::Core
|
|
12
|
+
module Persistence
|
|
13
|
+
# base class handling searches. A Search represent a set of parameters
|
|
14
|
+
# which when executed returns a set of similar object (.i.e the same class).
|
|
15
|
+
# A search is savable.
|
|
16
|
+
class Search
|
|
17
|
+
include Resource
|
|
18
|
+
attribute :description, String, :required => true, :initializable => true, :write => :private
|
|
19
|
+
attribute :model, Class, :required => true, :initializable => true, :writer => :private
|
|
20
|
+
attribute :filter, Filter, :required => true, :initializable => true, :writer => :private
|
|
21
|
+
|
|
22
|
+
# Main method. Take an session an return an filtered persistor.
|
|
23
|
+
# @param [Session]
|
|
24
|
+
# @return [Persistor]
|
|
25
|
+
def call(session)
|
|
26
|
+
filter.call(session.persistor_for(@model))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Base persistor for Search object.
|
|
30
|
+
# It should be called Persistence::Search but, this is
|
|
31
|
+
# already taken by the main Search class.
|
|
32
|
+
class SearchPersistor < Persistence::Persistor
|
|
33
|
+
Model = Persistence::Search
|
|
34
|
+
# Override default one, because filter are serialized
|
|
35
|
+
# and even though they are a Resource they are not counted as parents
|
|
36
|
+
def parents(resource)
|
|
37
|
+
[]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
|
|
3
|
+
require 'lims-core/persistence/search/search_persistor'
|
|
4
|
+
require 'lims-core/persistence/sequel/persistor'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module Lims::Core
|
|
8
|
+
module Persistence
|
|
9
|
+
# Not a search but a search persistor.
|
|
10
|
+
class Search
|
|
11
|
+
class SearchSequelPersistor < Search::SearchPersistor
|
|
12
|
+
include Persistence::Sequel::Persistor
|
|
13
|
+
|
|
14
|
+
def self.table_name
|
|
15
|
+
:searches
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def filter_attributes_on_load(attributes)
|
|
19
|
+
filter_parameters = @session.unserialize(attributes[:filter_parameters])
|
|
20
|
+
# The first key should be a symbol, @see persistence/comparison_filter.rb for example
|
|
21
|
+
filter_parameters.rekey! { |k| k.to_sym }
|
|
22
|
+
{
|
|
23
|
+
:description => attributes[:description],
|
|
24
|
+
:model => constant(attributes[:model]),
|
|
25
|
+
:filter => Persistence.const_get(attributes[:filter_type]).new(filter_parameters)
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
def filter_attributes_on_save(attributes, *args)
|
|
29
|
+
filter = attributes[:filter]
|
|
30
|
+
{
|
|
31
|
+
:description => attributes[:description],
|
|
32
|
+
:model => attributes[:model].name,
|
|
33
|
+
:filter_type => filter.class.name.split('::').last,
|
|
34
|
+
:filter_parameters => @session.serialize(filter.attributes)
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
|
2
|
+
# please keep 2 empty lines to avoid to comment to be
|
|
3
|
+
# seen as Persistence doc.
|
|
4
|
+
require 'common'
|
|
5
|
+
|
|
6
|
+
module Lims::Core::Persistence
|
|
7
|
+
# Persistence module using the {http://sequel.rubyforge.org/ sequel} gem.
|
|
8
|
+
# It implements abstract/base classes : {Store}, {Session} etc.
|
|
9
|
+
module Sequel
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# require everything
|
|
14
|
+
require 'lims-core/persistence/sequel/store'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'lims-core/persistence/sequel'
|
|
2
|
+
require 'active_support/inflector'
|
|
3
|
+
|
|
4
|
+
module Lims::Core
|
|
5
|
+
module Persistence
|
|
6
|
+
# Implementes filter methods needed by persitors.
|
|
7
|
+
module Sequel::Filters
|
|
8
|
+
|
|
9
|
+
LIKE_OPERATOR = 'LIKE'
|
|
10
|
+
COMPARISON_OPERATORS = ["<", "<=", "=", ">=", ">", LIKE_OPERATOR]
|
|
11
|
+
|
|
12
|
+
# Implement a multicriteria filter for a Sequel::Persistor.
|
|
13
|
+
# Value can be either a String, an Array or a Hash.
|
|
14
|
+
# Strings and Arrays are normal filters, whereas Hashes
|
|
15
|
+
# correspond to a joined search. The criteria will apply to the
|
|
16
|
+
# joined object corresponding to the key.
|
|
17
|
+
# @param [Hash<String, Object > criteria
|
|
18
|
+
# @return [Persistor]
|
|
19
|
+
def multi_criteria_filter(criteria)
|
|
20
|
+
# We need to create the adequat dataset.
|
|
21
|
+
dataset = __multi_criteria_filter(criteria).dataset
|
|
22
|
+
# As the dataset can include join, we need to select only the columns
|
|
23
|
+
# corresponding to the persistor
|
|
24
|
+
self.class.new(self, dataset.qualify(table_name).distinct())
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Implement a comparison filter for a Sequel::Persistor.
|
|
28
|
+
# Key being the name of the resource's field and the value is a Hash.
|
|
29
|
+
# The key of the hash is a comparison operator
|
|
30
|
+
# and the value is the given value the filter do the comparison against.
|
|
31
|
+
# @param [Hash<String, Object>] criteria
|
|
32
|
+
# @return [Persistor]
|
|
33
|
+
def comparison_filter(criteria)
|
|
34
|
+
clause = ""
|
|
35
|
+
criteria.each do |field, comparison_expression|
|
|
36
|
+
comparison_expression.each do |operator, value|
|
|
37
|
+
if operator.upcase == LIKE_OPERATOR
|
|
38
|
+
operator = operator.upcase
|
|
39
|
+
value = "%#{value}%" if operator == LIKE_OPERATOR
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
raise ArgumentError, "Not supported comparison operator has been given: '#{operator}'" unless COMPARISON_OPERATORS.include?(operator)
|
|
43
|
+
|
|
44
|
+
clause = clause + ') & (' unless clause == ""
|
|
45
|
+
clause = clause + " #{field} " + operator + "'#{value}'"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
self.class.new(self, dataset.where(clause).qualify)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Joins the comparison filter to the existing persistor.
|
|
53
|
+
# @param [Dataset] dataset
|
|
54
|
+
# @param [Hash<String, Object>] criteria for the comparison
|
|
55
|
+
# @return [Persistor]
|
|
56
|
+
def add_comparison_filter(dataset, comparison_criteria)
|
|
57
|
+
comparison_persistor = comparison_filter(comparison_criteria)
|
|
58
|
+
self.class.new(self, dataset.join(comparison_persistor.dataset, :id => :key).qualify)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
# @param Hash criteria
|
|
63
|
+
# @return Persistor
|
|
64
|
+
def __multi_criteria_filter(criteria)
|
|
65
|
+
# Extract critera recursively and apply subhashes to
|
|
66
|
+
# joined table
|
|
67
|
+
# Hash value are criteria for the corresponding joined tabled
|
|
68
|
+
# We need to extract them and do the obvious join
|
|
69
|
+
# Values are passed to filter_attributes_on save to get
|
|
70
|
+
# the right format if needed.
|
|
71
|
+
joined = criteria.reduce(self) do |persistor, (key, value)|
|
|
72
|
+
case value
|
|
73
|
+
when Hash
|
|
74
|
+
criteria_persistor = persistor.send(key)
|
|
75
|
+
filtered_value = criteria_persistor.filter_attributes_on_save(value.rekey {|k| k.to_sym})
|
|
76
|
+
joined_persistor = criteria_persistor.__multi_criteria_filter(filtered_value)
|
|
77
|
+
__join(joined_persistor)
|
|
78
|
+
else persistor
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# We need to passes to filter are applied on the joined persistor.
|
|
83
|
+
# This is needed because the __join function expected bare persistor
|
|
84
|
+
# and will loose any filter applied on the original persistor
|
|
85
|
+
criteria.reduce(joined) do |persistor, (key, value)|
|
|
86
|
+
case value
|
|
87
|
+
when Hash
|
|
88
|
+
persistor
|
|
89
|
+
else
|
|
90
|
+
self.class.new(persistor, persistor.dataset.filter(::Sequel.qualify(table_name, key) => value))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Assume that the original persistor his *blank* ie
|
|
96
|
+
# it doesn't contain any SQL modifier
|
|
97
|
+
# @param [Persistor] persistor
|
|
98
|
+
# @return [Persistor]
|
|
99
|
+
def __join(persistor)
|
|
100
|
+
self.class.new(self, persistor.dataset.join(table_name, primary_key => :"#{table_name.to_s.singularize}_#{persistor.primary_key}"))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|