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/global.rb CHANGED
@@ -16,7 +16,7 @@ module ROM
16
16
  super
17
17
 
18
18
  rom.instance_variable_set('@adapters', {})
19
- rom.instance_variable_set('@repositories', {})
19
+ rom.instance_variable_set('@gateways', {})
20
20
  rom.instance_variable_set('@plugin_registry', PluginRegistry.new)
21
21
  end
22
22
 
@@ -27,12 +27,12 @@ module ROM
27
27
  # @api private
28
28
  attr_reader :adapters
29
29
 
30
- # An internal repo => identifier map used by the setup
30
+ # An internal gateway => identifier map used by the setup
31
31
  #
32
32
  # @return [Hash]
33
33
  #
34
34
  # @api private
35
- attr_reader :repositories
35
+ attr_reader :gateways
36
36
 
37
37
  # An internal identifier => plugin map used by the setup
38
38
  #
@@ -60,29 +60,29 @@ module ROM
60
60
  # Starts the setup process for relations, mappers and commands.
61
61
  #
62
62
  # @overload setup(type, *args)
63
- # Sets up a single-repository environment given a repository type provided
64
- # under the ROM umbrella. For custom repositories, create an instance and
63
+ # Sets up a single-gateway environment given a gateway type provided
64
+ # under the ROM umbrella. For custom gateways, create an instance and
65
65
  # pass it directly.
66
66
  #
67
67
  # @param [Symbol] type
68
68
  # @param [Array] *args
69
69
  #
70
- # @overload setup(repository)
71
- # @param [Repository] repository
70
+ # @overload setup(gateway)
71
+ # @param [Gateway] gateway
72
72
  #
73
- # @overload setup(repositories)
74
- # Sets up multiple repositories.
73
+ # @overload setup(gateways)
74
+ # Sets up multiple gateways.
75
75
  #
76
- # @param [Hash{Symbol=>Symbol,Array}] repositories
76
+ # @param [Hash{Symbol=>Symbol,Array}] gateways
77
77
  #
78
78
  # @return [Setup] boot object
79
79
  #
80
80
  # @example
81
- # # Use the in-memory adapter shipped with ROM as the default repository.
81
+ # # Use the in-memory adapter shipped with ROM as the default gateway.
82
82
  # env = ROM.setup(:memory, 'memory://test')
83
- # # Use `rom-sql` with an in-memory sqlite database as default repository.
83
+ # # Use `rom-sql` with an in-memory sqlite database as default gateway.
84
84
  # ROM.setup(:sql, 'sqlite::memory')
85
- # # Registers a `default` and a `warehouse` repository.
85
+ # # Registers a `default` and a `warehouse` gateway.
86
86
  # env = ROM.setup(
87
87
  # default: [:sql, 'sqlite::memory'],
88
88
  # warehouse: [:sql, 'postgres://localhost/warehouse']
@@ -114,7 +114,7 @@ module ROM
114
114
  # @api public
115
115
  def setup(*args, &block)
116
116
  config = setup_config(*args)
117
- @boot = Setup.new(setup_repositories(config), adapters.keys.first)
117
+ @boot = Setup.new(setup_gateways(config), adapters.keys.first)
118
118
 
119
119
  if block
120
120
  @boot.instance_exec(&block)
@@ -245,16 +245,16 @@ module ROM
245
245
  args.first.is_a?(Hash) ? args.first : { default: args }
246
246
  end
247
247
 
248
- # Build repositories using the setup interface
248
+ # Build gateways using the setup interface
249
249
  #
250
250
  # @api private
251
- def setup_repositories(config)
251
+ def setup_gateways(config)
252
252
  config.each_with_object({}) do |(name, spec), hash|
253
253
  identifier, *args = Array(spec)
254
- repository = Repository.setup(identifier, *(args.flatten))
255
- hash[name] = repository
254
+ gateway = Gateway.setup(identifier, *(args.flatten))
255
+ hash[name] = gateway
256
256
 
257
- repositories[repository] = identifier unless identifier.is_a?(Repository)
257
+ gateways[gateway] = identifier unless identifier.is_a?(Gateway)
258
258
  end
259
259
  end
260
260
  end
data/lib/rom/header.rb CHANGED
@@ -35,6 +35,11 @@ module ROM
35
35
  # @api private
36
36
  attr_reader :tuple_keys
37
37
 
38
+ # @return [Array] all attribute names that are popping from a tuple
39
+ #
40
+ # @api private
41
+ attr_reader :pop_keys
42
+
38
43
  # Coerce array with attribute definitions into a header object
39
44
  #
40
45
  # @param [Array<Array>] input attribute name/option pairs
@@ -62,6 +67,7 @@ module ROM
62
67
  @attributes = attributes
63
68
  initialize_mapping
64
69
  initialize_tuple_keys
70
+ initialize_pop_keys
65
71
  end
66
72
 
67
73
  # Iterate over attributes
@@ -98,13 +104,31 @@ module ROM
98
104
  attributes.fetch(name)
99
105
  end
100
106
 
101
- # Return all Group attributes
107
+ # Return all Combined attributes
108
+ #
109
+ # @return [Array<Combined>]
110
+ #
111
+ # @api private
112
+ def combined
113
+ by_type(Combined)
114
+ end
115
+
116
+ # Returns all attributes that require preprocessing
117
+ #
118
+ # @return [Array<Group,Fold>]
119
+ #
120
+ # @api private
121
+ def preprocessed
122
+ by_type(Group, Fold)
123
+ end
124
+
125
+ # Returns all attributes that require postprocessing
102
126
  #
103
- # @return [Array<Group>]
127
+ # @return [Array<Ungroup,Unfold>]
104
128
  #
105
129
  # @api private
106
- def groups
107
- by_type(Group)
130
+ def postprocessed
131
+ by_type(Ungroup, Unfold)
108
132
  end
109
133
 
110
134
  # Return all Wrap attributes
@@ -116,12 +140,16 @@ module ROM
116
140
  by_type(Wrap)
117
141
  end
118
142
 
143
+ # Return all non-primitive attributes that don't require mapping
144
+ #
145
+ # @return [Array<Group,Fold,Ungroup,Unfold,Wrap,Unwrap>]
146
+ #
119
147
  # @api private
120
- def combined
121
- by_type(Combined)
148
+ def non_primitives
149
+ preprocessed + wraps
122
150
  end
123
151
 
124
- # Return all primitive attributes (no Group and Wrap)
152
+ # Return all primitive attributes that require mapping
125
153
  #
126
154
  # @return [Array<Attribute>]
127
155
  #
@@ -130,15 +158,6 @@ module ROM
130
158
  to_a - non_primitives
131
159
  end
132
160
 
133
- # Return all non-primitive attributes (only Group and Wrap types)
134
- #
135
- # @return [Array<Group,Wrap>]
136
- #
137
- # @api private
138
- def non_primitives
139
- groups + wraps
140
- end
141
-
142
161
  private
143
162
 
144
163
  # Find all attribute matching specific attribute class (not kind)
@@ -163,5 +182,12 @@ module ROM
163
182
  def initialize_tuple_keys
164
183
  @tuple_keys = mapping.keys + non_primitives.flat_map(&:tuple_keys)
165
184
  end
185
+
186
+ # Set all tuple keys from all attributes popping from Unwrap and Ungroup
187
+ #
188
+ # @api private
189
+ def initialize_pop_keys
190
+ @pop_keys = mapping.values + non_primitives.flat_map(&:tuple_keys)
191
+ end
166
192
  end
167
193
  end
@@ -39,21 +39,8 @@ module ROM
39
39
  #
40
40
  # @api private
41
41
  def self.[](meta)
42
- type = meta[:type]
43
-
44
- if meta[:combine]
45
- Combined
46
- elsif meta[:unwrap]
47
- Unwrap
48
- elsif type.equal?(:hash)
49
- meta[:wrap] ? Wrap : Hash
50
- elsif meta[:fold]
51
- Group
52
- elsif type.equal?(:array)
53
- meta[:group] ? Group : Array
54
- else
55
- self
56
- end
42
+ key = (meta.keys & TYPE_MAP.keys).first
43
+ TYPE_MAP.fetch(key || meta[:type], self)
57
44
  end
58
45
 
59
46
  # Coerce an array with attribute meta-data into an attribute object
@@ -135,6 +122,10 @@ module ROM
135
122
  def tuple_keys
136
123
  header.tuple_keys
137
124
  end
125
+
126
+ def pop_keys
127
+ header.pop_keys
128
+ end
138
129
  end
139
130
 
140
131
  # Array is an embedded attribute type
@@ -158,5 +149,36 @@ module ROM
158
149
  # Group is a special type of Array attribute that requires grouping
159
150
  # transformation
160
151
  Group = Class.new(Array)
152
+
153
+ # Ungroup is a special type of Array attribute that requires ungrouping
154
+ # transformation
155
+ Ungroup = Class.new(Array)
156
+
157
+ # Fold is a special type of Array attribute that requires folding
158
+ # transformation
159
+ Fold = Class.new(Array)
160
+
161
+ # Unfold is a special type of Array attribute that requires unfolding
162
+ # transformation
163
+ Unfold = Class.new(Array)
164
+
165
+ # Exclude is a special type of Attribute to be removed
166
+ Exclude = Class.new(Attribute)
167
+
168
+ # TYPE_MAP is a (hash) map of ROM::Header identifiers to ROM::Header types
169
+ #
170
+ # @private
171
+ TYPE_MAP = {
172
+ combine: Combined,
173
+ wrap: Wrap,
174
+ unwrap: Unwrap,
175
+ group: Group,
176
+ ungroup: Ungroup,
177
+ fold: Fold,
178
+ unfold: Unfold,
179
+ hash: Hash,
180
+ array: Array,
181
+ exclude: Exclude
182
+ }
161
183
  end
162
184
  end
@@ -0,0 +1,94 @@
1
+ require 'rom/lint/linter'
2
+
3
+ module ROM
4
+ module Lint
5
+ # Ensures that a [ROM::Gateway] extension provides datasets through the
6
+ # expected methods
7
+ #
8
+ # @api public
9
+ class Gateway < ROM::Lint::Linter
10
+ # The gateway identifier e.g. +:memory+
11
+ #
12
+ # @api public
13
+ attr_reader :identifier
14
+
15
+ # The gateway class
16
+ #
17
+ # @api public
18
+ attr_reader :gateway
19
+
20
+ # The optional URI
21
+ #
22
+ # @api public
23
+ attr_reader :uri
24
+
25
+ # Gateway instance used in lint tests
26
+ #
27
+ # @api private
28
+ attr_reader :gateway_instance
29
+
30
+ # Create a gateway linter
31
+ #
32
+ # @param [Symbol] identifier
33
+ # @param [Class] gateway
34
+ # @param [String] uri optional
35
+ def initialize(identifier, gateway, uri = nil)
36
+ @identifier = identifier
37
+ @gateway = gateway
38
+ @uri = uri
39
+ @gateway_instance = setup_gateway_instance
40
+ end
41
+
42
+ # Lint: Ensure that +gateway+ setups up its instance
43
+ #
44
+ # @api public
45
+ def lint_gateway_setup
46
+ return if gateway_instance.instance_of? gateway
47
+
48
+ complain <<-STRING
49
+ #{gateway}.setup must return a gateway instance but
50
+ returned #{gateway_instance.inspect}
51
+ STRING
52
+ end
53
+
54
+ # Lint: Ensure that +gateway_instance+ responds to +[]+
55
+ #
56
+ # @api public
57
+ def lint_dataset_reader
58
+ return if gateway_instance.respond_to? :[]
59
+
60
+ complain "#{gateway_instance} must respond to []"
61
+ end
62
+
63
+ # Lint: Ensure that +gateway_instance+ responds to +dataset?+
64
+ #
65
+ # @api public
66
+ def lint_dataset_predicate
67
+ return if gateway_instance.respond_to? :dataset?
68
+
69
+ complain "#{gateway_instance} must respond to dataset?"
70
+ end
71
+
72
+ private
73
+
74
+ # Setup gateway instance
75
+ #
76
+ # @api private
77
+ def setup_gateway_instance
78
+ if uri
79
+ ROM::Gateway.setup(identifier, uri)
80
+ else
81
+ ROM::Gateway.setup(identifier)
82
+ end
83
+ end
84
+
85
+ # Run Gateway#disconnect
86
+ #
87
+ # @api private
88
+ def after_lint
89
+ super
90
+ gateway_instance.disconnect
91
+ end
92
+ end
93
+ end
94
+ end
data/lib/rom/lint/spec.rb CHANGED
@@ -1,10 +1,22 @@
1
- require 'rom/lint/repository'
1
+ require 'rom/lint/gateway'
2
2
  require 'rom/lint/enumerable_dataset'
3
3
 
4
4
  RSpec.shared_examples "a rom repository" do
5
- ROM::Lint::Repository.each_lint do |name, linter|
5
+ before(:all) do
6
+ ROM::Deprecations.announce "[Adapter]::Repository is", <<-MSG
7
+ Please use [Adapter]::Gateway instead.
8
+ MSG
9
+ end
10
+
11
+ let(:gateway) { repository }
12
+
13
+ include_examples "a rom gateway"
14
+ end
15
+
16
+ RSpec.shared_examples "a rom gateway" do
17
+ ROM::Lint::Gateway.each_lint do |name, linter|
6
18
  it name do
7
- result = linter.new(identifier, repository, uri).lint(name)
19
+ result = linter.new(identifier, gateway, uri).lint(name)
8
20
  expect(result).to be_truthy
9
21
  end
10
22
  end
data/lib/rom/lint/test.rb CHANGED
@@ -1,4 +1,4 @@
1
- require 'rom/lint/repository'
1
+ require 'rom/lint/gateway'
2
2
  require 'rom/lint/enumerable_dataset'
3
3
 
4
4
  module ROM
@@ -21,53 +21,84 @@ module ROM
21
21
  end
22
22
  end
23
23
 
24
- # This is a simple lint-test for repository class to ensure the
24
+ # This is a simple lint-test for gateway class to ensure the
25
25
  # basic interfaces are in place
26
26
  #
27
27
  # @example
28
28
  #
29
- # class MyRepoTest < Minitest::Test
30
- # include ROM::Lint::TestRepository
29
+ # class MyGatewayTest < Minitest::Test
30
+ # include ROM::Lint::TestGateway
31
31
  #
32
32
  # def setup
33
- # @repository = MyRepository
33
+ # @gateway = MyGateway
34
34
  # @uri = "super_db://something"
35
35
  # end
36
36
  # end
37
37
  #
38
38
  # @api public
39
- module TestRepository
39
+ module TestGateway
40
40
  extend ROM::Lint::Test
41
41
 
42
- # Returns the repository identifier e.g. +:memory+
42
+ # Returns the gateway identifier e.g. +:memory+
43
43
  #
44
44
  # @api public
45
45
  attr_reader :identifier
46
46
 
47
- # Returns the repository class
47
+ # Returns the gateway class
48
48
  #
49
49
  # @api public
50
- attr_reader :repository
50
+ attr_reader :gateway
51
51
 
52
- # Returns repostory's URI e.g. "super_db://something"
52
+ # Returns gateway's URI e.g. "super_db://something"
53
53
  #
54
54
  # @api public
55
55
  attr_reader :uri
56
56
 
57
- ROM::Lint::Repository.each_lint do |name, linter|
57
+ ROM::Lint::Gateway.each_lint do |name, linter|
58
58
  define_test_method name do
59
- assert linter.new(identifier, repository, uri).lint(name)
59
+ assert linter.new(identifier, gateway, uri).lint(name)
60
60
  end
61
61
  end
62
62
  end
63
63
 
64
- # This is a simple lint-test for an repository dataset class to ensure the
64
+ # This is a transitional lint-test loader for repository class
65
+ # to ensure that the basic interfaces are in place
66
+ #
67
+ # class MyRepositoryTest < Minitest::Test
68
+ # include ROM::Lint::TestRepository
69
+ #
70
+ # def setup
71
+ # @repository = MyRepository
72
+ # @uri = "super_db://something"
73
+ # end
74
+ # end
75
+ #
76
+ # @see [ROM::Lint::TestGateway]
77
+ #
78
+ # @api public
79
+ module TestRepository
80
+ extend TestGateway
81
+
82
+ # Returns the reposiotry class
83
+ #
84
+ # @api public
85
+ attr_reader :repository
86
+
87
+ def setup
88
+ ROM::Deprecations.announce "[Adapter]::Repository is", <<-MSG
89
+ Please use [Adapter]::Gateway instead.
90
+ MSG
91
+ @gateway = repository
92
+ end
93
+ end
94
+
95
+ # This is a simple lint-test for a gateway dataset class to ensure the
65
96
  # basic behavior is correct
66
97
  #
67
98
  # @example
68
99
  #
69
100
  # class MyDatasetLintTest < Minitest::Test
70
- # include ROM::Repository::Lint::TestEnumerableDataset
101
+ # include ROM::Lint::TestEnumerableDataset
71
102
  #
72
103
  # def setup
73
104
  # @data = [{ name: 'Jane', age: 24 }, { name: 'Joe', age: 25 }]