rom-repository 1.0.0.beta2 → 1.0.0.beta3
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 +4 -4
- data/.travis.yml +1 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +6 -4
- data/lib/rom/repository/changeset/pipe.rb +8 -0
- data/lib/rom/repository/changeset/update.rb +13 -0
- data/lib/rom/repository/changeset.rb +67 -5
- data/lib/rom/repository/class_interface.rb +8 -4
- data/lib/rom/repository/root.rb +44 -3
- data/lib/rom/repository/session.rb +1 -1
- data/lib/rom/repository/version.rb +1 -1
- data/lib/rom/repository.rb +13 -3
- data/spec/integration/repository_spec.rb +40 -0
- data/spec/integration/root_repository_spec.rb +11 -0
- data/spec/unit/changeset/map_spec.rb +69 -27
- data/spec/unit/changeset_spec.rb +20 -0
- data/spec/unit/repository/transaction_spec.rb +28 -0
- data/spec/unit/session_spec.rb +3 -11
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8deef9fc5ae64394c2845f34d1575ff4f55d503d
|
4
|
+
data.tar.gz: 746ac682b439a3ffd603cd79e64b2bf52ead6d8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c970db02832c84c5287a4c537bcc851abd07013947fdaeccccf6db117bf5b7817b4b10dbbfa0efc6683be30830e826eeaf5aba504b3e47663348a94291c05b06
|
7
|
+
data.tar.gz: 410546d65acdec9fedb221809ed1e2b098621da1b034eb8580b34c333931fdee16ba5ece929f9ccdfbc9be52150eb7fe89c5531ebea852c2cbd3c090f8a3bc44
|
data/.travis.yml
CHANGED
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -2,15 +2,17 @@
|
|
2
2
|
|
3
3
|
### Added
|
4
4
|
|
5
|
-
* New `Repository#
|
6
|
-
* Support for inferring typed structs based on relation schemas (solnic)
|
5
|
+
* New `Repository#transaction` API for executing operations inside a database transaction (flash-gordon+solnic)
|
7
6
|
* `aggregate` and `combine` support nested association specs, ie `combine(users: [tasks: :tags])` (beauby)
|
8
7
|
* Changesets support data as arrays too (solnic)
|
9
8
|
* Changesets support custom command types via `Changeset#with(command_type: :my_command)` (solnic)
|
10
9
|
* `Changeset::Delete` was added and is accessible via `repo.changeset(delete: some_relation.by_pk(1))` (solnic)
|
11
|
-
* Ability to define custom changeset classes that can be instantiated via `repo.changeset(MyChangesetClass[:rel_name]).data(some_data)` where `MyChangesetClass` inherits from a core changeset class (solnic)
|
10
|
+
* Ability to define custom changeset classes that can be instantiated via `repo.changeset(MyChangesetClass[:rel_name]).data(some_data)` or `root_repo.changeset(MyChangesetClass)` where `MyChangesetClass` inherits from a core changeset class (solnic)
|
12
11
|
* `Changeset.map` which accepts a block and exposes a DSL for data transformations (all [transproc hash methods](https://github.com/solnic/transproc)) are available (solnic)
|
12
|
+
* `Changeset.map` which accepts a custom block that can transform data (executed in the context of a changeset object) (solnic)
|
13
|
+
* Support for composing multiple mappings via `Changeset.map` (solnic)
|
13
14
|
* `Changeset#associate` method that accepts another changeset or parent data and an association identifier, ie `post_changeset.associate(user, :author)` (solnic)
|
15
|
+
* Support for inferring typed structs based on relation schemas (solnic)
|
14
16
|
* You can now use `wrap_parent` in combined relations too (which is gazillion times faster than `combine_parents`) (solnic)
|
15
17
|
|
16
18
|
### Changed
|
@@ -20,7 +22,7 @@
|
|
20
22
|
|
21
23
|
### Fixed
|
22
24
|
|
23
|
-
*
|
25
|
+
* FKs are always included in auto-generated structs used in aggregates (solnic)
|
24
26
|
|
25
27
|
[Compare v0.3.1...v1.0.0](https://github.com/rom-rb/rom-repository/compare/v0.3.1...v1.0.0)
|
26
28
|
|
@@ -27,6 +27,14 @@ module ROM
|
|
27
27
|
self.class[name]
|
28
28
|
end
|
29
29
|
|
30
|
+
def bind(context)
|
31
|
+
if processor.is_a?(Proc)
|
32
|
+
self.class.new(Pipe[-> *args { context.instance_exec(*args, &processor) }])
|
33
|
+
else
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
30
38
|
def >>(other)
|
31
39
|
if processor
|
32
40
|
Pipe.new(processor >> other)
|
@@ -8,6 +8,19 @@ module ROM
|
|
8
8
|
# @return [Symbol] The name of the relation's primary key attribute
|
9
9
|
option :primary_key, reader: true
|
10
10
|
|
11
|
+
# Commit update changeset if there's a diff
|
12
|
+
#
|
13
|
+
# This returns original tuple if there's no diff
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
#
|
17
|
+
# @see Changeset#commit
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def commit
|
21
|
+
diff? ? super : original
|
22
|
+
end
|
23
|
+
|
11
24
|
# Return true
|
12
25
|
#
|
13
26
|
# @return [TrueClass]
|
@@ -23,7 +23,7 @@ module ROM
|
|
23
23
|
# @!attribute [r] pipe
|
24
24
|
# @return [Changeset::Pipe] data transformation pipe
|
25
25
|
option :pipe, reader: true, accept: [Proc, Pipe], default: -> changeset {
|
26
|
-
changeset.class.default_pipe
|
26
|
+
changeset.class.default_pipe(changeset)
|
27
27
|
}
|
28
28
|
|
29
29
|
# @!attribute [r] command_compiler
|
@@ -44,14 +44,50 @@ module ROM
|
|
44
44
|
#
|
45
45
|
# @api public
|
46
46
|
def self.[](relation_name)
|
47
|
-
fetch_or_store(relation_name) {
|
47
|
+
fetch_or_store([relation_name, self]) {
|
48
48
|
Class.new(self) { relation(relation_name) }
|
49
49
|
}
|
50
50
|
end
|
51
51
|
|
52
|
+
# Define a changeset mapping
|
53
|
+
#
|
54
|
+
# Subsequent mapping definitions will be composed together
|
55
|
+
# and applied in the order they way defined
|
56
|
+
#
|
57
|
+
# @example Transformation DSL
|
58
|
+
# class NewUser < ROM::Changeset::Create
|
59
|
+
# map do
|
60
|
+
# unwrap :address, prefix: true
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# @example Using custom block
|
65
|
+
# class NewUser < ROM::Changeset::Create
|
66
|
+
# map do |tuple|
|
67
|
+
# tuple.merge(created_at: Time.now)
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# @example Multiple mappings (executed in the order of definition)
|
72
|
+
# class NewUser < ROM::Changeset::Create
|
73
|
+
# map do
|
74
|
+
# unwrap :address, prefix: true
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# map do |tuple|
|
78
|
+
# tuple.merge(created_at: Time.now)
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# @return [Array<Pipe, Transproc::Function>]
|
83
|
+
#
|
52
84
|
# @api public
|
53
85
|
def self.map(&block)
|
54
|
-
|
86
|
+
if block.arity.zero?
|
87
|
+
pipes << Class.new(Pipe, &block).new
|
88
|
+
else
|
89
|
+
pipes << Pipe.new(block)
|
90
|
+
end
|
55
91
|
end
|
56
92
|
|
57
93
|
# Build default pipe object
|
@@ -59,8 +95,20 @@ module ROM
|
|
59
95
|
# This can be overridden in a custom changeset subclass
|
60
96
|
#
|
61
97
|
# @return [Pipe]
|
62
|
-
def self.default_pipe
|
63
|
-
|
98
|
+
def self.default_pipe(context)
|
99
|
+
pipes.size > 0 ? pipes.map { |p| p.bind(context) }.reduce(:>>) : Pipe.new
|
100
|
+
end
|
101
|
+
|
102
|
+
# @api private
|
103
|
+
def self.inherited(klass)
|
104
|
+
return if klass == ROM::Changeset
|
105
|
+
super
|
106
|
+
klass.instance_variable_set(:@__pipes__, pipes ? pipes.dup : [])
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def self.pipes
|
111
|
+
@__pipes__
|
64
112
|
end
|
65
113
|
|
66
114
|
# Pipe changeset's data using custom steps define on the pipe
|
@@ -124,6 +172,20 @@ module ROM
|
|
124
172
|
with(__data__: data)
|
125
173
|
end
|
126
174
|
|
175
|
+
# Persist changeset
|
176
|
+
#
|
177
|
+
# @example
|
178
|
+
# changeset = user_repo.changeset(name: 'Jane')
|
179
|
+
# changeset.commit
|
180
|
+
# # => { id: 1, name: 'Jane' }
|
181
|
+
#
|
182
|
+
# @return [Hash, Array]
|
183
|
+
#
|
184
|
+
# @api public
|
185
|
+
def commit
|
186
|
+
command.call
|
187
|
+
end
|
188
|
+
|
127
189
|
# Return command result type
|
128
190
|
#
|
129
191
|
# @return [Symbol]
|
@@ -112,8 +112,12 @@ module ROM
|
|
112
112
|
|
113
113
|
# @api private
|
114
114
|
def define_command_method(type, **opts)
|
115
|
-
define_method(type) do
|
116
|
-
|
115
|
+
define_method(type) do |input|
|
116
|
+
if input.is_a?(Changeset)
|
117
|
+
map_tuple(input.relation, input.commit)
|
118
|
+
else
|
119
|
+
command(type => self.class.root, **opts).call(input)
|
120
|
+
end
|
117
121
|
end
|
118
122
|
end
|
119
123
|
|
@@ -127,8 +131,8 @@ module ROM
|
|
127
131
|
|
128
132
|
changeset = input.first
|
129
133
|
|
130
|
-
if changeset.is_a?(Changeset)
|
131
|
-
map_tuple(changeset.relation, changeset.
|
134
|
+
if changeset.is_a?(Changeset)
|
135
|
+
map_tuple(changeset.relation, changeset.commit)
|
132
136
|
else
|
133
137
|
command(type => self.class.root, **opts)
|
134
138
|
.public_send(view_name, *view_args)
|
data/lib/rom/repository/root.rb
CHANGED
@@ -61,9 +61,23 @@ module ROM
|
|
61
61
|
#
|
62
62
|
# @param *associations [Array<Symbol>] A list of association names
|
63
63
|
#
|
64
|
+
# @overload aggregate(*associations, *assoc_opts)
|
65
|
+
# Composes an aggregate from configured associations and assoc opts
|
66
|
+
# on the root relation
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# user_repo.aggregate(:tasks, posts: :tags)
|
70
|
+
#
|
71
|
+
# @param *associations [Array<Symbol>] A list of association names
|
72
|
+
# @param [Hash] Association options for nested aggregates
|
73
|
+
#
|
64
74
|
# @overload aggregate(options)
|
65
75
|
# Composes an aggregate by delegating to combine_children method.
|
66
76
|
#
|
77
|
+
# @example
|
78
|
+
# user_repo.aggregate(tasks: :labels)
|
79
|
+
# user_repo.aggregate(posts: [:tags, :comments])
|
80
|
+
#
|
67
81
|
# @param options [Hash] An option hash
|
68
82
|
#
|
69
83
|
# @see RelationProxy::Combine#combine_children
|
@@ -72,33 +86,60 @@ module ROM
|
|
72
86
|
#
|
73
87
|
# @api public
|
74
88
|
def aggregate(*args)
|
75
|
-
|
89
|
+
if args.all? { |arg| arg.is_a?(Symbol) }
|
90
|
+
root.combine(*args)
|
91
|
+
else
|
92
|
+
args.reduce(root) { |a, e| a.combine(e) }
|
93
|
+
end
|
76
94
|
end
|
77
95
|
|
78
96
|
# @overload changeset(name, *args)
|
79
97
|
# Delegate to Repository#changeset
|
98
|
+
#
|
80
99
|
# @see Repository#changeset
|
81
100
|
#
|
82
101
|
# @overload changeset(data)
|
83
102
|
# Builds a create changeset for the root relation
|
103
|
+
#
|
84
104
|
# @example
|
85
105
|
# user_repo.changeset(name: "Jane")
|
106
|
+
#
|
86
107
|
# @param data [Hash] New data
|
108
|
+
#
|
87
109
|
# @return [Changeset::Create]
|
88
110
|
#
|
89
|
-
# @overload changeset(
|
111
|
+
# @overload changeset(primary_key, data)
|
90
112
|
# Builds an update changeset for the root relation
|
113
|
+
#
|
91
114
|
# @example
|
92
115
|
# user_repo.changeset(1, name: "Jane Doe")
|
93
|
-
#
|
116
|
+
#
|
117
|
+
# @param primary_key [Object] Primary key for restricting relation
|
118
|
+
#
|
94
119
|
# @return [Changeset::Update]
|
95
120
|
#
|
121
|
+
# @overload changeset(changeset_class)
|
122
|
+
# Return a changeset prepared for repo's root relation
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# changeset = user_repo.changeset(MyChangeset)
|
126
|
+
#
|
127
|
+
# changeset.relation == user_repo.root
|
128
|
+
# # true
|
129
|
+
#
|
130
|
+
# @param [Class] changeset_class Your custom changeset class
|
131
|
+
#
|
132
|
+
# @return [Changeset]
|
133
|
+
#
|
96
134
|
# @override Repository#changeset
|
97
135
|
#
|
98
136
|
# @api public
|
99
137
|
def changeset(*args)
|
100
138
|
if args.first.is_a?(Symbol) && relations.key?(args.first)
|
101
139
|
super
|
140
|
+
elsif args.first.is_a?(Class)
|
141
|
+
klass, *rest = args
|
142
|
+
super(klass[self.class.root], *rest)
|
102
143
|
else
|
103
144
|
super(root.name, *args)
|
104
145
|
end
|
data/lib/rom/repository.rb
CHANGED
@@ -139,7 +139,7 @@ module ROM
|
|
139
139
|
end
|
140
140
|
|
141
141
|
# @overload changeset(name, attributes)
|
142
|
-
#
|
142
|
+
# Return a create changeset for a given relation identifier
|
143
143
|
#
|
144
144
|
# @example
|
145
145
|
# repo.changeset(:users, name: "Jane")
|
@@ -149,8 +149,8 @@ module ROM
|
|
149
149
|
#
|
150
150
|
# @return [Changeset::Create]
|
151
151
|
#
|
152
|
-
# @overload changeset(name,
|
153
|
-
#
|
152
|
+
# @overload changeset(name, primary_key, attributes)
|
153
|
+
# Return an update changeset for a given relation identifier
|
154
154
|
#
|
155
155
|
# @example
|
156
156
|
# repo.changeset(:users, 1, name: "Jane Doe")
|
@@ -160,6 +160,16 @@ module ROM
|
|
160
160
|
#
|
161
161
|
# @return [Changeset::Update]
|
162
162
|
#
|
163
|
+
# @overload changeset(changeset_class)
|
164
|
+
# Return a changeset object using provided class
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# repo.changeset(NewUserChangeset).data(attributes)
|
168
|
+
#
|
169
|
+
# @param [Class] changeset_class Custom changeset class
|
170
|
+
#
|
171
|
+
# @return [Changeset]
|
172
|
+
#
|
163
173
|
# @api public
|
164
174
|
def changeset(*args)
|
165
175
|
opts = { command_compiler: command_compiler }
|
@@ -84,6 +84,46 @@ RSpec.describe 'ROM repository' do
|
|
84
84
|
expect(jane.posts.first.title).to eql('Hello From Jane')
|
85
85
|
end
|
86
86
|
|
87
|
+
it 'loads an aggregate via assoc options' do
|
88
|
+
jane = repo.aggregate(posts: :labels).where(name: 'Jane').one
|
89
|
+
|
90
|
+
expect(jane.name).to eql('Jane')
|
91
|
+
expect(jane.posts.size).to be(1)
|
92
|
+
expect(jane.posts.first.title).to eql('Hello From Jane')
|
93
|
+
expect(jane.posts[0].labels.size).to be(2)
|
94
|
+
expect(jane.posts[0].labels[0].name).to eql('red')
|
95
|
+
expect(jane.posts[0].labels[1].name).to eql('blue')
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'loads an aggregate with multiple assoc options' do
|
99
|
+
jane = repo.aggregate(:labels, posts: :labels).where(name: 'Jane').one
|
100
|
+
|
101
|
+
expect(jane.name).to eql('Jane')
|
102
|
+
|
103
|
+
expect(jane.labels.size).to be(2)
|
104
|
+
expect(jane.labels[0].name).to eql('red')
|
105
|
+
expect(jane.labels[1].name).to eql('blue')
|
106
|
+
|
107
|
+
expect(jane.posts.size).to be(1)
|
108
|
+
expect(jane.posts[0].title).to eql('Hello From Jane')
|
109
|
+
|
110
|
+
expect(jane.posts[0].labels.size).to be(2)
|
111
|
+
expect(jane.posts[0].labels[0].name).to eql('red')
|
112
|
+
expect(jane.posts[0].labels[1].name).to eql('blue')
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'loads an aggregate with deeply nested assoc options' do
|
116
|
+
jane = repo.aggregate(posts: [{ author: :labels }]).where(name: 'Jane').one
|
117
|
+
|
118
|
+
expect(jane.posts.size).to be(1)
|
119
|
+
expect(jane.posts[0].title).to eql('Hello From Jane')
|
120
|
+
|
121
|
+
expect(jane.posts[0].author.id).to eql(jane.id)
|
122
|
+
expect(jane.posts[0].author.labels.size).to be(2)
|
123
|
+
expect(jane.posts[0].author.labels[0].name).to eql('red')
|
124
|
+
expect(jane.posts[0].author.labels[1].name).to eql('blue')
|
125
|
+
end
|
126
|
+
|
87
127
|
it 'loads an aggregate with multiple associations' do
|
88
128
|
jane = repo.aggregate(:posts, :labels).where(name: 'Jane').one
|
89
129
|
|
@@ -45,6 +45,17 @@ RSpec.describe ROM::Repository::Root do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
describe '#changeset' do
|
49
|
+
it 'returns a changeset automatically set for root relation' do
|
50
|
+
klass = Class.new(ROM::Changeset::Create)
|
51
|
+
|
52
|
+
changeset = repo.changeset(klass)
|
53
|
+
|
54
|
+
expect(changeset.relation).to be(repo.users)
|
55
|
+
expect(changeset).to be_kind_of(klass)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
48
59
|
describe '#aggregate' do
|
49
60
|
include_context 'seeds'
|
50
61
|
|
@@ -1,42 +1,84 @@
|
|
1
1
|
RSpec.describe ROM::Changeset, '.map' do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
context 'single mapping with transaction DSL' do
|
3
|
+
subject(:changeset) do
|
4
|
+
Class.new(ROM::Changeset::Create[:users]) do
|
5
|
+
map do
|
6
|
+
unwrap :address
|
7
|
+
rename_keys street: :address_street, city: :address_city, country: :address_country
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_command_type
|
11
|
+
:test
|
12
|
+
end
|
13
|
+
end.new(relation, __data__: user_data)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:relation) { double(:relation) }
|
17
|
+
|
18
|
+
context 'with a hash' do
|
19
|
+
let(:user_data) do
|
20
|
+
{ name: 'Jane', address: { street: 'Street 1', city: 'NYC', country: 'US' } }
|
7
21
|
end
|
8
22
|
|
9
|
-
|
10
|
-
|
23
|
+
it 'sets up custom data pipe' do
|
24
|
+
expect(changeset.to_h)
|
25
|
+
.to eql(name: 'Jane', address_street: 'Street 1', address_city: 'NYC', address_country: 'US' )
|
11
26
|
end
|
12
|
-
end
|
13
|
-
end
|
27
|
+
end
|
14
28
|
|
15
|
-
|
29
|
+
context 'with an array' do
|
30
|
+
let(:user_data) do
|
31
|
+
[{ name: 'Jane', address: { street: 'Street 1', city: 'NYC', country: 'US' } },
|
32
|
+
{ name: 'Joe', address: { street: 'Street 2', city: 'KRK', country: 'PL' } }]
|
33
|
+
end
|
16
34
|
|
17
|
-
|
18
|
-
|
19
|
-
|
35
|
+
it 'sets up custom data pipe' do
|
36
|
+
expect(changeset.to_a)
|
37
|
+
.to eql([
|
38
|
+
{ name: 'Jane', address_street: 'Street 1', address_city: 'NYC', address_country: 'US' },
|
39
|
+
{ name: 'Joe', address_street: 'Street 2', address_city: 'KRK', address_country: 'PL' }
|
40
|
+
])
|
41
|
+
end
|
20
42
|
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'multi mapping with custom blocks' do
|
46
|
+
subject(:changeset) do
|
47
|
+
Class.new(ROM::Changeset::Create[:users]) do
|
48
|
+
map do |tuple|
|
49
|
+
tuple.merge(one: next_value)
|
50
|
+
end
|
51
|
+
|
52
|
+
map do |tuple|
|
53
|
+
tuple.merge(two: next_value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(*args)
|
57
|
+
super
|
58
|
+
@counter = 0
|
59
|
+
end
|
21
60
|
|
22
|
-
|
23
|
-
|
24
|
-
|
61
|
+
def default_command_type
|
62
|
+
:test
|
63
|
+
end
|
64
|
+
|
65
|
+
def next_value
|
66
|
+
@counter += 1
|
67
|
+
end
|
68
|
+
end.new(relation).data(user_data)
|
25
69
|
end
|
26
|
-
end
|
27
70
|
|
28
|
-
|
29
|
-
let(:user_data)
|
30
|
-
|
31
|
-
|
71
|
+
let(:relation) { double(:relation) }
|
72
|
+
let(:user_data) { { name: 'Jane' } }
|
73
|
+
|
74
|
+
it 'applies mappings in order of definition' do
|
75
|
+
expect(changeset.to_h).to eql(name: 'Jane', one: 1, two: 2)
|
32
76
|
end
|
33
77
|
|
34
|
-
it '
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
{ name: 'Joe', address_street: 'Street 2', address_city: 'KRK', address_country: 'PL' }
|
39
|
-
])
|
78
|
+
it 'inherits pipes' do
|
79
|
+
klass = Class.new(changeset.class)
|
80
|
+
|
81
|
+
expect(klass.pipes).to eql(changeset.class.pipes)
|
40
82
|
end
|
41
83
|
end
|
42
84
|
end
|
data/spec/unit/changeset_spec.rb
CHANGED
@@ -2,6 +2,26 @@ RSpec.describe ROM::Changeset do
|
|
2
2
|
let(:jane) { { id: 2, name: "Jane" } }
|
3
3
|
let(:relation) { double(ROM::Relation, primary_key: :id) }
|
4
4
|
|
5
|
+
describe '.[]' do
|
6
|
+
it 'returns a changeset preconfigured for a specific relation' do
|
7
|
+
klass = ROM::Changeset::Create[:users]
|
8
|
+
|
9
|
+
expect(klass.relation).to be(:users)
|
10
|
+
expect(klass < ROM::Changeset::Create).to be(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'caches results' do
|
14
|
+
create = ROM::Changeset::Create[:users]
|
15
|
+
update = ROM::Changeset::Update[:users]
|
16
|
+
|
17
|
+
expect(create).to be(ROM::Changeset::Create[:users])
|
18
|
+
expect(create < ROM::Changeset::Create).to be(true)
|
19
|
+
|
20
|
+
expect(update).to be(ROM::Changeset::Update[:users])
|
21
|
+
expect(update < ROM::Changeset::Update).to be(true)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
5
25
|
describe '#diff' do
|
6
26
|
it 'returns a hash with changes' do
|
7
27
|
expect(relation).to receive(:fetch).with(2).and_return(jane)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
RSpec.describe ROM::Repository, '#transaction' do
|
2
|
+
let(:user_repo) do
|
3
|
+
Class.new(ROM::Repository[:users]) { commands :create }.new(rom)
|
4
|
+
end
|
5
|
+
|
6
|
+
let(:task_repo) do
|
7
|
+
Class.new(ROM::Repository[:tasks]) { commands :create }.new(rom)
|
8
|
+
end
|
9
|
+
|
10
|
+
include_context 'database'
|
11
|
+
include_context 'relations'
|
12
|
+
|
13
|
+
it 'creating user with tasks' do
|
14
|
+
user, task = user_repo.transaction do
|
15
|
+
user_changeset = user_repo.changeset(name: 'Jane')
|
16
|
+
task_changeset = task_repo.changeset(title: 'Task One')
|
17
|
+
|
18
|
+
user = user_repo.create(user_changeset)
|
19
|
+
task = task_repo.create(task_changeset.associate(user, :user))
|
20
|
+
|
21
|
+
[user, task]
|
22
|
+
end
|
23
|
+
|
24
|
+
expect(user.name).to eql('Jane')
|
25
|
+
expect(task.user_id).to be(user.id)
|
26
|
+
expect(task.title).to eql('Task One')
|
27
|
+
end
|
28
|
+
end
|
data/spec/unit/session_spec.rb
CHANGED
@@ -7,8 +7,6 @@ RSpec.describe ROM::Session do
|
|
7
7
|
let(:create_changeset) { instance_double(ROM::Changeset::Create, relation: relation) }
|
8
8
|
let(:delete_changeset) { instance_double(ROM::Changeset::Delete, relation: relation) }
|
9
9
|
let(:relation) { double.as_null_object }
|
10
|
-
let(:create_command) { spy(:create_command) }
|
11
|
-
let(:delete_command) { spy(:delete_command) }
|
12
10
|
|
13
11
|
describe '#pending?' do
|
14
12
|
it 'returns true before commit' do
|
@@ -22,21 +20,17 @@ RSpec.describe ROM::Session do
|
|
22
20
|
|
23
21
|
describe '#commit!' do
|
24
22
|
it 'executes ops and restores pristine state' do
|
25
|
-
expect(create_changeset).to receive(:
|
23
|
+
expect(create_changeset).to receive(:commit).and_return(true)
|
26
24
|
|
27
25
|
session.add(create_changeset).commit!
|
28
26
|
session.commit!
|
29
27
|
|
30
28
|
expect(session).to be_success
|
31
|
-
|
32
|
-
expect(create_command).to have_received(:call)
|
33
29
|
end
|
34
30
|
|
35
31
|
it 'executes ops and restores pristine state when exception was raised' do
|
36
|
-
expect(create_changeset).
|
37
|
-
expect(delete_changeset).to receive(:
|
38
|
-
|
39
|
-
expect(delete_command).to receive(:call).and_raise(StandardError, 'oops')
|
32
|
+
expect(create_changeset).to_not receive(:commit)
|
33
|
+
expect(delete_changeset).to receive(:commit).and_raise(StandardError, 'oops')
|
40
34
|
|
41
35
|
expect {
|
42
36
|
session.add(delete_changeset)
|
@@ -47,8 +41,6 @@ RSpec.describe ROM::Session do
|
|
47
41
|
expect(session).to be_failure
|
48
42
|
|
49
43
|
session.commit!
|
50
|
-
|
51
|
-
expect(create_command).not_to have_received(:call)
|
52
44
|
end
|
53
45
|
end
|
54
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rom-repository
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rom
|
@@ -110,6 +110,7 @@ files:
|
|
110
110
|
- ".gitignore"
|
111
111
|
- ".rspec"
|
112
112
|
- ".travis.yml"
|
113
|
+
- ".yardopts"
|
113
114
|
- CHANGELOG.md
|
114
115
|
- Gemfile
|
115
116
|
- LICENSE.txt
|
@@ -161,6 +162,7 @@ files:
|
|
161
162
|
- spec/unit/repository/changeset_spec.rb
|
162
163
|
- spec/unit/repository/inspect_spec.rb
|
163
164
|
- spec/unit/repository/session_spec.rb
|
165
|
+
- spec/unit/repository/transaction_spec.rb
|
164
166
|
- spec/unit/session_spec.rb
|
165
167
|
- spec/unit/struct_builder_spec.rb
|
166
168
|
homepage: http://rom-rb.org
|