rom-repository 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -5
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +21 -5
  6. data/README.md +6 -110
  7. data/lib/rom/repository/changeset/create.rb +26 -0
  8. data/lib/rom/repository/changeset/pipe.rb +40 -0
  9. data/lib/rom/repository/changeset/update.rb +82 -0
  10. data/lib/rom/repository/changeset.rb +99 -0
  11. data/lib/rom/repository/class_interface.rb +142 -0
  12. data/lib/rom/repository/command_compiler.rb +214 -0
  13. data/lib/rom/repository/command_proxy.rb +22 -0
  14. data/lib/rom/repository/header_builder.rb +13 -16
  15. data/lib/rom/repository/mapper_builder.rb +7 -14
  16. data/lib/rom/repository/{loading_proxy → relation_proxy}/wrap.rb +7 -7
  17. data/lib/rom/repository/relation_proxy.rb +225 -0
  18. data/lib/rom/repository/root.rb +110 -0
  19. data/lib/rom/repository/struct_attributes.rb +46 -0
  20. data/lib/rom/repository/struct_builder.rb +31 -14
  21. data/lib/rom/repository/version.rb +1 -1
  22. data/lib/rom/repository.rb +192 -31
  23. data/lib/rom/struct.rb +13 -8
  24. data/rom-repository.gemspec +9 -10
  25. data/spec/integration/changeset_spec.rb +86 -0
  26. data/spec/integration/command_macros_spec.rb +175 -0
  27. data/spec/integration/command_spec.rb +224 -0
  28. data/spec/integration/multi_adapter_spec.rb +3 -3
  29. data/spec/integration/repository_spec.rb +97 -2
  30. data/spec/integration/root_repository_spec.rb +88 -0
  31. data/spec/shared/database.rb +47 -3
  32. data/spec/shared/mappers.rb +35 -0
  33. data/spec/shared/models.rb +41 -0
  34. data/spec/shared/plugins.rb +66 -0
  35. data/spec/shared/relations.rb +76 -0
  36. data/spec/shared/repo.rb +38 -17
  37. data/spec/shared/seeds.rb +19 -0
  38. data/spec/spec_helper.rb +4 -1
  39. data/spec/support/mapper_registry.rb +1 -3
  40. data/spec/unit/changeset_spec.rb +58 -0
  41. data/spec/unit/header_builder_spec.rb +34 -35
  42. data/spec/unit/relation_proxy_spec.rb +170 -0
  43. data/spec/unit/sql/relation_spec.rb +5 -5
  44. data/spec/unit/struct_builder_spec.rb +7 -4
  45. data/spec/unit/struct_spec.rb +22 -0
  46. metadata +38 -41
  47. data/lib/rom/plugins/relation/key_inference.rb +0 -31
  48. data/lib/rom/repository/loading_proxy/combine.rb +0 -158
  49. data/lib/rom/repository/loading_proxy.rb +0 -182
  50. data/spec/unit/loading_proxy_spec.rb +0 -147
@@ -1,182 +0,0 @@
1
- require 'rom/support/options'
2
- require 'rom/relation/materializable'
3
-
4
- require 'rom/repository/loading_proxy/combine'
5
- require 'rom/repository/loading_proxy/wrap'
6
-
7
- module ROM
8
- class Repository
9
- # LoadingProxy decorates a relation and automatically generate mappers that
10
- # will map raw tuples into structs
11
- #
12
- # @api public
13
- class LoadingProxy
14
- include Options
15
- include Relation::Materializable
16
-
17
- include LoadingProxy::Combine
18
- include LoadingProxy::Wrap
19
-
20
- option :name, reader: true, type: Symbol
21
- option :mapper_builder, reader: true, default: proc { MapperBuilder.new }
22
- option :meta, reader: true, type: Hash, default: EMPTY_HASH
23
-
24
- # @attr_reader [ROM::Relation::Lazy] relation Decorated relation
25
- attr_reader :relation
26
-
27
- # @api private
28
- def initialize(relation, options = {})
29
- super
30
- @relation = relation
31
- end
32
-
33
- # Materialize wrapped relation and send it through a mapper
34
- #
35
- # For performance reasons a combined relation will skip mapping since
36
- # we only care about extracting key values for combining
37
- #
38
- # @api public
39
- def call(*args)
40
- ((combine? || composite?) ? relation : (relation >> mapper)).call(*args)
41
- end
42
-
43
- # Map this relation with other mappers too
44
- #
45
- # @api public
46
- def map_with(*names)
47
- if names.size == 1 && names[0].is_a?(Class)
48
- with(meta: meta.merge(model: names[0]))
49
- else
50
- mappers = [mapper]+names.map { |name| relation.mappers[name] }
51
- mappers.reduce(self) { |a, e| a >> e }
52
- end
53
- end
54
- alias_method :as, :map_with
55
-
56
- # Return AST for this relation
57
- #
58
- # @return [Array]
59
- #
60
- # @api private
61
- def to_ast
62
- attr_ast = attributes.map { |name| [:attribute, name] }
63
-
64
- node_ast = nodes.map(&:to_ast)
65
- wrap_ast = wraps.map(&:to_ast)
66
-
67
- wrap_attrs = wraps.flat_map { |wrap|
68
- wrap.attributes.map { |c| [:attribute, :"#{wrap.base_name}_#{c}"] }
69
- }
70
-
71
- meta = options[:meta].merge(base_name: relation.base_name)
72
- meta.delete(:wraps)
73
-
74
- [:relation, name, [:header, (attr_ast - wrap_attrs) + node_ast + wrap_ast], meta]
75
- end
76
-
77
- # Infer a mapper for the relation
78
- #
79
- # @return [ROM::Mapper]
80
- #
81
- # @api private
82
- def mapper
83
- mapper_builder[to_ast]
84
- end
85
-
86
- # @api private
87
- def respond_to_missing?(name, include_private = false)
88
- relation.respond_to?(name) || super
89
- end
90
-
91
- # Return new instance with new options
92
- #
93
- # @return [LoadingProxy]
94
- #
95
- # @api private
96
- def with(new_options)
97
- __new__(relation, new_options)
98
- end
99
-
100
- # Return if this relation is combined
101
- #
102
- # @return [Boolean]
103
- #
104
- # @api private
105
- def combine?
106
- meta[:combine_type]
107
- end
108
-
109
- # Return if this relation is a composite
110
- #
111
- # @return [Boolean]
112
- #
113
- # @api private
114
- def composite?
115
- relation.is_a?(Relation::Composite)
116
- end
117
-
118
- # Return meta info for this relation
119
- #
120
- # @return [Hash]
121
- #
122
- # @api private
123
- def meta
124
- options[:meta]
125
- end
126
-
127
- # Return all nodes that this relation combines
128
- #
129
- # @return [Array<LoadingProxy>]
130
- #
131
- # @api private
132
- def nodes
133
- relation.respond_to?(:nodes) ? relation.nodes : []
134
- end
135
-
136
- private
137
-
138
- # Return a new instance with another relation and options
139
- #
140
- # @return [LoadingProxy]
141
- #
142
- # @api private
143
- def __new__(relation, new_options = {})
144
- self.class.new(relation, options.merge(new_options))
145
- end
146
-
147
- # Return all nodes that this relation wraps
148
- #
149
- # @return [Array<LoadingProxy>]
150
- #
151
- # @api private
152
- def wraps
153
- meta.fetch(:wraps, [])
154
- end
155
-
156
- # Forward to relation and wrap it with proxy if response was a relation too
157
- #
158
- # TODO: this will be simplified once ROM::Relation has lazy-features built-in
159
- # and ROM::Lazy is gone
160
- #
161
- # @api private
162
- def method_missing(meth, *args, &block)
163
- if relation.respond_to?(meth)
164
- result = relation.__send__(meth, *args, &block)
165
-
166
- if result.kind_of?(Relation::Materializable) && !result.is_a?(Relation::Loaded)
167
- __new__(result)
168
- else
169
- result
170
- end
171
- else
172
- super
173
- end
174
- end
175
-
176
- # @api private
177
- def respond_to_missing?(meth, _include_private = false)
178
- relation.respond_to?(meth) || super
179
- end
180
- end
181
- end
182
- end
@@ -1,147 +0,0 @@
1
- RSpec.describe 'loading proxy' do
2
- include_context 'database'
3
- include_context 'relations'
4
- include_context 'repo'
5
- include_context 'structs'
6
- include_context 'seeds'
7
-
8
- let(:users) do
9
- ROM::Repository::LoadingProxy.new(rom.relation(:users), name: :users)
10
- end
11
-
12
- let(:tasks) do
13
- ROM::Repository::LoadingProxy.new(rom.relation(:tasks), name: :tasks)
14
- end
15
-
16
- let(:tags) do
17
- ROM::Repository::LoadingProxy.new(rom.relation(:tags), name: :tags)
18
- end
19
-
20
- describe '#each' do
21
- it 'yields loaded structs' do
22
- result = []
23
-
24
- users.each { |user| result << user }
25
-
26
- expect(result).to eql([jane, joe])
27
- end
28
-
29
- it 'returns an enumerator when block is not given' do
30
- expect(users.each.to_a).to eql([jane, joe])
31
- end
32
- end
33
-
34
- describe '#map_with/#as' do
35
- before do
36
- configuration.mappers do
37
- register :users, name_list: -> users { users.map(&:name) }
38
- end
39
- end
40
-
41
- it 'sends the relation through multiple mappers' do
42
- expect(users.map_with(:name_list).to_a).to eql(%w(Jane Joe))
43
- end
44
-
45
- context 'setting custom model type' do
46
- let(:user_type) { Class.new { include Anima.new(:id, :name) } }
47
- let(:custom_users) { users.as(user_type) }
48
-
49
- it 'instantiates custom model' do
50
- expect(custom_users.where(name: 'Jane').one).to be_instance_of(user_type)
51
- end
52
- end
53
- end
54
-
55
- describe 'retrieving a single struct' do
56
- describe '#first' do
57
- it 'returns exactly one struct' do
58
- expect(users.first).to eql(jane)
59
- end
60
- end
61
-
62
- describe '#one' do
63
- it 'returns exactly one struct' do
64
- expect(users.find(id: 1).one).to eql(jane)
65
-
66
- expect(users.find(id: 3).one).to be(nil)
67
-
68
- expect { users.find(id: [1,2]).one }.to raise_error(ROM::TupleCountMismatchError)
69
- end
70
- end
71
-
72
- describe '#one!' do
73
- it 'returns exactly one struct' do
74
- expect(users.find(id: 1).one!).to eql(jane)
75
-
76
- expect { users.find(id: [1, 2]).one! }.to raise_error(ROM::TupleCountMismatchError)
77
- expect { users.find(id: [3]).one! }.to raise_error(ROM::TupleCountMismatchError)
78
- end
79
- end
80
- end
81
-
82
- describe '#to_ast' do
83
- it 'returns valid ast for a single relation' do
84
- expect(users.to_ast).to eql(
85
- [
86
- :relation, :users, [
87
- :header, [[:attribute, :id], [:attribute, :name]]
88
- ],
89
- base_name: :users
90
- ]
91
- )
92
- end
93
-
94
- it 'returns valid ast for a combined relation' do
95
- relation = users.combine(many: { user_tasks: [tasks, id: :user_id] })
96
-
97
- expect(relation.to_ast).to eql(
98
- [
99
- :relation, :users, [
100
- :header, [
101
- [:attribute, :id],
102
- [:attribute, :name],
103
- [
104
- :relation, :user_tasks, [
105
- :header, [
106
- [:attribute, :id],
107
- [:attribute, :user_id],
108
- [:attribute, :title]
109
- ]
110
- ],
111
- { base_name: :tasks, keys: { id: :user_id }, combine_type: :many }
112
- ]
113
- ]
114
- ],
115
- base_name: :users
116
- ]
117
- )
118
- end
119
-
120
- it 'returns valid ast for a wrapped relation' do
121
- relation = tags.wrap_parent(task: tasks)
122
-
123
- expect(relation.to_ast).to eql(
124
- [
125
- :relation, :tags, [
126
- :header, [
127
- [:attribute, :id],
128
- [:attribute, :task_id],
129
- [:attribute, :name],
130
- [
131
- :relation, :task, [
132
- :header, [
133
- [:attribute, :id],
134
- [:attribute, :user_id],
135
- [:attribute, :title]
136
- ]
137
- ],
138
- { base_name: :tasks, keys: { id: :task_id }, wrap: true }
139
- ]
140
- ]
141
- ],
142
- base_name: :tags
143
- ]
144
- )
145
- end
146
- end
147
- end