rom-repository 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +5 -5
- data/CHANGELOG.md +24 -0
- data/Gemfile +21 -5
- data/README.md +6 -110
- data/lib/rom/repository/changeset/create.rb +26 -0
- data/lib/rom/repository/changeset/pipe.rb +40 -0
- data/lib/rom/repository/changeset/update.rb +82 -0
- data/lib/rom/repository/changeset.rb +99 -0
- data/lib/rom/repository/class_interface.rb +142 -0
- data/lib/rom/repository/command_compiler.rb +214 -0
- data/lib/rom/repository/command_proxy.rb +22 -0
- data/lib/rom/repository/header_builder.rb +13 -16
- data/lib/rom/repository/mapper_builder.rb +7 -14
- data/lib/rom/repository/{loading_proxy → relation_proxy}/wrap.rb +7 -7
- data/lib/rom/repository/relation_proxy.rb +225 -0
- data/lib/rom/repository/root.rb +110 -0
- data/lib/rom/repository/struct_attributes.rb +46 -0
- data/lib/rom/repository/struct_builder.rb +31 -14
- data/lib/rom/repository/version.rb +1 -1
- data/lib/rom/repository.rb +192 -31
- data/lib/rom/struct.rb +13 -8
- data/rom-repository.gemspec +9 -10
- data/spec/integration/changeset_spec.rb +86 -0
- data/spec/integration/command_macros_spec.rb +175 -0
- data/spec/integration/command_spec.rb +224 -0
- data/spec/integration/multi_adapter_spec.rb +3 -3
- data/spec/integration/repository_spec.rb +97 -2
- data/spec/integration/root_repository_spec.rb +88 -0
- data/spec/shared/database.rb +47 -3
- data/spec/shared/mappers.rb +35 -0
- data/spec/shared/models.rb +41 -0
- data/spec/shared/plugins.rb +66 -0
- data/spec/shared/relations.rb +76 -0
- data/spec/shared/repo.rb +38 -17
- data/spec/shared/seeds.rb +19 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/support/mapper_registry.rb +1 -3
- data/spec/unit/changeset_spec.rb +58 -0
- data/spec/unit/header_builder_spec.rb +34 -35
- data/spec/unit/relation_proxy_spec.rb +170 -0
- data/spec/unit/sql/relation_spec.rb +5 -5
- data/spec/unit/struct_builder_spec.rb +7 -4
- data/spec/unit/struct_spec.rb +22 -0
- metadata +38 -41
- data/lib/rom/plugins/relation/key_inference.rb +0 -31
- data/lib/rom/repository/loading_proxy/combine.rb +0 -158
- data/lib/rom/repository/loading_proxy.rb +0 -182
- data/spec/unit/loading_proxy_spec.rb +0 -147
@@ -0,0 +1,110 @@
|
|
1
|
+
module ROM
|
2
|
+
class Repository
|
3
|
+
# A specialized repository type dedicated to work with a root relation
|
4
|
+
#
|
5
|
+
# This repository type builds commands and aggregates for its root relation
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class UserRepo < ROM::Repository[:users]
|
9
|
+
# commands :create, update: :by_pk, delete: :by_pk
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# rom = ROM.container(:sql, 'sqlite::memory') do |conf|
|
13
|
+
# conf.default.create_table(:users) do
|
14
|
+
# primary_key :id
|
15
|
+
# column :name, String
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# user_repo = UserRepo.new(rom)
|
20
|
+
#
|
21
|
+
# user = user_repo.create(name: "Jane")
|
22
|
+
#
|
23
|
+
# changeset = user_repo.changeset(user.id, name: "Jane Doe")
|
24
|
+
# user_repo.update(user.id, changeset)
|
25
|
+
#
|
26
|
+
# user_repo.delete(user.id)
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
class Root < Repository
|
30
|
+
extend ClassMacros
|
31
|
+
|
32
|
+
defines :root
|
33
|
+
|
34
|
+
# @!attribute [r] root
|
35
|
+
# @return [RelationProxy] The root relation
|
36
|
+
attr_reader :root
|
37
|
+
|
38
|
+
# Sets descendant root relation
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
def self.inherited(klass)
|
42
|
+
super
|
43
|
+
klass.root(root)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @see Repository#initialize
|
47
|
+
def initialize(container)
|
48
|
+
super
|
49
|
+
@root = relations[self.class.root]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Compose a relation aggregate from the root relation
|
53
|
+
#
|
54
|
+
# @overload aggregate(*associations)
|
55
|
+
# Composes an aggregate from configured associations on the root relation
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# user_repo.aggregate(:tasks, :posts)
|
59
|
+
#
|
60
|
+
# @param *associations [Array<Symbol>] A list of association names
|
61
|
+
#
|
62
|
+
# @overload aggregate(options)
|
63
|
+
# Composes an aggregate by delegating to combine_children method.
|
64
|
+
#
|
65
|
+
# @param options [Hash] An option hash
|
66
|
+
#
|
67
|
+
# @see RelationProxy::Combine#combine_children
|
68
|
+
#
|
69
|
+
# @return [RelationProxy]
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def aggregate(*args)
|
73
|
+
if args[0].is_a?(Hash) && args.size == 1
|
74
|
+
root.combine_children(args[0])
|
75
|
+
else
|
76
|
+
root.combine(*args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @overload changeset(name, *args)
|
81
|
+
# Delegate to Repository#changeset
|
82
|
+
# @see Repository#changeset
|
83
|
+
#
|
84
|
+
# @overload changeset(data)
|
85
|
+
# Builds a create changeset for the root relation
|
86
|
+
# @example
|
87
|
+
# user_repo.changeset(name: "Jane")
|
88
|
+
# @param data [Hash] New data
|
89
|
+
# @return [Changeset::Create]
|
90
|
+
#
|
91
|
+
# @overload changeset(restriction_arg, data)
|
92
|
+
# Builds an update changeset for the root relation
|
93
|
+
# @example
|
94
|
+
# user_repo.changeset(1, name: "Jane Doe")
|
95
|
+
# @param restriction_arg [Object] An argument for the restriction view
|
96
|
+
# @return [Changeset::Update]
|
97
|
+
#
|
98
|
+
# @override Repository#changeset
|
99
|
+
#
|
100
|
+
# @api public
|
101
|
+
def changeset(*args)
|
102
|
+
if args.first.is_a?(Symbol) && relations.key?(args.first)
|
103
|
+
super
|
104
|
+
else
|
105
|
+
super(root.name, *args)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ROM
|
2
|
+
class Repository
|
3
|
+
# @api private
|
4
|
+
class StructAttributes < Module
|
5
|
+
def initialize(attributes)
|
6
|
+
super()
|
7
|
+
|
8
|
+
define_constructor(attributes)
|
9
|
+
|
10
|
+
module_eval do
|
11
|
+
include Dry::Equalizer.new(*attributes)
|
12
|
+
|
13
|
+
attr_reader(*attributes)
|
14
|
+
|
15
|
+
define_method(:to_h) do
|
16
|
+
attributes.each_with_object({}) do |attribute, h|
|
17
|
+
h[attribute] = __send__(attribute)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def define_constructor(attributes)
|
24
|
+
module_eval do
|
25
|
+
def __missing_keyword__(keyword)
|
26
|
+
raise ArgumentError.new("missing keyword: #{keyword}")
|
27
|
+
end
|
28
|
+
private :__missing_keyword__
|
29
|
+
end
|
30
|
+
|
31
|
+
kwargs = attributes.map { |a| "#{a}: __missing_keyword__(:#{a})" }.join(', ')
|
32
|
+
|
33
|
+
ivs = attributes.map { |a| "@#{a}" }.join(', ')
|
34
|
+
values = attributes.join(', ')
|
35
|
+
|
36
|
+
assignment = attributes.size > 0 ? "#{ivs} = #{values}" : EMPTY_STRING
|
37
|
+
|
38
|
+
module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
39
|
+
def initialize(#{kwargs})
|
40
|
+
#{assignment}
|
41
|
+
end
|
42
|
+
RUBY
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,30 +1,47 @@
|
|
1
|
-
require 'anima'
|
2
|
-
|
3
1
|
require 'rom/struct'
|
4
2
|
|
3
|
+
require 'rom/support/cache'
|
4
|
+
require 'rom/support/constants'
|
5
|
+
require 'rom/support/class_builder'
|
6
|
+
|
7
|
+
require 'rom/repository/struct_attributes'
|
8
|
+
|
5
9
|
module ROM
|
6
10
|
class Repository
|
7
11
|
# @api private
|
8
12
|
class StructBuilder
|
9
|
-
|
13
|
+
extend Cache
|
14
|
+
|
15
|
+
def call(*args)
|
16
|
+
fetch_or_store(*args) do
|
17
|
+
name, header = args
|
18
|
+
|
19
|
+
build_class(name) { |klass|
|
20
|
+
klass.send(:include, StructAttributes.new(visit(header)))
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias_method :[], :call
|
25
|
+
|
26
|
+
private
|
10
27
|
|
11
|
-
def
|
12
|
-
|
28
|
+
def visit(ast)
|
29
|
+
name, node = ast
|
30
|
+
__send__("visit_#{name}", node)
|
13
31
|
end
|
14
32
|
|
15
|
-
def
|
16
|
-
|
33
|
+
def visit_header(node)
|
34
|
+
node.map(&method(:visit))
|
17
35
|
end
|
18
36
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
klass.send(:include, Anima.new(*columns))
|
23
|
-
}
|
37
|
+
def visit_relation(node)
|
38
|
+
relation_name, meta, * = node
|
39
|
+
meta[:combine_name] || relation_name.relation
|
24
40
|
end
|
25
|
-
alias_method :[], :call
|
26
41
|
|
27
|
-
|
42
|
+
def visit_attribute(node)
|
43
|
+
node
|
44
|
+
end
|
28
45
|
|
29
46
|
def build_class(name, &block)
|
30
47
|
ROM::ClassBuilder.new(name: class_name(name), parent: Struct).call(&block)
|
data/lib/rom/repository.rb
CHANGED
@@ -1,60 +1,221 @@
|
|
1
1
|
require 'rom/support/deprecations'
|
2
2
|
require 'rom/support/options'
|
3
3
|
|
4
|
+
require 'rom/repository/class_interface'
|
4
5
|
require 'rom/repository/mapper_builder'
|
5
|
-
require 'rom/repository/
|
6
|
+
require 'rom/repository/relation_proxy'
|
7
|
+
require 'rom/repository/command_compiler'
|
8
|
+
|
9
|
+
require 'rom/repository/root'
|
10
|
+
require 'rom/repository/changeset'
|
6
11
|
|
7
12
|
module ROM
|
13
|
+
# Abstract repository class to inherit from
|
14
|
+
#
|
15
|
+
# A repository provides access to composable relations and commands. Its job is
|
16
|
+
# to provide application-specific data that is already materialized, so that
|
17
|
+
# relations don't leak into your application layer.
|
18
|
+
#
|
19
|
+
# Typically, you're going to work with Repository::Root that are configured to
|
20
|
+
# use a single relation as its root, and compose aggregates and use commands
|
21
|
+
# against the root relation.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# class MyRepo < ROM::Repository[:users]
|
25
|
+
# relations :users, :tasks
|
26
|
+
#
|
27
|
+
# def users_with_tasks
|
28
|
+
# users.combine_children(tasks: tasks).to_a
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# rom = ROM.container(:sql, 'sqlite::memory') do |conf|
|
33
|
+
# conf.default.create_table(:users) do
|
34
|
+
# primary_key :id
|
35
|
+
# column :name, String
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# conf.default.create_table(:tasks) do
|
39
|
+
# primary_key :id
|
40
|
+
# column :user_id, Integer
|
41
|
+
# column :title, String
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# my_repo = MyRepo.new(rom)
|
46
|
+
# my_repo.users_with_tasks
|
47
|
+
#
|
48
|
+
# @see Repository::Root
|
49
|
+
#
|
50
|
+
# @api public
|
8
51
|
class Repository
|
9
|
-
#
|
52
|
+
# @deprecated
|
53
|
+
class Base < Repository
|
54
|
+
def self.inherited(klass)
|
55
|
+
super
|
56
|
+
Deprecations.announce(self, 'inherit from Repository instead')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
extend ClassInterface
|
61
|
+
|
62
|
+
# @!attribute [r] container
|
63
|
+
# @return [ROM::Container] The container used to set up a repo
|
64
|
+
attr_reader :container
|
65
|
+
|
66
|
+
# @!attribute [r] relations
|
67
|
+
# @return [RelationRegistry] The relation proxy registry used by a repo
|
68
|
+
attr_reader :relations
|
69
|
+
|
70
|
+
# @!attribute [r] mappers
|
71
|
+
# @return [MapperBuilder] The auto-generated mappers for repo relations
|
72
|
+
attr_reader :mappers
|
73
|
+
|
74
|
+
# Initializes a new repo by establishing configured relation proxies from
|
75
|
+
# the passed container
|
10
76
|
#
|
11
|
-
#
|
77
|
+
# @param container [ROM::Container] The rom container with relations and optional commands
|
12
78
|
#
|
13
79
|
# @api public
|
14
|
-
|
80
|
+
def initialize(container)
|
81
|
+
@container = container
|
82
|
+
@mappers = MapperBuilder.new
|
83
|
+
@relations = RelationRegistry.new do |registry, relations|
|
84
|
+
self.class.relations.each do |name|
|
85
|
+
relation = container.relation(name)
|
15
86
|
|
16
|
-
|
87
|
+
proxy = RelationProxy.new(
|
88
|
+
relation, name: name, mappers: mappers, registry: registry
|
89
|
+
)
|
90
|
+
|
91
|
+
instance_variable_set("@#{name}", proxy)
|
92
|
+
|
93
|
+
relations[name] = proxy
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
17
97
|
|
18
|
-
#
|
98
|
+
# @overload command(type, relation)
|
99
|
+
# Returns a command for a relation
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# repo.command(:create, repo.users)
|
103
|
+
#
|
104
|
+
# @param type [Symbol] The command type (:create, :update or :delete)
|
105
|
+
# @param relation [RelationProxy] The relation for which command should be built for
|
106
|
+
#
|
107
|
+
# @overload command(options)
|
108
|
+
# Builds a command for a given relation identifier
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# repo.command(create: :users)
|
112
|
+
#
|
113
|
+
# @param options [Hash<Symbol=>Symbol>] A type => rel_name map
|
114
|
+
#
|
115
|
+
# @overload command(rel_name)
|
116
|
+
# Returns command registry for a given relation identifier
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# repo.command(:users)[:my_custom_command]
|
19
120
|
#
|
20
|
-
#
|
21
|
-
# class MyRepo < ROM::Repository::Base
|
22
|
-
# relations :users, :tasks
|
23
|
-
# end
|
121
|
+
# @param rel_name [Symbol] The relation identifier from the container
|
24
122
|
#
|
25
|
-
#
|
123
|
+
# @return [CommandRegistry]
|
26
124
|
#
|
27
|
-
#
|
28
|
-
#
|
125
|
+
# @overload command(rel_name, &block)
|
126
|
+
# Yields a command graph composer for a given relation identifier
|
29
127
|
#
|
30
|
-
#
|
128
|
+
# @param rel_name [Symbol] The relation identifier from the container
|
129
|
+
#
|
130
|
+
# @return [ROM::Command]
|
31
131
|
#
|
32
132
|
# @api public
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
133
|
+
def command(*args, **opts, &block)
|
134
|
+
all_args = args + opts.to_a.flatten
|
135
|
+
|
136
|
+
if all_args.size > 1
|
137
|
+
commands.fetch_or_store(all_args.hash) do
|
138
|
+
compile_command(*args, **opts)
|
139
|
+
end
|
37
140
|
else
|
38
|
-
|
141
|
+
container.command(*args, &block)
|
39
142
|
end
|
40
143
|
end
|
41
144
|
|
42
|
-
# @
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
145
|
+
# @overload changeset(name, attributes)
|
146
|
+
# Returns a create changeset for a given relation identifier
|
147
|
+
#
|
148
|
+
# @example
|
149
|
+
# repo.changeset(:users, name: "Jane")
|
150
|
+
#
|
151
|
+
# @param name [Symbol] The relation container identifier
|
152
|
+
# @param attributes [Hash]
|
153
|
+
#
|
154
|
+
# @return [Changeset::Create]
|
155
|
+
#
|
156
|
+
# @overload changeset(name, restriction_arg, attributes)
|
157
|
+
# Returns an update changeset for a given relation identifier
|
158
|
+
#
|
159
|
+
# @example
|
160
|
+
# repo.changeset(:users, 1, name: "Jane Doe")
|
161
|
+
#
|
162
|
+
# @param name [Symbol] The relation container identifier
|
163
|
+
# @param restriction_arg [Object] The argument passed to restricted view
|
164
|
+
#
|
165
|
+
# @return [Changeset::Update]
|
166
|
+
#
|
167
|
+
# @api public
|
168
|
+
def changeset(*args)
|
169
|
+
if args.size == 2
|
170
|
+
name, data = args
|
171
|
+
elsif args.size == 3
|
172
|
+
name, pk, data = args
|
173
|
+
else
|
174
|
+
raise ArgumentError, 'Repository#changeset accepts 2 or 3 arguments'
|
175
|
+
end
|
176
|
+
|
177
|
+
relation = relations[name]
|
178
|
+
|
179
|
+
if pk
|
180
|
+
Changeset::Update.new(relation, data, primary_key: pk)
|
181
|
+
else
|
182
|
+
Changeset::Create.new(relation, data)
|
50
183
|
end
|
51
184
|
end
|
52
185
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
186
|
+
private
|
187
|
+
|
188
|
+
# Local command cache
|
189
|
+
#
|
190
|
+
# @api private
|
191
|
+
def commands
|
192
|
+
@__commands__ ||= Concurrent::Map.new
|
193
|
+
end
|
194
|
+
|
195
|
+
# Build a new command or return existing one
|
196
|
+
#
|
197
|
+
# @api private
|
198
|
+
def compile_command(*args, mapper: nil, use: nil, **opts)
|
199
|
+
type, name = args + opts.to_a.flatten(1)
|
200
|
+
|
201
|
+
relation = name.is_a?(Symbol) ? relations[name] : name
|
202
|
+
|
203
|
+
ast = relation.to_ast
|
204
|
+
adapter = relations[relation.name].adapter
|
205
|
+
|
206
|
+
if mapper
|
207
|
+
mapper_instance = container.mappers[relation.name.relation][mapper]
|
208
|
+
else
|
209
|
+
mapper_instance = mappers[ast]
|
57
210
|
end
|
211
|
+
|
212
|
+
command = CommandCompiler[container, type, adapter, ast, use]
|
213
|
+
command >> mapper_instance
|
214
|
+
end
|
215
|
+
|
216
|
+
# @api private
|
217
|
+
def map_tuple(relation, tuple)
|
218
|
+
relations[relation.name].mapper.([tuple]).first
|
58
219
|
end
|
59
220
|
end
|
60
221
|
end
|
data/lib/rom/struct.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
require 'anima'
|
2
|
-
|
3
|
-
require 'rom/support/class_builder'
|
4
|
-
|
5
1
|
module ROM
|
6
2
|
# Simple data-struct
|
7
3
|
#
|
@@ -9,7 +5,7 @@ module ROM
|
|
9
5
|
#
|
10
6
|
# @api public
|
11
7
|
class Struct
|
12
|
-
#
|
8
|
+
# Coerces a struct to a hash
|
13
9
|
#
|
14
10
|
# @return [Hash]
|
15
11
|
#
|
@@ -18,15 +14,24 @@ module ROM
|
|
18
14
|
to_h
|
19
15
|
end
|
20
16
|
|
21
|
-
#
|
17
|
+
# Reads an attribute value
|
22
18
|
#
|
23
|
-
# @param [Symbol]
|
19
|
+
# @param name [Symbol] The name of the attribute
|
24
20
|
#
|
25
21
|
# @return [Object]
|
26
22
|
#
|
27
23
|
# @api public
|
28
24
|
def [](name)
|
29
|
-
|
25
|
+
__send__(name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a short string representation
|
29
|
+
#
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def to_s
|
34
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
|
30
35
|
end
|
31
36
|
end
|
32
37
|
end
|
data/rom-repository.gemspec
CHANGED
@@ -4,22 +4,21 @@ require File.expand_path('../lib/rom/repository/version', __FILE__)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = 'rom-repository'
|
7
|
-
gem.summary = 'Repository for
|
8
|
-
gem.description =
|
7
|
+
gem.summary = 'Repository abstraction for rom-rb'
|
8
|
+
gem.description = 'rom-repository adds support for auto-mapping and commands on top of rom-rb relations'
|
9
9
|
gem.author = 'Piotr Solnica'
|
10
|
-
gem.email = 'piotr.solnica@gmail.com'
|
10
|
+
gem.email = 'piotr.solnica+oss@gmail.com'
|
11
11
|
gem.homepage = 'http://rom-rb.org'
|
12
12
|
gem.require_paths = ['lib']
|
13
13
|
gem.version = ROM::Repository::VERSION.dup
|
14
|
-
gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') }
|
14
|
+
gem.files = `git ls-files`.split("\n").reject { |name| name.include?('benchmarks') || name.include?('examples') || name.include?('bin') }
|
15
15
|
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
16
16
|
gem.license = 'MIT'
|
17
17
|
|
18
|
-
gem.add_runtime_dependency '
|
19
|
-
gem.add_runtime_dependency 'rom', '~>
|
20
|
-
gem.add_runtime_dependency 'rom-
|
21
|
-
gem.add_runtime_dependency 'rom-mapper', '~> 0.3.0'
|
18
|
+
gem.add_runtime_dependency 'rom', '~> 2.0'
|
19
|
+
gem.add_runtime_dependency 'rom-support', '~> 2.0'
|
20
|
+
gem.add_runtime_dependency 'rom-mapper', '~> 0.4'
|
22
21
|
|
23
|
-
gem.add_development_dependency 'rake', '~>
|
24
|
-
gem.add_development_dependency 'rspec', '~> 3.
|
22
|
+
gem.add_development_dependency 'rake', '~> 11.2'
|
23
|
+
gem.add_development_dependency 'rspec', '~> 3.5'
|
25
24
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
RSpec.describe 'Using changesets' do
|
2
|
+
include_context 'database'
|
3
|
+
include_context 'relations'
|
4
|
+
|
5
|
+
describe 'Create' do
|
6
|
+
subject(:repo) do
|
7
|
+
Class.new(ROM::Repository[:users]) {
|
8
|
+
relations :books, :posts
|
9
|
+
commands :create, update: :by_pk
|
10
|
+
}.new(rom)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can be passed to a command' do
|
14
|
+
changeset = repo.changeset(name: "Jane Doe")
|
15
|
+
command = repo.command(:create, repo.users)
|
16
|
+
result = command.(changeset)
|
17
|
+
|
18
|
+
expect(result.id).to_not be(nil)
|
19
|
+
expect(result.name).to eql("Jane Doe")
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can be passed to a command graph' do
|
23
|
+
changeset = repo.changeset(
|
24
|
+
name: "Jane Doe", posts: [{ title: "Just Do It", alien: "or sutin" }]
|
25
|
+
)
|
26
|
+
|
27
|
+
command = repo.command(:create, repo.aggregate(:posts))
|
28
|
+
result = command.(changeset)
|
29
|
+
|
30
|
+
expect(result.id).to_not be(nil)
|
31
|
+
expect(result.name).to eql("Jane Doe")
|
32
|
+
expect(result.posts.size).to be(1)
|
33
|
+
expect(result.posts[0].title).to eql("Just Do It")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'preprocesses data using changeset pipes' do
|
37
|
+
changeset = repo.changeset(:books, title: "rom-rb is awesome").map(:add_timestamps)
|
38
|
+
command = repo.command(:create, repo.books)
|
39
|
+
result = command.(changeset)
|
40
|
+
|
41
|
+
expect(result.id).to_not be(nil)
|
42
|
+
expect(result.title).to eql("rom-rb is awesome")
|
43
|
+
expect(result.created_at).to be_instance_of(Time)
|
44
|
+
expect(result.updated_at).to be_instance_of(Time)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'Update' do
|
49
|
+
subject(:repo) do
|
50
|
+
Class.new(ROM::Repository[:books]) {
|
51
|
+
commands :create, update: :by_pk
|
52
|
+
}.new(rom)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'can be passed to a command' do
|
56
|
+
book = repo.create(title: 'rom-rb is awesome')
|
57
|
+
|
58
|
+
changeset = repo
|
59
|
+
.changeset(book.id, title: 'rom-rb is awesome for real')
|
60
|
+
.map(:touch)
|
61
|
+
|
62
|
+
expect(changeset.diff).to eql(title: 'rom-rb is awesome for real')
|
63
|
+
|
64
|
+
result = repo.update(book.id, changeset)
|
65
|
+
|
66
|
+
expect(result.id).to be(book.id)
|
67
|
+
expect(result.title).to eql('rom-rb is awesome for real')
|
68
|
+
expect(result.updated_at).to be_instance_of(Time)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'skips update execution with no diff' do
|
72
|
+
book = repo.create(title: 'rom-rb is awesome')
|
73
|
+
|
74
|
+
changeset = repo
|
75
|
+
.changeset(book.id, title: 'rom-rb is awesome')
|
76
|
+
|
77
|
+
expect(changeset).to_not be_diff
|
78
|
+
|
79
|
+
result = repo.update(book.id, changeset)
|
80
|
+
|
81
|
+
expect(result.id).to be(book.id)
|
82
|
+
expect(result.title).to eql('rom-rb is awesome')
|
83
|
+
expect(result.updated_at).to be(nil)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|