rom 1.0.0 → 2.0.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/.travis.yml +5 -3
  4. data/CHANGELOG.md +38 -0
  5. data/Gemfile +2 -14
  6. data/README.md +11 -17
  7. data/lib/rom.rb +2 -0
  8. data/lib/rom/association_set.rb +26 -0
  9. data/lib/rom/command.rb +50 -45
  10. data/lib/rom/command_registry.rb +26 -3
  11. data/lib/rom/commands/class_interface.rb +52 -19
  12. data/lib/rom/commands/composite.rb +5 -0
  13. data/lib/rom/commands/delete.rb +1 -5
  14. data/lib/rom/commands/graph.rb +11 -0
  15. data/lib/rom/commands/lazy.rb +2 -0
  16. data/lib/rom/commands/update.rb +1 -5
  17. data/lib/rom/configuration.rb +2 -0
  18. data/lib/rom/container.rb +3 -3
  19. data/lib/rom/global.rb +1 -23
  20. data/lib/rom/memory/commands.rb +2 -0
  21. data/lib/rom/memory/relation.rb +3 -0
  22. data/lib/rom/memory/storage.rb +4 -7
  23. data/lib/rom/memory/types.rb +9 -0
  24. data/lib/rom/pipeline.rb +26 -12
  25. data/lib/rom/plugin_registry.rb +2 -2
  26. data/lib/rom/plugins/command/schema.rb +26 -0
  27. data/lib/rom/plugins/configuration/configuration_dsl.rb +2 -1
  28. data/lib/rom/plugins/relation/key_inference.rb +18 -3
  29. data/lib/rom/plugins/relation/registry_reader.rb +3 -1
  30. data/lib/rom/plugins/relation/view.rb +11 -6
  31. data/lib/rom/relation.rb +76 -16
  32. data/lib/rom/relation/class_interface.rb +44 -3
  33. data/lib/rom/relation/curried.rb +13 -4
  34. data/lib/rom/relation/graph.rb +15 -5
  35. data/lib/rom/relation/loaded.rb +42 -6
  36. data/lib/rom/relation/name.rb +102 -0
  37. data/lib/rom/relation_registry.rb +5 -0
  38. data/lib/rom/schema.rb +87 -0
  39. data/lib/rom/schema/dsl.rb +58 -0
  40. data/lib/rom/setup/auto_registration.rb +2 -2
  41. data/lib/rom/setup/finalize.rb +5 -5
  42. data/lib/rom/setup/finalize/{commands.rb → finalize_commands.rb} +2 -22
  43. data/lib/rom/setup/finalize/{mappers.rb → finalize_mappers.rb} +0 -0
  44. data/lib/rom/setup/finalize/finalize_relations.rb +60 -0
  45. data/lib/rom/types.rb +18 -0
  46. data/lib/rom/version.rb +1 -1
  47. data/log/.gitkeep +0 -0
  48. data/rom.gemspec +4 -2
  49. data/spec/integration/command_registry_spec.rb +13 -0
  50. data/spec/integration/commands/delete_spec.rb +0 -17
  51. data/spec/integration/commands/graph_builder_spec.rb +1 -1
  52. data/spec/integration/commands/graph_spec.rb +1 -1
  53. data/spec/integration/commands/update_spec.rb +0 -19
  54. data/spec/integration/commands_spec.rb +10 -3
  55. data/spec/integration/multi_repo_spec.rb +1 -1
  56. data/spec/integration/relations/default_dataset_spec.rb +27 -4
  57. data/spec/integration/setup_spec.rb +1 -4
  58. data/spec/shared/command_behavior.rb +17 -7
  59. data/spec/shared/container.rb +2 -2
  60. data/spec/shared/gateway_only.rb +1 -1
  61. data/spec/spec_helper.rb +5 -6
  62. data/spec/unit/rom/association_set_spec.rb +23 -0
  63. data/spec/unit/rom/auto_registration_spec.rb +1 -1
  64. data/spec/unit/rom/commands/lazy_spec.rb +8 -0
  65. data/spec/unit/rom/commands_spec.rb +45 -7
  66. data/spec/unit/rom/configurable_spec.rb +1 -1
  67. data/spec/unit/rom/container_spec.rb +6 -0
  68. data/spec/unit/rom/create_container_spec.rb +1 -1
  69. data/spec/unit/rom/environment_spec.rb +1 -1
  70. data/spec/unit/rom/memory/commands_spec.rb +43 -0
  71. data/spec/unit/rom/plugins/relation/key_inference_spec.rb +70 -12
  72. data/spec/unit/rom/plugins/relation/view_spec.rb +4 -0
  73. data/spec/unit/rom/relation/graph_spec.rb +10 -0
  74. data/spec/unit/rom/relation/lazy_spec.rb +3 -3
  75. data/spec/unit/rom/relation/loaded_spec.rb +15 -0
  76. data/spec/unit/rom/relation/name_spec.rb +51 -0
  77. data/spec/unit/rom/relation/schema_spec.rb +117 -0
  78. data/spec/unit/rom/relation_spec.rb +37 -7
  79. data/spec/unit/rom/schema_spec.rb +10 -0
  80. metadata +51 -12
  81. data/lib/rom/setup/finalize/relations.rb +0 -53
  82. data/spec/unit/rom/global_spec.rb +0 -18
  83. data/spec/unit/rom/registry_spec.rb +0 -38
@@ -1,3 +1,5 @@
1
+ require 'rom/support/class_builder'
2
+
1
3
  module ROM
2
4
  # Base command class with factory class-level interface and setup-related logic
3
5
  #
@@ -52,6 +54,28 @@ module ROM
52
54
  new(relation, self.options.merge(options))
53
55
  end
54
56
 
57
+ # Create a command class with a specific type
58
+ #
59
+ # @param [Symbol] command name
60
+ # @param [Class] parent class
61
+ #
62
+ # @yield [Class] create class
63
+ #
64
+ # @return [Class, Object] return result of the block if it was provided
65
+ #
66
+ # @api public
67
+ def create_class(name, type, &block)
68
+ klass = ClassBuilder
69
+ .new(name: "#{Inflector.classify(type)}[:#{name}]", parent: type)
70
+ .call
71
+
72
+ if block
73
+ yield(klass)
74
+ else
75
+ klass
76
+ end
77
+ end
78
+
55
79
  # Use a configured plugin in this relation
56
80
  #
57
81
  # @example
@@ -70,25 +94,15 @@ module ROM
70
94
  ROM.plugin_registry.commands.fetch(plugin, adapter).apply_to(self)
71
95
  end
72
96
 
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
97
+ # Extend a command class with relation view methods
98
+ #
99
+ # @param [Relation]
100
+ #
101
+ # @return [Class]
102
+ #
103
+ # @api public
104
+ def extend_for_relation(relation)
105
+ include(relation_methods_mod(relation.class))
92
106
  end
93
107
 
94
108
  # Return default name of the command class based on its name
@@ -110,6 +124,25 @@ module ROM
110
124
  def options
111
125
  { input: input, validator: validator, result: result }
112
126
  end
127
+
128
+ # @api private
129
+ def relation_methods_mod(relation_class)
130
+ Module.new do
131
+ relation_class.view_methods.each do |meth|
132
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
133
+ def #{meth}(*args)
134
+ response = relation.public_send(:#{meth}, *args)
135
+
136
+ if response.is_a?(relation.class)
137
+ new(response)
138
+ else
139
+ response
140
+ end
141
+ end
142
+ RUBY
143
+ end
144
+ end
145
+ end
113
146
  end
114
147
  end
115
148
  end
@@ -1,4 +1,5 @@
1
1
  require 'rom/pipeline'
2
+ require 'rom/support/constants'
2
3
 
3
4
  module ROM
4
5
  module Commands
@@ -16,6 +17,10 @@ module ROM
16
17
  def call(*args)
17
18
  response = left.call(*args)
18
19
 
20
+ if response.nil? || (many? && response.size == 0)
21
+ return one? ? nil : EMPTY_ARRAY
22
+ end
23
+
19
24
  if one? && !graph?
20
25
  if right.is_a?(Command) || right.is_a?(Commands::Composite)
21
26
  right.call([response].first)
@@ -8,11 +8,7 @@ module ROM
8
8
  #
9
9
  # @abstract
10
10
  class Delete < Command
11
- # @see AbstractCommand#call
12
- def call(*args)
13
- assert_tuple_count
14
- super
15
- end
11
+ restrictable true
16
12
  end
17
13
  end
18
14
  end
@@ -22,6 +22,9 @@ module ROM
22
22
  # @attr_reader [Array<Command>] nodes The child commands
23
23
  attr_reader :nodes
24
24
 
25
+ # @attr_reader [Symbol] root's relation name
26
+ attr_reader :name
27
+
25
28
  alias_method :left, :root
26
29
  alias_method :right, :nodes
27
30
 
@@ -32,6 +35,7 @@ module ROM
32
35
  super
33
36
  @root = root
34
37
  @nodes = nodes
38
+ @name = root.name
35
39
  end
36
40
 
37
41
  # Calls root and all nodes with the result from root
@@ -87,6 +91,13 @@ module ROM
87
91
  def graph?
88
92
  true
89
93
  end
94
+
95
+ private
96
+
97
+ # @api public
98
+ def composite_class
99
+ Command::Composite
100
+ end
90
101
  end
91
102
  end
92
103
  end
@@ -12,6 +12,8 @@ module ROM
12
12
  # @attr_reader [Command] command The wrapped command
13
13
  attr_reader :command
14
14
 
15
+ alias_method :unwrap, :command
16
+
15
17
  # @attr_reader [Proc] evaluator The proc that will evaluate the input
16
18
  attr_reader :evaluator
17
19
 
@@ -8,11 +8,7 @@ module ROM
8
8
  #
9
9
  # @abstract
10
10
  class Update < Command
11
- # @see AbstractCommand#call
12
- def call(*args)
13
- assert_tuple_count
14
- super
15
- end
11
+ restrictable true
16
12
  end
17
13
  end
18
14
  end
@@ -1,9 +1,11 @@
1
1
  require 'rom/environment'
2
2
  require 'rom/setup'
3
+ require 'rom/configuration_dsl'
3
4
 
4
5
  module ROM
5
6
  class Configuration
6
7
  extend Forwardable
8
+ include ROM::ConfigurationDSL
7
9
 
8
10
  NoDefaultAdapterError = Class.new(StandardError)
9
11
 
@@ -110,7 +110,7 @@ module ROM
110
110
  name = graph.name
111
111
 
112
112
  if mappers.key?(name)
113
- graph.with(mappers: mappers[graph.name])
113
+ graph.with(mappers: mappers[name])
114
114
  else
115
115
  graph
116
116
  end
@@ -120,9 +120,9 @@ module ROM
120
120
  raise ArgumentError, "#{self.class}#command accepts a symbol or an array"
121
121
  end
122
122
  end
123
-
123
+
124
124
  def disconnect
125
- gateways.each_key(&:disconnect)
125
+ gateways.each_value(&:disconnect)
126
126
  end
127
127
  end
128
128
  end
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'rom/plugin_registry'
2
3
  require 'rom/global/plugin_dsl'
3
4
  require 'rom/support/deprecations'
@@ -13,7 +14,6 @@ module ROM
13
14
  def self.extended(rom)
14
15
  super
15
16
 
16
- rom.instance_variable_set('@env', nil)
17
17
  rom.instance_variable_set('@adapters', {})
18
18
  rom.instance_variable_set('@plugin_registry', PluginRegistry.new)
19
19
  end
@@ -56,27 +56,5 @@ module ROM
56
56
  adapters[identifier] = adapter
57
57
  self
58
58
  end
59
-
60
- def env=(container)
61
- @env = container
62
- end
63
-
64
- def env
65
- if @env.nil?
66
- ROM::Deprecations.announce(:env, %q{
67
- ROM.env is no longer automatically populated with your container.
68
- If possible, refactor your code to remove the dependency on global ROM state. If it is not
69
- possible—or as a temporary solution—you can assign your container to `ROM.env` upon
70
- creation:
71
-
72
- ROM.env = ROM.container(:memory) do |rom|
73
- ...
74
- end
75
- })
76
- nil
77
- else
78
- @env
79
- end
80
- end
81
59
  end
82
60
  end
@@ -11,6 +11,7 @@ module ROM
11
11
  # @api public
12
12
  class Create < ROM::Commands::Create
13
13
  adapter :memory
14
+ use :schema
14
15
 
15
16
  # @see ROM::Commands::Create#execute
16
17
  def execute(tuples)
@@ -28,6 +29,7 @@ module ROM
28
29
  # @api public
29
30
  class Update < ROM::Commands::Update
30
31
  adapter :memory
32
+ use :schema
31
33
 
32
34
  # @see ROM::Commands::Update#execute
33
35
  def execute(params)
@@ -1,3 +1,5 @@
1
+ require 'rom/memory/types'
2
+
1
3
  module ROM
2
4
  module Memory
3
5
  # Relation subclass for memory adapter
@@ -9,6 +11,7 @@ module ROM
9
11
  # @api public
10
12
  class Relation < ROM::Relation
11
13
  include Enumerable
14
+ include Memory
12
15
 
13
16
  adapter :memory
14
17
 
@@ -1,8 +1,5 @@
1
- begin
2
- require 'thread_safe'
3
- rescue LoadError
4
- raise LoadError, 'Please install the `thread_safe` gem.'
5
- end
1
+ require 'concurrent/hash'
2
+ require 'concurrent/array'
6
3
 
7
4
  require 'rom/memory/dataset'
8
5
 
@@ -21,7 +18,7 @@ module ROM
21
18
 
22
19
  # @api private
23
20
  def initialize
24
- @data = ThreadSafe::Hash.new
21
+ @data = Concurrent::Hash.new
25
22
  end
26
23
 
27
24
  # @return [Dataset]
@@ -37,7 +34,7 @@ module ROM
37
34
  #
38
35
  # @api private
39
36
  def create_dataset(name)
40
- data[name] = Dataset.new(ThreadSafe::Array.new)
37
+ data[name] = Dataset.new(Concurrent::Array.new)
41
38
  end
42
39
 
43
40
  # Check if there's dataset under specified key
@@ -0,0 +1,9 @@
1
+ require 'rom/types'
2
+
3
+ module ROM
4
+ module Memory
5
+ module Types
6
+ include ROM::Types
7
+ end
8
+ end
9
+ end
@@ -3,20 +3,34 @@ module ROM
3
3
  #
4
4
  # @api private
5
5
  module Pipeline
6
- # Compose two relation with a left-to-right composition
6
+ # Common `>>` operator extension
7
7
  #
8
- # @example
9
- # users.by_name('Jane') >> tasks.for_users
10
- #
11
- # @param [Relation] other The right relation
12
- #
13
- # @return [Relation::Composite]
14
- #
15
- # @api public
16
- def >>(other)
17
- Relation::Composite.new(self, other)
8
+ # @api private
9
+ module Operator
10
+ # Compose two relation with a left-to-right composition
11
+ #
12
+ # @example
13
+ # users.by_name('Jane') >> tasks.for_users
14
+ #
15
+ # @param [Relation] other The right relation
16
+ #
17
+ # @return [Relation::Composite]
18
+ #
19
+ # @api public
20
+ def >>(other)
21
+ composite_class.new(self, other)
22
+ end
23
+
24
+ private
25
+
26
+ # @api private
27
+ def composite_class
28
+ raise NotImplementedError
29
+ end
18
30
  end
19
31
 
32
+ include Operator
33
+
20
34
  # Send data through specified mappers
21
35
  #
22
36
  # @return [Relation::Composite]
@@ -24,7 +38,7 @@ module ROM
24
38
  # @api public
25
39
  def map_with(*names)
26
40
  [self, *names.map { |name| mappers[name] }]
27
- .reduce { |a, e| Relation::Composite.new(a, e) }
41
+ .reduce { |a, e| composite_class.new(a, e) }
28
42
  end
29
43
  alias_method :as, :map_with
30
44
 
@@ -11,7 +11,7 @@ module ROM
11
11
  #
12
12
  # @api private
13
13
  attr_reader :configuration
14
-
14
+
15
15
  # Internal registry for command plugins
16
16
  #
17
17
  # @return [InternalPluginRegistry]
@@ -158,7 +158,7 @@ module ROM
158
158
  # Return the plugin for a given adapter
159
159
  #
160
160
  # @param [Symbol] name The name of the plugin
161
- # @param [Symbol] adapter (:default) The name of the adapter used
161
+ # @param [Symbol] adapter_name (:default) The name of the adapter used
162
162
  #
163
163
  # @raises [UnknownPluginError] if no plugin is found with the given name
164
164
  #
@@ -0,0 +1,26 @@
1
+ module ROM
2
+ module Plugins
3
+ module Command
4
+ # @api private
5
+ module Schema
6
+ def self.included(klass)
7
+ super
8
+ klass.extend(ClassInterface)
9
+ end
10
+
11
+ # @api private
12
+ module ClassInterface
13
+ # @see Command.build
14
+ # @api public
15
+ def build(relation, options = {})
16
+ if options.key?(:input) || !relation.schema?
17
+ super
18
+ else
19
+ super(relation, options.merge(input: relation.schema_hash))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rom/configuration_dsl'
2
+ require 'rom/support/deprecations'
2
3
 
3
4
  module ROM
4
5
  module ConfigurationPlugins
@@ -9,7 +10,7 @@ module ROM
9
10
 
10
11
  # @api private
11
12
  def self.apply(configuration, options = {})
12
- configuration.extend(ROM::ConfigurationDSL)
13
+ ROM::Deprecations.announce(:macros, "Calling `use(:macros)` is no longer necessary. Macros are enabled by default.")
13
14
  end
14
15
  end
15
16
  end
@@ -9,13 +9,28 @@ module ROM
9
9
  # @return [Symbol]
10
10
  #
11
11
  # @api private
12
- def foreign_key
13
- :"#{Inflector.singularize(name)}_id"
12
+ def foreign_key(other = nil)
13
+ if other
14
+ if schema
15
+ rel_name = other.respond_to?(:to_sym) ?
16
+ ROM::Relation::Name[other.to_sym] : other.base_name
17
+
18
+ key = schema.foreign_key(rel_name.dataset)
19
+ key ? key.meta[:name] : __registry__[rel_name].foreign_key
20
+ else
21
+ relation = other.respond_to?(:to_sym) ?
22
+ __registry__[other] : other
23
+
24
+ relation.foreign_key
25
+ end
26
+ else
27
+ :"#{Inflector.singularize(name.dataset)}_id"
28
+ end
14
29
  end
15
30
 
16
31
  # Return base name which defaults to name attribute
17
32
  #
18
- # @return [Symbol]
33
+ # @return [ROM::Relation::Name]
19
34
  #
20
35
  # @api private
21
36
  def base_name