db-model 0.3.0

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