rom-relation 0.1.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.
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +21 -0
- data/.yardopts +4 -0
- data/Gemfile +24 -0
- data/Gemfile.devtools +55 -0
- data/Guardfile +25 -0
- data/LICENSE +20 -0
- data/README.md +21 -0
- data/Rakefile +4 -0
- data/TODO.md +4 -0
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +8 -0
- data/config/reek.yml +97 -0
- data/config/rubocop.yml +41 -0
- data/lib/rom/environment.rb +133 -0
- data/lib/rom/mapping/definition.rb +127 -0
- data/lib/rom/mapping.rb +81 -0
- data/lib/rom/relation.rb +339 -0
- data/lib/rom/repository.rb +62 -0
- data/lib/rom/schema/definition/relation/base.rb +25 -0
- data/lib/rom/schema/definition/relation.rb +44 -0
- data/lib/rom/schema/definition.rb +82 -0
- data/lib/rom/schema.rb +49 -0
- data/lib/rom/support/axiom/adapter/data_objects.rb +39 -0
- data/lib/rom/support/axiom/adapter/memory.rb +25 -0
- data/lib/rom/support/axiom/adapter/postgres.rb +19 -0
- data/lib/rom/support/axiom/adapter/sqlite3.rb +19 -0
- data/lib/rom/support/axiom/adapter.rb +100 -0
- data/lib/rom/version.rb +7 -0
- data/lib/rom-relation.rb +45 -0
- data/rom-relation.gemspec +26 -0
- data/spec/integration/environment_setup_spec.rb +22 -0
- data/spec/integration/mapping_relations_spec.rb +64 -0
- data/spec/integration/schema_definition_spec.rb +94 -0
- data/spec/shared/unit/environment_context.rb +6 -0
- data/spec/shared/unit/relation_context.rb +25 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/helper.rb +17 -0
- data/spec/support/test_mapper.rb +23 -0
- data/spec/unit/rom/environment/class_methods/setup_spec.rb +25 -0
- data/spec/unit/rom/environment/element_reader_spec.rb +23 -0
- data/spec/unit/rom/environment/mapping_spec.rb +26 -0
- data/spec/unit/rom/environment/repository_spec.rb +21 -0
- data/spec/unit/rom/environment/schema_spec.rb +33 -0
- data/spec/unit/rom/mapping/class_methods/build_spec.rb +77 -0
- data/spec/unit/rom/relation/class_methods/build_spec.rb +19 -0
- data/spec/unit/rom/relation/delete_spec.rb +15 -0
- data/spec/unit/rom/relation/drop_spec.rb +11 -0
- data/spec/unit/rom/relation/each_spec.rb +23 -0
- data/spec/unit/rom/relation/first_spec.rb +19 -0
- data/spec/unit/rom/relation/inject_mapper_spec.rb +17 -0
- data/spec/unit/rom/relation/insert_spec.rb +13 -0
- data/spec/unit/rom/relation/last_spec.rb +19 -0
- data/spec/unit/rom/relation/one_spec.rb +49 -0
- data/spec/unit/rom/relation/replace_spec.rb +13 -0
- data/spec/unit/rom/relation/restrict_spec.rb +25 -0
- data/spec/unit/rom/relation/sort_by_spec.rb +25 -0
- data/spec/unit/rom/relation/take_spec.rb +11 -0
- data/spec/unit/rom/relation/to_a_spec.rb +20 -0
- data/spec/unit/rom/relation/update_spec.rb +25 -0
- data/spec/unit/rom/repository/class_methods/build_spec.rb +27 -0
- data/spec/unit/rom/repository/element_reader_spec.rb +21 -0
- data/spec/unit/rom/repository/element_writer_spec.rb +18 -0
- data/spec/unit/rom/schema/class_methods/build_spec.rb +103 -0
- metadata +249 -0
data/lib/rom/mapping.rb
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
|
|
5
|
+
# Builder DSL for ROM relations
|
|
6
|
+
#
|
|
7
|
+
class Mapping
|
|
8
|
+
include Adamantium::Flat
|
|
9
|
+
|
|
10
|
+
attr_reader :environment, :schema, :model
|
|
11
|
+
private :environment, :schema, :model
|
|
12
|
+
|
|
13
|
+
# Build ROM relations
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# relation = Axiom::Relation::Base.new(:users, [[:id, Integer], [:user_name, String]])
|
|
17
|
+
# env = { users: relation }
|
|
18
|
+
#
|
|
19
|
+
# User = Class.new(OpenStruct.new)
|
|
20
|
+
#
|
|
21
|
+
# registry = Mapping.build(env) do
|
|
22
|
+
# users do
|
|
23
|
+
# map :id
|
|
24
|
+
# map :user_name, to: :name
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# registry[:users]
|
|
29
|
+
# # #<ROM::Relation:0x000000025d3160>
|
|
30
|
+
#
|
|
31
|
+
# @param [Environment] rom environment
|
|
32
|
+
# @param [Schema] rom schema
|
|
33
|
+
#
|
|
34
|
+
# @return [Hash]
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
def self.build(environment, schema, &block)
|
|
38
|
+
new(environment, schema, &block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Initialize a new mapping instance
|
|
42
|
+
#
|
|
43
|
+
# @return [undefined]
|
|
44
|
+
#
|
|
45
|
+
# @api private
|
|
46
|
+
def initialize(environment, schema, &block)
|
|
47
|
+
@environment = environment
|
|
48
|
+
@schema = schema
|
|
49
|
+
instance_eval(&block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Method missing hook
|
|
55
|
+
#
|
|
56
|
+
# @return [Relation]
|
|
57
|
+
#
|
|
58
|
+
# @api private
|
|
59
|
+
def method_missing(name, *, &block)
|
|
60
|
+
relation = schema[name]
|
|
61
|
+
|
|
62
|
+
if relation
|
|
63
|
+
build_relation(relation, &block)
|
|
64
|
+
else
|
|
65
|
+
super
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Build relation
|
|
70
|
+
#
|
|
71
|
+
# @return [Relation]
|
|
72
|
+
#
|
|
73
|
+
# @api private
|
|
74
|
+
def build_relation(relation, &block)
|
|
75
|
+
definition = Definition.build(relation.header, &block)
|
|
76
|
+
environment[relation.name] = Relation.build(relation, definition.mapper)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end # Mapping
|
|
80
|
+
|
|
81
|
+
end # ROM
|
data/lib/rom/relation.rb
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
|
|
5
|
+
# Enhanced ROM relation wrapping axiom relation and using injected mapper to
|
|
6
|
+
# load/dump tuples/objects
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
#
|
|
10
|
+
# # set up an axiom relation
|
|
11
|
+
# header = [[:id, Integer], [:name, String]]
|
|
12
|
+
# data = [[1, 'John'], [2, 'Jane']]
|
|
13
|
+
# axiom = Axiom::Relation.new(header, data)
|
|
14
|
+
#
|
|
15
|
+
# # provide a simple mapper
|
|
16
|
+
# class Mapper < Struct.new(:header)
|
|
17
|
+
# def load(tuple)
|
|
18
|
+
# data = header.map { |attribute|
|
|
19
|
+
# [attribute.name, tuple[attribute.name]]
|
|
20
|
+
# }
|
|
21
|
+
# Hash[data]
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def dump(hash)
|
|
25
|
+
# header.each_with_object([]) { |attribute, tuple|
|
|
26
|
+
# tuple << hash[attribute.name]
|
|
27
|
+
# }
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # wrap axiom relation with ROM relation
|
|
32
|
+
# mapper = Mapper.new(axiom.header)
|
|
33
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
34
|
+
#
|
|
35
|
+
# # relation is an enumerable and it uses mapper to load/dump tuples/objects
|
|
36
|
+
# relation.to_a
|
|
37
|
+
# # => [{:id=>1, :name=>'John'}, {:id=>2, :name=>'Jane'}]
|
|
38
|
+
#
|
|
39
|
+
# # you can insert/update/delete objects
|
|
40
|
+
# relation.insert(id: 3, name: 'Piotr').to_a
|
|
41
|
+
# # => [{:id=>1, :name=>"John"}, {:id=>2, :name=>"Jane"}, {:id=>3, :name=>"Piotr"}]
|
|
42
|
+
#
|
|
43
|
+
# relation.delete(id: 1, name: 'John').to_a
|
|
44
|
+
# # => [{:id=>2, :name=>"Jane"}]
|
|
45
|
+
#
|
|
46
|
+
class Relation
|
|
47
|
+
include Enumerable, Concord::Public.new(:relation, :mapper)
|
|
48
|
+
|
|
49
|
+
# Build a new relation
|
|
50
|
+
#
|
|
51
|
+
# @param [Axiom::Relation]
|
|
52
|
+
# @param [Object] mapper
|
|
53
|
+
#
|
|
54
|
+
# @return [Relation]
|
|
55
|
+
#
|
|
56
|
+
# @api public
|
|
57
|
+
def self.build(relation, mapper)
|
|
58
|
+
new(mapper.call(relation), mapper)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Iterate over tuples yielded by the wrapped relation
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# mapper = Class.new {
|
|
65
|
+
# def load(value)
|
|
66
|
+
# value.to_s
|
|
67
|
+
# end
|
|
68
|
+
#
|
|
69
|
+
# def dump(value)
|
|
70
|
+
# value.to_i
|
|
71
|
+
# end
|
|
72
|
+
# }.new
|
|
73
|
+
#
|
|
74
|
+
# relation = ROM::Relation.new([1, 2, 3], mapper)
|
|
75
|
+
#
|
|
76
|
+
# relation.each do |value|
|
|
77
|
+
# puts value # => '1'
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
# @yieldparam [Object]
|
|
81
|
+
#
|
|
82
|
+
# @return [Relation]
|
|
83
|
+
#
|
|
84
|
+
# @api public
|
|
85
|
+
def each
|
|
86
|
+
return to_enum unless block_given?
|
|
87
|
+
relation.each { |tuple| yield(mapper.load(tuple)) }
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Insert an object into relation
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
95
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
96
|
+
#
|
|
97
|
+
# relation.insert(id: 3)
|
|
98
|
+
# relation.to_a # => [[1], [2], [3]]
|
|
99
|
+
#
|
|
100
|
+
# @param [Object]
|
|
101
|
+
#
|
|
102
|
+
# @return [Relation]
|
|
103
|
+
#
|
|
104
|
+
# @api public
|
|
105
|
+
def insert(object)
|
|
106
|
+
new(relation.insert([mapper.dump(object)]))
|
|
107
|
+
end
|
|
108
|
+
alias_method :<<, :insert
|
|
109
|
+
|
|
110
|
+
# Update an object
|
|
111
|
+
#
|
|
112
|
+
# @example
|
|
113
|
+
# data = [[1, 'John'], [2, 'Jane']]
|
|
114
|
+
# axiom = Axiom::Relation.new([[:id, Integer], [:name, String]], data)
|
|
115
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
116
|
+
#
|
|
117
|
+
# relation.update({id: 2, name: 'Jane Doe'}, {id:2, name: 'Jane'})
|
|
118
|
+
# relation.to_a # => [[1, 'John'], [2, 'Jane Doe']]
|
|
119
|
+
#
|
|
120
|
+
# @param [Object]
|
|
121
|
+
# @param [Hash] original attributes
|
|
122
|
+
#
|
|
123
|
+
# @return [Relation]
|
|
124
|
+
#
|
|
125
|
+
# @api public
|
|
126
|
+
def update(object, original_tuple)
|
|
127
|
+
new(relation.delete([original_tuple]).insert([mapper.dump(object)]))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Delete an object from the relation
|
|
131
|
+
#
|
|
132
|
+
# @example
|
|
133
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
134
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
135
|
+
#
|
|
136
|
+
# relation.delete(id: 1)
|
|
137
|
+
# relation.to_a # => [[2]]
|
|
138
|
+
#
|
|
139
|
+
# @param [Object]
|
|
140
|
+
#
|
|
141
|
+
# @return [Relation]
|
|
142
|
+
#
|
|
143
|
+
# @api public
|
|
144
|
+
def delete(object)
|
|
145
|
+
new(relation.delete([mapper.dump(object)]))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Replace all objects in the relation with new ones
|
|
149
|
+
#
|
|
150
|
+
# @example
|
|
151
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
152
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
153
|
+
#
|
|
154
|
+
# relation.replace([{id: 3}, {id: 4}])
|
|
155
|
+
# relation.to_a # => [[3], [4]]
|
|
156
|
+
#
|
|
157
|
+
# @param [Array<Object>]
|
|
158
|
+
#
|
|
159
|
+
# @return [Relation]
|
|
160
|
+
#
|
|
161
|
+
# @api public
|
|
162
|
+
def replace(objects)
|
|
163
|
+
new(relation.replace(objects.map(&mapper.method(:dump))))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Restrict the relation
|
|
167
|
+
#
|
|
168
|
+
# @example
|
|
169
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
170
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
171
|
+
#
|
|
172
|
+
# relation.restrict(id: 2).to_a # => [[2]]
|
|
173
|
+
#
|
|
174
|
+
# @param [Hash] conditions
|
|
175
|
+
#
|
|
176
|
+
# @return [Relation]
|
|
177
|
+
#
|
|
178
|
+
# @api public
|
|
179
|
+
def restrict(*args, &block)
|
|
180
|
+
new(relation.restrict(*args, &block))
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Take objects form the relation with provided limit
|
|
184
|
+
#
|
|
185
|
+
# @example
|
|
186
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
187
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
188
|
+
#
|
|
189
|
+
# relation.take(2).to_a # => [[2]]
|
|
190
|
+
#
|
|
191
|
+
# @param [Integer] limit
|
|
192
|
+
#
|
|
193
|
+
# @return [Relation]
|
|
194
|
+
#
|
|
195
|
+
# @api public
|
|
196
|
+
def take(limit)
|
|
197
|
+
new(sorted.take(limit))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Take first n-objects from the relation
|
|
201
|
+
#
|
|
202
|
+
# @example
|
|
203
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
204
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
205
|
+
#
|
|
206
|
+
# relation.first.to_a # => [[1]]
|
|
207
|
+
# relation.first(2).to_a # => [[1], [2]]
|
|
208
|
+
#
|
|
209
|
+
# @param [Integer]
|
|
210
|
+
#
|
|
211
|
+
# @return [Relation]
|
|
212
|
+
#
|
|
213
|
+
# @api public
|
|
214
|
+
def first(limit = 1)
|
|
215
|
+
new(sorted.first(limit))
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Take last n-objects from the relation
|
|
219
|
+
#
|
|
220
|
+
# @example
|
|
221
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
222
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
223
|
+
#
|
|
224
|
+
# relation.last.to_a # => [[2]]
|
|
225
|
+
# relation.last(2).to_a # => [[1], [2]]
|
|
226
|
+
#
|
|
227
|
+
# @param [Integer] limit
|
|
228
|
+
#
|
|
229
|
+
# @return [Relation]
|
|
230
|
+
#
|
|
231
|
+
# @api public
|
|
232
|
+
def last(limit = 1)
|
|
233
|
+
new(sorted.last(limit))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Drop objects from the relation by the given offset
|
|
237
|
+
#
|
|
238
|
+
# @example
|
|
239
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[1], [2]])
|
|
240
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
241
|
+
#
|
|
242
|
+
# relation.drop(1).to_a # => [[2]]
|
|
243
|
+
#
|
|
244
|
+
# @param [Integer]
|
|
245
|
+
#
|
|
246
|
+
# @return [Relation]
|
|
247
|
+
#
|
|
248
|
+
# @api public
|
|
249
|
+
def drop(offset)
|
|
250
|
+
new(sorted.drop(offset))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Sort the relation by provided attributes
|
|
254
|
+
#
|
|
255
|
+
# @example
|
|
256
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [[2], [1]])
|
|
257
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
258
|
+
#
|
|
259
|
+
# relation.sort_by(:id).to_a # => [[1], [2]]
|
|
260
|
+
#
|
|
261
|
+
# @param [Array<Symbol>]
|
|
262
|
+
#
|
|
263
|
+
# @return [Relation]
|
|
264
|
+
#
|
|
265
|
+
# @api public
|
|
266
|
+
def sort_by(*args, &block)
|
|
267
|
+
new(relation.sort_by(*args, &block))
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Return exactly one object matching criteria or raise an error
|
|
271
|
+
#
|
|
272
|
+
# @example
|
|
273
|
+
# axiom = Axiom::Relation.new([[:id, Integer]], [1]])
|
|
274
|
+
# relation = ROM::Relation.new(axiom, mapper)
|
|
275
|
+
#
|
|
276
|
+
# relation.one.to_a # => {id: 1}
|
|
277
|
+
#
|
|
278
|
+
# @param [Proc] block
|
|
279
|
+
# optional block to call in case no tuple is returned
|
|
280
|
+
#
|
|
281
|
+
# @return [Object]
|
|
282
|
+
#
|
|
283
|
+
# @raise NoTuplesError
|
|
284
|
+
# if no tuples were returned
|
|
285
|
+
#
|
|
286
|
+
# @raise ManyTuplesError
|
|
287
|
+
# if more than one tuple was returned
|
|
288
|
+
#
|
|
289
|
+
# @api public
|
|
290
|
+
def one(&block)
|
|
291
|
+
block ||= ->() { raise NoTuplesError }
|
|
292
|
+
tuples = take(2).to_a
|
|
293
|
+
|
|
294
|
+
if tuples.count > 1
|
|
295
|
+
raise ManyTuplesError
|
|
296
|
+
else
|
|
297
|
+
tuples.first || block.call
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Inject a new mapper into this relation
|
|
302
|
+
#
|
|
303
|
+
# @example
|
|
304
|
+
#
|
|
305
|
+
# relation = ROM::Relation.new([], mapper)
|
|
306
|
+
# relation.inject_mapper(new_mapper)
|
|
307
|
+
#
|
|
308
|
+
# @param [Object] a mapper object
|
|
309
|
+
#
|
|
310
|
+
# @return [Relation]
|
|
311
|
+
#
|
|
312
|
+
# @api public
|
|
313
|
+
def inject_mapper(mapper)
|
|
314
|
+
new(relation, mapper)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
private
|
|
318
|
+
|
|
319
|
+
# Sort wrapped relation using all attributes in the header
|
|
320
|
+
#
|
|
321
|
+
# @return [Axiom::Relation]
|
|
322
|
+
#
|
|
323
|
+
# @api private
|
|
324
|
+
def sorted
|
|
325
|
+
relation.sort
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Return new relation instance
|
|
329
|
+
#
|
|
330
|
+
# @return [Relation]
|
|
331
|
+
#
|
|
332
|
+
# @api private
|
|
333
|
+
def new(new_relation, new_mapper = mapper)
|
|
334
|
+
self.class.new(new_relation, new_mapper)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
end # class Relation
|
|
338
|
+
|
|
339
|
+
end # module ROM
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
|
|
5
|
+
# A repository with a given +name+ and +adapter+
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class Repository
|
|
9
|
+
include Concord.new(:name, :adapter, :relations)
|
|
10
|
+
|
|
11
|
+
# Build a repository with a given +name+ and +uri+
|
|
12
|
+
#
|
|
13
|
+
# @param [Symbol] name
|
|
14
|
+
# the repository's name
|
|
15
|
+
#
|
|
16
|
+
# @param [Addressable::URI] uri
|
|
17
|
+
# the uri for initializing the adapter
|
|
18
|
+
#
|
|
19
|
+
# @return [Repository]
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
def self.build(name, uri, relations = {})
|
|
23
|
+
new(name, Axiom::Adapter.build(uri), relations)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Return the relation identified by +name+
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
#
|
|
30
|
+
# repo = Repository.coerce(:test, 'in_memory://test')
|
|
31
|
+
# repo.register(:foo, [[:id, String], [:foo, String]])
|
|
32
|
+
# repo[:foo]
|
|
33
|
+
#
|
|
34
|
+
# # => <Axiom::Relation header=Axiom::Header ...>
|
|
35
|
+
#
|
|
36
|
+
# @param [Symbol] name
|
|
37
|
+
# the name of the relation
|
|
38
|
+
#
|
|
39
|
+
# @return [Axiom::Relation]
|
|
40
|
+
#
|
|
41
|
+
# @raise [KeyError]
|
|
42
|
+
#
|
|
43
|
+
# @api public
|
|
44
|
+
def [](name)
|
|
45
|
+
relations.fetch(name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Register a relation with this repository
|
|
49
|
+
#
|
|
50
|
+
# @param [Axiom::Relation::Base] relation
|
|
51
|
+
#
|
|
52
|
+
# @return [Object] relation gateway
|
|
53
|
+
#
|
|
54
|
+
# @api public
|
|
55
|
+
def []=(name, relation)
|
|
56
|
+
adapter[name] = relation
|
|
57
|
+
relations[name] = adapter[name]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end # Repository
|
|
61
|
+
|
|
62
|
+
end # ROM
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
class Schema
|
|
5
|
+
class Definition
|
|
6
|
+
class Relation
|
|
7
|
+
|
|
8
|
+
# Base relation builder object
|
|
9
|
+
#
|
|
10
|
+
class Base < self
|
|
11
|
+
|
|
12
|
+
def repository(name = Undefined)
|
|
13
|
+
if name == Undefined
|
|
14
|
+
@repository
|
|
15
|
+
else
|
|
16
|
+
@repository = name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end # Base
|
|
21
|
+
|
|
22
|
+
end # Relation
|
|
23
|
+
end # Definition
|
|
24
|
+
end # Schema
|
|
25
|
+
end # ROM
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
class Schema
|
|
5
|
+
class Definition
|
|
6
|
+
|
|
7
|
+
# Builder object for Axiom relation
|
|
8
|
+
#
|
|
9
|
+
# @private
|
|
10
|
+
class Relation
|
|
11
|
+
include Equalizer.new(:header, :keys)
|
|
12
|
+
|
|
13
|
+
# @api private
|
|
14
|
+
def initialize(&block)
|
|
15
|
+
@header = []
|
|
16
|
+
@keys = []
|
|
17
|
+
instance_eval(&block)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @api private
|
|
21
|
+
def call(name)
|
|
22
|
+
Axiom::Relation::Base.new(name, header)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
def header
|
|
27
|
+
Axiom::Relation::Header.coerce(@header, keys: @keys)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @api private
|
|
31
|
+
def attribute(name, type)
|
|
32
|
+
@header << [name, type]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
def key(*attribute_names)
|
|
37
|
+
@keys.concat(attribute_names)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end # Relation
|
|
41
|
+
|
|
42
|
+
end # Definition
|
|
43
|
+
end # Schema
|
|
44
|
+
end # ROM
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
class Schema
|
|
5
|
+
|
|
6
|
+
# Builder object used by schema DSL to establish Axiom relations
|
|
7
|
+
#
|
|
8
|
+
# @private
|
|
9
|
+
class Definition
|
|
10
|
+
include Equalizer.new(:repositories, :relations)
|
|
11
|
+
|
|
12
|
+
attr_reader :repositories, :relations
|
|
13
|
+
|
|
14
|
+
# @api private
|
|
15
|
+
def initialize(repositories, &block)
|
|
16
|
+
@repositories = repositories
|
|
17
|
+
@relations = {}
|
|
18
|
+
instance_eval(&block) if block
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Build a base relation
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
#
|
|
25
|
+
# Schema.build do
|
|
26
|
+
# base_relation :users do
|
|
27
|
+
# # ...
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @return [Definition]
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
def base_relation(name, &block)
|
|
35
|
+
builder = Relation::Base.new(&block)
|
|
36
|
+
repository = repositories.fetch(builder.repository)
|
|
37
|
+
|
|
38
|
+
repository[name] = builder.call(name)
|
|
39
|
+
relations[name] = repository[name]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Build a relation
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
#
|
|
46
|
+
# Schema.build do
|
|
47
|
+
# relation :users do
|
|
48
|
+
# # ...
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# @return [Definition]
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
def relation(name, &block)
|
|
56
|
+
relations[name] = instance_eval(&block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Return relation identified by name
|
|
60
|
+
#
|
|
61
|
+
# @return [Axiom::Relation, Axiom::Relation::Base]
|
|
62
|
+
#
|
|
63
|
+
# @api private
|
|
64
|
+
def [](name)
|
|
65
|
+
relations[name]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Method missing hook
|
|
71
|
+
#
|
|
72
|
+
# @return [Axiom::Relation, Axiom::Relation::Base]
|
|
73
|
+
#
|
|
74
|
+
# @api private
|
|
75
|
+
def method_missing(name)
|
|
76
|
+
self[name] || super
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end # Definition
|
|
80
|
+
|
|
81
|
+
end # Schema
|
|
82
|
+
end # ROM
|
data/lib/rom/schema.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module ROM
|
|
4
|
+
|
|
5
|
+
# Schema builder DSL
|
|
6
|
+
#
|
|
7
|
+
class Schema
|
|
8
|
+
include Concord.new(:definition), Adamantium::Flat
|
|
9
|
+
|
|
10
|
+
# Build a relation schema
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
#
|
|
14
|
+
# Schema.build do
|
|
15
|
+
# base_relation :users do
|
|
16
|
+
# repository :test
|
|
17
|
+
# attribute :id, :name
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @return [Schema]
|
|
22
|
+
#
|
|
23
|
+
# @api public
|
|
24
|
+
def self.build(repositories, &block)
|
|
25
|
+
new(Definition.new(repositories, &block))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Return defined relation identified by name
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
#
|
|
32
|
+
# schema[:users] # => #<Axiom::Relation::Base ..>
|
|
33
|
+
#
|
|
34
|
+
# @return [Axiom::Relation, Axiom::Relation::Base]
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
def [](name)
|
|
38
|
+
definition[name]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @api private
|
|
42
|
+
def call(&block)
|
|
43
|
+
definition.instance_eval(&block)
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end # Schema
|
|
48
|
+
|
|
49
|
+
end # ROM
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'axiom-do-adapter'
|
|
4
|
+
|
|
5
|
+
module Axiom
|
|
6
|
+
module Adapter
|
|
7
|
+
|
|
8
|
+
# Reopenend to add functionality that should eventually
|
|
9
|
+
# be puhsed down to Adapter::DataObjects proper, or whatever
|
|
10
|
+
# will be the base class.
|
|
11
|
+
#
|
|
12
|
+
class DataObjects
|
|
13
|
+
|
|
14
|
+
extend Adapter
|
|
15
|
+
|
|
16
|
+
include Equalizer.new(:uri)
|
|
17
|
+
|
|
18
|
+
# The URI this adapter uses for establishing a connection
|
|
19
|
+
#
|
|
20
|
+
# @return [Addressable::URI]
|
|
21
|
+
#
|
|
22
|
+
# @api private
|
|
23
|
+
attr_reader :uri
|
|
24
|
+
|
|
25
|
+
# Wrap the given +relation+ with a gateway
|
|
26
|
+
#
|
|
27
|
+
# @param [Axiom::Relation] relation
|
|
28
|
+
# the relation to wrap with a gateway
|
|
29
|
+
#
|
|
30
|
+
# @return [Axiom::Relation::Gateway]
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
def gateway(relation)
|
|
34
|
+
Axiom::Relation::Gateway.new(self, relation)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end # class DataObjects
|
|
38
|
+
end # module Adapter
|
|
39
|
+
end # module Axiom
|