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 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