rom 0.9.1 → 1.0.0.beta1

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