rom 0.9.1 → 1.0.0.beta1

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +30 -12
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +7 -3
  6. data/README.md +24 -11
  7. data/lib/rom.rb +9 -26
  8. data/lib/rom/command.rb +113 -75
  9. data/lib/rom/commands/class_interface.rb +115 -0
  10. data/lib/rom/commands/graph.rb +17 -23
  11. data/lib/rom/commands/graph/builder.rb +176 -0
  12. data/lib/rom/commands/graph/class_interface.rb +8 -2
  13. data/lib/rom/commands/graph/input_evaluator.rb +13 -9
  14. data/lib/rom/commands/lazy.rb +23 -17
  15. data/lib/rom/commands/lazy/create.rb +23 -0
  16. data/lib/rom/commands/lazy/delete.rb +27 -0
  17. data/lib/rom/commands/lazy/update.rb +34 -0
  18. data/lib/rom/commands/result.rb +14 -0
  19. data/lib/rom/commands/update.rb +0 -4
  20. data/lib/rom/configuration.rb +86 -0
  21. data/lib/rom/{setup_dsl/setup.rb → configuration_dsl.rb} +9 -7
  22. data/lib/rom/configuration_dsl/command.rb +43 -0
  23. data/lib/rom/{setup_dsl → configuration_dsl}/command_dsl.rb +5 -4
  24. data/lib/rom/configuration_dsl/mapper.rb +37 -0
  25. data/lib/rom/{setup_dsl → configuration_dsl}/mapper_dsl.rb +11 -5
  26. data/lib/rom/configuration_dsl/relation.rb +26 -0
  27. data/lib/rom/configuration_plugin.rb +17 -0
  28. data/lib/rom/constants.rb +5 -12
  29. data/lib/rom/container.rb +11 -8
  30. data/lib/rom/create_container.rb +61 -0
  31. data/lib/rom/environment.rb +27 -241
  32. data/lib/rom/gateway.rb +18 -2
  33. data/lib/rom/global.rb +24 -0
  34. data/lib/rom/global/plugin_dsl.rb +2 -0
  35. data/lib/rom/lint/spec.rb +0 -12
  36. data/lib/rom/lint/test.rb +0 -31
  37. data/lib/rom/memory/commands.rb +2 -2
  38. data/lib/rom/memory/gateway.rb +2 -0
  39. data/lib/rom/pipeline.rb +1 -1
  40. data/lib/rom/plugin_base.rb +1 -1
  41. data/lib/rom/plugin_registry.rb +12 -10
  42. data/lib/rom/plugins/configuration/configuration_dsl.rb +16 -0
  43. data/lib/rom/plugins/relation/key_inference.rb +31 -0
  44. data/lib/rom/plugins/relation/view.rb +90 -0
  45. data/lib/rom/plugins/relation/view/dsl.rb +32 -0
  46. data/lib/rom/relation.rb +1 -11
  47. data/lib/rom/relation/class_interface.rb +37 -50
  48. data/lib/rom/setup.rb +13 -104
  49. data/lib/rom/setup/auto_registration.rb +55 -0
  50. data/lib/rom/setup/finalize.rb +113 -127
  51. data/lib/rom/setup/finalize/commands.rb +67 -0
  52. data/lib/rom/setup/finalize/mappers.rb +36 -0
  53. data/lib/rom/setup/finalize/relations.rb +53 -0
  54. data/lib/rom/support/configurable.rb +21 -7
  55. data/lib/rom/version.rb +1 -1
  56. data/rakelib/mutant.rake +4 -1
  57. data/rom.gemspec +3 -4
  58. data/spec/fixtures/app/commands/create_user.rb +2 -0
  59. data/spec/fixtures/app/mappers/user_list.rb +2 -0
  60. data/spec/fixtures/app/relations/users.rb +2 -0
  61. data/spec/fixtures/lib/persistence/commands/create_user.rb +6 -0
  62. data/spec/fixtures/lib/persistence/mappers/user_list.rb +6 -0
  63. data/spec/fixtures/lib/persistence/relations/users.rb +6 -0
  64. data/spec/{unit/rom → integration}/command_registry_spec.rb +8 -9
  65. data/spec/integration/commands/create_spec.rb +17 -13
  66. data/spec/integration/commands/delete_spec.rb +12 -11
  67. data/spec/integration/commands/error_handling_spec.rb +5 -4
  68. data/spec/integration/commands/graph_builder_spec.rb +213 -0
  69. data/spec/integration/commands/graph_spec.rb +112 -49
  70. data/spec/integration/commands/update_spec.rb +14 -11
  71. data/spec/integration/commands_spec.rb +60 -0
  72. data/spec/integration/mappers/combine_spec.rb +7 -6
  73. data/spec/integration/mappers/deep_embedded_spec.rb +5 -6
  74. data/spec/integration/mappers/definition_dsl_spec.rb +19 -18
  75. data/spec/integration/mappers/embedded_spec.rb +11 -12
  76. data/spec/integration/mappers/exclude_spec.rb +5 -6
  77. data/spec/integration/mappers/fold_spec.rb +8 -7
  78. data/spec/integration/mappers/group_spec.rb +16 -15
  79. data/spec/integration/mappers/overwrite_attributes_value_spec.rb +5 -5
  80. data/spec/integration/mappers/prefix_separator_spec.rb +5 -7
  81. data/spec/integration/mappers/prefix_spec.rb +5 -7
  82. data/spec/integration/mappers/prefixing_attributes_spec.rb +7 -7
  83. data/spec/integration/mappers/registering_custom_mappers_spec.rb +4 -5
  84. data/spec/integration/mappers/renaming_attributes_spec.rb +18 -18
  85. data/spec/integration/mappers/step_spec.rb +11 -12
  86. data/spec/integration/mappers/symbolizing_attributes_spec.rb +11 -8
  87. data/spec/integration/mappers/unfold_spec.rb +9 -10
  88. data/spec/integration/mappers/ungroup_spec.rb +10 -11
  89. data/spec/integration/mappers/unwrap_spec.rb +10 -15
  90. data/spec/integration/mappers/wrap_spec.rb +16 -15
  91. data/spec/{unit/rom → integration}/memory/commands/create_spec.rb +7 -5
  92. data/spec/{unit/rom → integration}/memory/commands/delete_spec.rb +7 -5
  93. data/spec/{unit/rom → integration}/memory/commands/update_spec.rb +7 -5
  94. data/spec/integration/multi_env_spec.rb +16 -124
  95. data/spec/integration/multi_repo_spec.rb +9 -9
  96. data/spec/integration/relations/default_dataset_spec.rb +15 -0
  97. data/spec/integration/relations/inheritance_spec.rb +5 -7
  98. data/spec/integration/relations/reading_spec.rb +32 -65
  99. data/spec/integration/relations/registry_dsl_spec.rb +5 -4
  100. data/spec/integration/repositories/extending_relations_spec.rb +6 -7
  101. data/spec/integration/repositories/setting_logger_spec.rb +5 -7
  102. data/spec/integration/setup_spec.rb +49 -61
  103. data/spec/shared/command_graph.rb +50 -0
  104. data/spec/shared/container.rb +9 -0
  105. data/spec/shared/gateway_only.rb +6 -0
  106. data/spec/shared/no_container.rb +16 -0
  107. data/spec/shared/one_behavior.rb +4 -4
  108. data/spec/shared/users_and_tasks.rb +5 -17
  109. data/spec/spec_helper.rb +5 -3
  110. data/spec/test/memory_repository_lint_test.rb +1 -1
  111. data/spec/unit/rom/auto_registration_spec.rb +54 -0
  112. data/spec/unit/rom/commands/graph_spec.rb +18 -44
  113. data/spec/unit/rom/commands/lazy_spec.rb +246 -35
  114. data/spec/unit/rom/commands/result_spec.rb +56 -0
  115. data/spec/unit/rom/commands_spec.rb +9 -73
  116. data/spec/unit/rom/configurable_spec.rb +49 -0
  117. data/spec/unit/rom/configuration_spec.rb +61 -0
  118. data/spec/unit/rom/container_spec.rb +39 -33
  119. data/spec/unit/rom/create_container_spec.rb +151 -0
  120. data/spec/unit/rom/environment_spec.rb +123 -0
  121. data/spec/unit/rom/gateway_spec.rb +58 -2
  122. data/spec/unit/rom/global_spec.rb +10 -7
  123. data/spec/unit/rom/plugin_spec.rb +44 -25
  124. data/spec/unit/rom/plugins/relation/key_inference_spec.rb +27 -0
  125. data/spec/unit/rom/plugins/relation/view_spec.rb +47 -0
  126. data/spec/unit/rom/relation/composite_spec.rb +20 -20
  127. data/spec/unit/rom/relation/curried_spec.rb +10 -11
  128. data/spec/unit/rom/relation/graph_spec.rb +27 -27
  129. data/spec/unit/rom/relation/lazy/combine_spec.rb +26 -20
  130. data/spec/unit/rom/relation/lazy_spec.rb +38 -38
  131. data/spec/unit/rom/relation/loaded_spec.rb +2 -3
  132. data/spec/unit/rom/relation_spec.rb +39 -2
  133. metadata +58 -66
  134. data/lib/rom/commands/abstract.rb +0 -184
  135. data/lib/rom/environment_plugin.rb +0 -17
  136. data/lib/rom/environment_plugins/auto_registration.rb +0 -38
  137. data/lib/rom/repository.rb +0 -16
  138. data/lib/rom/setup_dsl/command.rb +0 -36
  139. data/lib/rom/setup_dsl/mapper.rb +0 -32
  140. data/lib/rom/setup_dsl/relation.rb +0 -30
  141. data/spec/integration/inline_setup_spec.rb +0 -65
  142. data/spec/unit/rom/repository_spec.rb +0 -12
  143. data/spec/unit/rom/setup_spec.rb +0 -253
@@ -0,0 +1,115 @@
1
+ module ROM
2
+ # Base command class with factory class-level interface and setup-related logic
3
+ #
4
+ # @private
5
+ class Command
6
+ module ClassInterface
7
+ # Return adapter specific sub-class based on the adapter identifier
8
+ #
9
+ # This is a syntax sugar to make things consistent
10
+ #
11
+ # @example
12
+ # ROM::Commands::Create[:memory]
13
+ # # => ROM::Memory::Commands::Create
14
+ #
15
+ # @param [Symbol] adapter identifier
16
+ #
17
+ # @return [Class]
18
+ #
19
+ # @api public
20
+ def [](adapter)
21
+ adapter_namespace(adapter).const_get(Inflector.demodulize(name))
22
+ end
23
+
24
+ # Return namespaces that contains command subclasses of a specific adapter
25
+ #
26
+ # @param [Symbol] adapter identifier
27
+ #
28
+ # @return [Module]
29
+ #
30
+ # @api private
31
+ def adapter_namespace(adapter)
32
+ ROM.adapters.fetch(adapter).const_get(:Commands)
33
+ rescue KeyError
34
+ raise AdapterNotPresentError.new(adapter, :relation)
35
+ end
36
+
37
+ # Build a command class for a specific relation with options
38
+ #
39
+ # @example
40
+ # class CreateUser < ROM::Commands::Create[:memory]
41
+ # end
42
+ #
43
+ # command = CreateUser.build(rom.relations[:users])
44
+ #
45
+ # @param [Relation] relation
46
+ # @param [Hash] options
47
+ #
48
+ # @return [Command]
49
+ #
50
+ # @api public
51
+ def build(relation, options = EMPTY_HASH)
52
+ new(relation, self.options.merge(options))
53
+ end
54
+
55
+ # Use a configured plugin in this relation
56
+ #
57
+ # @example
58
+ # class CreateUser < ROM::Commands::Create[:memory]
59
+ # use :pagintion
60
+ #
61
+ # per_page 30
62
+ # end
63
+ #
64
+ # @param [Symbol] plugin
65
+ # @param [Hash] options
66
+ # @option options [Symbol] :adapter (:default) first adapter to check for plugin
67
+ #
68
+ # @api public
69
+ def use(plugin, _options = EMPTY_HASH)
70
+ ROM.plugin_registry.commands.fetch(plugin, adapter).apply_to(self)
71
+ end
72
+
73
+ # @api private
74
+ def relation_methods_mod(relation_class)
75
+ mod = Module.new
76
+
77
+ relation_class.view_methods.each do |meth|
78
+ mod.module_eval <<-RUBY
79
+ def #{meth}(*args)
80
+ response = relation.public_send(:#{meth}, *args)
81
+
82
+ if response.is_a?(relation.class)
83
+ new(response)
84
+ else
85
+ response
86
+ end
87
+ end
88
+ RUBY
89
+ end
90
+
91
+ mod
92
+ end
93
+
94
+ # Return default name of the command class based on its name
95
+ #
96
+ # During setup phase this is used by defalut as `register_as` option
97
+ #
98
+ # @return [Symbol]
99
+ #
100
+ # @api private
101
+ def default_name
102
+ Inflector.underscore(Inflector.demodulize(name)).to_sym
103
+ end
104
+
105
+ # Return default options based on class macros
106
+ #
107
+ # @return [Hash]
108
+ #
109
+ # @api private
110
+ def options
111
+ { input: input, validator: validator, result: result }
112
+ end
113
+ end
114
+ end
115
+ end
@@ -8,6 +8,8 @@ module ROM
8
8
  #
9
9
  # @api private
10
10
  class Graph
11
+ include Dry::Equalizer(:root, :nodes)
12
+
11
13
  extend ClassInterface
12
14
 
13
15
  include Options
@@ -50,35 +52,27 @@ module ROM
50
52
  #
51
53
  # @api public
52
54
  def call(*args)
53
- begin
54
- left = root.call(*args)
55
-
56
- right = nodes.map do |node|
57
- begin
58
- response =
59
- if node.lazy?
60
- node.call(args.first, left)
61
- else
62
- node.call(left)
63
- end
64
- rescue => err
65
- raise CommandFailure.new(node, err)
66
- end
55
+ left = root.call(*args)
67
56
 
68
- if node.one? && !node.graph?
69
- [response]
57
+ right = nodes.map { |node|
58
+ response =
59
+ if node.lazy?
60
+ node.call(args.first, left)
70
61
  else
71
- response
62
+ node.call(left)
72
63
  end
73
- end
74
64
 
75
- if one?
76
- [[left], right]
65
+ if node.one? && !node.graph?
66
+ [response]
77
67
  else
78
- [left, right]
68
+ response
79
69
  end
80
- rescue => err
81
- raise CommandFailure.new(root, err)
70
+ }
71
+
72
+ if one?
73
+ [[left], right]
74
+ else
75
+ [left, right]
82
76
  end
83
77
  end
84
78
 
@@ -0,0 +1,176 @@
1
+ module ROM
2
+ module Commands
3
+ class Graph
4
+ # Command graph builder DSL
5
+ #
6
+ # @api public
7
+ class Builder
8
+ # @api private
9
+ UnspecifiedRelationError = Class.new(StandardError)
10
+ DoubleRestrictionError = Class.new(StandardError)
11
+
12
+ # @api private
13
+ class Node
14
+ # @api private
15
+ Restriction = Struct.new(:relation, :proc)
16
+
17
+ # @api private
18
+ Command = Struct.new(:name, :relation, :key, :proc)
19
+
20
+ # @api private
21
+ def initialize
22
+ @nodes = []
23
+ end
24
+
25
+ # @api public
26
+ def to_ast
27
+ []
28
+ end
29
+
30
+ # Any missing method called on this is treated as a ROM command
31
+ #
32
+ # @api private
33
+ def method_missing(*args, &block)
34
+ command(*args, &block)
35
+ end
36
+
37
+ # @api public
38
+ def command(name, relation = nil, key = nil, proc = nil, &block)
39
+ if relation.is_a?(Hash)
40
+ key, relation = relation.to_a.first
41
+ end
42
+
43
+ raise UnspecifiedRelationError if relation.nil?
44
+
45
+ if relation.is_a?(RestrictionNode)
46
+ RestrictionNode.new(relation.restriction).command(name, from: key, &block)
47
+ else
48
+ key ||= relation
49
+
50
+ command = Command.new(name, relation, key, proc)
51
+ node = CommandNode.new(command)
52
+ block.call(node) if block
53
+ node
54
+ end
55
+ end
56
+
57
+ # @api public
58
+ def restrict(name, &block)
59
+ RestrictionNode.new(Restriction.new(name, block), self)
60
+ end
61
+ end
62
+
63
+ # @api private
64
+ class CommandNode < Node
65
+ # @api private
66
+ def initialize(command = nil)
67
+ @command = command
68
+ @nodes = []
69
+ end
70
+
71
+ # Tiny bit of synctactic sugar
72
+ #
73
+ # @api public
74
+ def each(&block)
75
+ block.call(self)
76
+ end
77
+
78
+ # Return command ast from this union node
79
+ #
80
+ # @return [Array]
81
+ #
82
+ # @api private
83
+ def to_ast
84
+ if @command.proc
85
+ command = [@command.name => @command.proc]
86
+ else
87
+ command = [@command.name]
88
+ end
89
+
90
+ key_relation_map = { @command.key => @command.relation }
91
+
92
+ command << @nodes.map(&:to_ast) unless @nodes.empty?
93
+
94
+ [key_relation_map, command]
95
+ end
96
+
97
+ # @api public
98
+ def command(*args, &block)
99
+ node = super
100
+ @nodes << node
101
+ node
102
+ end
103
+ end
104
+
105
+ # @api private
106
+ class RestrictionNode < Node
107
+ attr_reader :restriction
108
+
109
+ def initialize(restriction, parent_node = nil)
110
+ super()
111
+ @restriction = restriction
112
+ @parent_node = parent_node
113
+ end
114
+
115
+ # @api public
116
+ def command(name, options = {}, &block)
117
+ relation = @restriction.relation
118
+ key = options[:from] || relation
119
+ proc = @restriction.proc
120
+
121
+ if @parent_node
122
+ @parent_node.command(name, relation, key, proc, &block)
123
+ else
124
+ super(name, relation, key, proc, &block)
125
+ end
126
+ end
127
+
128
+ # @api private
129
+ def restrict(*args, &block)
130
+ raise DoubleRestrictionError
131
+ end
132
+ end
133
+
134
+ # @api private
135
+ class RootNode < Node
136
+ def to_ast
137
+ if @nodes.size > 0
138
+ @nodes.first.to_ast
139
+ else
140
+ []
141
+ end
142
+ end
143
+
144
+ def command(*args, &block)
145
+ node = super
146
+ @nodes << node
147
+ node
148
+ end
149
+ end
150
+
151
+ # @api private
152
+ class BuilderNode < RootNode
153
+ def initialize(container)
154
+ super()
155
+ @container = container
156
+ end
157
+
158
+ def command(*args, &block)
159
+ super
160
+ @container.command(to_ast)
161
+ end
162
+ end
163
+
164
+ # @api public
165
+ def initialize(container)
166
+ @container = container
167
+ end
168
+
169
+ # @api private
170
+ def method_missing(*args, &block)
171
+ BuilderNode.new(@container).send(*args, &block)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -24,7 +24,7 @@ module ROM
24
24
 
25
25
  # @api private
26
26
  def build_command(registry, spec, other, path)
27
- name, nodes = other
27
+ cmd_opts, nodes = other
28
28
 
29
29
  key, relation =
30
30
  if spec.is_a?(Hash)
@@ -33,12 +33,18 @@ module ROM
33
33
  [spec, spec]
34
34
  end
35
35
 
36
+ name, opts =
37
+ if cmd_opts.is_a?(Hash)
38
+ cmd_opts.to_a.first
39
+ else
40
+ [cmd_opts]
41
+ end
36
42
 
37
43
  command = registry[relation][name]
38
44
  tuple_path = Array[*path] << key
39
45
  input_proc = InputEvaluator.build(tuple_path, nodes)
40
46
 
41
- command = command.with(input_proc)
47
+ command = command.with(input_proc, opts)
42
48
 
43
49
  if nodes
44
50
  if nodes.all? { |node| node.is_a?(Array) }
@@ -2,6 +2,8 @@ module ROM
2
2
  module Commands
3
3
  class Graph
4
4
  class InputEvaluator
5
+ include Dry::Equalizer(:tuple_path, :excluded_keys)
6
+
5
7
  attr_reader :tuple_path
6
8
 
7
9
  attr_reader :excluded_keys
@@ -35,21 +37,23 @@ module ROM
35
37
  def call(*args)
36
38
  input, index = args
37
39
 
38
- begin
39
- value =
40
+ value =
41
+ begin
40
42
  if index
41
43
  tuple_path[0..tuple_path.size-2]
42
- .reduce(input) { |a,e| a.fetch(e) }
44
+ .reduce(input) { |a, e| a.fetch(e) }
43
45
  .at(index)[tuple_path.last]
44
46
  else
45
- tuple_path.reduce(input) { |a,e| a.fetch(e) }
47
+ tuple_path.reduce(input) { |a, e| a.fetch(e) }
46
48
  end
47
-
48
- if excluded_keys
49
- value.is_a?(Array) ? value.map(&exclude_proc) : exclude_proc[value]
50
- else
51
- value
49
+ rescue KeyError => e
50
+ raise KeyMissing, e.message
52
51
  end
52
+
53
+ if excluded_keys
54
+ value.is_a?(Array) ? value.map(&exclude_proc) : exclude_proc[value]
55
+ else
56
+ value
53
57
  end
54
58
  end
55
59
  end
@@ -7,16 +7,32 @@ module ROM
7
7
  #
8
8
  # @api private
9
9
  class Lazy
10
+ include Dry::Equalizer(:command, :evaluator)
11
+
10
12
  # @attr_reader [Command] command The wrapped command
11
13
  attr_reader :command
12
14
 
13
15
  # @attr_reader [Proc] evaluator The proc that will evaluate the input
14
16
  attr_reader :evaluator
15
17
 
18
+ attr_reader :command_proc
19
+
16
20
  # @api private
17
- def initialize(command, evaluator)
21
+ def self.[](command)
22
+ case command
23
+ when Commands::Create then Lazy::Create
24
+ when Commands::Update then Lazy::Update
25
+ when Commands::Delete then Lazy::Delete
26
+ else
27
+ self
28
+ end
29
+ end
30
+
31
+ # @api private
32
+ def initialize(command, evaluator, command_proc = nil)
18
33
  @command = command
19
34
  @evaluator = evaluator
35
+ @command_proc = command_proc || proc { |*| command }
20
36
  end
21
37
 
22
38
  # Evaluate command's input using the input proc and pass to command
@@ -25,21 +41,7 @@ module ROM
25
41
  #
26
42
  # @api public
27
43
  def call(*args)
28
- first = args.first
29
- last = args.last
30
- size = args.size
31
-
32
- if size > 1 && last.is_a?(Array)
33
- last.map.with_index do |item, index|
34
- input = evaluator.call(first, index)
35
- command.call(input, item)
36
- end.reduce(:concat)
37
- else
38
- input = evaluator.call(first)
39
- command.call(input, *args[1..size-1])
40
- end
41
- rescue => err
42
- raise CommandFailure.new(command, err)
44
+ raise NotImplementedError
43
45
  end
44
46
 
45
47
  # Compose a lazy command with another one
@@ -78,7 +80,7 @@ module ROM
78
80
  response = command.public_send(name, *args, &block)
79
81
 
80
82
  if response.instance_of?(command.class)
81
- self.class.new(response, evaluator)
83
+ self.class.new(response, evaluator, command_proc)
82
84
  else
83
85
  response
84
86
  end
@@ -89,3 +91,7 @@ module ROM
89
91
  end
90
92
  end
91
93
  end
94
+
95
+ require 'rom/commands/lazy/create'
96
+ require 'rom/commands/lazy/update'
97
+ require 'rom/commands/lazy/delete'