-
-
Notifications
You must be signed in to change notification settings - Fork 92
/
json.rb
235 lines (217 loc) · 8.11 KB
/
json.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
require 'sequel/core'
Sequel.extension(*%i(pg_json pg_json_ops))
module ROM
module SQL
module Postgres
module Types
JSONRead = (SQL::Types::Array | SQL::Types::Hash).constructor do |value|
if value.respond_to?(:to_hash)
value.to_hash
elsif value.respond_to?(:to_ary)
value.to_ary
else
value
end
end
JSON = Type('json') do
(SQL::Types::Array | SQL::Types::Hash)
.constructor(Sequel.method(:pg_json))
.meta(read: JSONRead)
end
JSONB = Type('jsonb') do
(SQL::Types::Array | SQL::Types::Hash)
.constructor(Sequel.method(:pg_jsonb))
.meta(read: JSONRead)
end
# @!parse
# class SQL::Attribute
# # @!method contain(value)
# # Check whether the JSON value includes a json value
# # Translates to the @> operator
# #
# # @example
# # people.where { fields.contain(gender: 'Female') }
# # people.where(people[:fields].contain([name: 'age']))
# # people.select { fields.contain(gender: 'Female').as(:is_female) }
# #
# # @param [Hash,Array,Object] value
# #
# # @return [SQL::Attribute<Types::Bool>]
# #
# # @api public
#
# # @!method contained_by(value)
# # Check whether the JSON value is contained by other value
# # Translates to the <@ operator
# #
# # @example
# # people.where { custom_values.contained_by(age: 25, foo: 'bar') }
# #
# # @param [Hash,Array] value
# #
# # @return [SQL::Attribute<Types::Bool>]
# #
# # @api public
#
# # @!method get(*path)
# # Extract the JSON value using at the specified path
# # Translates to -> or #> depending on the number of arguments
# #
# # @example
# # people.select { data.get('age').as(:person_age) }
# # people.select { fields.get(0).as(:first_field) }
# # people.select { fields.get('0', 'value').as(:first_field_value) }
# #
# # @param [Array<Integer>,Array<String>] path Path to extract
# #
# # @return [SQL::Attribute<Types::PG::JSON>,SQL::Attribute<Types::PG::JSONB>]
# #
# # @api public
#
# # @!method get_text(*path)
# # Extract the JSON value as text using at the specified path
# # Translates to ->> or #>> depending on the number of arguments
# #
# # @example
# # people.select { data.get('age').as(:person_age) }
# # people.select { fields.get(0).as(:first_field) }
# # people.select { fields.get('0', 'value').as(:first_field_value) }
# #
# # @param [Array<Integer>,Array<String>] path Path to extract
# #
# # @return [SQL::Attribute<Types::String>]
# #
# # @api public
#
# # @!method has_key(key)
# # Does the JSON value have the specified top-level key
# # Translates to ?
# #
# # @example
# # people.where { data.has_key('age') }
# #
# # @param [String] key
# #
# # @return [SQL::Attribute<Types::Bool>]
# #
# # @api public
#
# # @!method has_any_key(*keys)
# # Does the JSON value have any of the specified top-level keys
# # Translates to ?|
# #
# # @example
# # people.where { data.has_any_key('age', 'height') }
# #
# # @param [Array<String>] keys
# #
# # @return [SQL::Attribute<Types::Bool>]
# #
# # @api public
#
# # @!method has_all_keys(*keys)
# # Does the JSON value have all the specified top-level keys
# # Translates to ?&
# #
# # @example
# # people.where { data.has_all_keys('age', 'height') }
# #
# # @param [Array<String>] keys
# #
# # @return [SQL::Attribute<Types::Bool>]
# #
# # @api public
#
# # @!method merge(value)
# # Concatenate two JSON values
# # Translates to ||
# #
# # @example
# # people.select { data.merge(fetched_at: Time.now).as(:data) }
# # people.select { (fields + [name: 'height', value: 165]).as(:fields) }
# #
# # @param [Hash,Array] value
# #
# # @return [SQL::Attribute<Types::PG::JSONB>]
# #
# # @api public
#
# # @!method +(value)
# # An alias for SQL::Attribute<JSONB>#merge
# #
# # @api public
#
# # @!method delete(*path)
# # Deletes the specified value by key, index, or path
# # Translates to - or #- depending on the number of arguments
# #
# # @example
# # people.select { data.delete('age').as(:data_without_age) }
# # people.select { fields.delete(0).as(:fields_without_first) }
# # people.select { fields.delete(-1).as(:fields_without_last) }
# # people.select { data.delete('deeply', 'nested', 'value').as(:data) }
# # people.select { fields.delete('0', 'name').as(:data) }
# #
# # @param [Array<String>] path
# #
# # @return [SQL::Attribute<Types::PG::JSONB>]
# #
# # @api public
# end
module JSONMethods
def self.[](type, wrap)
parent = self
Module.new do
include parent
define_method(:json_type) { type }
define_method(:wrap, wrap)
end
end
def get(type, expr, *path)
Attribute[json_type].meta(sql_expr: wrap(expr)[path_args(path)])
end
def get_text(type, expr, *path)
Attribute[SQL::Types::String].meta(sql_expr: wrap(expr).get_text(path_args(path)))
end
private
def path_args(path)
case path.size
when 0 then raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
when 1 then path[0]
else path
end
end
end
TypeExtensions.register(JSON) do
include JSONMethods[JSON, :pg_json.to_proc]
end
TypeExtensions.register(JSONB) do
include JSONMethods[JSONB, :pg_jsonb.to_proc]
def contain(type, expr, value)
Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contains(value))
end
def contained_by(type, expr, value)
Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contained_by(value))
end
def has_key(type, expr, key)
Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).has_key?(key))
end
def has_any_key(type, expr, *keys)
Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contain_any(keys))
end
def has_all_keys(type, expr, *keys)
Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contain_all(keys))
end
def merge(type, expr, value)
Attribute[JSONB].meta(sql_expr: wrap(expr).concat(value))
end
alias_method :+, :merge
def delete(type, expr, *path)
sql_expr = path.size == 1 ? wrap(expr) - path : wrap(expr).delete_path(path)
Attribute[JSONB].meta(sql_expr: sql_expr)
end
end
end
end
end
end