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