rom 1.0.0 → 2.0.0

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