rom 0.6.2 → 0.7.0

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