db-model 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/db/model/cache.rb +65 -0
- data/lib/db/model/countable.rb +48 -0
- data/lib/db/model/deletable.rb +35 -0
- data/lib/db/model/record.rb +267 -0
- data/lib/db/model/relation.rb +160 -0
- data/lib/db/model/schema.rb +46 -0
- data/lib/db/model/scope.rb +57 -0
- data/lib/db/model/statement/assignment.rb +50 -0
- data/lib/db/model/statement/clause.rb +39 -0
- data/lib/db/model/statement/count.rb +45 -0
- data/lib/db/model/statement/delete.rb +56 -0
- data/lib/db/model/statement/equal.rb +55 -0
- data/lib/db/model/statement/fields.rb +44 -0
- data/lib/db/model/statement/insert.rb +73 -0
- data/lib/db/model/statement/join.rb +47 -0
- data/lib/db/model/statement/limit.rb +52 -0
- data/lib/db/model/statement/literal.rb +47 -0
- data/lib/db/model/statement/multiple.rb +46 -0
- data/lib/db/model/statement/predicate.rb +221 -0
- data/lib/db/model/statement/replace.rb +62 -0
- data/lib/db/model/statement/select.rb +99 -0
- data/lib/db/model/statement/truncate.rb +45 -0
- data/lib/db/model/statement/tuple.rb +58 -0
- data/lib/db/model/statement/update.rb +54 -0
- data/lib/db/model/table.rb +38 -0
- data/lib/db/model/version.rb +27 -0
- data/lib/db/model/where.rb +93 -0
- data/lib/db/model.rb +25 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4961d9ec72ec4c904a5d2e31edc7348a54f3566f943bbff050221c8f3a74c308
|
4
|
+
data.tar.gz: 0fd96962a1344ba0523bd1a1092c1c53f52217d457596d0d9099346aaa7136e0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fc5eeb9b36dc16a1fd698e9ffdc7eba85c9eb88a94a1cc1f7804c734a39677b663ce7f2544e78023b6dee5f95d104d76ca9cac1c954dda7017fab94dd4c4231a
|
7
|
+
data.tar.gz: d7a0b177c3956a6cb016094d2031220b1881bc7341f9440fa1aa88b7eb3e9edc6f953d28da556e5dd60e4fb061448e508a2c84e5c0c5a89266b7a7cffb6b338c
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'statement/select'
|
24
|
+
require_relative 'statement/count'
|
25
|
+
|
26
|
+
module DB
|
27
|
+
module Model
|
28
|
+
class Cache
|
29
|
+
def initialize
|
30
|
+
@relations = {}
|
31
|
+
@records = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def empty?
|
35
|
+
@relations.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def size
|
39
|
+
@relations.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch(key)
|
43
|
+
@relations.fetch(key) do
|
44
|
+
deduplicate(yield)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(key, records)
|
49
|
+
@relations[key] = records
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
"\#<#{self.class} #{@relations.size} relations; #{@records.size} records>"
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def deduplicate(records)
|
59
|
+
records.map do |record|
|
60
|
+
@records[record] = record
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'statement/select'
|
24
|
+
require_relative 'statement/count'
|
25
|
+
|
26
|
+
module DB
|
27
|
+
module Model
|
28
|
+
module Countable
|
29
|
+
def count(fields = Statement::Count::ALL)
|
30
|
+
Statement::Select.new(@model,
|
31
|
+
fields: fields,
|
32
|
+
where: self.predicate,
|
33
|
+
).to_sql(@context).call do |connection|
|
34
|
+
result = connection.next_result
|
35
|
+
|
36
|
+
row = result.to_a.first
|
37
|
+
|
38
|
+
# Return the count:
|
39
|
+
return row.first
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def empty?
|
44
|
+
self.count == 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'statement/delete'
|
24
|
+
|
25
|
+
module DB
|
26
|
+
module Model
|
27
|
+
module Deletable
|
28
|
+
def delete
|
29
|
+
Statement::Delete.new(@model,
|
30
|
+
where: self.predicate,
|
31
|
+
).call(@context)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'statement/select'
|
24
|
+
require_relative 'statement/equal'
|
25
|
+
require_relative 'statement/limit'
|
26
|
+
|
27
|
+
require_relative 'statement/insert'
|
28
|
+
require_relative 'statement/multiple'
|
29
|
+
require_relative 'statement/fields'
|
30
|
+
require_relative 'statement/tuple'
|
31
|
+
|
32
|
+
require_relative 'statement/update'
|
33
|
+
require_relative 'statement/assignment'
|
34
|
+
|
35
|
+
require_relative 'scope'
|
36
|
+
require_relative 'where'
|
37
|
+
|
38
|
+
module DB
|
39
|
+
module Model
|
40
|
+
module Record
|
41
|
+
class SerializationError < RuntimeError
|
42
|
+
end
|
43
|
+
|
44
|
+
module Base
|
45
|
+
def self.extended(klass)
|
46
|
+
klass.instance_variable_set(:@properties, {})
|
47
|
+
klass.instance_variable_set(:@relationships, {})
|
48
|
+
|
49
|
+
klass.instance_variable_set(:@key_columns, [:id].freeze)
|
50
|
+
|
51
|
+
default_type = klass.name.split('::').last.gsub(/(.)([A-Z])/,'\1_\2').downcase!.to_sym
|
52
|
+
klass.instance_variable_set(:@type, default_type)
|
53
|
+
end
|
54
|
+
|
55
|
+
attr :type
|
56
|
+
attr :properties
|
57
|
+
attr :relationships
|
58
|
+
attr :key_columns
|
59
|
+
|
60
|
+
def primary_key
|
61
|
+
@key_columns.map do |name|
|
62
|
+
DB::Identifier[@type, name]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Directly create one record.
|
67
|
+
def create(context, **attributes)
|
68
|
+
Statement::Insert.new(self,
|
69
|
+
Statement::Fields.new(attributes.keys),
|
70
|
+
Statement::Tuple.new(attributes.values)
|
71
|
+
).to_a(context).first
|
72
|
+
end
|
73
|
+
|
74
|
+
# Directly insert one or more records.
|
75
|
+
def insert(context, keys, rows, **attributes)
|
76
|
+
if attributes.any?
|
77
|
+
fields = Statement::Fields.new(attributes.keys + keys)
|
78
|
+
values = attributes.values
|
79
|
+
tuples = rows.map{|row| Statement::Tuple.new(values + row)}
|
80
|
+
else
|
81
|
+
fields = Statement::Fields.new(keys)
|
82
|
+
tuples = rows.map{|row| Statement::Tuple.new(row)}
|
83
|
+
end
|
84
|
+
|
85
|
+
return Statement::Insert.new(self, fields, Statement::Multiple.new(tuples)).to_a(context)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Find records which match the given primary key.
|
89
|
+
def find(context, *key)
|
90
|
+
Statement::Select.new(self,
|
91
|
+
where: find_predicate(*key),
|
92
|
+
limit: Statement::Limit::ONE
|
93
|
+
).to_a(context).first
|
94
|
+
end
|
95
|
+
|
96
|
+
def find_predicate(*key)
|
97
|
+
Statement::Equal.new(self, self.primary_key, key)
|
98
|
+
end
|
99
|
+
|
100
|
+
def where(context, *arguments, **options, &block)
|
101
|
+
Where.new(context, self, *arguments, **options, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def property(name, klass = nil)
|
105
|
+
if @properties.key?(name)
|
106
|
+
raise ArgumentError.new("Property #{name.inspect} already defined!")
|
107
|
+
end
|
108
|
+
|
109
|
+
@properties[name] = klass
|
110
|
+
|
111
|
+
if klass
|
112
|
+
self.define_method(name) do
|
113
|
+
if @changed&.key?(name)
|
114
|
+
return @changed[name]
|
115
|
+
elsif @attributes.key?(name)
|
116
|
+
value = @attributes[name]
|
117
|
+
klass.load(value)
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
else
|
123
|
+
self.define_method(name) do
|
124
|
+
if @changed&.key?(name)
|
125
|
+
return @changed[name]
|
126
|
+
else
|
127
|
+
@attributes[name]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
self.define_method(:"#{name}=") do |value|
|
133
|
+
@changed ||= Hash.new
|
134
|
+
@changed[name] = value
|
135
|
+
end
|
136
|
+
|
137
|
+
self.define_method(:"#{name}?") do
|
138
|
+
value = self.send(name)
|
139
|
+
|
140
|
+
if !value
|
141
|
+
false
|
142
|
+
elsif value.respond_to?(:empty?)
|
143
|
+
!value.empty?
|
144
|
+
else
|
145
|
+
true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.included(klass)
|
152
|
+
klass.extend(Base)
|
153
|
+
end
|
154
|
+
|
155
|
+
def initialize(context, attributes, cache = nil)
|
156
|
+
@context = context
|
157
|
+
@attributes = attributes
|
158
|
+
@changed = changed
|
159
|
+
@cache = cache
|
160
|
+
end
|
161
|
+
|
162
|
+
attr :context
|
163
|
+
attr :attributes
|
164
|
+
attr :changed
|
165
|
+
attr :cache
|
166
|
+
|
167
|
+
def to_s
|
168
|
+
"\#<#{self.class.type} #{@attributes.inspect}>"
|
169
|
+
end
|
170
|
+
|
171
|
+
def inspect
|
172
|
+
if @changed&.any?
|
173
|
+
"\#<#{self.class.type} #{@attributes.inspect} changed=#{@changed.inspect}>"
|
174
|
+
else
|
175
|
+
to_s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def assign(changed)
|
180
|
+
@changed = changed
|
181
|
+
|
182
|
+
return self
|
183
|
+
end
|
184
|
+
|
185
|
+
def reload(context = @context)
|
186
|
+
if key = self.persisted?
|
187
|
+
self.class.find(context, *key)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# A record which has a valid primary key is considered to be persisted.
|
192
|
+
def persisted?
|
193
|
+
self.class.key_columns.map do |field|
|
194
|
+
@attributes[field] or return false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def new_record?
|
199
|
+
!persisted?
|
200
|
+
end
|
201
|
+
|
202
|
+
def save(context: @context)
|
203
|
+
return unless attributes = self.flatten!
|
204
|
+
|
205
|
+
if key = persisted?
|
206
|
+
statement = Statement::Update.new(self.class,
|
207
|
+
Statement::Assignment.new(attributes),
|
208
|
+
self.class.find_predicate(*key)
|
209
|
+
)
|
210
|
+
else
|
211
|
+
statement = Statement::Insert.new(self.class,
|
212
|
+
Statement::Fields.new(attributes.keys),
|
213
|
+
Statement::Tuple.new(attributes.values)
|
214
|
+
)
|
215
|
+
end
|
216
|
+
|
217
|
+
statement.call(@context) do |attributes|
|
218
|
+
# Only insert will hit this code path:
|
219
|
+
@attributes.update(attributes)
|
220
|
+
end
|
221
|
+
|
222
|
+
return self
|
223
|
+
end
|
224
|
+
|
225
|
+
def scope(model, attributes)
|
226
|
+
Scope.new(@context, model, attributes, @cache)
|
227
|
+
end
|
228
|
+
|
229
|
+
def eql?(other)
|
230
|
+
return false unless self.class.eql?(other.class)
|
231
|
+
return false unless key = self.persisted?
|
232
|
+
|
233
|
+
return key.eql?(other.persisted?)
|
234
|
+
end
|
235
|
+
|
236
|
+
def hash
|
237
|
+
if key = self.persisted?
|
238
|
+
key.hash
|
239
|
+
else
|
240
|
+
raise KeyError, "Record is not persisted!"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
|
246
|
+
# Moves values from `@changed` into `@attributes`.
|
247
|
+
def flatten!
|
248
|
+
return nil unless @changed&.any?
|
249
|
+
|
250
|
+
properties = self.class.properties
|
251
|
+
changed = {}
|
252
|
+
|
253
|
+
@changed.each do |key, value|
|
254
|
+
if klass = properties[key]
|
255
|
+
value = klass.dump(value)
|
256
|
+
end
|
257
|
+
|
258
|
+
changed[key] = @attributes[key] = value
|
259
|
+
end
|
260
|
+
|
261
|
+
@changed = nil
|
262
|
+
|
263
|
+
return changed
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'countable'
|
24
|
+
require_relative 'deletable'
|
25
|
+
|
26
|
+
module DB
|
27
|
+
module Model
|
28
|
+
class Relation
|
29
|
+
def initialize(context, model, cache = nil)
|
30
|
+
@context = context
|
31
|
+
@model = model
|
32
|
+
@cache = cache
|
33
|
+
|
34
|
+
@select = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
attr :context
|
38
|
+
attr :model
|
39
|
+
attr :cache
|
40
|
+
|
41
|
+
include Countable, Deletable
|
42
|
+
|
43
|
+
def create(**attributes)
|
44
|
+
self.new(**attributes).save
|
45
|
+
end
|
46
|
+
|
47
|
+
def new(**attributes)
|
48
|
+
@model.new(@context, {}, @cache).assign(attributes)
|
49
|
+
end
|
50
|
+
|
51
|
+
def insert(keys, rows, **attributes)
|
52
|
+
@model.insert(@context, keys, rows, **attributes)
|
53
|
+
end
|
54
|
+
|
55
|
+
def find(*key)
|
56
|
+
if predicate = self.predicate
|
57
|
+
predicate = predicate & @model.find_predicate(*key)
|
58
|
+
else
|
59
|
+
predicate = @model.find_predicate(*key)
|
60
|
+
end
|
61
|
+
|
62
|
+
return Statement::Select.new(@model,
|
63
|
+
where: predicate,
|
64
|
+
limit: Statement::Limit::ONE
|
65
|
+
).to_a(@context).first
|
66
|
+
end
|
67
|
+
|
68
|
+
def where(*arguments, **options, &block)
|
69
|
+
where = @model.where(@context, *arguments, **options, &block)
|
70
|
+
|
71
|
+
if predicate = self.predicate
|
72
|
+
where.predicate &= predicate
|
73
|
+
end
|
74
|
+
|
75
|
+
return where
|
76
|
+
end
|
77
|
+
|
78
|
+
def predicate
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def preload(name)
|
83
|
+
@cache ||= Cache.new
|
84
|
+
|
85
|
+
scopes = []
|
86
|
+
self.each do |record|
|
87
|
+
scopes << record.send(name)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Build a buffer of queries:
|
91
|
+
query = @context.query
|
92
|
+
first = true
|
93
|
+
|
94
|
+
scopes.each do |scope|
|
95
|
+
query.clause(";") unless first
|
96
|
+
first = false
|
97
|
+
|
98
|
+
scope.select.append_to(query)
|
99
|
+
end
|
100
|
+
|
101
|
+
query.call do |connection|
|
102
|
+
scopes.each do |scope|
|
103
|
+
result = connection.next_result
|
104
|
+
scope.update_cache(result)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
return self
|
109
|
+
end
|
110
|
+
|
111
|
+
def each(cache: @cache, &block)
|
112
|
+
if @cache
|
113
|
+
@cache.fetch(self.cache_key) do
|
114
|
+
self.select.to_a(@context, @cache)
|
115
|
+
end.each(&block)
|
116
|
+
else
|
117
|
+
self.select.each(@context, &block)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def first(count = nil)
|
122
|
+
if count
|
123
|
+
self.select.first(@context, count, @cache)
|
124
|
+
else
|
125
|
+
self.select.first(@context, 1, @cache).first
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_a
|
130
|
+
records = []
|
131
|
+
|
132
|
+
self.each do |record|
|
133
|
+
records << record
|
134
|
+
end
|
135
|
+
|
136
|
+
return records
|
137
|
+
end
|
138
|
+
|
139
|
+
def cache_key
|
140
|
+
[@model, self.predicate]
|
141
|
+
end
|
142
|
+
|
143
|
+
def update_cache(result)
|
144
|
+
@cache.update(self.cache_key, self.select.apply(@context, result))
|
145
|
+
end
|
146
|
+
|
147
|
+
def select
|
148
|
+
@select ||= Statement::Select.new(@model, where: self.predicate)
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
"\#<#{self.class} #{@model}>"
|
153
|
+
end
|
154
|
+
|
155
|
+
def inspect
|
156
|
+
to_s
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'table'
|
24
|
+
require_relative 'cache'
|
25
|
+
|
26
|
+
module DB
|
27
|
+
module Model
|
28
|
+
module Schema
|
29
|
+
def initialize(context, cache: Cache.new)
|
30
|
+
@context = context
|
31
|
+
@cache = cache
|
32
|
+
end
|
33
|
+
|
34
|
+
attr :context
|
35
|
+
attr :cache
|
36
|
+
|
37
|
+
def table(model)
|
38
|
+
Table.new(@context, model, @cache)
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"\#<#{self.class} #{@context.class} cache=#{@cache}>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'relation'
|
24
|
+
|
25
|
+
module DB
|
26
|
+
module Model
|
27
|
+
class Scope < Relation
|
28
|
+
def initialize(context, model, attributes, cache = nil)
|
29
|
+
super(context, model, cache)
|
30
|
+
|
31
|
+
@attributes = attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
attr :attributes
|
35
|
+
|
36
|
+
def new(**attributes)
|
37
|
+
@model.new(@context, {}, @cache).assign(**@attributes.merge(attributes))
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert(keys, rows, **attributes)
|
41
|
+
@model.insert(@context, keys, rows, **@attributes.merge(attributes))
|
42
|
+
end
|
43
|
+
|
44
|
+
def predicate
|
45
|
+
Statement::Equal.new(@model, @attributes.keys, @attributes.values)
|
46
|
+
end
|
47
|
+
|
48
|
+
def cache_key
|
49
|
+
[@model, @attributes]
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"\#<#{self.class} #{@model} #{@attributes}>"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|