db-model 0.3.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.
- 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
|