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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +5 -8
  4. data/CHANGELOG.md +28 -1
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +2 -2
  7. data/lib/rom.rb +1 -1
  8. data/lib/rom/command.rb +7 -5
  9. data/lib/rom/command_registry.rb +1 -1
  10. data/lib/rom/commands.rb +0 -2
  11. data/lib/rom/commands/abstract.rb +55 -25
  12. data/lib/rom/commands/composite.rb +13 -1
  13. data/lib/rom/commands/delete.rb +0 -8
  14. data/lib/rom/commands/graph.rb +102 -0
  15. data/lib/rom/commands/graph/class_interface.rb +69 -0
  16. data/lib/rom/commands/lazy.rb +87 -0
  17. data/lib/rom/constants.rb +22 -0
  18. data/lib/rom/env.rb +48 -18
  19. data/lib/rom/gateway.rb +132 -0
  20. data/lib/rom/global.rb +19 -19
  21. data/lib/rom/header.rb +42 -16
  22. data/lib/rom/header/attribute.rb +37 -15
  23. data/lib/rom/lint/gateway.rb +94 -0
  24. data/lib/rom/lint/spec.rb +15 -3
  25. data/lib/rom/lint/test.rb +45 -14
  26. data/lib/rom/mapper.rb +23 -10
  27. data/lib/rom/mapper/attribute_dsl.rb +157 -18
  28. data/lib/rom/memory.rb +1 -1
  29. data/lib/rom/memory/commands.rb +10 -8
  30. data/lib/rom/memory/dataset.rb +22 -2
  31. data/lib/rom/memory/{repository.rb → gateway.rb} +10 -10
  32. data/lib/rom/pipeline.rb +2 -1
  33. data/lib/rom/processor/transproc.rb +105 -14
  34. data/lib/rom/relation.rb +4 -4
  35. data/lib/rom/relation/class_interface.rb +19 -13
  36. data/lib/rom/relation/graph.rb +22 -0
  37. data/lib/rom/relation/lazy.rb +5 -3
  38. data/lib/rom/repository.rb +9 -118
  39. data/lib/rom/setup.rb +21 -14
  40. data/lib/rom/setup/finalize.rb +19 -19
  41. data/lib/rom/setup_dsl/relation.rb +10 -1
  42. data/lib/rom/support/deprecations.rb +21 -3
  43. data/lib/rom/support/enumerable_dataset.rb +1 -1
  44. data/lib/rom/version.rb +1 -1
  45. data/rom.gemspec +2 -4
  46. data/spec/integration/commands/delete_spec.rb +6 -0
  47. data/spec/integration/commands/graph_spec.rb +235 -0
  48. data/spec/integration/mappers/combine_spec.rb +14 -5
  49. data/spec/integration/mappers/definition_dsl_spec.rb +6 -1
  50. data/spec/integration/mappers/exclude_spec.rb +28 -0
  51. data/spec/integration/mappers/fold_spec.rb +16 -0
  52. data/spec/integration/mappers/group_spec.rb +0 -22
  53. data/spec/integration/mappers/prefix_separator_spec.rb +54 -0
  54. data/spec/integration/mappers/prefix_spec.rb +50 -0
  55. data/spec/integration/mappers/reusing_mappers_spec.rb +21 -0
  56. data/spec/integration/mappers/step_spec.rb +120 -0
  57. data/spec/integration/mappers/unfold_spec.rb +93 -0
  58. data/spec/integration/mappers/ungroup_spec.rb +127 -0
  59. data/spec/integration/mappers/unwrap_spec.rb +2 -2
  60. data/spec/integration/multi_repo_spec.rb +11 -11
  61. data/spec/integration/repositories/setting_logger_spec.rb +2 -2
  62. data/spec/integration/setup_spec.rb +11 -1
  63. data/spec/shared/command_behavior.rb +18 -0
  64. data/spec/shared/materializable.rb +4 -2
  65. data/spec/shared/users_and_tasks.rb +3 -3
  66. data/spec/test/memory_repository_lint_test.rb +4 -4
  67. data/spec/unit/rom/commands/graph_spec.rb +198 -0
  68. data/spec/unit/rom/commands/lazy_spec.rb +88 -0
  69. data/spec/unit/rom/commands_spec.rb +2 -2
  70. data/spec/unit/rom/env_spec.rb +26 -0
  71. data/spec/unit/rom/gateway_spec.rb +90 -0
  72. data/spec/unit/rom/global_spec.rb +4 -3
  73. data/spec/unit/rom/mapper/dsl_spec.rb +42 -1
  74. data/spec/unit/rom/mapper_spec.rb +4 -1
  75. data/spec/unit/rom/memory/commands/create_spec.rb +21 -0
  76. data/spec/unit/rom/memory/commands/delete_spec.rb +21 -0
  77. data/spec/unit/rom/memory/commands/update_spec.rb +21 -0
  78. data/spec/unit/rom/memory/relation_spec.rb +42 -10
  79. data/spec/unit/rom/memory/repository_spec.rb +3 -3
  80. data/spec/unit/rom/processor/transproc_spec.rb +75 -0
  81. data/spec/unit/rom/relation/lazy/combine_spec.rb +33 -4
  82. data/spec/unit/rom/relation/lazy_spec.rb +9 -1
  83. data/spec/unit/rom/repository_spec.rb +4 -63
  84. data/spec/unit/rom/setup_spec.rb +19 -5
  85. metadata +28 -38
  86. data/.ruby-version +0 -1
  87. 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
- include Equalizer.new(:repositories, :env)
9
+ extend Deprecations
10
+ include Equalizer.new(:gateways, :env)
9
11
 
10
- # @return [Hash] configured repositories
12
+ # @return [Hash] configured gateways
11
13
  #
12
14
  # @api private
13
- attr_reader :repositories
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(repositories, default_adapter = nil)
42
- @repositories = repositories
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 repositories, relations,
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
- repositories, relation_classes, mapper_classes, command_classes
66
+ gateways, relation_classes, mapper_classes, command_classes
60
67
  )
61
68
  @env = finalize.run!
62
69
  end
63
70
 
64
- # Return repository identified by name
71
+ # Return gateway identified by name
65
72
  #
66
- # @return [Repository]
73
+ # @return [Gateway]
67
74
  #
68
75
  # @api private
69
76
  def [](name)
70
- repositories.fetch(name)
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
- repositories.key?(name)
105
+ gateways.key?(name)
99
106
  end
100
107
 
101
108
  private
102
109
 
103
- # Returns repository if method is a name of a registered repository
110
+ # Returns gateway if method is a name of a registered gateway
104
111
  #
105
- # @return [Repository]
112
+ # @return [Gateway]
106
113
  #
107
114
  # @api private
108
115
  def method_missing(name, *)
109
- repositories.fetch(name) { super }
116
+ gateways.fetch(name) { super }
110
117
  end
111
118
  end
112
119
  end
@@ -16,13 +16,13 @@ module ROM
16
16
  #
17
17
  # @private
18
18
  class Finalize
19
- attr_reader :repositories, :repo_adapter, :datasets,
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(repositories, relation_classes, mappers, command_classes)
24
- @repositories = repositories
25
- @repo_adapter_map = ROM.repositories
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 repository object
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(repository)
39
- @repo_adapter_map.fetch(repositories[repository])
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(repositories, relations, mappers, commands)
56
+ Env.new(gateways, relations, mappers, commands)
57
57
  end
58
58
 
59
59
  private
60
60
 
61
- # Infer all datasets using configured repositories
61
+ # Infer all datasets using configured gateways
62
62
  #
63
- # Not all repositories can do that, by default an empty array is returned
63
+ # Not all gateways can do that, by default an empty array is returned
64
64
  #
65
- # @return [Hash] repository name => array with datasets map
65
+ # @return [Hash] gateway name => array with datasets map
66
66
  #
67
67
  # @api private
68
68
  def initialize_datasets
69
- @datasets = repositories.each_with_object({}) do |(key, repository), h|
70
- h[key] = repository.schema
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(repositories, relation_classes)
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, repositories, command_classes)
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 repositories we infer a relation
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 |repository, schema|
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(repository))
128
- klass.repository(repository)
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.repository(options.fetch(:repository) { :default })
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
- warn <<-MSG.gsub(/^\s+/, '')
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 repositories
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
@@ -1,3 +1,3 @@
1
1
  module ROM
2
- VERSION = '0.7.1'.freeze
2
+ VERSION = '0.8.0'.freeze
3
3
  end
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.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-core', '~> 3.2'
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