rom-session 0.1.0
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.
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +22 -0
- data/Gemfile +30 -0
- data/Gemfile.devtools +55 -0
- data/Guardfile +19 -0
- data/LICENSE +20 -0
- data/README.md +21 -0
- data/Rakefile +4 -0
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +96 -0
- data/config/rubocop.yml +45 -0
- data/lib/rom-session.rb +43 -0
- data/lib/rom/session.rb +62 -0
- data/lib/rom/session/environment.rb +66 -0
- data/lib/rom/session/identity_map.rb +43 -0
- data/lib/rom/session/mapper.rb +43 -0
- data/lib/rom/session/relation.rb +126 -0
- data/lib/rom/session/state.rb +50 -0
- data/lib/rom/session/state/created.rb +22 -0
- data/lib/rom/session/state/deleted.rb +25 -0
- data/lib/rom/session/state/persisted.rb +29 -0
- data/lib/rom/session/state/transient.rb +20 -0
- data/lib/rom/session/state/updated.rb +29 -0
- data/lib/rom/session/tracker.rb +69 -0
- data/lib/rom/session/version.rb +9 -0
- data/lib/rom/support/proxy.rb +50 -0
- data/rom-session.gemspec +24 -0
- data/spec/integration/session_spec.rb +71 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/unit/environment_context.rb +11 -0
- data/spec/shared/unit/relation_context.rb +18 -0
- data/spec/spec_helper.rb +70 -0
- data/spec/unit/rom/session/class_methods/start_spec.rb +23 -0
- data/spec/unit/rom/session/clean_predicate_spec.rb +21 -0
- data/spec/unit/rom/session/environment/element_reader_spec.rb +13 -0
- data/spec/unit/rom/session/flush_spec.rb +58 -0
- data/spec/unit/rom/session/mapper/load_spec.rb +44 -0
- data/spec/unit/rom/session/relation/delete_spec.rb +28 -0
- data/spec/unit/rom/session/relation/dirty_predicate_spec.rb +38 -0
- data/spec/unit/rom/session/relation/identity_spec.rb +11 -0
- data/spec/unit/rom/session/relation/new_spec.rb +50 -0
- data/spec/unit/rom/session/relation/save_spec.rb +50 -0
- data/spec/unit/rom/session/relation/state_spec.rb +26 -0
- data/spec/unit/rom/session/relation/track_spec.rb +23 -0
- data/spec/unit/rom/session/relation/tracking_predicate_spec.rb +23 -0
- data/spec/unit/rom/session/state_spec.rb +79 -0
- metadata +167 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
|
6
|
+
# Session-specific environment wrapping ROM's environment
|
7
|
+
#
|
8
|
+
# It works exactly the same as ROM::Environment except it returns
|
9
|
+
# session relations
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class Environment < ROM::Environment
|
13
|
+
include Proxy
|
14
|
+
|
15
|
+
attr_reader :environment, :tracker, :memory
|
16
|
+
private :environment, :tracker, :memory
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def self.build(environment, tracker = Tracker.new)
|
20
|
+
new(environment, tracker)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def initialize(environment, tracker)
|
25
|
+
@environment = environment
|
26
|
+
@tracker = tracker
|
27
|
+
initialize_memory
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return a relation identified by name
|
31
|
+
#
|
32
|
+
# @param [Symbol] name of a relation
|
33
|
+
#
|
34
|
+
# @return [Session::Relation] rom's relation wrapped by session
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def [](name)
|
38
|
+
memory[name]
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
def commit
|
43
|
+
tracker.commit
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def clean?
|
48
|
+
tracker.clean?
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def initialize_memory
|
55
|
+
@memory = Hash.new { |_, name| memory[name] = build_relation(name) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# @api private
|
59
|
+
def build_relation(name)
|
60
|
+
Relation.build(environment[name], tracker)
|
61
|
+
end
|
62
|
+
|
63
|
+
end # Environment
|
64
|
+
|
65
|
+
end # Session
|
66
|
+
end # ROM
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
|
6
|
+
# @api private
|
7
|
+
class IdentityMap
|
8
|
+
include Concord.new(:objects)
|
9
|
+
|
10
|
+
class LoadedObject
|
11
|
+
include Concord::Public.new(:object, :tuple)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def self.build(objects = {})
|
16
|
+
new(objects)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @api private
|
20
|
+
def [](identity)
|
21
|
+
objects[identity]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def fetch_object(identity, &block)
|
26
|
+
objects.fetch(identity, &block).object
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def fetch_tuple(identity)
|
31
|
+
self[identity].tuple
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
def store(identity, object, tuple)
|
36
|
+
objects[identity] = LoadedObject.new(object, tuple)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
end # IdentityMap
|
41
|
+
|
42
|
+
end # Session
|
43
|
+
end # ROM
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
|
6
|
+
# @api private
|
7
|
+
class Mapper < ROM::Mapper
|
8
|
+
include Proxy, Concord::Public.new(:mapper, :tracker, :identity_map)
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
def self.build(mapper, tracker)
|
12
|
+
new(mapper, tracker, IdentityMap.build)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
def dirty?(object)
|
17
|
+
identity_map.fetch_tuple(identity(object)) != dump(object)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def load(tuple)
|
22
|
+
identity = mapper.identity_from_tuple(tuple)
|
23
|
+
identity_map.fetch_object(identity) { load_and_track(identity, tuple) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def store_in_identity_map(object)
|
28
|
+
identity_map.store(identity(object), object, dump(object))
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def load_and_track(identity, tuple)
|
35
|
+
object = mapper.load(tuple)
|
36
|
+
tracker.store_persisted(object, self)
|
37
|
+
identity_map.store(identity, object, tuple)[identity]
|
38
|
+
end
|
39
|
+
|
40
|
+
end # Mapper
|
41
|
+
|
42
|
+
end # Session
|
43
|
+
end # ROM
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
|
6
|
+
# Adds session-specific functionality on top of ROM's relation.
|
7
|
+
#
|
8
|
+
# A session relation builds a queue of state changes that will be committed
|
9
|
+
# when a session is flushed.
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class Relation < ROM::Relation
|
13
|
+
include Proxy
|
14
|
+
|
15
|
+
attr_reader :relation, :tracker
|
16
|
+
private :relation, :tracker
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def self.build(relation, tracker)
|
20
|
+
mapper = Mapper.build(relation.mapper, tracker)
|
21
|
+
new(relation.inject_mapper(mapper), tracker)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def initialize(relation, tracker)
|
26
|
+
@relation, @tracker = relation, tracker
|
27
|
+
end
|
28
|
+
|
29
|
+
# Transition an object into a saved state
|
30
|
+
#
|
31
|
+
# Transient object's state turns into Created
|
32
|
+
# Persisted object's state turns into Updated
|
33
|
+
#
|
34
|
+
# @param [Object] an object to be saved
|
35
|
+
#
|
36
|
+
# @return [Session::Relation]
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def save(object)
|
40
|
+
tracker.queue(state(object).save(relation))
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Transient an object into a deleted state
|
45
|
+
#
|
46
|
+
# @param [Object] an object to be deleted
|
47
|
+
#
|
48
|
+
# @return [Session::Relation]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def delete(object)
|
52
|
+
tracker.queue(state(object).delete(relation))
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return current state of the tracked object
|
57
|
+
#
|
58
|
+
# @param [Object] an object
|
59
|
+
#
|
60
|
+
# @return [Session::State]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def state(object)
|
64
|
+
tracker.fetch(object)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return object's identity
|
68
|
+
#
|
69
|
+
# @param [Object] an object
|
70
|
+
#
|
71
|
+
# @return [Array]
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
def identity(object)
|
75
|
+
mapper.identity(object)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Start tracking an object within this session
|
79
|
+
#
|
80
|
+
# @param [Object] an object to be track
|
81
|
+
#
|
82
|
+
# @return [Session::Relation]
|
83
|
+
#
|
84
|
+
# @api public
|
85
|
+
def track(object)
|
86
|
+
tracker.store_transient(object, mapper)
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Build a new object instance and start tracking
|
91
|
+
#
|
92
|
+
# @return [Object]
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
def new(*args, &block)
|
96
|
+
object = mapper.new_object(*args, &block)
|
97
|
+
track(object)
|
98
|
+
object
|
99
|
+
end
|
100
|
+
|
101
|
+
# Check if a tracked object is dirty
|
102
|
+
#
|
103
|
+
# @param [Object] an object
|
104
|
+
#
|
105
|
+
# @return [Boolean]
|
106
|
+
#
|
107
|
+
# @api public
|
108
|
+
def dirty?(object)
|
109
|
+
state(object).transient? || mapper.dirty?(object)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check if an object is being tracked
|
113
|
+
#
|
114
|
+
# @param [Object]
|
115
|
+
#
|
116
|
+
# @return [Boolean]
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def tracking?(object)
|
120
|
+
tracker.include?(object)
|
121
|
+
end
|
122
|
+
|
123
|
+
end # Relation
|
124
|
+
|
125
|
+
end # Session
|
126
|
+
end # ROM
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
|
6
|
+
# @api private
|
7
|
+
class State
|
8
|
+
include Concord::Public.new(:object)
|
9
|
+
|
10
|
+
TransitionError = Class.new(StandardError)
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def save(*)
|
14
|
+
raise TransitionError, "cannot save object with #{self.class} state"
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
def delete(*)
|
19
|
+
raise TransitionError, "cannot delete object with #{self.class} state"
|
20
|
+
end
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
def transient?
|
24
|
+
instance_of?(Transient)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def created?
|
29
|
+
instance_of?(Created)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def persisted?
|
34
|
+
instance_of?(Persisted)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def updated?
|
39
|
+
instance_of?(Updated)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def deleted?
|
44
|
+
instance_of?(Deleted)
|
45
|
+
end
|
46
|
+
|
47
|
+
end # State
|
48
|
+
|
49
|
+
end # Session
|
50
|
+
end # ROM
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
class State
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
class Created < self
|
9
|
+
include Concord::Public.new(:object, :mapper, :relation), Adamantium::Flat
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
def commit
|
13
|
+
relation.insert(object)
|
14
|
+
mapper.store_in_identity_map(object)
|
15
|
+
Persisted.new(object, mapper)
|
16
|
+
end
|
17
|
+
|
18
|
+
end # Created
|
19
|
+
|
20
|
+
end # State
|
21
|
+
end # Session
|
22
|
+
end # ROM
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
class State
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
class Deleted < self
|
9
|
+
include Concord::Public.new(:object, :relation), Adamantium::Flat
|
10
|
+
|
11
|
+
class Committed < State
|
12
|
+
include Adamantium
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
def commit
|
17
|
+
relation.delete(object)
|
18
|
+
Committed.new(object)
|
19
|
+
end
|
20
|
+
|
21
|
+
end # Deleted
|
22
|
+
|
23
|
+
end # State
|
24
|
+
end # Session
|
25
|
+
end # ROM
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
class State
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
class Persisted < self
|
9
|
+
include Concord::Public.new(:object, :mapper)
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
def save(relation)
|
13
|
+
if mapper.dirty?(object)
|
14
|
+
Updated.new(object, mapper, relation)
|
15
|
+
else
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def delete(relation)
|
22
|
+
Deleted.new(object, relation)
|
23
|
+
end
|
24
|
+
|
25
|
+
end # Persisted
|
26
|
+
|
27
|
+
end # State
|
28
|
+
end # Session
|
29
|
+
end # ROM
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
class State
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
class Transient < self
|
9
|
+
include Concord::Public.new(:object, :mapper)
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
def save(relation)
|
13
|
+
Created.new(object, mapper, relation)
|
14
|
+
end
|
15
|
+
|
16
|
+
end # Transient
|
17
|
+
|
18
|
+
end # State
|
19
|
+
end # Session
|
20
|
+
end # ROM
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Session
|
5
|
+
class State
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
class Updated < self
|
9
|
+
include Adamantium::Flat
|
10
|
+
include Concord::Public.new(:object, :mapper, :relation)
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
def commit
|
14
|
+
relation.update(object, original_tuple)
|
15
|
+
Persisted.new(object, mapper)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def original_tuple
|
22
|
+
mapper.identity_map.fetch_tuple(mapper.identity(object))
|
23
|
+
end
|
24
|
+
|
25
|
+
end # Updated
|
26
|
+
|
27
|
+
end # State
|
28
|
+
end # Session
|
29
|
+
end # ROM
|