rom 0.7.1 → 0.8.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/.rubocop.yml +5 -8
- data/CHANGELOG.md +28 -1
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +2 -2
- data/lib/rom.rb +1 -1
- data/lib/rom/command.rb +7 -5
- data/lib/rom/command_registry.rb +1 -1
- data/lib/rom/commands.rb +0 -2
- data/lib/rom/commands/abstract.rb +55 -25
- data/lib/rom/commands/composite.rb +13 -1
- data/lib/rom/commands/delete.rb +0 -8
- data/lib/rom/commands/graph.rb +102 -0
- data/lib/rom/commands/graph/class_interface.rb +69 -0
- data/lib/rom/commands/lazy.rb +87 -0
- data/lib/rom/constants.rb +22 -0
- data/lib/rom/env.rb +48 -18
- data/lib/rom/gateway.rb +132 -0
- data/lib/rom/global.rb +19 -19
- data/lib/rom/header.rb +42 -16
- data/lib/rom/header/attribute.rb +37 -15
- data/lib/rom/lint/gateway.rb +94 -0
- data/lib/rom/lint/spec.rb +15 -3
- data/lib/rom/lint/test.rb +45 -14
- data/lib/rom/mapper.rb +23 -10
- data/lib/rom/mapper/attribute_dsl.rb +157 -18
- data/lib/rom/memory.rb +1 -1
- data/lib/rom/memory/commands.rb +10 -8
- data/lib/rom/memory/dataset.rb +22 -2
- data/lib/rom/memory/{repository.rb → gateway.rb} +10 -10
- data/lib/rom/pipeline.rb +2 -1
- data/lib/rom/processor/transproc.rb +105 -14
- data/lib/rom/relation.rb +4 -4
- data/lib/rom/relation/class_interface.rb +19 -13
- data/lib/rom/relation/graph.rb +22 -0
- data/lib/rom/relation/lazy.rb +5 -3
- data/lib/rom/repository.rb +9 -118
- data/lib/rom/setup.rb +21 -14
- data/lib/rom/setup/finalize.rb +19 -19
- data/lib/rom/setup_dsl/relation.rb +10 -1
- data/lib/rom/support/deprecations.rb +21 -3
- data/lib/rom/support/enumerable_dataset.rb +1 -1
- data/lib/rom/version.rb +1 -1
- data/rom.gemspec +2 -4
- data/spec/integration/commands/delete_spec.rb +6 -0
- data/spec/integration/commands/graph_spec.rb +235 -0
- data/spec/integration/mappers/combine_spec.rb +14 -5
- data/spec/integration/mappers/definition_dsl_spec.rb +6 -1
- data/spec/integration/mappers/exclude_spec.rb +28 -0
- data/spec/integration/mappers/fold_spec.rb +16 -0
- data/spec/integration/mappers/group_spec.rb +0 -22
- data/spec/integration/mappers/prefix_separator_spec.rb +54 -0
- data/spec/integration/mappers/prefix_spec.rb +50 -0
- data/spec/integration/mappers/reusing_mappers_spec.rb +21 -0
- data/spec/integration/mappers/step_spec.rb +120 -0
- data/spec/integration/mappers/unfold_spec.rb +93 -0
- data/spec/integration/mappers/ungroup_spec.rb +127 -0
- data/spec/integration/mappers/unwrap_spec.rb +2 -2
- data/spec/integration/multi_repo_spec.rb +11 -11
- data/spec/integration/repositories/setting_logger_spec.rb +2 -2
- data/spec/integration/setup_spec.rb +11 -1
- data/spec/shared/command_behavior.rb +18 -0
- data/spec/shared/materializable.rb +4 -2
- data/spec/shared/users_and_tasks.rb +3 -3
- data/spec/test/memory_repository_lint_test.rb +4 -4
- data/spec/unit/rom/commands/graph_spec.rb +198 -0
- data/spec/unit/rom/commands/lazy_spec.rb +88 -0
- data/spec/unit/rom/commands_spec.rb +2 -2
- data/spec/unit/rom/env_spec.rb +26 -0
- data/spec/unit/rom/gateway_spec.rb +90 -0
- data/spec/unit/rom/global_spec.rb +4 -3
- data/spec/unit/rom/mapper/dsl_spec.rb +42 -1
- data/spec/unit/rom/mapper_spec.rb +4 -1
- data/spec/unit/rom/memory/commands/create_spec.rb +21 -0
- data/spec/unit/rom/memory/commands/delete_spec.rb +21 -0
- data/spec/unit/rom/memory/commands/update_spec.rb +21 -0
- data/spec/unit/rom/memory/relation_spec.rb +42 -10
- data/spec/unit/rom/memory/repository_spec.rb +3 -3
- data/spec/unit/rom/processor/transproc_spec.rb +75 -0
- data/spec/unit/rom/relation/lazy/combine_spec.rb +33 -4
- data/spec/unit/rom/relation/lazy_spec.rb +9 -1
- data/spec/unit/rom/repository_spec.rb +4 -63
- data/spec/unit/rom/setup_spec.rb +19 -5
- metadata +28 -38
- data/.ruby-version +0 -1
- data/lib/rom/lint/repository.rb +0 -94
data/lib/rom/setup.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
1
|
require 'rom/setup/finalize'
|
2
|
+
require 'rom/support/deprecations'
|
2
3
|
|
3
4
|
module ROM
|
4
5
|
# Exposes DSL for defining relations, mappers and commands
|
5
6
|
#
|
6
7
|
# @api public
|
7
8
|
class Setup
|
8
|
-
|
9
|
+
extend Deprecations
|
10
|
+
include Equalizer.new(:gateways, :env)
|
9
11
|
|
10
|
-
# @return [Hash] configured
|
12
|
+
# @return [Hash] configured gateways
|
11
13
|
#
|
12
14
|
# @api private
|
13
|
-
attr_reader :
|
15
|
+
attr_reader :gateways
|
16
|
+
|
17
|
+
# Deprecated accessor for gateways.
|
18
|
+
#
|
19
|
+
# @see gateways
|
20
|
+
deprecate :repositories, :gateways
|
14
21
|
|
15
22
|
# @return [Symbol] default (first) adapter
|
16
23
|
#
|
@@ -38,8 +45,8 @@ module ROM
|
|
38
45
|
attr_reader :env
|
39
46
|
|
40
47
|
# @api private
|
41
|
-
def initialize(
|
42
|
-
@
|
48
|
+
def initialize(gateways, default_adapter = nil)
|
49
|
+
@gateways = gateways
|
43
50
|
@default_adapter = default_adapter
|
44
51
|
@relation_classes = []
|
45
52
|
@mapper_classes = []
|
@@ -49,25 +56,25 @@ module ROM
|
|
49
56
|
|
50
57
|
# Finalize the setup
|
51
58
|
#
|
52
|
-
# @return [Env] frozen env with access to
|
59
|
+
# @return [Env] frozen env with access to gateways, relations,
|
53
60
|
# mappers and commands
|
54
61
|
#
|
55
62
|
# @api public
|
56
63
|
def finalize
|
57
64
|
raise EnvAlreadyFinalizedError if env
|
58
65
|
finalize = Finalize.new(
|
59
|
-
|
66
|
+
gateways, relation_classes, mapper_classes, command_classes
|
60
67
|
)
|
61
68
|
@env = finalize.run!
|
62
69
|
end
|
63
70
|
|
64
|
-
# Return
|
71
|
+
# Return gateway identified by name
|
65
72
|
#
|
66
|
-
# @return [
|
73
|
+
# @return [Gateway]
|
67
74
|
#
|
68
75
|
# @api private
|
69
76
|
def [](name)
|
70
|
-
|
77
|
+
gateways.fetch(name)
|
71
78
|
end
|
72
79
|
|
73
80
|
# Relation sub-classes are being registered with this method during setup
|
@@ -95,18 +102,18 @@ module ROM
|
|
95
102
|
#
|
96
103
|
# @api private
|
97
104
|
def respond_to_missing?(name, _include_context = false)
|
98
|
-
|
105
|
+
gateways.key?(name)
|
99
106
|
end
|
100
107
|
|
101
108
|
private
|
102
109
|
|
103
|
-
# Returns
|
110
|
+
# Returns gateway if method is a name of a registered gateway
|
104
111
|
#
|
105
|
-
# @return [
|
112
|
+
# @return [Gateway]
|
106
113
|
#
|
107
114
|
# @api private
|
108
115
|
def method_missing(name, *)
|
109
|
-
|
116
|
+
gateways.fetch(name) { super }
|
110
117
|
end
|
111
118
|
end
|
112
119
|
end
|
data/lib/rom/setup/finalize.rb
CHANGED
@@ -16,13 +16,13 @@ module ROM
|
|
16
16
|
#
|
17
17
|
# @private
|
18
18
|
class Finalize
|
19
|
-
attr_reader :
|
19
|
+
attr_reader :gateways, :repo_adapter, :datasets,
|
20
20
|
:relation_classes, :mapper_classes, :mappers, :command_classes
|
21
21
|
|
22
22
|
# @api private
|
23
|
-
def initialize(
|
24
|
-
@
|
25
|
-
@repo_adapter_map = ROM.
|
23
|
+
def initialize(gateways, relation_classes, mappers, command_classes)
|
24
|
+
@gateways = gateways
|
25
|
+
@repo_adapter_map = ROM.gateways
|
26
26
|
@relation_classes = relation_classes
|
27
27
|
@mapper_classes = mappers.select { |mapper| mapper.is_a?(Class) }
|
28
28
|
@mappers = (mappers - @mapper_classes).reduce(:merge) || {}
|
@@ -30,13 +30,13 @@ module ROM
|
|
30
30
|
initialize_datasets
|
31
31
|
end
|
32
32
|
|
33
|
-
# Return adapter identifier for a given
|
33
|
+
# Return adapter identifier for a given gateway object
|
34
34
|
#
|
35
35
|
# @return [Symbol]
|
36
36
|
#
|
37
37
|
# @api private
|
38
|
-
def adapter_for(
|
39
|
-
@repo_adapter_map.fetch(
|
38
|
+
def adapter_for(gateway)
|
39
|
+
@repo_adapter_map.fetch(gateways[gateway])
|
40
40
|
end
|
41
41
|
|
42
42
|
# Run the finalization process
|
@@ -53,21 +53,21 @@ module ROM
|
|
53
53
|
mappers = load_mappers
|
54
54
|
commands = load_commands(relations)
|
55
55
|
|
56
|
-
Env.new(
|
56
|
+
Env.new(gateways, relations, mappers, commands)
|
57
57
|
end
|
58
58
|
|
59
59
|
private
|
60
60
|
|
61
|
-
# Infer all datasets using configured
|
61
|
+
# Infer all datasets using configured gateways
|
62
62
|
#
|
63
|
-
# Not all
|
63
|
+
# Not all gateways can do that, by default an empty array is returned
|
64
64
|
#
|
65
|
-
# @return [Hash]
|
65
|
+
# @return [Hash] gateway name => array with datasets map
|
66
66
|
#
|
67
67
|
# @api private
|
68
68
|
def initialize_datasets
|
69
|
-
@datasets =
|
70
|
-
h[key] =
|
69
|
+
@datasets = gateways.each_with_object({}) do |(key, gateway), h|
|
70
|
+
h[key] = gateway.schema
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -77,7 +77,7 @@ module ROM
|
|
77
77
|
#
|
78
78
|
# @api private
|
79
79
|
def load_relations
|
80
|
-
relations = Relation.registry(
|
80
|
+
relations = Relation.registry(gateways, relation_classes)
|
81
81
|
RelationRegistry.new(relations)
|
82
82
|
end
|
83
83
|
|
@@ -106,7 +106,7 @@ module ROM
|
|
106
106
|
#
|
107
107
|
# @api private
|
108
108
|
def load_commands(relations)
|
109
|
-
registry = Command.registry(relations,
|
109
|
+
registry = Command.registry(relations, gateways, command_classes)
|
110
110
|
|
111
111
|
commands = registry.each_with_object({}) do |(name, rel_commands), h|
|
112
112
|
h[name] = CommandRegistry.new(rel_commands)
|
@@ -115,17 +115,17 @@ module ROM
|
|
115
115
|
Registry.new(commands)
|
116
116
|
end
|
117
117
|
|
118
|
-
# For every dataset infered from
|
118
|
+
# For every dataset infered from gateways we infer a relation
|
119
119
|
#
|
120
120
|
# Relations explicitly defined are being skipped
|
121
121
|
#
|
122
122
|
# @api private
|
123
123
|
def infer_schema_relations
|
124
|
-
datasets.each do |
|
124
|
+
datasets.each do |gateway, schema|
|
125
125
|
schema.each do |name|
|
126
126
|
next if relation_classes.any? { |klass| klass.dataset == name }
|
127
|
-
klass = Relation.build_class(name, adapter: adapter_for(
|
128
|
-
klass.
|
127
|
+
klass = Relation.build_class(name, adapter: adapter_for(gateway))
|
128
|
+
klass.gateway(gateway)
|
129
129
|
klass.dataset(name)
|
130
130
|
end
|
131
131
|
end
|
@@ -13,7 +13,16 @@ module ROM
|
|
13
13
|
adapter = options.fetch(:adapter)
|
14
14
|
|
15
15
|
ClassBuilder.new(name: class_name, parent: self[adapter]).call do |klass|
|
16
|
-
klass.
|
16
|
+
klass.gateway(options.fetch(:gateway) {
|
17
|
+
if options.key?(:repository)
|
18
|
+
ROM::Deprecations.announce "The :repository key is", <<-MSG
|
19
|
+
Please use `gateway: :#{options.fetch(:repository)}` instead.
|
20
|
+
MSG
|
21
|
+
options.fetch(:repository)
|
22
|
+
else
|
23
|
+
:default
|
24
|
+
end
|
25
|
+
})
|
17
26
|
klass.dataset(name)
|
18
27
|
end
|
19
28
|
end
|
@@ -4,15 +4,33 @@ module ROM
|
|
4
4
|
def deprecate(old_name, new_name, msg = nil)
|
5
5
|
class_eval do
|
6
6
|
define_method(old_name) do |*args, &block|
|
7
|
-
|
8
|
-
#{self.class}##{old_name} is deprecated and will be removed in 1.0.0.
|
7
|
+
ROM::Deprecations.announce "#{self.class}##{old_name} is", <<-MSG
|
9
8
|
Please use #{self.class}##{new_name} instead.
|
10
9
|
#{msg}
|
11
|
-
#{caller.detect { |l| !l.include?('lib/rom')}}
|
12
10
|
MSG
|
13
11
|
__send__(new_name, *args, &block)
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
15
|
+
|
16
|
+
def deprecate_class_method(old_name, new_name, msg = nil)
|
17
|
+
class_eval do
|
18
|
+
define_singleton_method(old_name) do |*args, &block|
|
19
|
+
ROM::Deprecations.announce"#{self}.#{old_name} is", <<-MSG
|
20
|
+
Please use #{self}.#{new_name} instead.
|
21
|
+
#{msg}
|
22
|
+
MSG
|
23
|
+
__send__(new_name, *args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.announce(name, msg)
|
29
|
+
warn <<-MSG.gsub(/^\s+/, '')
|
30
|
+
#{name} deprecated and will be removed in 1.0.0.
|
31
|
+
#{msg}
|
32
|
+
#{caller.detect { |l| !l.include?('lib/rom')}}
|
33
|
+
MSG
|
34
|
+
end
|
17
35
|
end
|
18
36
|
end
|
@@ -3,7 +3,7 @@ require 'rom/support/data_proxy'
|
|
3
3
|
module ROM
|
4
4
|
# A helper module that adds data-proxy behavior to an enumerable object
|
5
5
|
#
|
6
|
-
# This module is intended to be used by
|
6
|
+
# This module is intended to be used by gateways
|
7
7
|
#
|
8
8
|
# Class that includes this module can define `row_proc` class method which
|
9
9
|
# must return a proc-like object which will be used to process each element
|
data/lib/rom/version.rb
CHANGED
data/rom.gemspec
CHANGED
@@ -15,11 +15,9 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
16
16
|
gem.license = 'MIT'
|
17
17
|
|
18
|
-
gem.add_runtime_dependency 'transproc', '~> 0.2', '>= 0.2.
|
18
|
+
gem.add_runtime_dependency 'transproc', '~> 0.2', '>= 0.2.4'
|
19
19
|
gem.add_runtime_dependency 'equalizer', '~> 0.0', '>= 0.0.9'
|
20
20
|
|
21
21
|
gem.add_development_dependency 'rake', '~> 10.3'
|
22
|
-
gem.add_development_dependency 'rspec
|
23
|
-
gem.add_development_dependency 'rspec-mocks', '~> 3.2'
|
24
|
-
gem.add_development_dependency 'rspec-expectations', '~> 3.2'
|
22
|
+
gem.add_development_dependency 'rspec', '~> 3.3'
|
25
23
|
end
|
@@ -24,6 +24,8 @@ describe 'Commands / Delete' do
|
|
24
24
|
{ name: 'Jane', email: 'jane@doe.org' },
|
25
25
|
{ name: 'Joe', email: 'joe@doe.org' }
|
26
26
|
])
|
27
|
+
|
28
|
+
expect(rom.relation(:users)).to match_array([])
|
27
29
|
end
|
28
30
|
|
29
31
|
it 'deletes tuples matching restriction' do
|
@@ -34,6 +36,10 @@ describe 'Commands / Delete' do
|
|
34
36
|
result = users.try { users.delete.by_name('Joe').call }
|
35
37
|
|
36
38
|
expect(result).to match_array([{ name: 'Joe', email: 'joe@doe.org' }])
|
39
|
+
|
40
|
+
expect(rom.relation(:users)).to match_array([
|
41
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
42
|
+
])
|
37
43
|
end
|
38
44
|
|
39
45
|
it 'returns untouched relation if there are no tuples to delete' do
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Building up a command graph for nested input' do
|
4
|
+
let(:rom) { setup.finalize }
|
5
|
+
let(:setup) { ROM.setup(:memory) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
setup.relation :users
|
9
|
+
setup.relation :tasks
|
10
|
+
setup.relation :books
|
11
|
+
setup.relation :tags
|
12
|
+
|
13
|
+
setup.commands(:users) do
|
14
|
+
define(:create) do
|
15
|
+
input Transproc(:accept_keys, [:name])
|
16
|
+
result :one
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
setup.commands(:books) do
|
21
|
+
define(:create) do
|
22
|
+
input Transproc(:accept_keys, [:title, :user])
|
23
|
+
|
24
|
+
def execute(tuples, user)
|
25
|
+
super(tuples.map { |t| t.merge(user: user.fetch(:name)) })
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
setup.commands(:tags) do
|
31
|
+
define(:create) do
|
32
|
+
input Transproc(:accept_keys, [:name, :task])
|
33
|
+
|
34
|
+
def execute(tuples, task)
|
35
|
+
super(tuples.map { |t| t.merge(task: task.fetch(:title)) })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'creates a command graph for nested input :one result as root' do
|
42
|
+
setup.commands(:tasks) do
|
43
|
+
define(:create) do
|
44
|
+
input Transproc(:accept_keys, [:title, :user])
|
45
|
+
result :one
|
46
|
+
|
47
|
+
def execute(tuple, user)
|
48
|
+
super(tuple.merge(user: user.fetch(:name)))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
input = {
|
54
|
+
user: {
|
55
|
+
name: 'Jane',
|
56
|
+
task: {
|
57
|
+
title: 'Task One',
|
58
|
+
tags: [
|
59
|
+
{ name: 'red' }, { name: 'green' }, { name: 'blue' }
|
60
|
+
]
|
61
|
+
},
|
62
|
+
books: [
|
63
|
+
{ title: 'Book One' },
|
64
|
+
{ title: 'Book Two' }
|
65
|
+
]
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
options = [
|
70
|
+
{ user: :users }, [
|
71
|
+
:create, [
|
72
|
+
[{ task: :tasks }, [:create, [:tags, [:create]]]],
|
73
|
+
[:books, [:create]]
|
74
|
+
]
|
75
|
+
]
|
76
|
+
]
|
77
|
+
|
78
|
+
command = rom.command(options)
|
79
|
+
|
80
|
+
command.call(input)
|
81
|
+
|
82
|
+
expect(rom.relation(:users)).to match_array([
|
83
|
+
{ name: 'Jane' }
|
84
|
+
])
|
85
|
+
|
86
|
+
expect(rom.relation(:tasks)).to match_array([
|
87
|
+
{ title: 'Task One', user: 'Jane' }
|
88
|
+
])
|
89
|
+
|
90
|
+
expect(rom.relation(:books)).to match_array([
|
91
|
+
{ title: 'Book One', user: 'Jane' },
|
92
|
+
{ title: 'Book Two', user: 'Jane' }
|
93
|
+
])
|
94
|
+
|
95
|
+
expect(rom.relation(:tags)).to match_array([
|
96
|
+
{ name: 'red', task: 'Task One' },
|
97
|
+
{ name: 'green', task: 'Task One' },
|
98
|
+
{ name: 'blue', task: 'Task One' }
|
99
|
+
])
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'creates a command graph for nested input with :many results as root' do
|
103
|
+
setup.commands(:tasks) do
|
104
|
+
define(:create) do
|
105
|
+
input Transproc(:accept_keys, [:title, :user])
|
106
|
+
|
107
|
+
def execute(tuples, user)
|
108
|
+
super(tuples.map { |t| t.merge(user: user.fetch(:name)) })
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
input = {
|
114
|
+
user: {
|
115
|
+
name: 'Jane',
|
116
|
+
tasks: [
|
117
|
+
{
|
118
|
+
title: 'Task One',
|
119
|
+
tags: [{ name: 'red' }, { name: 'green' }]
|
120
|
+
},
|
121
|
+
{
|
122
|
+
title: 'Task Two',
|
123
|
+
tags: [{ name: 'blue' }]
|
124
|
+
}
|
125
|
+
]
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
options = [
|
130
|
+
{ user: :users }, [
|
131
|
+
:create, [
|
132
|
+
[:tasks, [:create, [:tags, [:create]]]],
|
133
|
+
]
|
134
|
+
]
|
135
|
+
]
|
136
|
+
|
137
|
+
command = rom.command(options)
|
138
|
+
|
139
|
+
command.call(input)
|
140
|
+
|
141
|
+
expect(rom.relation(:users)).to match_array([
|
142
|
+
{ name: 'Jane' }
|
143
|
+
])
|
144
|
+
|
145
|
+
expect(rom.relation(:tasks)).to match_array([
|
146
|
+
{ title: 'Task One', user: 'Jane' },
|
147
|
+
{ title: 'Task Two', user: 'Jane' }
|
148
|
+
])
|
149
|
+
|
150
|
+
expect(rom.relation(:tags)).to match_array([
|
151
|
+
{ name: 'red', task: 'Task One' },
|
152
|
+
{ name: 'green', task: 'Task One' },
|
153
|
+
{ name: 'blue', task: 'Task Two' }
|
154
|
+
])
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'works with auto-mapping' do
|
158
|
+
setup.mappers do
|
159
|
+
define(:users) do
|
160
|
+
register_as :entity
|
161
|
+
reject_keys true
|
162
|
+
|
163
|
+
model name: 'Test::User'
|
164
|
+
|
165
|
+
attribute :name
|
166
|
+
|
167
|
+
combine :tasks, on: { name: :user } do
|
168
|
+
model name: 'Test::Task'
|
169
|
+
attribute :title
|
170
|
+
|
171
|
+
combine :tags, on: { title: :task } do
|
172
|
+
model name: 'Test::Tag'
|
173
|
+
attribute :name
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
setup.commands(:tasks) do
|
180
|
+
define(:create) do
|
181
|
+
input Transproc(:accept_keys, [:title, :user])
|
182
|
+
|
183
|
+
def execute(tuples, user)
|
184
|
+
super(tuples.map { |t| t.merge(user: user.fetch(:name)) })
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
input = {
|
190
|
+
user: {
|
191
|
+
name: 'Jane',
|
192
|
+
tasks: [
|
193
|
+
{
|
194
|
+
title: 'Task One',
|
195
|
+
tags: [{ name: 'red' }, { name: 'green' }]
|
196
|
+
},
|
197
|
+
{
|
198
|
+
title: 'Task Two',
|
199
|
+
tags: [{ name: 'blue' }]
|
200
|
+
}
|
201
|
+
]
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
options = [
|
206
|
+
{ user: :users }, [
|
207
|
+
:create, [
|
208
|
+
[:tasks, [:create, [:tags, [:create]]]],
|
209
|
+
]
|
210
|
+
]
|
211
|
+
]
|
212
|
+
|
213
|
+
command = rom.command(options).as(:entity)
|
214
|
+
|
215
|
+
result = command.call(input).one
|
216
|
+
|
217
|
+
expect(result).to be_instance_of(Test::User)
|
218
|
+
expect(result.tasks.first).to be_instance_of(Test::Task)
|
219
|
+
expect(result.tasks.first.tags.first).to be_instance_of(Test::Tag)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'raises a proper error when the input has invalid structure' do
|
223
|
+
input = { user: { name: 'Jane' } }
|
224
|
+
|
225
|
+
options = [
|
226
|
+
{ user: :users }, [:create, [{ book: :books }, [:create]]]
|
227
|
+
]
|
228
|
+
|
229
|
+
command = rom.command(options)
|
230
|
+
|
231
|
+
expect {
|
232
|
+
command.call(input)
|
233
|
+
}.to raise_error(ROM::CommandFailure, /book/)
|
234
|
+
end
|
235
|
+
end
|