rom 0.6.2 → 0.7.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +9 -0
  4. data/CHANGELOG.md +34 -0
  5. data/CONTRIBUTING.md +1 -0
  6. data/Gemfile +1 -1
  7. data/README.md +12 -7
  8. data/lib/rom.rb +8 -0
  9. data/lib/rom/command.rb +19 -0
  10. data/lib/rom/commands/abstract.rb +6 -1
  11. data/lib/rom/commands/composite.rb +1 -52
  12. data/lib/rom/commands/update.rb +4 -1
  13. data/lib/rom/constants.rb +1 -0
  14. data/lib/rom/env.rb +3 -25
  15. data/lib/rom/global.rb +23 -0
  16. data/lib/rom/global/plugin_dsl.rb +47 -0
  17. data/lib/rom/header.rb +19 -8
  18. data/lib/rom/header/attribute.rb +14 -2
  19. data/lib/rom/lint/enumerable_dataset.rb +3 -1
  20. data/lib/rom/lint/repository.rb +5 -5
  21. data/lib/rom/mapper.rb +2 -1
  22. data/lib/rom/mapper/attribute_dsl.rb +86 -13
  23. data/lib/rom/mapper/dsl.rb +20 -1
  24. data/lib/rom/memory/commands.rb +3 -1
  25. data/lib/rom/memory/dataset.rb +1 -1
  26. data/lib/rom/memory/relation.rb +1 -1
  27. data/lib/rom/pipeline.rb +91 -0
  28. data/lib/rom/plugin.rb +31 -0
  29. data/lib/rom/plugin_registry.rb +134 -0
  30. data/lib/rom/plugins/relation/registry_reader.rb +30 -0
  31. data/lib/rom/processor/transproc.rb +78 -3
  32. data/lib/rom/relation/class_interface.rb +14 -2
  33. data/lib/rom/relation/composite.rb +9 -97
  34. data/lib/rom/relation/graph.rb +76 -0
  35. data/lib/rom/relation/lazy.rb +15 -63
  36. data/lib/rom/relation/materializable.rb +66 -0
  37. data/lib/rom/setup/finalize.rb +16 -5
  38. data/lib/rom/setup_dsl/mapper_dsl.rb +10 -2
  39. data/lib/rom/setup_dsl/setup.rb +1 -1
  40. data/lib/rom/support/array_dataset.rb +7 -4
  41. data/lib/rom/support/data_proxy.rb +7 -7
  42. data/lib/rom/support/deprecations.rb +17 -0
  43. data/lib/rom/support/enumerable_dataset.rb +10 -3
  44. data/lib/rom/support/inflector.rb +1 -1
  45. data/lib/rom/version.rb +1 -1
  46. data/rom.gemspec +1 -1
  47. data/spec/integration/commands/create_spec.rb +3 -3
  48. data/spec/integration/commands/update_spec.rb +24 -4
  49. data/spec/integration/mappers/combine_spec.rb +107 -0
  50. data/spec/integration/mappers/registering_custom_mappers_spec.rb +29 -0
  51. data/spec/integration/mappers/reusing_mappers_spec.rb +22 -0
  52. data/spec/integration/mappers/unwrap_spec.rb +98 -0
  53. data/spec/integration/multi_repo_spec.rb +2 -2
  54. data/spec/integration/repositories/extending_relations_spec.rb +9 -0
  55. data/spec/integration/setup_spec.rb +2 -2
  56. data/spec/shared/enumerable_dataset.rb +4 -1
  57. data/spec/shared/materializable.rb +34 -0
  58. data/spec/shared/proxy.rb +0 -0
  59. data/spec/spec_helper.rb +6 -2
  60. data/spec/support/mutant.rb +9 -6
  61. data/spec/unit/rom/commands_spec.rb +3 -3
  62. data/spec/unit/rom/header_spec.rb +2 -2
  63. data/spec/unit/rom/mapper/dsl_spec.rb +102 -1
  64. data/spec/unit/rom/memory/dataset_spec.rb +10 -33
  65. data/spec/unit/rom/memory/relation_spec.rb +63 -0
  66. data/spec/unit/rom/memory/storage_spec.rb +2 -2
  67. data/spec/unit/rom/plugin_spec.rb +121 -0
  68. data/spec/unit/rom/processor/transproc_spec.rb +47 -6
  69. data/spec/unit/rom/relation/composite_spec.rb +3 -1
  70. data/spec/unit/rom/relation/graph_spec.rb +78 -0
  71. data/spec/unit/rom/relation/lazy/combine_spec.rb +130 -0
  72. data/spec/unit/rom/relation/lazy_spec.rb +3 -1
  73. data/spec/unit/rom/relation/loaded_spec.rb +3 -1
  74. data/spec/unit/rom/setup_spec.rb +8 -8
  75. data/spec/unit/rom/support/array_dataset_spec.rb +3 -1
  76. data/spec/unit/rom/support/class_builder_spec.rb +2 -2
  77. metadata +24 -7
  78. data/lib/rom/relation/registry_reader.rb +0 -23
@@ -0,0 +1,76 @@
1
+ require 'rom/relation/loaded'
2
+ require 'rom/relation/materializable'
3
+ require 'rom/pipeline'
4
+
5
+ module ROM
6
+ class Relation
7
+ # Load a relation with its associations
8
+ #
9
+ # @example
10
+ # ROM.setup(:memory)
11
+ #
12
+ # class Users < ROM::Relation[:memory]
13
+ # end
14
+ #
15
+ # class Tasks < ROM::Relation[:memory]
16
+ # def for_users(users)
17
+ # restrict(user: users.map { |user| user[:name] })
18
+ # end
19
+ # end
20
+ #
21
+ # rom = ROM.finalize.env
22
+ #
23
+ # rom.relations[:users] << { name: 'Jane' }
24
+ # rom.relations[:tasks] << { user: 'Jane', title: 'Do something' }
25
+ #
26
+ # rom.relation(:users).combine(rom.relation(:tasks).for_users)
27
+ #
28
+ # @api public
29
+ class Graph
30
+ include Materializable
31
+ include Pipeline
32
+ include Pipeline::Proxy
33
+
34
+ # Root aka parent relation
35
+ #
36
+ # @return [Relation::Lazy]
37
+ #
38
+ # @api private
39
+ attr_reader :root
40
+
41
+ # Child relation nodes
42
+ #
43
+ # @return [Array<Relation::Lazy>]
44
+ #
45
+ # @api private
46
+ attr_reader :nodes
47
+
48
+ alias_method :left, :root
49
+ alias_method :right, :nodes
50
+
51
+ # @api private
52
+ def initialize(root, nodes)
53
+ @root = root
54
+ @nodes = nodes
55
+ end
56
+
57
+ # Materialize this relation graph
58
+ #
59
+ # @return [Loaded]
60
+ #
61
+ # @api public
62
+ def call(*args)
63
+ left = root.call(*args)
64
+
65
+ right =
66
+ if left.count > 0
67
+ nodes.map { |node| node.call(left) }
68
+ else
69
+ nodes.map { |node| Loaded.new(node, []) }
70
+ end
71
+
72
+ Loaded.new(self, [left, right])
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,5 +1,8 @@
1
1
  require 'rom/relation/loaded'
2
2
  require 'rom/relation/composite'
3
+ require 'rom/relation/graph'
4
+ require 'rom/relation/materializable'
5
+ require 'rom/pipeline'
3
6
 
4
7
  module ROM
5
8
  class Relation
@@ -28,6 +31,8 @@ module ROM
28
31
  class Lazy
29
32
  include Equalizer.new(:relation, :options)
30
33
  include Options
34
+ include Materializable
35
+ include Pipeline
31
36
 
32
37
  option :mappers, reader: true, default: EMPTY_HASH
33
38
 
@@ -41,27 +46,24 @@ module ROM
41
46
  # @return [Hash<Symbol=>TrueClass>]
42
47
  #
43
48
  # @api private
44
- attr_reader :methods
49
+ attr_reader :exposed_relations
45
50
 
46
51
  # @api private
47
52
  def initialize(relation, options = {})
48
53
  super
49
54
  @relation = relation
50
- @methods = @relation.exposed_relations
55
+ @exposed_relations = @relation.exposed_relations
51
56
  end
52
57
 
53
- # Compose two relation with a left-to-right composition
58
+ # Eager load other relation(s) for this relation
54
59
  #
55
- # @example
56
- # users.by_name('Jane') >> tasks.for_users
57
- #
58
- # @param [Relation] other The right relation
60
+ # @param [Array<Relation>] others The other relation(s) to eager load
59
61
  #
60
- # @return [Relation::Composite]
62
+ # @return [Relation::Graph]
61
63
  #
62
64
  # @api public
63
- def >>(other)
64
- Composite.new(self, other)
65
+ def combine(*others)
66
+ Graph.new(self, others)
65
67
  end
66
68
 
67
69
  # Build a relation pipeline using registered mappers
@@ -78,16 +80,6 @@ module ROM
78
80
  end
79
81
  alias_method :as, :map_with
80
82
 
81
- # Coerce lazy relation to an array
82
- #
83
- # @return [Array]
84
- #
85
- # @api public
86
- def to_a
87
- call.to_a
88
- end
89
- alias_method :to_ary, :to_a
90
-
91
83
  # Load relation
92
84
  #
93
85
  # @return [Relation::Loaded]
@@ -97,50 +89,9 @@ module ROM
97
89
  Loaded.new(relation)
98
90
  end
99
91
 
100
- # Yield relation tuples
101
- #
102
- # @yield [Object]
103
- #
104
- # @api public
105
- def each(&block)
106
- return to_enum unless block
107
- to_a.each { |tuple| yield(tuple) }
108
- end
109
-
110
- # Delegate to loaded relation and return one object
111
- #
112
- # @return [Object]
113
- #
114
- # @see Loaded#one
115
- #
116
- # @api public
117
- def one
118
- call.one
119
- end
120
-
121
- # Delegate to loaded relation and return one object
122
- #
123
- # @return [Object]
124
- #
125
- # @see Loaded#one
126
- #
127
- # @api public
128
- def one!
129
- call.one!
130
- end
131
-
132
- # Return first tuple from a relation coerced to an array
133
- #
134
- # @return [Object]
135
- #
136
- # @api public
137
- def first
138
- to_a.first
139
- end
140
-
141
92
  # @api private
142
93
  def respond_to_missing?(name, include_private = false)
143
- methods.include?(name) || super
94
+ exposed_relations.include?(name) || super
144
95
  end
145
96
 
146
97
  # Return if this lazy relation is curried
@@ -162,13 +113,14 @@ module ROM
162
113
  #
163
114
  # @api private
164
115
  def method_missing(meth, *args, &block)
165
- if !methods.include?(meth) || (curried? && name != meth)
116
+ if !exposed_relations.include?(meth) || (curried? && name != meth)
166
117
  super
167
118
  else
168
119
  arity = relation.method(meth).arity
169
120
 
170
121
  if arity < 0 || arity == args.size
171
122
  response = relation.__send__(meth, *args, &block)
123
+
172
124
  if response.is_a?(Relation)
173
125
  __new__(response)
174
126
  else
@@ -0,0 +1,66 @@
1
+ module ROM
2
+ class Relation
3
+ # Interface for objects that can be materialized into a loaded relation
4
+ #
5
+ # @api public
6
+ module Materializable
7
+ # @abstract
8
+ #
9
+ # @api public
10
+ def call(*)
11
+ raise NotImplementedError, "#{self.class}#call must be implemented"
12
+ end
13
+
14
+ # Coerce the relation to an array
15
+ #
16
+ # @return [Array]
17
+ #
18
+ # @api public
19
+ def to_a
20
+ call.to_a
21
+ end
22
+ alias_method :to_ary, :to_a
23
+
24
+ # Yield relation tuples
25
+ #
26
+ # @yield [Hash,Object]
27
+ #
28
+ # @api public
29
+ def each(&block)
30
+ return to_enum unless block
31
+ to_a.each { |tuple| yield(tuple) }
32
+ end
33
+
34
+ # Delegate to loaded relation and return one object
35
+ #
36
+ # @return [Object]
37
+ #
38
+ # @see Loaded#one
39
+ #
40
+ # @api public
41
+ def one
42
+ call.one
43
+ end
44
+
45
+ # Delegate to loaded relation and return one object
46
+ #
47
+ # @return [Object]
48
+ #
49
+ # @see Loaded#one
50
+ #
51
+ # @api public
52
+ def one!
53
+ call.one!
54
+ end
55
+
56
+ # Return first tuple from a relation coerced to an array
57
+ #
58
+ # @return [Object]
59
+ #
60
+ # @api public
61
+ def first
62
+ to_a.first
63
+ end
64
+ end
65
+ end
66
+ end
@@ -17,14 +17,15 @@ module ROM
17
17
  # @private
18
18
  class Finalize
19
19
  attr_reader :repositories, :repo_adapter, :datasets,
20
- :relation_classes, :mapper_classes, :command_classes
20
+ :relation_classes, :mapper_classes, :mappers, :command_classes
21
21
 
22
22
  # @api private
23
- def initialize(repositories, relation_classes, mapper_classes, command_classes)
23
+ def initialize(repositories, relation_classes, mappers, command_classes)
24
24
  @repositories = repositories
25
25
  @repo_adapter_map = ROM.repositories
26
26
  @relation_classes = relation_classes
27
- @mapper_classes = mapper_classes
27
+ @mapper_classes = mappers.select { |mapper| mapper.is_a?(Class) }
28
+ @mappers = (mappers - @mapper_classes).reduce(:merge) || {}
28
29
  @command_classes = command_classes
29
30
  initialize_datasets
30
31
  end
@@ -83,9 +84,19 @@ module ROM
83
84
  # @api private
84
85
  def load_mappers
85
86
  mapper_registry = Mapper.registry(mapper_classes).each_with_object({})
86
- registry_hash = mapper_registry.each do |(name, mappers), h|
87
- h[name] = MapperRegistry.new(mappers)
87
+
88
+ registry_hash = mapper_registry.each { |(relation, mappers), h|
89
+ h[relation] = MapperRegistry.new(mappers)
90
+ }
91
+
92
+ mappers.each do |relation, mappers|
93
+ if registry_hash.key?(relation)
94
+ mappers.each { |name, mapper| registry[name] = mapper }
95
+ else
96
+ registry_hash[relation] = MapperRegistry.new(mappers)
97
+ end
88
98
  end
99
+
89
100
  Registry.new(registry_hash)
90
101
  end
91
102
 
@@ -6,10 +6,11 @@ module ROM
6
6
  #
7
7
  # @private
8
8
  class MapperDSL
9
- attr_reader :mappers
9
+ attr_reader :registry
10
10
 
11
11
  # @api private
12
- def initialize(&block)
12
+ def initialize(registry, &block)
13
+ @registry = registry
13
14
  instance_exec(&block)
14
15
  end
15
16
 
@@ -25,6 +26,13 @@ module ROM
25
26
  Mapper.build_class(name, options, &block)
26
27
  self
27
28
  end
29
+
30
+ # TODO
31
+ #
32
+ # @api public
33
+ def register(relation, mappers)
34
+ registry.register_mapper(relation => mappers)
35
+ end
28
36
  end
29
37
  end
30
38
  end
@@ -42,7 +42,7 @@ module ROM
42
42
  #
43
43
  # @api public
44
44
  def mappers(&block)
45
- MapperDSL.new(&block)
45
+ MapperDSL.new(self, &block)
46
46
  end
47
47
 
48
48
  # Command definition DSL
@@ -14,13 +14,16 @@ module ROM
14
14
  #
15
15
  # @api private
16
16
  def self.included(klass)
17
- klass.send(:include, DataProxy)
17
+ klass.class_eval do
18
+ include Options
19
+ include DataProxy
20
+ end
18
21
  end
19
22
 
20
23
  forward(
21
24
  :*, :+, :-, :compact, :compact!, :flatten, :flatten!, :length, :pop,
22
- :reverse, :reverse!, :sample, :select!, :size, :shift, :shuffle, :shuffle!,
23
- :slice, :slice!, :sort!, :sort_by!, :uniq, :uniq!, :unshift, :values_at
25
+ :reverse, :reverse!, :sample, :size, :shift, :shuffle, :shuffle!,
26
+ :slice, :slice!, :sort!, :uniq, :uniq!, :unshift, :values_at
24
27
  )
25
28
 
26
29
  [
@@ -30,7 +33,7 @@ module ROM
30
33
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
31
34
  def #{method}(*args, &block)
32
35
  return to_enum unless block
33
- self.class.new(data.send(:#{method}, *args, &block))
36
+ self.class.new(data.send(:#{method}, *args, &block), options)
34
37
  end
35
38
  RUBY
36
39
  end
@@ -12,6 +12,8 @@ module ROM
12
12
  :each, :to_a, :to_ary, :kind_of?, :instance_of?, :is_a?
13
13
  ].freeze
14
14
 
15
+ # Wrapped data array
16
+ #
15
17
  # @return [Object] Data object for the iterator
16
18
  #
17
19
  # @api private
@@ -30,19 +32,17 @@ module ROM
30
32
  def self.included(klass)
31
33
  klass.class_eval do
32
34
  extend ClassMethods
35
+
33
36
  include Equalizer.new(:data)
37
+
38
+ option :row_proc, reader: true, default: proc { |obj| obj.class.row_proc }
34
39
  end
35
40
  end
36
41
 
37
- # Constructor for dataset objects
38
- #
39
- # @param [Object] data
40
- # @param [Proc] row_proc processing proc
41
- #
42
42
  # @api private
43
- def initialize(data, row_proc = self.class.row_proc)
43
+ def initialize(data, options = {})
44
44
  @data = data
45
- @row_proc = row_proc
45
+ super(data, options)
46
46
  end
47
47
 
48
48
  # Iterate over data using row_proc
@@ -0,0 +1,17 @@
1
+ module ROM
2
+ module Deprecations
3
+ # @api private
4
+ def deprecate(old_name, new_name, msg = nil)
5
+ class_eval do
6
+ define_method(old_name) do |*args, &block|
7
+ warn <<-MSG.gsub(/^\s+/, '')
8
+ #{self.class}##{old_name} is deprecated and will be removed in 1.0.0.
9
+ Please use #{self.class}##{new_name} instead."
10
+ #{msg}
11
+ MSG
12
+ __send__(new_name, *args, &block)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -23,6 +23,7 @@ module ROM
23
23
  #
24
24
  # @api public
25
25
  module EnumerableDataset
26
+ extend DataProxy::ClassMethods
26
27
  include Enumerable
27
28
 
28
29
  # Coerce a dataset to an array
@@ -40,17 +41,23 @@ module ROM
40
41
  # @api private
41
42
  def self.included(klass)
42
43
  return unless klass.is_a?(Class)
43
- klass.send(:include, DataProxy)
44
+
45
+ klass.class_eval do
46
+ include Options
47
+ include DataProxy
48
+ end
44
49
  end
45
50
 
51
+ forward :take
52
+
46
53
  [
47
54
  :chunk, :collect, :collect_concat, :drop_while, :find_all, :flat_map,
48
- :grep, :map, :reject, :select, :sort, :sort_by, :take, :take_while
55
+ :grep, :map, :reject, :select, :sort, :sort_by, :take_while
49
56
  ].each do |method|
50
57
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
51
58
  def #{method}(*args, &block)
52
59
  return to_enum unless block
53
- self.class.new(super(*args, &block))
60
+ self.class.new(super(*args, &block), options)
54
61
  end
55
62
  RUBY
56
63
  end