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
@@ -0,0 +1,69 @@
1
+ module ROM
2
+ module Commands
3
+ class Graph
4
+ # Class methods for command Graph
5
+ #
6
+ # @api private
7
+ module ClassInterface
8
+ # Build a command graph recursively
9
+ #
10
+ # This is used by `Env#command` when array with options is passed in
11
+ #
12
+ # @param [Registry] registry The command registry from env
13
+ # @param [Array] options The options array
14
+ # @param [Array] path The path for input evaluator proc
15
+ #
16
+ # @return [Graph]
17
+ #
18
+ # @api private
19
+ def build(registry, options, path = EMPTY_ARRAY)
20
+ options.reduce { |spec, other| build_command(registry, spec, other, path) }
21
+ end
22
+
23
+ # @api private
24
+ def build_command(registry, spec, other, path)
25
+ name, nodes = other
26
+
27
+ key, relation =
28
+ if spec.is_a?(Hash)
29
+ spec.to_a.first
30
+ else
31
+ [spec, spec]
32
+ end
33
+
34
+ command = registry[relation][name]
35
+
36
+ tuple_path = Array[*path] << key
37
+
38
+ input_proc = -> *args do
39
+ input, index = args
40
+
41
+ begin
42
+ if index
43
+ tuple_path[0..tuple_path.size-2]
44
+ .reduce(input) { |a,e| a.fetch(e) }
45
+ .at(index)[tuple_path.last]
46
+ else
47
+ tuple_path.reduce(input) { |a,e| a.fetch(e) }
48
+ end
49
+ rescue KeyError => err
50
+ raise CommandFailure.new(command, err)
51
+ end
52
+ end
53
+
54
+ command = command.with(input_proc)
55
+
56
+ if nodes
57
+ if nodes.all? { |node| node.is_a?(Array) }
58
+ command.combine(*nodes.map { |node| build(registry, node, tuple_path) })
59
+ else
60
+ command.combine(build(registry, nodes, tuple_path))
61
+ end
62
+ else
63
+ command
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,87 @@
1
+ require 'rom/commands/composite'
2
+ require 'rom/commands/graph'
3
+
4
+ module ROM
5
+ module Commands
6
+ # Lazy command wraps another command and evaluates its input when called
7
+ #
8
+ # @api private
9
+ class Lazy
10
+ # @attr_reader [Command] command The wrapped command
11
+ attr_reader :command
12
+
13
+ # @attr_reader [Proc] evaluator The proc that will evaluate the input
14
+ attr_reader :evaluator
15
+
16
+ # @api private
17
+ def initialize(command, evaluator)
18
+ @command = command
19
+ @evaluator = evaluator
20
+ end
21
+
22
+ # Evaluate command's input using the input proc and pass to command
23
+ #
24
+ # @return [Array,Hash]
25
+ #
26
+ # @api public
27
+ 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
+ command.call(evaluator[first, index], item)
35
+ end.reduce(:concat)
36
+ else
37
+ command.call(evaluator[first], *args[1..size-1])
38
+ end
39
+ end
40
+
41
+ # Compose a lazy command with another one
42
+ #
43
+ # @see Commands::Abstract#>>
44
+ #
45
+ # @return [Composite]
46
+ #
47
+ # @api public
48
+ def >>(other)
49
+ Composite.new(self, other)
50
+ end
51
+
52
+ # @see Abstract#combine
53
+ #
54
+ # @api public
55
+ def combine(*others)
56
+ Graph.new(self, others)
57
+ end
58
+
59
+ # @api private
60
+ def lazy?
61
+ true
62
+ end
63
+
64
+ # @api private
65
+ def respond_to_missing?(name, include_private = false)
66
+ super || command.respond_to?(name)
67
+ end
68
+
69
+ private
70
+
71
+ # @api private
72
+ def method_missing(name, *args, &block)
73
+ if command.respond_to?(name)
74
+ response = command.public_send(name, *args, &block)
75
+
76
+ if response.instance_of?(command.class)
77
+ self.class.new(response, evaluator)
78
+ else
79
+ response
80
+ end
81
+ else
82
+ super
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
data/lib/rom/constants.rb CHANGED
@@ -4,17 +4,39 @@ module ROM
4
4
 
5
5
  AdapterLoadError = Class.new(StandardError)
6
6
 
7
+ class AdapterNotPresentError < StandardError
8
+ def initialize(adapter, component)
9
+ super(
10
+ "Failed to find #{component} class for #{adapter} adapter. " \
11
+ "Make sure ROM setup was started and the adapter identifier is correct."
12
+ )
13
+ end
14
+ end
15
+
7
16
  EnvAlreadyFinalizedError = Class.new(StandardError)
8
17
  RelationAlreadyDefinedError = Class.new(StandardError)
9
18
  NoRelationError = Class.new(StandardError)
10
19
  CommandError = Class.new(StandardError)
11
20
  TupleCountMismatchError = Class.new(CommandError)
12
21
  MapperMissingError = Class.new(StandardError)
22
+ MapperMisconfiguredError = Class.new(StandardError)
13
23
  UnknownPluginError = Class.new(StandardError)
24
+ UnsupportedRelationError = Class.new(StandardError)
14
25
 
15
26
  InvalidOptionValueError = Class.new(StandardError)
16
27
  InvalidOptionKeyError = Class.new(StandardError)
17
28
 
29
+ class CommandFailure < StandardError
30
+ attr_reader :command
31
+ attr_reader :original_error
32
+
33
+ def initialize(command, err)
34
+ super("command: #{command.inspect}; original message: #{err.message}")
35
+ @command = command
36
+ @original_error = original_error
37
+ end
38
+ end
39
+
18
40
  EMPTY_ARRAY = [].freeze
19
41
  EMPTY_HASH = {}.freeze
20
42
  end
data/lib/rom/env.rb CHANGED
@@ -1,27 +1,30 @@
1
1
  require 'rom/relation/loaded'
2
+ require 'rom/commands/graph'
2
3
  require 'rom/support/deprecations'
3
4
 
4
5
  module ROM
5
- # Exposes defined repositories, relations and mappers
6
+ # Exposes defined gateways, relations and mappers
6
7
  #
7
8
  # @api public
8
9
  class Env
9
10
  extend Deprecations
10
- include Equalizer.new(:repositories, :relations, :mappers, :commands)
11
+ include Equalizer.new(:gateways, :relations, :mappers, :commands)
11
12
 
12
- # @return [Hash] configured repositories
13
+ # @return [Hash] configured gateways
13
14
  #
14
15
  # @api public
15
- attr_reader :repositories
16
+ attr_reader :gateways
17
+
18
+ deprecate :repositories, :gateways
16
19
 
17
20
  # @return [RelationRegistry] relation registry
18
21
  #
19
- # @api public
22
+ # @api private
20
23
  attr_reader :relations
21
24
 
22
25
  # @return [Registry] command registry
23
26
  #
24
- # @api public
27
+ # @api private
25
28
  attr_reader :commands
26
29
 
27
30
  # @return [Registry] mapper registry
@@ -30,31 +33,34 @@ module ROM
30
33
  attr_reader :mappers
31
34
 
32
35
  # @api private
33
- def initialize(repositories, relations, mappers, commands)
34
- @repositories = repositories
36
+ def initialize(gateways, relations, mappers, commands)
37
+ @gateways = gateways
35
38
  @relations = relations
36
39
  @mappers = mappers
37
40
  @commands = commands
38
41
  freeze
39
42
  end
40
43
 
41
- # Load relation by name
44
+ # Get lazy relation identified by its name
42
45
  #
43
46
  # @example
44
- #
45
47
  # rom.relation(:users)
46
- # rom.relation(:users) { |r| r.by_name('Jane') }
48
+ # rom.relation(:users).by_name('Jane')
49
+ #
50
+ # # block syntax allows accessing lower-level query DSLs (usage is discouraged though)
51
+ # rom.relation { |r| r.restrict(name: 'Jane') }
47
52
  #
48
53
  # # with mapping
49
54
  # rom.relation(:users).map_with(:presenter)
50
55
  #
51
- # rom.relation(:users) { |r| r.page(1) }.map_with(:presenter, :json_serializer)
56
+ # # using multiple mappers
57
+ # rom.relation(:users).page(1).map_with(:presenter, :json_serializer)
52
58
  #
53
59
  # @param [Symbol] name of the relation to load
54
60
  #
55
61
  # @yield [Relation]
56
62
  #
57
- # @return [Relation::Loaded]
63
+ # @return [Relation::Lazy]
58
64
  #
59
65
  # @api public
60
66
  def relation(name, &block)
@@ -80,15 +86,39 @@ module ROM
80
86
  # # plain command returning tuples
81
87
  # rom.command(:users).create
82
88
  #
83
- # # allow auto-mapping using registered mappers
89
+ # # allows auto-mapping using registered mappers
84
90
  # rom.command(:users).as(:entity)
85
91
  #
92
+ # # allows building up a command graph for nested input
93
+ # command = rom.command([:users, [:create, [:tasks, [:create]]]])
94
+ #
95
+ # command.call(users: [{ name: 'Jane', tasks: [{ title: 'One' }] }])
96
+ #
97
+ # @param [Array,Symbol] options Either graph options or registered command name
98
+ #
99
+ # @return [Command, Command::Graph]
100
+ #
86
101
  # @api public
87
- def command(name)
88
- if mappers.key?(name)
89
- commands[name].with(mappers: mappers[name])
102
+ def command(options)
103
+ case options
104
+ when Symbol
105
+ name = options
106
+ if mappers.key?(name)
107
+ commands[name].with(mappers: mappers[name])
108
+ else
109
+ commands[name]
110
+ end
111
+ when Array
112
+ graph = Commands::Graph.build(commands, options)
113
+ name = graph.name
114
+
115
+ if mappers.key?(name)
116
+ graph.with(mappers: mappers[graph.name])
117
+ else
118
+ graph
119
+ end
90
120
  else
91
- commands[name]
121
+ raise ArgumentError, "#{self.class}#command accepts a symbol or an array"
92
122
  end
93
123
  end
94
124
  end
@@ -0,0 +1,132 @@
1
+ module ROM
2
+ # Abstract gateway class
3
+ #
4
+ # @api public
5
+ class Gateway
6
+ # Return connection object
7
+ #
8
+ # @return [Object] type varies depending on the gateway
9
+ #
10
+ # @api public
11
+ attr_reader :connection
12
+
13
+ # Setup a gateway
14
+ #
15
+ # @overload setup(type, *args)
16
+ # Sets up a single-gateway given a gateway type.
17
+ # For custom gateways, create an instance and pass it directly.
18
+ #
19
+ # @param [Symbol] type
20
+ # @param [Array] *args
21
+ #
22
+ # @overload setup(gateway)
23
+ # @param [Gateway] gateway
24
+ #
25
+ # @return [Gateway] a specific gateway subclass
26
+ #
27
+ # @example
28
+ # module SuperDB
29
+ # class Gateway < ROM::Gateway
30
+ # def initialize(options)
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ # ROM.register_adapter(:super_db, SuperDB)
36
+ #
37
+ # Gateway.setup(:super_db, some: 'options')
38
+ # # SuperDB::Gateway.new(some: 'options') is called
39
+ #
40
+ # # or alternatively
41
+ # super_db = Gateway.setup(SuperDB::Gateway.new(some: 'options'))
42
+ # Gateway.setup(super_db)
43
+ #
44
+ # @api public
45
+ def self.setup(gateway_or_scheme, *args)
46
+ case gateway_or_scheme
47
+ when String
48
+ raise ArgumentError, <<-STRING.gsub(/^ {10}/, '')
49
+ URIs without an explicit scheme are not supported anymore.
50
+ See https://github.com/rom-rb/rom/blob/master/CHANGELOG.md
51
+ STRING
52
+ when Symbol
53
+ class_from_symbol(gateway_or_scheme).new(*args)
54
+ else
55
+ if args.empty?
56
+ gateway_or_scheme
57
+ else
58
+ raise ArgumentError, "Can't accept arguments when passing an instance"
59
+ end
60
+ end
61
+ end
62
+
63
+ # Get gateway subclass for a specific adapter
64
+ #
65
+ # @param [Symbol] type adapter identifier
66
+ #
67
+ # @return [Class]
68
+ #
69
+ # @api private
70
+ def self.class_from_symbol(type)
71
+ adapter = ROM.adapters.fetch(type) do
72
+ begin
73
+ require "rom/#{type}"
74
+ rescue LoadError
75
+ raise AdapterLoadError, "Failed to load adapter rom/#{type}"
76
+ end
77
+
78
+ ROM.adapters.fetch(type)
79
+ end
80
+
81
+ if adapter.const_defined?(:Gateway)
82
+ adapter.const_get(:Gateway)
83
+ else
84
+ adapter.const_get(:Repository)
85
+ end
86
+ end
87
+
88
+ # A generic interface for setting up a logger
89
+ #
90
+ # @api public
91
+ def use_logger(*)
92
+ # noop
93
+ end
94
+
95
+ # A generic interface for returning default logger
96
+ #
97
+ # @api public
98
+ def logger
99
+ # noop
100
+ end
101
+
102
+ # Extension hook for adding gateway-specific behavior to a command class
103
+ #
104
+ # @param [Class] klass command class
105
+ # @param [Object] _dataset dataset that will be used with this command class
106
+ #
107
+ # @return [Class]
108
+ #
109
+ # @api public
110
+ def extend_command_class(klass, _dataset)
111
+ klass
112
+ end
113
+
114
+ # Schema inference hook
115
+ #
116
+ # Every gateway that supports schema inference should implement this method
117
+ #
118
+ # @return [Array] array with datasets and their names
119
+ #
120
+ # @api private
121
+ def schema
122
+ []
123
+ end
124
+
125
+ # Disconnect is optional and it's a no-op by default
126
+ #
127
+ # @api public
128
+ def disconnect
129
+ # noop
130
+ end
131
+ end
132
+ end