rom 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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