active_postgrest 0.1.0 → 0.2.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/certs/gem-public_cert.pem +26 -0
- data/lib/active_postgrest/active_model.rb +37 -0
- data/lib/active_postgrest/base.rb +103 -7
- data/lib/active_postgrest/client.rb +32 -0
- data/lib/active_postgrest/errors.rb +14 -0
- data/lib/active_postgrest/mutations.rb +67 -0
- data/lib/active_postgrest/railtie.rb +12 -0
- data/lib/active_postgrest/relation.rb +22 -15
- data/lib/active_postgrest/sql_builder.rb +40 -0
- data/lib/active_postgrest/version.rb +1 -1
- data/lib/active_postgrest.rb +1 -0
- data.tar.gz.sig +4 -0
- metadata +81 -7
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 259774ecd63c27ab02ed5e5e0990a0743a6ca3a55d2ca9133c355961518a3c8e
|
|
4
|
+
data.tar.gz: '094b279dfe08457efaba684a08891fb70a3b1258ae96fbad3e700a5b4a4739c9'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6dfdb95b61ac389dc8c96f36400ef45a29971c18b153ad8e84ad62bb89acecc8df562cb698e6ee384d307e0293f30fd8d1b393caee729fa9e19e08bcd722606a
|
|
7
|
+
data.tar.gz: b8bb2f348ac1cb705f065456a7b9d3cdf81d56a26df6c5f067d5ccd0ecfd600f224bd26e7a5fa0aabd04ccca2b857a7ba28b9617ada655ed7a84228aff106616
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEUjCCArqgAwIBAgIBATANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDDB5ldmdl
|
|
3
|
+
bnkuc29rb2xvdi9EQz1nbWFpbC9EQz1jb20wHhcNMjYwNjE4MTE0NDQ3WhcNMjcw
|
|
4
|
+
NjE4MTE0NDQ3WjApMScwJQYDVQQDDB5ldmdlbnkuc29rb2xvdi9EQz1nbWFpbC9E
|
|
5
|
+
Qz1jb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDInkRoXT6ySd5G
|
|
6
|
+
BuDN0QUAqbWqrqAShEwA0zQIVooLu9SF5pV7MCXTU4w3b12qLX0irddo3qrFp9rz
|
|
7
|
+
c/P+gX737W8CdglkboI0lGikghH0A23V7h1omv303kVrZHEKzOklu77cpVMhVeaO
|
|
8
|
+
y6KVd4N8o4AQF/D2IKM1Fi2o3uvcj9IK38W+939YMlZrilD1NSgnbQYcVI8Pcqwr
|
|
9
|
+
hpCGO7PGo5Ns3b9TSErCfEH+DbNovRfoYPNCXtbFdVym4qSl5SyNGZEvUcw22ZfE
|
|
10
|
+
BiN5iGC6W7kvwKUBMDyRibqu+1J76rpgpz15iXxqs62QqOrXpN9Id3IlFFRuRVu3
|
|
11
|
+
LtaPfd+y3AATtUsIMTg73Wv2g4EHT0h5v2raiQ/7O8fM3aKW6n3c6p6POAgOhohR
|
|
12
|
+
F2lcsgbH55tz8lymdmgz5rke5CpZaOiHqcE7LKFKdKEDtACB3GEDfy69VqzqkcqG
|
|
13
|
+
wwwHZYbhB1+1WZCFmIVHeQEg1Mmgy7joOHzNjEgwzzcuZICnsuUCAwEAAaOBhDCB
|
|
14
|
+
gTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUzhn3dA5CJtlBTkc0
|
|
15
|
+
7hxTo2PvpiMwIwYDVR0RBBwwGoEYZXZnZW55LnNva29sb3ZAZ21haWwuY29tMCMG
|
|
16
|
+
A1UdEgQcMBqBGGV2Z2VueS5zb2tvbG92QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsF
|
|
17
|
+
AAOCAYEAadjhjeh6S9/kv7jvJ4AlyhWfO7r0xqkwpTECuJmDWKDQeIZs8yKvgsYl
|
|
18
|
+
RPss8oJZ2OVkEOnMFx4fL+RB19oTEp5ZUrtZq+Nt47Ypci93mTFOykMRyu42HQ1W
|
|
19
|
+
BEsgwGRLuloYBD+Eubghm1Mll5T2qo/jYPvs9av+Ma2bkp/NoMGxajVzBHFe39PN
|
|
20
|
+
0wP8BokxXTtexk7ybnqIePVPTvhHo/VrMMfD2SAy7o0WPT2UwWZNfjd59aaW7ruc
|
|
21
|
+
5jgdKw4qfnIXrVbISSZaovUdvCVloYOcKUFI+K4BEFFjIcfbCxrnU6GfonAEyzxJ
|
|
22
|
+
84+w5Rus55cqLKEiFIR7auVJkbLsrIHsbrH+2uJDgxgXGryUq3I/k5ZaORg2gocP
|
|
23
|
+
DvU/iWx+jPPscxukSQ+1qVLfWOYZlQ0BEqFJJrvro9lEbimK1j6aibza2InnPwTn
|
|
24
|
+
tC9NYXuv1LyPD+PZZRO4LDDSjUVx+RF/dJIE23hzgbLexpyriCMFyKGhHi2KApEl
|
|
25
|
+
eIQUsjZL
|
|
26
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2026 Evgeny Sokolov (FastJoe)
|
|
3
|
+
|
|
4
|
+
require 'active_model'
|
|
5
|
+
require 'active_postgrest'
|
|
6
|
+
|
|
7
|
+
module ActivePostgrest
|
|
8
|
+
module ActiveModelSupport
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.include ::ActiveModel::Validations
|
|
11
|
+
base.extend ::ActiveModel::Naming
|
|
12
|
+
base.include ::ActiveModel::Conversion
|
|
13
|
+
base.prepend SaveWithValidations
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read_attribute_for_validation(key)
|
|
17
|
+
@attributes[key.to_s]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns primary key value(s) for ActionView form helpers.
|
|
21
|
+
def to_key
|
|
22
|
+
pk = self.class.primary_key
|
|
23
|
+
val = @attributes[pk]
|
|
24
|
+
val ? [val] : nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module SaveWithValidations
|
|
28
|
+
def save
|
|
29
|
+
return false unless valid?
|
|
30
|
+
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
ActivePostgrest::Base.include(ActivePostgrest::ActiveModelSupport)
|
|
@@ -81,7 +81,7 @@ module ActivePostgrest
|
|
|
81
81
|
val = @attributes[key]
|
|
82
82
|
return nil if val.nil? || (val.is_a?(Array) && val.empty?)
|
|
83
83
|
|
|
84
|
-
klass.constantize.new(val.is_a?(Array) ? val.first : val)
|
|
84
|
+
klass.constantize.new(val.is_a?(Array) ? val.first : val, true)
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
define_singleton_method(:"with_#{assoc}") do |fields: []|
|
|
@@ -101,7 +101,7 @@ module ActivePostgrest
|
|
|
101
101
|
val = @attributes[assoc]
|
|
102
102
|
return nil if val.nil? || (val.is_a?(Array) && val.empty?)
|
|
103
103
|
|
|
104
|
-
klass.constantize.new(val.is_a?(Array) ? val.first : val)
|
|
104
|
+
klass.constantize.new(val.is_a?(Array) ? val.first : val, true)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
define_singleton_method(:"with_#{assoc}") do |fields: []|
|
|
@@ -117,7 +117,7 @@ module ActivePostgrest
|
|
|
117
117
|
val = @attributes[assoc]
|
|
118
118
|
return [] if val.nil?
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
(val.is_a?(Array) ? val : [val]).map { klass.constantize.new(_1, true) }
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
define_singleton_method(:"with_#{assoc}") do |fields: []|
|
|
@@ -171,14 +171,42 @@ module ActivePostgrest
|
|
|
171
171
|
def self.one? = relation.one?
|
|
172
172
|
def self.many? = relation.many?
|
|
173
173
|
|
|
174
|
-
def
|
|
174
|
+
def self.create(attrs)
|
|
175
|
+
relation.insert(attrs)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def self.create!(attrs)
|
|
179
|
+
relation.insert(attrs) || raise(RecordNotSaved.new(self, attrs))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def self.insert(attrs) = relation.insert(attrs)
|
|
183
|
+
def self.insert_all(records) = relation.insert_all(records)
|
|
184
|
+
def self.upsert(attrs) = relation.upsert(attrs)
|
|
185
|
+
def self.upsert_all(records) = relation.upsert_all(records)
|
|
186
|
+
def self.update_all(attrs) = relation.update_all(attrs)
|
|
187
|
+
def self.delete_all = relation.delete_all
|
|
188
|
+
|
|
189
|
+
def initialize(attrs = {}, persisted = false, client = nil) # rubocop:disable Style/OptionalBooleanParameter
|
|
175
190
|
types = self.class.attribute_types
|
|
176
191
|
@attributes = attrs.to_h.transform_keys(&:to_s).to_h do |k, v|
|
|
177
192
|
[k, types[k] ? cast_attribute(v, types[k]) : v]
|
|
178
193
|
end
|
|
194
|
+
@new_record = !persisted
|
|
195
|
+
@destroyed = false
|
|
196
|
+
@_client = client
|
|
179
197
|
end
|
|
180
198
|
|
|
199
|
+
def new_record? = @new_record
|
|
200
|
+
def persisted? = !@new_record && !@destroyed
|
|
201
|
+
def destroyed? = @destroyed
|
|
202
|
+
|
|
181
203
|
def [](key) = @attributes[key.to_s]
|
|
204
|
+
|
|
205
|
+
def []=(key, value)
|
|
206
|
+
str_key = key.to_s
|
|
207
|
+
type = self.class.attribute_types[str_key]
|
|
208
|
+
@attributes[str_key] = type ? cast_attribute(value, type) : value
|
|
209
|
+
end
|
|
182
210
|
attr_reader :attributes
|
|
183
211
|
|
|
184
212
|
def to_h = @attributes
|
|
@@ -187,19 +215,87 @@ module ActivePostgrest
|
|
|
187
215
|
"#<#{self.class.name} #{@attributes.map { "#{_1}: #{_2.inspect}" }.join(', ')}>"
|
|
188
216
|
end
|
|
189
217
|
|
|
190
|
-
def method_missing(name, *)
|
|
218
|
+
def method_missing(name, *args)
|
|
191
219
|
key = name.to_s
|
|
192
|
-
|
|
220
|
+
if key.end_with?('=')
|
|
221
|
+
attr = key.delete_suffix('=')
|
|
222
|
+
if @attributes.key?(attr)
|
|
223
|
+
type = self.class.attribute_types[attr]
|
|
224
|
+
return @attributes[attr] = type ? cast_attribute(args.first, type) : args.first
|
|
225
|
+
end
|
|
226
|
+
elsif @attributes.key?(key)
|
|
227
|
+
return @attributes[key]
|
|
228
|
+
end
|
|
193
229
|
|
|
194
230
|
super
|
|
195
231
|
end
|
|
196
232
|
|
|
197
233
|
def respond_to_missing?(name, include_private = false)
|
|
198
|
-
|
|
234
|
+
key = name.to_s
|
|
235
|
+
attr = key.delete_suffix('=')
|
|
236
|
+
@attributes.key?(attr) || super
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def save # rubocop:disable Naming/PredicateMethod
|
|
240
|
+
return false if @destroyed
|
|
241
|
+
|
|
242
|
+
if new_record?
|
|
243
|
+
saved = self.class.insert(@attributes)
|
|
244
|
+
return false unless saved
|
|
245
|
+
|
|
246
|
+
@attributes = saved.attributes
|
|
247
|
+
@new_record = false
|
|
248
|
+
else
|
|
249
|
+
pk = self.class.primary_key
|
|
250
|
+
pk_val = @attributes[pk]
|
|
251
|
+
raise ArgumentError, 'Cannot save a record without a primary key value' if pk_val.nil?
|
|
252
|
+
|
|
253
|
+
saved = _base_relation.where(pk => pk_val).update_all(scalar_attributes.except(pk)).first
|
|
254
|
+
return false unless saved
|
|
255
|
+
|
|
256
|
+
@attributes = saved.attributes
|
|
257
|
+
end
|
|
258
|
+
true
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def update(attrs)
|
|
262
|
+
attrs.each { |k, v| @attributes[k.to_s] = v }
|
|
263
|
+
save
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def destroy
|
|
267
|
+
pk = self.class.primary_key
|
|
268
|
+
pk_val = @attributes[pk]
|
|
269
|
+
raise ArgumentError, 'Cannot destroy a record without a primary key value' if pk_val.nil?
|
|
270
|
+
|
|
271
|
+
_base_relation.where(pk => pk_val).delete_all
|
|
272
|
+
@destroyed = true
|
|
273
|
+
self
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def reload
|
|
277
|
+
raise RecordNotFound.new(self.class, nil) unless persisted?
|
|
278
|
+
|
|
279
|
+
pk = self.class.primary_key
|
|
280
|
+
fresh = _base_relation.where(pk => @attributes[pk]).first
|
|
281
|
+
raise RecordNotFound.new(self.class, @attributes[pk]) unless fresh
|
|
282
|
+
|
|
283
|
+
@attributes = fresh.attributes
|
|
284
|
+
self
|
|
199
285
|
end
|
|
200
286
|
|
|
201
287
|
private
|
|
202
288
|
|
|
289
|
+
def scalar_attributes
|
|
290
|
+
@attributes.reject { |_, v| v.is_a?(Hash) || v.is_a?(Array) }
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def _base_relation
|
|
294
|
+
client = @_client || self.class.connection
|
|
295
|
+
rel = ActivePostgrest::Relation.new(self.class.table_name, client, self.class)
|
|
296
|
+
self.class.schema_name ? rel.with_schema(self.class.schema_name) : rel
|
|
297
|
+
end
|
|
298
|
+
|
|
203
299
|
def cast_attribute(value, type)
|
|
204
300
|
return nil if value.nil?
|
|
205
301
|
|
|
@@ -44,6 +44,38 @@ module ActivePostgrest
|
|
|
44
44
|
response
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
def post(resource, body, prefer: 'return=representation', schema: nil)
|
|
48
|
+
response = @conn.post(resource, body) do |req|
|
|
49
|
+
auth_headers(req)
|
|
50
|
+
req.headers['Prefer'] = prefer
|
|
51
|
+
req.headers['Content-Profile'] = schema if schema
|
|
52
|
+
end
|
|
53
|
+
raise_on_error!(response)
|
|
54
|
+
response
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def patch(resource, params, body, prefer: 'return=representation', schema: nil)
|
|
58
|
+
response = @conn.patch(resource, body) do |req|
|
|
59
|
+
auth_headers(req)
|
|
60
|
+
req.params.update(params)
|
|
61
|
+
req.headers['Prefer'] = prefer
|
|
62
|
+
req.headers['Content-Profile'] = schema if schema
|
|
63
|
+
end
|
|
64
|
+
raise_on_error!(response)
|
|
65
|
+
response
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def delete(resource, params, prefer: 'return=representation', schema: nil)
|
|
69
|
+
response = @conn.delete(resource) do |req|
|
|
70
|
+
auth_headers(req)
|
|
71
|
+
req.params.update(params)
|
|
72
|
+
req.headers['Prefer'] = prefer
|
|
73
|
+
req.headers['Content-Profile'] = schema if schema
|
|
74
|
+
end
|
|
75
|
+
raise_on_error!(response)
|
|
76
|
+
response
|
|
77
|
+
end
|
|
78
|
+
|
|
47
79
|
def anonymous
|
|
48
80
|
self.class.new(@base_url)
|
|
49
81
|
end
|
|
@@ -29,4 +29,18 @@ module ActivePostgrest
|
|
|
29
29
|
class UnprocessableEntity < Error; end
|
|
30
30
|
# 5xx
|
|
31
31
|
class ServerError < Error; end
|
|
32
|
+
|
|
33
|
+
class RecordNotFound < StandardError
|
|
34
|
+
def initialize(model, id)
|
|
35
|
+
super("#{model.name} not found: #{id.inspect}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class RecordNotSaved < StandardError
|
|
40
|
+
def initialize(model, attrs)
|
|
41
|
+
super("#{model.name} could not be saved: #{attrs.inspect}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class CountNotAvailable < StandardError; end
|
|
32
46
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2026 Evgeny Sokolov (FastJoe)
|
|
3
|
+
|
|
4
|
+
module ActivePostgrest
|
|
5
|
+
module Mutations
|
|
6
|
+
def create(attrs)
|
|
7
|
+
insert(attrs)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create!(attrs)
|
|
11
|
+
insert(attrs) || raise(ActivePostgrest::RecordNotSaved.new(@model_class, attrs))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def insert(attrs)
|
|
15
|
+
result = @client.post(@table, attrs, prefer: 'return=representation', schema: @schema)
|
|
16
|
+
instantiate_result(result.body)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def insert_all(records)
|
|
20
|
+
result = @client.post(@table, records, prefer: 'return=representation', schema: @schema)
|
|
21
|
+
instantiate_all(result.body)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def upsert(attrs)
|
|
25
|
+
result = @client.post(@table, attrs,
|
|
26
|
+
prefer: 'return=representation,resolution=merge-duplicates',
|
|
27
|
+
schema: @schema)
|
|
28
|
+
instantiate_result(result.body)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def upsert_all(records)
|
|
32
|
+
result = @client.post(@table, records,
|
|
33
|
+
prefer: 'return=representation,resolution=merge-duplicates',
|
|
34
|
+
schema: @schema)
|
|
35
|
+
instantiate_all(result.body)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def update_all(attrs)
|
|
39
|
+
return [] if @null
|
|
40
|
+
|
|
41
|
+
result = @client.patch(@table, build_params, attrs,
|
|
42
|
+
prefer: 'return=representation',
|
|
43
|
+
schema: @schema)
|
|
44
|
+
instantiate_all(result.body)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def delete_all
|
|
48
|
+
return [] if @null
|
|
49
|
+
|
|
50
|
+
result = @client.delete(@table, build_params,
|
|
51
|
+
prefer: 'return=representation',
|
|
52
|
+
schema: @schema)
|
|
53
|
+
instantiate_all(result.body)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def instantiate_all(body)
|
|
59
|
+
Array(body).map { @model_class.new(_1, true, @client) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def instantiate_result(body)
|
|
63
|
+
attrs = Array(body).first
|
|
64
|
+
attrs ? @model_class.new(attrs, true, @client) : nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Copyright 2026 Evgeny Sokolov (FastJoe)
|
|
3
|
+
|
|
4
|
+
require 'rails/railtie'
|
|
5
|
+
|
|
6
|
+
module ActivePostgrest
|
|
7
|
+
class Railtie < Rails::Railtie
|
|
8
|
+
initializer 'active_postgrest.active_model' do
|
|
9
|
+
require 'active_postgrest/active_model'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -2,14 +2,6 @@
|
|
|
2
2
|
# Copyright 2026 Evgeny Sokolov (FastJoe)
|
|
3
3
|
|
|
4
4
|
module ActivePostgrest
|
|
5
|
-
class RecordNotFound < StandardError
|
|
6
|
-
def initialize(model, id)
|
|
7
|
-
super("#{model.name} not found: #{id.inspect}")
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
class CountNotAvailable < StandardError; end
|
|
12
|
-
|
|
13
5
|
class Relation
|
|
14
6
|
include Enumerable
|
|
15
7
|
|
|
@@ -89,6 +81,23 @@ module ActivePostgrest
|
|
|
89
81
|
clone_with { @or_conditions.concat(parts) }
|
|
90
82
|
end
|
|
91
83
|
|
|
84
|
+
# where(a: 1).or(where(b: 2)) → or=(a.eq.1,b.eq.2)
|
|
85
|
+
# where(a: 1).where(c: 3).or(where(b: 2)) → or=(and(a.eq.1,c.eq.3),b.eq.2)
|
|
86
|
+
def or(other)
|
|
87
|
+
other_or = other.instance_variable_get(:@or_conditions)
|
|
88
|
+
other_and = other.instance_variable_get(:@and_conditions)
|
|
89
|
+
if @or_conditions.any? || @and_conditions.any? || other_or.any? || other_and.any?
|
|
90
|
+
raise ArgumentError, '#or does not support a relation with existing or_where/and_where conditions'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
left = or_group(encoded_filter_conditions)
|
|
94
|
+
right = or_group(other.encoded_filter_conditions)
|
|
95
|
+
clone_with do
|
|
96
|
+
@filters = {}
|
|
97
|
+
@or_conditions.concat([left, right].compact)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
92
101
|
# and_where([{ age: { gt: 18 } }, { status: "active" }]) → and=(age.gt.18,status.eq.active)
|
|
93
102
|
def and_where(conditions)
|
|
94
103
|
parts = Array(conditions).flat_map { |f| condition_parts(f) }
|
|
@@ -118,7 +127,8 @@ module ActivePostgrest
|
|
|
118
127
|
def to_a
|
|
119
128
|
return [] if @null
|
|
120
129
|
|
|
121
|
-
Array(@client.get(@table, build_params, schema: @schema).body)
|
|
130
|
+
Array(@client.get(@table, build_params, schema: @schema).body)
|
|
131
|
+
.map { |attrs| @model_class.new(attrs, true, @client) }
|
|
122
132
|
end
|
|
123
133
|
|
|
124
134
|
def first
|
|
@@ -221,6 +231,9 @@ module ActivePostgrest
|
|
|
221
231
|
end
|
|
222
232
|
|
|
223
233
|
include SqlBuilder
|
|
234
|
+
include Mutations
|
|
235
|
+
|
|
236
|
+
protected :encoded_filter_conditions
|
|
224
237
|
|
|
225
238
|
private
|
|
226
239
|
|
|
@@ -276,12 +289,6 @@ module ActivePostgrest
|
|
|
276
289
|
end.to_a.first&.[](key)
|
|
277
290
|
end
|
|
278
291
|
|
|
279
|
-
def condition_parts(filters)
|
|
280
|
-
filters.flat_map do |col, val|
|
|
281
|
-
Array(encode_value(val)).map { "#{col}.#{_1}" }
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
|
|
285
292
|
def build_params
|
|
286
293
|
params = {}
|
|
287
294
|
params[:select] = build_select if @selects.any? || @joins.any?
|
|
@@ -81,6 +81,22 @@ module ActivePostgrest
|
|
|
81
81
|
val
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
def encoded_filter_conditions
|
|
85
|
+
@filters.flat_map { |col, enc| Array(enc).map { "#{col}.#{_1}" } }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def or_group(conditions)
|
|
89
|
+
return nil if conditions.empty?
|
|
90
|
+
|
|
91
|
+
conditions.one? ? conditions.first : "and(#{conditions.join(',')})"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def condition_parts(filters)
|
|
95
|
+
filters.flat_map do |col, val|
|
|
96
|
+
Array(encode_value(val)).map { "#{col}.#{_1}" }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
84
100
|
def merge_filter!(col, encoded)
|
|
85
101
|
existing = @filters[col]
|
|
86
102
|
@filters[col] = existing ? [*Array(existing), *Array(encoded)] : encoded
|
|
@@ -106,6 +122,11 @@ module ActivePostgrest
|
|
|
106
122
|
end
|
|
107
123
|
|
|
108
124
|
def decode_condition(cond)
|
|
125
|
+
if cond.start_with?('and(') && cond.end_with?(')')
|
|
126
|
+
inner = cond[4..-2]
|
|
127
|
+
return "(#{split_conditions(inner).map { decode_condition(_1) }.join(' AND ')})"
|
|
128
|
+
end
|
|
129
|
+
|
|
109
130
|
parts = cond.split('.')
|
|
110
131
|
op_idx = parts.index { KNOWN_OP_KEYS.include?(_1) }
|
|
111
132
|
return cond unless op_idx
|
|
@@ -115,6 +136,25 @@ module ActivePostgrest
|
|
|
115
136
|
decode_filter(col, encoded)
|
|
116
137
|
end
|
|
117
138
|
|
|
139
|
+
def split_conditions(str)
|
|
140
|
+
depth = 0
|
|
141
|
+
start = 0
|
|
142
|
+
result = []
|
|
143
|
+
str.each_char.with_index do |c, i|
|
|
144
|
+
case c
|
|
145
|
+
when '(' then depth += 1
|
|
146
|
+
when ')' then depth -= 1
|
|
147
|
+
when ','
|
|
148
|
+
if depth.zero?
|
|
149
|
+
result << str[start...i]
|
|
150
|
+
start = i + 1
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
result << str[start..]
|
|
155
|
+
result.reject(&:empty?)
|
|
156
|
+
end
|
|
157
|
+
|
|
118
158
|
def sql_quote(val)
|
|
119
159
|
return val if val&.match?(/\A-?\d+(\.\d+)?\z/)
|
|
120
160
|
|
data/lib/active_postgrest.rb
CHANGED
data.tar.gz.sig
ADDED
metadata
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_postgrest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgeny Sokolov
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
|
-
cert_chain:
|
|
11
|
-
|
|
10
|
+
cert_chain:
|
|
11
|
+
- |
|
|
12
|
+
-----BEGIN CERTIFICATE-----
|
|
13
|
+
MIIEUjCCArqgAwIBAgIBATANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDDB5ldmdl
|
|
14
|
+
bnkuc29rb2xvdi9EQz1nbWFpbC9EQz1jb20wHhcNMjYwNjE4MTE0NDQ3WhcNMjcw
|
|
15
|
+
NjE4MTE0NDQ3WjApMScwJQYDVQQDDB5ldmdlbnkuc29rb2xvdi9EQz1nbWFpbC9E
|
|
16
|
+
Qz1jb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDInkRoXT6ySd5G
|
|
17
|
+
BuDN0QUAqbWqrqAShEwA0zQIVooLu9SF5pV7MCXTU4w3b12qLX0irddo3qrFp9rz
|
|
18
|
+
c/P+gX737W8CdglkboI0lGikghH0A23V7h1omv303kVrZHEKzOklu77cpVMhVeaO
|
|
19
|
+
y6KVd4N8o4AQF/D2IKM1Fi2o3uvcj9IK38W+939YMlZrilD1NSgnbQYcVI8Pcqwr
|
|
20
|
+
hpCGO7PGo5Ns3b9TSErCfEH+DbNovRfoYPNCXtbFdVym4qSl5SyNGZEvUcw22ZfE
|
|
21
|
+
BiN5iGC6W7kvwKUBMDyRibqu+1J76rpgpz15iXxqs62QqOrXpN9Id3IlFFRuRVu3
|
|
22
|
+
LtaPfd+y3AATtUsIMTg73Wv2g4EHT0h5v2raiQ/7O8fM3aKW6n3c6p6POAgOhohR
|
|
23
|
+
F2lcsgbH55tz8lymdmgz5rke5CpZaOiHqcE7LKFKdKEDtACB3GEDfy69VqzqkcqG
|
|
24
|
+
wwwHZYbhB1+1WZCFmIVHeQEg1Mmgy7joOHzNjEgwzzcuZICnsuUCAwEAAaOBhDCB
|
|
25
|
+
gTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUzhn3dA5CJtlBTkc0
|
|
26
|
+
7hxTo2PvpiMwIwYDVR0RBBwwGoEYZXZnZW55LnNva29sb3ZAZ21haWwuY29tMCMG
|
|
27
|
+
A1UdEgQcMBqBGGV2Z2VueS5zb2tvbG92QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsF
|
|
28
|
+
AAOCAYEAadjhjeh6S9/kv7jvJ4AlyhWfO7r0xqkwpTECuJmDWKDQeIZs8yKvgsYl
|
|
29
|
+
RPss8oJZ2OVkEOnMFx4fL+RB19oTEp5ZUrtZq+Nt47Ypci93mTFOykMRyu42HQ1W
|
|
30
|
+
BEsgwGRLuloYBD+Eubghm1Mll5T2qo/jYPvs9av+Ma2bkp/NoMGxajVzBHFe39PN
|
|
31
|
+
0wP8BokxXTtexk7ybnqIePVPTvhHo/VrMMfD2SAy7o0WPT2UwWZNfjd59aaW7ruc
|
|
32
|
+
5jgdKw4qfnIXrVbISSZaovUdvCVloYOcKUFI+K4BEFFjIcfbCxrnU6GfonAEyzxJ
|
|
33
|
+
84+w5Rus55cqLKEiFIR7auVJkbLsrIHsbrH+2uJDgxgXGryUq3I/k5ZaORg2gocP
|
|
34
|
+
DvU/iWx+jPPscxukSQ+1qVLfWOYZlQ0BEqFJJrvro9lEbimK1j6aibza2InnPwTn
|
|
35
|
+
tC9NYXuv1LyPD+PZZRO4LDDSjUVx+RF/dJIE23hzgbLexpyriCMFyKGhHi2KApEl
|
|
36
|
+
eIQUsjZL
|
|
37
|
+
-----END CERTIFICATE-----
|
|
38
|
+
date: 2026-06-18 00:00:00.000000000 Z
|
|
12
39
|
dependencies:
|
|
13
40
|
- !ruby/object:Gem::Dependency
|
|
14
41
|
name: faraday
|
|
@@ -66,6 +93,20 @@ dependencies:
|
|
|
66
93
|
- - "~>"
|
|
67
94
|
- !ruby/object:Gem::Version
|
|
68
95
|
version: '3.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: simplecov
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.22'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.22'
|
|
69
110
|
- !ruby/object:Gem::Dependency
|
|
70
111
|
name: rubocop
|
|
71
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,6 +121,34 @@ dependencies:
|
|
|
80
121
|
- - "~>"
|
|
81
122
|
- !ruby/object:Gem::Version
|
|
82
123
|
version: '1.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: rubocop-rspec
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '3.0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '3.0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: bundler-audit
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '0.9'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '0.9'
|
|
83
152
|
description: Query PostgREST APIs using a familiar ActiveRecord-like interface
|
|
84
153
|
email:
|
|
85
154
|
- evgeny.sokolov@gmail.com
|
|
@@ -88,10 +157,14 @@ extensions: []
|
|
|
88
157
|
extra_rdoc_files: []
|
|
89
158
|
files:
|
|
90
159
|
- LICENSE
|
|
160
|
+
- certs/gem-public_cert.pem
|
|
91
161
|
- lib/active_postgrest.rb
|
|
162
|
+
- lib/active_postgrest/active_model.rb
|
|
92
163
|
- lib/active_postgrest/base.rb
|
|
93
164
|
- lib/active_postgrest/client.rb
|
|
94
165
|
- lib/active_postgrest/errors.rb
|
|
166
|
+
- lib/active_postgrest/mutations.rb
|
|
167
|
+
- lib/active_postgrest/railtie.rb
|
|
95
168
|
- lib/active_postgrest/relation.rb
|
|
96
169
|
- lib/active_postgrest/sql_builder.rb
|
|
97
170
|
- lib/active_postgrest/version.rb
|
|
@@ -102,7 +175,8 @@ licenses:
|
|
|
102
175
|
metadata:
|
|
103
176
|
source_code_uri: https://github.com/FastJoe/active-postgrest
|
|
104
177
|
changelog_uri: https://github.com/FastJoe/active-postgrest/blob/main/CHANGELOG.md
|
|
105
|
-
|
|
178
|
+
rubygems_mfa_required: 'true'
|
|
179
|
+
post_install_message:
|
|
106
180
|
rdoc_options: []
|
|
107
181
|
require_paths:
|
|
108
182
|
- lib
|
|
@@ -117,8 +191,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
117
191
|
- !ruby/object:Gem::Version
|
|
118
192
|
version: '0'
|
|
119
193
|
requirements: []
|
|
120
|
-
rubygems_version: 3.
|
|
121
|
-
signing_key:
|
|
194
|
+
rubygems_version: 3.5.22
|
|
195
|
+
signing_key:
|
|
122
196
|
specification_version: 4
|
|
123
197
|
summary: ActiveRecord-style Ruby client for PostgREST
|
|
124
198
|
test_files: []
|
metadata.gz.sig
ADDED
|
Binary file
|