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,21 @@
|
|
|
1
|
+
# vi: ts=2 sts=2 et sw=2 spell spelllang=en
|
|
2
|
+
require 'common'
|
|
3
|
+
require 'lims-core/base'
|
|
4
|
+
require 'lims-core/subclass_tracker'
|
|
5
|
+
|
|
6
|
+
module Lims::Core
|
|
7
|
+
module Resource
|
|
8
|
+
extend SubclassTracker
|
|
9
|
+
# We need to rename the actual self.included method
|
|
10
|
+
class << self
|
|
11
|
+
alias_method :tracker_included, :included
|
|
12
|
+
end
|
|
13
|
+
def self.included(klass)
|
|
14
|
+
klass.class_eval do
|
|
15
|
+
include Base
|
|
16
|
+
end
|
|
17
|
+
tracker_included(klass)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# vi: ts=2 sts=2 et sw=2 spell spelllang=en
|
|
2
|
+
# This module allows a class or module to track
|
|
3
|
+
# all of its descendants, i.e. being able to iterate
|
|
4
|
+
# all over all subclasses (submodules) of the tracking class.
|
|
5
|
+
# To do so, just include the following in the tracking class/module
|
|
6
|
+
# then call ::subclasses.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
module Lims
|
|
10
|
+
module Core
|
|
11
|
+
module SubclassTracker
|
|
12
|
+
def self.extended(klass)
|
|
13
|
+
(class <<klass; self; end).send :attr_accessor, :subclasses
|
|
14
|
+
(class <<klass; self; end).send :define_method, :inherited do |subclass|
|
|
15
|
+
klass.subclasses << subclass
|
|
16
|
+
super(subclass)
|
|
17
|
+
end
|
|
18
|
+
(class <<klass; self; end).send :define_method, :included do |submodule|
|
|
19
|
+
klass.subclasses << submodule
|
|
20
|
+
(class <<submodule; self; end).send :define_method, :inherited do |subclass|
|
|
21
|
+
klass.subclasses << subclass
|
|
22
|
+
super(subclass)
|
|
23
|
+
end
|
|
24
|
+
super(submodule)
|
|
25
|
+
end
|
|
26
|
+
klass.subclasses = []
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lims-core.gemspec
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "lims-core/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "lims-core"
|
|
7
|
+
s.version = Lims::Core::VERSION
|
|
8
|
+
s.authors = ["Maxime Bourget"]
|
|
9
|
+
s.email = ["mb14@sanger.ac.uk"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Core of new LIMS system}
|
|
12
|
+
s.description = %q{Provide the core classes and persistence needed to build an API used by the pipelines applications.}
|
|
13
|
+
s.description = %q{provides all the necessary to interact with the core database}
|
|
14
|
+
|
|
15
|
+
s.rubyforge_project = "lims-core"
|
|
16
|
+
|
|
17
|
+
s.files = `git ls-files`.split("\n")
|
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
20
|
+
s.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
# specify any dependencies here; for example:
|
|
23
|
+
# s.add_development_dependency "rspec"
|
|
24
|
+
# s.add_runtime_dependency "rest-client"
|
|
25
|
+
s.add_dependency('facets', '2.9.3')
|
|
26
|
+
s.add_dependency('virtus', '0.2.0')
|
|
27
|
+
s.add_dependency('aequitas')
|
|
28
|
+
s.add_dependency('modularity', '0.6.1')
|
|
29
|
+
s.add_dependency('dm-validations')
|
|
30
|
+
s.add_dependency('sequel')
|
|
31
|
+
s.add_dependency('activesupport')
|
|
32
|
+
s.add_dependency('uuid')
|
|
33
|
+
s.add_dependency('state_machine')
|
|
34
|
+
s.add_dependency('bunny', '0.9.0.pre10')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#development
|
|
38
|
+
s.add_development_dependency('rspec', '~> 2')
|
|
39
|
+
s.add_development_dependency('rake')
|
|
40
|
+
end
|
data/makefile
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Yes we could have use rake, but rake is so slow and verbose ...
|
|
2
|
+
# Look at this as a collection of shell commands.
|
|
3
|
+
|
|
4
|
+
.PHONY: migrate_test test doc clean clean_tree profile
|
|
5
|
+
|
|
6
|
+
# launch rspec
|
|
7
|
+
test:
|
|
8
|
+
bundle exec rspec
|
|
9
|
+
|
|
10
|
+
focus:
|
|
11
|
+
bundle exec rspec -tfocus
|
|
12
|
+
|
|
13
|
+
profile:
|
|
14
|
+
bundle exec rspec -p
|
|
15
|
+
|
|
16
|
+
# generate yard doc
|
|
17
|
+
doc:
|
|
18
|
+
bundle exec yardoc
|
|
19
|
+
|
|
20
|
+
# migrate test database
|
|
21
|
+
migrate_test:
|
|
22
|
+
bundle exec sequel -m db/migrations -e test config/database.yml
|
|
23
|
+
|
|
24
|
+
# Start server for core presentation
|
|
25
|
+
show:
|
|
26
|
+
bundle exec showoff serve showoff/core-2012-06-11
|
|
27
|
+
|
|
28
|
+
# console
|
|
29
|
+
console:
|
|
30
|
+
bundle exec sequel -e test config/database.yml
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# generate constants tree of a starting required file
|
|
34
|
+
constants:
|
|
35
|
+
|
|
36
|
+
%.tree:
|
|
37
|
+
bundle exec ruby utils/constant_tree.rb $* | tee $@
|
|
38
|
+
|
|
39
|
+
core.tree: \ .tree
|
|
40
|
+
mv $< $@
|
|
41
|
+
|
|
42
|
+
clean_tree:
|
|
43
|
+
rm *.tree
|
|
44
|
+
|
|
45
|
+
clean: clean_tree
|
|
46
|
+
|
|
47
|
+
guard:
|
|
48
|
+
bundle exec guard
|
|
49
|
+
|
|
50
|
+
guardt:
|
|
51
|
+
bundle exec guard start Guardfile.tmux
|
|
52
|
+
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
!SLIDE smaller transition=scrollLeft
|
|
2
|
+
# `lims::core`
|
|
3
|
+
### A set of dependent components
|
|
4
|
+
|
|
5
|
+
* API : `Actions`
|
|
6
|
+
* Persistence : `Session`
|
|
7
|
+
* Domain: `Laboratoy`, `LabProcess`, `Organization`
|
|
8
|
+
|
|
9
|
+
### API => Persistence => Domains.
|
|
10
|
+
!SLIDE small transition=scrollUp subsection
|
|
11
|
+
# Lims::core
|
|
12
|
+
## Principles
|
|
13
|
+
* Models exists without the notion of persistence
|
|
14
|
+
* Everything created within a `Session` is saved (at the end)
|
|
15
|
+
* Everything manipulating objects is done through *external* `Actions`.
|
|
16
|
+
|
|
17
|
+
!SLIDE small transition=scrollUp subsection
|
|
18
|
+
# Lims::core
|
|
19
|
+
## Foundation #
|
|
20
|
+
### The lims::core is based on 3 gems :
|
|
21
|
+
|
|
22
|
+
* `Facets` ruby extensions.
|
|
23
|
+
* `Datamapper2`
|
|
24
|
+
* `Virtus` - model definition
|
|
25
|
+
* <span class="red">`Aequitas`</span>- validation library
|
|
26
|
+
* `Sequel` '*The database toolkit for ruby*'.
|
|
27
|
+
|
|
28
|
+
.notes they both provide a full active record like
|
|
29
|
+
|
|
30
|
+
!SLIDE code transition=scrollLeft smaller subsection
|
|
31
|
+
# Foundation
|
|
32
|
+
## Virtus, Aequitas
|
|
33
|
+
|
|
34
|
+
@@@ ruby
|
|
35
|
+
class Sample
|
|
36
|
+
include Virtus
|
|
37
|
+
include Aequitas
|
|
38
|
+
attribute :name, String, :required => true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
s = Sample.new
|
|
42
|
+
s.valid? #=> false
|
|
43
|
+
|
|
44
|
+
s = Sample.new(:name => "my sample")
|
|
45
|
+
s.name #=> "my sample"
|
|
46
|
+
s.attributes #=> { :name => "my sample" }
|
|
47
|
+
|
|
48
|
+
!SLIDE code transition=scrollUp smaller subsection
|
|
49
|
+
# Foundation
|
|
50
|
+
## Sequel #
|
|
51
|
+
|
|
52
|
+
@@@ ruby
|
|
53
|
+
samples = DB[:samples] #=> proxy
|
|
54
|
+
|
|
55
|
+
samples.first #=> { :id => 1, :name => 'my sample' }
|
|
56
|
+
|
|
57
|
+
with_aliquots = samples.join(:aliquots, :sample_id => :id)
|
|
58
|
+
#=> proxy
|
|
59
|
+
|
|
60
|
+
samples_with_aliquots.first
|
|
61
|
+
#=> { :sample_id => 1, :name => 'my sample', :id => 3)
|
|
62
|
+
|
|
63
|
+
!SLIDE smaller transition=scrollRight subsection
|
|
64
|
+
# Persistence-less model
|
|
65
|
+
## Plate declaration
|
|
66
|
+
|
|
67
|
+
@@@ ruby
|
|
68
|
+
class Plate
|
|
69
|
+
include Resource
|
|
70
|
+
# declare row_number and column_number
|
|
71
|
+
%w(row column).each do |w|
|
|
72
|
+
attribute :"#{w}_number", Fixnum, :required => true,
|
|
73
|
+
:gte => 0, :writer => :private
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Well
|
|
77
|
+
include Resource
|
|
78
|
+
...
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
is_array_of Well do |p,t|
|
|
82
|
+
(p.row_number*p.column_number).times.map { t.new }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
### Plate is like an Array
|
|
86
|
+
|
|
87
|
+
!SLIDE code smaller transition=scrollUp subsection
|
|
88
|
+
# Persistence-less model
|
|
89
|
+
## Plate spec
|
|
90
|
+
|
|
91
|
+
@@@ ruby
|
|
92
|
+
describe Plate do
|
|
93
|
+
subject { Plate.new(:row_number => 8,
|
|
94
|
+
:column_number => 12) }
|
|
95
|
+
|
|
96
|
+
it "can be indexed with a symbol " do
|
|
97
|
+
subject[:B3].should be_a(Plate::Well)
|
|
98
|
+
aliquot = mock(:aliquot)
|
|
99
|
+
|
|
100
|
+
subject[:B3] << aliquot
|
|
101
|
+
subject[:B3].should include(aliquot)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
### Well is like an array too.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
!SLIDE small transition=scrollLeft
|
|
109
|
+
# Session
|
|
110
|
+
|
|
111
|
+
* A session is in charge of restoring and saving objects to a `Store`.
|
|
112
|
+
* Every modifications within a session **block** is saved.
|
|
113
|
+
* Everything is saved only once.
|
|
114
|
+
* <span class="red">It's the only way to save an object</span>
|
|
115
|
+
* It provides an **IdentityMap**.
|
|
116
|
+
|
|
117
|
+
!SLIDE small transition=scrollUp subsection
|
|
118
|
+
# Session
|
|
119
|
+
## Benefits #
|
|
120
|
+
|
|
121
|
+
* Persistence operations not mixed with domain model.
|
|
122
|
+
* Everything is automatically wrapped in a transaction.
|
|
123
|
+
* Saves can be bulk (not implemented)
|
|
124
|
+
* Eager loading (not implemented) can be set at session, or application level
|
|
125
|
+
* Session information (user, date, client) can be audited in a `session` table
|
|
126
|
+
|
|
127
|
+
!SLIDE small transition=scrollUp subsection
|
|
128
|
+
# Session #
|
|
129
|
+
## Purpose ##
|
|
130
|
+
### Sessions have been designed to make some stuff hard for the developer as:
|
|
131
|
+
|
|
132
|
+
* Load and save object(s) in different sessions.
|
|
133
|
+
* Save an object in the middle of an *action* (method, function, ...).
|
|
134
|
+
* User can't create a Session without a store
|
|
135
|
+
|
|
136
|
+
### If you find yourself struggling trying to do something above, you **probably **shouldn't be doing it.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
!SLIDE smaller subsection transition=scrollLeft
|
|
141
|
+
# Session #
|
|
142
|
+
## Example
|
|
143
|
+
|
|
144
|
+
@@@ ruby
|
|
145
|
+
source_id = ...
|
|
146
|
+
# Session can't be created on its own
|
|
147
|
+
store.with_session(params) do |s|
|
|
148
|
+
# load a plate
|
|
149
|
+
source_plate = s.plate[source_id]
|
|
150
|
+
|
|
151
|
+
# create a new plate and add it to the session
|
|
152
|
+
s << target_plate= L::Plate.new(plate.attributes)
|
|
153
|
+
|
|
154
|
+
# transfer from one well to the another well
|
|
155
|
+
# modifies BOTH plates
|
|
156
|
+
target_plate[:A1] << source_plate[:C3].take(0.5)
|
|
157
|
+
|
|
158
|
+
end # saves source and target
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
!SLIDE small subsection transition=scrollUp
|
|
162
|
+
# Session
|
|
163
|
+
## API
|
|
164
|
+
|
|
165
|
+
* Session needs to be created from a store :
|
|
166
|
+
`store.with_session { |s| .... }`
|
|
167
|
+
* Tells a session to manage an object :
|
|
168
|
+
` session << object_to_manage`
|
|
169
|
+
* Load a object :
|
|
170
|
+
`session.plate[plate_id]`
|
|
171
|
+
* Get the id of an object :
|
|
172
|
+
`session.id_for(plate)`
|
|
173
|
+
|
|
174
|
+
!SLIDE small subsection transition=scrollUp
|
|
175
|
+
# Session
|
|
176
|
+
## Objects no managed are not saved
|
|
177
|
+
|
|
178
|
+
### The following code saves only one plate.
|
|
179
|
+
|
|
180
|
+
@@@ ruby
|
|
181
|
+
store.with_session do |s|
|
|
182
|
+
plate1 = new_plate
|
|
183
|
+
s << plate2=new_plate
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
### `plate2`.
|
|
187
|
+
|
|
188
|
+
!SLIDE small subsection transition=scrollUp
|
|
189
|
+
# Session
|
|
190
|
+
## Object loaded are automatically managed
|
|
191
|
+
|
|
192
|
+
@@@ ruby
|
|
193
|
+
store.with_session do |s|
|
|
194
|
+
plate = s.plate[plate_id]
|
|
195
|
+
# s << plate - unnecessary
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
!SLIDE small subsection transition=scrollUp
|
|
199
|
+
# Session
|
|
200
|
+
## Post save block
|
|
201
|
+
### Objects are saved at the end of a `with_session` block, but ids of new objects are only available after the block, when the session doesn't exist anymore.
|
|
202
|
+
|
|
203
|
+
@@@ ruby
|
|
204
|
+
store.with_session do |s|
|
|
205
|
+
s << new_plate = Plate.new(params)
|
|
206
|
+
{:new_plate => s.id_for(new_plate)}.to_json
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
### Doesn't work.
|
|
211
|
+
|
|
212
|
+
!SLIDE small subsection transition=shuffle
|
|
213
|
+
# Session / Post save block
|
|
214
|
+
## returning the session ...
|
|
215
|
+
|
|
216
|
+
@@@ ruby
|
|
217
|
+
session, new_plate = store.with_session do |s|
|
|
218
|
+
s << new_plate = Plate.new(params)
|
|
219
|
+
[ s, new_plate ]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
{:new_plate => session.id_for(new_plate}.to_json
|
|
223
|
+
|
|
224
|
+
### Would probably work, but splits the block in 2 different places. Probably one of those things session has been to designed to make hard.
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
!SLIDE smaller subsection transition=shuffle
|
|
228
|
+
# Session / Post save block
|
|
229
|
+
## Using a lambda
|
|
230
|
+
|
|
231
|
+
@@@ ruby
|
|
232
|
+
store.with_session do |s|
|
|
233
|
+
s << new_plate = Plate.new(params)
|
|
234
|
+
lambda { {:new_plate => .id_for(new_plate)}.to_json }
|
|
235
|
+
end.call
|
|
236
|
+
|
|
237
|
+
### Better, all the code is inside one block and the `lambda` is saying 'I am a future statement'. Note the `call` at the end.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
!SLIDE small transition=scrollLeft
|
|
2
|
+
# Actions #
|
|
3
|
+
## A *reversible* functor:
|
|
4
|
+
|
|
5
|
+
* Has parameters.
|
|
6
|
+
* Can be called.
|
|
7
|
+
* Can be reverted (when possible).
|
|
8
|
+
|
|
9
|
+
### corresponding to an API call.
|
|
10
|
+
|
|
11
|
+
!SLIDE small transition=scrollUp subsection
|
|
12
|
+
# Actions #
|
|
13
|
+
* Persistence aware
|
|
14
|
+
* Called as-is by the API server.
|
|
15
|
+
* Can be pipeline specific.
|
|
16
|
+
* Executed within a `Session` and
|
|
17
|
+
* ... a context (user, application).
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
!SLIDE small transition=scrollUp subsection
|
|
21
|
+
# Actions
|
|
22
|
+
## How to call an action
|
|
23
|
+
|
|
24
|
+
1. Pass context parameters (store, user, application),
|
|
25
|
+
2. pass a parameters initializer block,
|
|
26
|
+
3. call it.
|
|
27
|
+
|
|
28
|
+
!SLIDE smaller subsection transition=scrollLeft
|
|
29
|
+
# Actions / call
|
|
30
|
+
## Example
|
|
31
|
+
@@@ ruby
|
|
32
|
+
target_id = ...
|
|
33
|
+
source_id = ...
|
|
34
|
+
action = PlateTransfer.new(:store => store,
|
|
35
|
+
:user => user,
|
|
36
|
+
:application => application
|
|
37
|
+
) do |action, session|
|
|
38
|
+
# session needed
|
|
39
|
+
action.target = session.plate[target_id]
|
|
40
|
+
action.source = session.plate[source_id]
|
|
41
|
+
|
|
42
|
+
action.transfer_map = { :C3 => :B1 }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
action.call
|
|
46
|
+
|
|
47
|
+
### This also separates parameter decoding from implementation, allowing
|
|
48
|
+
parameters validation.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
!SLIDE small transition=scrollRight subsection
|
|
53
|
+
# Actions
|
|
54
|
+
## Design
|
|
55
|
+
* Include `Action`.
|
|
56
|
+
* Declare parameters (attributes).
|
|
57
|
+
* Implement `_call_in_session` to modify/create the model.
|
|
58
|
+
* Attributes and results are automatically managed.
|
|
59
|
+
|
|
60
|
+
!SLIDE smaller transition=scrollUp subsection
|
|
61
|
+
# Actions / Design
|
|
62
|
+
## PlateTransfer Action
|
|
63
|
+
@@@ ruby
|
|
64
|
+
class PlateTransfer
|
|
65
|
+
include Action
|
|
66
|
+
|
|
67
|
+
attribute :source, Laboratory::Plate, :required => true
|
|
68
|
+
attribute :target, Laboratory::Plate, :required => true
|
|
69
|
+
attribute :transfer_map, Hash, :required => true
|
|
70
|
+
|
|
71
|
+
# Transfers the content of from source to target
|
|
72
|
+
# according to a transef map.
|
|
73
|
+
def _call_in_session(session)
|
|
74
|
+
transfer_map.each do |from ,to|
|
|
75
|
+
target[to] << source[from].take
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
!SLIDE small transition=scrollRight
|
|
82
|
+
# Done
|
|
83
|
+
|
|
84
|
+
* Laboratory classes needed for pulldown.
|
|
85
|
+
* Persistence on Laboratory.
|
|
86
|
+
* SQL store and basic Persistors.
|
|
87
|
+
* Transfer, Create Plate, Tube, and Tag action.
|
|
88
|
+
|
|
89
|
+
!SLIDE small transition=scrollLeft
|
|
90
|
+
# To do
|
|
91
|
+
* Manage `dirty` attributes
|
|
92
|
+
* Integration with API server
|
|
93
|
+
* Add samples
|
|
94
|
+
* Add studies, projects
|
|
95
|
+
* Submissions, orders
|
|
96
|
+
|
|
97
|
+
!SLIDE small transition=scrollUp subsection
|
|
98
|
+
# To do
|
|
99
|
+
## wish list
|
|
100
|
+
|
|
101
|
+
* Audit session
|
|
102
|
+
* Eager loading
|
|
103
|
+
* Bulk saving
|
|
104
|
+
* Manage automatically relationship throug attributes.
|
|
105
|
+
|
|
106
|
+
!SLIDE transition=shuffle
|
|
107
|
+
# The End
|
|
108
|
+
Any Questions ?
|
|
109
|
+
|
|
110
|
+
|