active_record_extended_telescope 2.0.1

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +870 -0
  3. data/lib/active_record_extended.rb +10 -0
  4. data/lib/active_record_extended/active_record.rb +25 -0
  5. data/lib/active_record_extended/active_record/relation_patch.rb +50 -0
  6. data/lib/active_record_extended/arel.rb +7 -0
  7. data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
  8. data/lib/active_record_extended/arel/nodes.rb +49 -0
  9. data/lib/active_record_extended/arel/predications.rb +50 -0
  10. data/lib/active_record_extended/arel/sql_literal.rb +16 -0
  11. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +122 -0
  12. data/lib/active_record_extended/patch/5_1/where_clause.rb +11 -0
  13. data/lib/active_record_extended/patch/5_2/where_clause.rb +11 -0
  14. data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +20 -0
  15. data/lib/active_record_extended/query_methods/any_of.rb +93 -0
  16. data/lib/active_record_extended/query_methods/either.rb +62 -0
  17. data/lib/active_record_extended/query_methods/inet.rb +88 -0
  18. data/lib/active_record_extended/query_methods/json.rb +329 -0
  19. data/lib/active_record_extended/query_methods/select.rb +118 -0
  20. data/lib/active_record_extended/query_methods/unionize.rb +249 -0
  21. data/lib/active_record_extended/query_methods/where_chain.rb +132 -0
  22. data/lib/active_record_extended/query_methods/window.rb +93 -0
  23. data/lib/active_record_extended/query_methods/with_cte.rb +150 -0
  24. data/lib/active_record_extended/utilities/order_by.rb +77 -0
  25. data/lib/active_record_extended/utilities/support.rb +178 -0
  26. data/lib/active_record_extended/version.rb +5 -0
  27. data/lib/active_record_extended_telescope.rb +4 -0
  28. data/spec/active_record_extended_spec.rb +7 -0
  29. data/spec/query_methods/any_of_spec.rb +131 -0
  30. data/spec/query_methods/array_query_spec.rb +64 -0
  31. data/spec/query_methods/either_spec.rb +59 -0
  32. data/spec/query_methods/hash_query_spec.rb +45 -0
  33. data/spec/query_methods/inet_query_spec.rb +112 -0
  34. data/spec/query_methods/json_spec.rb +157 -0
  35. data/spec/query_methods/select_spec.rb +115 -0
  36. data/spec/query_methods/unionize_spec.rb +165 -0
  37. data/spec/query_methods/window_spec.rb +51 -0
  38. data/spec/query_methods/with_cte_spec.rb +50 -0
  39. data/spec/spec_helper.rb +28 -0
  40. data/spec/sql_inspections/any_of_sql_spec.rb +41 -0
  41. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
  42. data/spec/sql_inspections/arel/array_spec.rb +63 -0
  43. data/spec/sql_inspections/arel/inet_spec.rb +66 -0
  44. data/spec/sql_inspections/contains_sql_queries_spec.rb +47 -0
  45. data/spec/sql_inspections/either_sql_spec.rb +55 -0
  46. data/spec/sql_inspections/json_sql_spec.rb +82 -0
  47. data/spec/sql_inspections/unionize_sql_spec.rb +124 -0
  48. data/spec/sql_inspections/window_sql_spec.rb +98 -0
  49. data/spec/sql_inspections/with_cte_sql_spec.rb +95 -0
  50. data/spec/support/database_cleaner.rb +15 -0
  51. data/spec/support/models.rb +68 -0
  52. metadata +245 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ar_outer_joins"
4
+
5
+ module ActiveRecordExtended
6
+ module QueryMethods
7
+ module Either
8
+ XOR_FIELD_SQL = "(CASE WHEN %<t1>s.%<c1>s IS NULL THEN %<t2>s.%<c2>s ELSE %<t1>s.%<c1>s END) "
9
+ XOR_FIELD_KEYS = [:t1, :c1, :t2, :c2].freeze
10
+
11
+ def either_join(initial_association, fallback_association)
12
+ associations = [initial_association, fallback_association]
13
+ association_options = xor_field_options_for_associations(associations)
14
+ condition__query = xor_field_sql(association_options) + "= #{table_name}.#{primary_key}"
15
+ outer_joins(associations).where(Arel.sql(condition__query))
16
+ end
17
+ alias either_joins either_join
18
+
19
+ def either_order(direction, **associations_and_columns)
20
+ reflected_columns = map_columns_to_tables(associations_and_columns)
21
+ conditional_query = xor_field_sql(reflected_columns) + sort_order_sql(direction)
22
+ outer_joins(associations_and_columns.keys).order(Arel.sql(conditional_query))
23
+ end
24
+ alias either_orders either_order
25
+
26
+ private
27
+
28
+ def xor_field_sql(options)
29
+ XOR_FIELD_SQL % Hash[xor_field_options(options)]
30
+ end
31
+
32
+ def sort_order_sql(dir)
33
+ ["asc", "desc"].include?(dir.to_s) ? dir.to_s : "asc"
34
+ end
35
+
36
+ def xor_field_options(options)
37
+ str_args = options.flatten.take(XOR_FIELD_KEYS.size).map(&:to_s)
38
+ Hash[XOR_FIELD_KEYS.zip(str_args)]
39
+ end
40
+
41
+ def map_columns_to_tables(associations_and_columns)
42
+ if associations_and_columns.respond_to?(:transform_keys)
43
+ associations_and_columns.transform_keys { |assc| reflect_on_association(assc).table_name }
44
+ else
45
+ associations_and_columns.each_with_object({}) do |(assc, value), key_table|
46
+ reflect_table = reflect_on_association(assc).table_name
47
+ key_table[reflect_table] = value
48
+ end
49
+ end
50
+ end
51
+
52
+ def xor_field_options_for_associations(associations)
53
+ associations.each_with_object({}) do |association_name, options|
54
+ reflection = reflect_on_association(association_name)
55
+ options[reflection.table_name] = reflection.foreign_key
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ ActiveRecord::Base.extend(ActiveRecordExtended::QueryMethods::Either)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module QueryMethods
5
+ module Inet
6
+ # Finds matching inet column records that fall within a given submasked IP range
7
+ #
8
+ # Column(inet) << "127.0.0.1/24"
9
+ #
10
+ # User.where.inet_contained_within(ip: "127.0.0.1/16")
11
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" << '127.0.0.1/16'"
12
+ #
13
+ # User.where.inet_contained_within(ip: IPAddr.new("192.168.2.0/24"))
14
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" << '192.168.2.0/24'"
15
+ #
16
+ def inet_contained_within(opts, *rest)
17
+ substitute_comparisons(opts, rest, Arel::Nodes::Inet::ContainedWithin, "inet_contained_within")
18
+ end
19
+
20
+ # Finds matching inet column records that fall within a given submasked IP range and also finds records that also
21
+ # contain a submasking field that fall within range too.
22
+ #
23
+ # Column(inet) <<= "127.0.0.1/24"
24
+ #
25
+ # User.where.inet_contained_within_or_equals(ip: "127.0.0.1/16")
26
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" <<= '127.0.0.44/32'"
27
+ #
28
+ # User.where.inet_contained_within_or_equals(ip: IPAddr.new("192.168.2.0/24"))
29
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" <<= '192.168.2.0/24'"
30
+ #
31
+ def inet_contained_within_or_equals(opts, *rest)
32
+ substitute_comparisons(opts, rest, Arel::Nodes::Inet::ContainedWithinEquals, "inet_contained_within_or_equals")
33
+ end
34
+
35
+ # Finds records that contain a submask and the supplied IP address falls within its range.
36
+ #
37
+ # Column(inet) >>= "127.0.0.1/24"
38
+ #
39
+ # User.where.inet_contained_within_or_equals(ip: "127.0.255.255")
40
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" >>= '127.0.255.255'"
41
+ #
42
+ # User.where.inet_contained_within_or_equals(ip: IPAddr.new("127.0.0.255"))
43
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" >>= '127.0.0.255/32'"
44
+ #
45
+ def inet_contains_or_equals(opts, *rest)
46
+ substitute_comparisons(opts, rest, Arel::Nodes::Inet::ContainsEquals, "inet_contains_or_equals")
47
+ end
48
+
49
+ # Strictly finds records that contain a submask and the supplied IP address falls within its range.
50
+ #
51
+ # Column(inet) >>= "127.0.0.1"
52
+ #
53
+ # User.where.inet_contained_within_or_equals(ip: "127.0.255.255")
54
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" >> '127.0.255.255'"
55
+ #
56
+ # User.where.inet_contained_within_or_equals(ip: IPAddr.new("127.0.0.255"))
57
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" >> '127.0.0.255/32'"
58
+ #
59
+ def inet_contains(opts, *rest)
60
+ substitute_comparisons(opts, rest, Arel::Nodes::Inet::Contains, "inet_contains")
61
+ end
62
+
63
+ # This method is a combination of `inet_contains` and `inet_contained_within`
64
+ #
65
+ # Finds records that are contained within a given submask. And will also find records where their submask is also
66
+ # contains a given IP or IP submask.
67
+ #
68
+ # Column(inet) && "127.0.0.1/28"
69
+ #
70
+ # User.where.inet_contains_or_is_contained_by(ip: "127.0.255.255/28")
71
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" && '127.0.255.255/28'"
72
+ #
73
+ # User.where.inet_contains_or_is_contained_by(ip: IPAddr.new("127.0.0.255"))
74
+ # #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" && '127.0.0.255/32'"
75
+ #
76
+ def inet_contains_or_is_contained_within(opts, *rest)
77
+ substitute_comparisons(
78
+ opts,
79
+ rest,
80
+ Arel::Nodes::Inet::ContainsOrContainedWithin,
81
+ "inet_contains_or_is_contained_within"
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ ActiveRecord::QueryMethods::WhereChain.prepend(ActiveRecordExtended::QueryMethods::Inet)
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module QueryMethods
5
+ module Json
6
+ JSON_QUERY_METHODS = [
7
+ :select_row_to_json,
8
+ :json_build_object,
9
+ :jsonb_build_object,
10
+ :json_build_literal,
11
+ :jsonb_build_literal
12
+ ].freeze
13
+
14
+ class JsonChain
15
+ include ::ActiveRecordExtended::Utilities::Support
16
+ include ::ActiveRecordExtended::Utilities::OrderBy
17
+
18
+ DEFAULT_ALIAS = '"results"'
19
+ TO_JSONB_OPTIONS = [:array_agg, :distinct, :to_jsonb].to_set.freeze
20
+ ARRAY_OPTIONS = [:array, true].freeze
21
+
22
+ def initialize(scope)
23
+ @scope = scope
24
+ end
25
+
26
+ def row_to_json!(**args, &block)
27
+ options = json_object_options(args, except: [:values, :value])
28
+ build_row_to_json(**options, &block)
29
+ end
30
+
31
+ def json_build_object!(*args)
32
+ options = json_object_options(args, except: [:values, :cast_with, :order_by])
33
+ build_json_object(Arel::Nodes::JsonBuildObject, **options)
34
+ end
35
+
36
+ def jsonb_build_object!(*args)
37
+ options = json_object_options(args, except: [:values, :cast_with, :order_by])
38
+ build_json_object(Arel::Nodes::JsonbBuildObject, **options)
39
+ end
40
+
41
+ def json_build_literal!(*args)
42
+ options = json_object_options(args, only: [:values, :col_alias])
43
+ build_json_literal(Arel::Nodes::JsonBuildObject, **options)
44
+ end
45
+
46
+ def jsonb_build_literal!(*args)
47
+ options = json_object_options(args, only: [:values, :col_alias])
48
+ build_json_literal(Arel::Nodes::JsonbBuildObject, **options)
49
+ end
50
+
51
+ private
52
+
53
+ def build_json_literal(arel_klass, values:, col_alias: DEFAULT_ALIAS)
54
+ json_values = flatten_to_sql(values.to_a) { |value| literal_key(value) }
55
+ col_alias = double_quote(col_alias)
56
+ json_build_obj = arel_klass.new(json_values)
57
+ @scope.select(nested_alias_escape(json_build_obj, col_alias))
58
+ end
59
+
60
+ def build_json_object(arel_klass, from:, key: key_generator, value: nil, col_alias: DEFAULT_ALIAS)
61
+ tbl_alias = double_quote(key)
62
+ col_alias = double_quote(col_alias)
63
+ col_key = literal_key(key)
64
+ col_value = to_arel_sql(value.presence || tbl_alias)
65
+ json_build_object = arel_klass.new(to_sql_array(col_key, col_value))
66
+
67
+ unless /".+"/.match?(col_value)
68
+ warn("`#{col_value}`: the `value` argument should contain a double quoted key reference for safety")
69
+ end
70
+
71
+ @scope.select(nested_alias_escape(json_build_object, col_alias)).from(nested_alias_escape(from, tbl_alias))
72
+ end
73
+
74
+ def build_row_to_json(from:, **options, &block)
75
+ key = options[:key]
76
+ row_to_json = ::Arel::Nodes::RowToJson.new(double_quote(key))
77
+ row_to_json = ::Arel::Nodes::ToJsonb.new(row_to_json) if options.dig(:cast_with, :to_jsonb)
78
+
79
+ dummy_table = from_clause_constructor(from, key).select(row_to_json)
80
+ dummy_table = dummy_table.instance_eval(&block) if block
81
+ return dummy_table if options[:col_alias].blank?
82
+
83
+ query = wrap_row_to_json(dummy_table, options)
84
+ @scope.select(query)
85
+ end
86
+
87
+ def wrap_row_to_json(dummy_table, options)
88
+ cast_opts = options[:cast_with]
89
+ col_alias = options[:col_alias]
90
+ order_by = options[:order_by]
91
+
92
+ if cast_opts[:array_agg] || cast_opts[:distinct]
93
+ wrap_with_agg_array(dummy_table, col_alias, order_by: order_by, distinct: cast_opts[:distinct])
94
+ elsif cast_opts[:array]
95
+ wrap_with_array(dummy_table, col_alias, order_by: order_by)
96
+ else
97
+ nested_alias_escape(dummy_table, col_alias)
98
+ end
99
+ end
100
+
101
+ def json_object_options(args, except: [], only: []) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
102
+ options = {}
103
+ lean_opts = lambda do |key, &block|
104
+ if only.present?
105
+ options[key] ||= block.call if only.include?(key)
106
+ elsif !except.include?(key)
107
+ options[key] ||= block.call
108
+ end
109
+ end
110
+
111
+ flatten_safely(args) do |arg|
112
+ next if arg.nil?
113
+
114
+ if arg.is_a?(Hash)
115
+ lean_opts.call(:key) { arg.fetch(:key, key_generator) }
116
+ lean_opts.call(:value) { arg[:value].presence }
117
+ lean_opts.call(:col_alias) { arg[:as] }
118
+ lean_opts.call(:order_by) { order_by_expression(arg[:order_by]) }
119
+ lean_opts.call(:from) { arg[:from].tap { |from_clause| pipe_cte_with!(from_clause) } }
120
+ lean_opts.call(:cast_with) { casting_options(arg[:cast_with]) }
121
+ end
122
+
123
+ unless except.include?(:values)
124
+ options[:values] ||= []
125
+ options[:values] << (arg.respond_to?(:to_a) ? arg.to_a : arg)
126
+ end
127
+ end
128
+
129
+ options.tap(&:compact!)
130
+ end
131
+
132
+ def casting_options(cast_with)
133
+ return {} if cast_with.blank?
134
+
135
+ skip_convert = [Symbol, TrueClass, FalseClass]
136
+ Array(cast_with).compact.each_with_object({}) do |arg, options|
137
+ arg = arg.to_sym unless skip_convert.include?(arg.class)
138
+ options[:to_jsonb] |= TO_JSONB_OPTIONS.include?(arg)
139
+ options[:array] |= ARRAY_OPTIONS.include?(arg)
140
+ options[:array_agg] |= arg == :array_agg
141
+ options[:distinct] |= arg == :distinct
142
+ end
143
+ end
144
+ end
145
+
146
+ # Appends a select statement that contains a subquery that is converted to a json response
147
+ #
148
+ # Arguments:
149
+ # - from: [String, Arel, or ActiveRecord::Relation] A subquery that can be nested into a ROW_TO_JSON clause
150
+ #
151
+ # Options:
152
+ # - as: [Symbol or String] (default="results"): What the column will be aliased to
153
+ #
154
+ # - key: [Symbol or String] (default=[random letter]) What the row clause will be set as.
155
+ # - This is useful if you would like to add additional mid-level clauses (see mid-level scope example)
156
+ #
157
+ # - cast_with [Symbol or Array of symbols]: Actions to transform your query
158
+ # * :to_jsonb
159
+ # * :array
160
+ # * :array_agg (including just :array with this option will favor :array_agg)
161
+ # * :distinct (auto applies :array_agg & :to_jsonb)
162
+ #
163
+ # - order_by [Symbol or hash]: Applies an ordering operation (similar to ActiveRecord #order)
164
+ # - NOTE: this option will be ignored if you need to order a DISTINCT Aggregated Array,
165
+ # since postgres will thrown an error.
166
+ #
167
+ #
168
+ #
169
+ # Examples:
170
+ # subquery = Group.select(:name, :category_id).where("user_id = users.id")
171
+ # User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_with: :array)
172
+ # #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
173
+ #
174
+ # - Adding mid-level scopes:
175
+ #
176
+ # subquery = Group.select(:name, :category_id)
177
+ # User.select_row_to_json(subquery, key: :group, cast_with: :array) do |scope|
178
+ # scope.where(group: { name: "Nerd Core" })
179
+ # end
180
+ # #=> ```sql
181
+ # SELECT ARRAY(
182
+ # SELECT ROW_TO_JSON("group")
183
+ # FROM(SELECT name, category_id FROM groups) AS group
184
+ # WHERE group.name = 'Nerd Core'
185
+ # )
186
+ # ```
187
+ #
188
+ #
189
+ # - Array of JSONB objects
190
+ #
191
+ # subquery = Group.select(:name, :category_id)
192
+ # User.select_row_to_json(subquery, key: :group, cast_with: [:array, :to_jsonb]) do |scope|
193
+ # scope.where(group: { name: "Nerd Core" })
194
+ # end
195
+ # #=> ```sql
196
+ # SELECT ARRAY(
197
+ # SELECT TO_JSONB(ROW_TO_JSON("group"))
198
+ # FROM(SELECT name, category_id FROM groups) AS group
199
+ # WHERE group.name = 'Nerd Core'
200
+ # )
201
+ # ```
202
+ #
203
+ # - Distinct Aggregated Array
204
+ #
205
+ # subquery = Group.select(:name, :category_id)
206
+ # User.select_row_to_json(subquery, key: :group, cast_with: [:array_agg, :distinct]) do |scope|
207
+ # scope.where(group: { name: "Nerd Core" })
208
+ # end
209
+ # #=> ```sql
210
+ # SELECT ARRAY_AGG(DISTINCT (
211
+ # SELECT TO_JSONB(ROW_TO_JSON("group"))
212
+ # FROM(SELECT name, category_id FROM groups) AS group
213
+ # WHERE group.name = 'Nerd Core'
214
+ # ))
215
+ # ```
216
+ #
217
+ # - Ordering a Non-aggregated Array
218
+ #
219
+ # subquery = Group.select(:name, :category_id)
220
+ # User.select_row_to_json(subquery, key: :group, cast_with: :array, order_by: { group: { name: :desc } })
221
+ # #=> ```sql
222
+ # SELECT ARRAY(
223
+ # SELECT ROW_TO_JSON("group")
224
+ # FROM(SELECT name, category_id FROM groups) AS group
225
+ # ORDER BY group.name DESC
226
+ # )
227
+ # ```
228
+ #
229
+ # - Ordering an Aggregated Array
230
+ #
231
+ # Subquery = Group.select(:name, :category_id)
232
+ # User
233
+ # .joins(:people_groups)
234
+ # .select_row_to_json(
235
+ # subquery,
236
+ # key: :group,
237
+ # cast_with: :array_agg,
238
+ # order_by: { people_groups: :category_id }
239
+ # )
240
+ # #=> ```sql
241
+ # SELECT ARRAY_AGG((
242
+ # SELECT ROW_TO_JSON("group")
243
+ # FROM(SELECT name, category_id FROM groups) AS group
244
+ # ORDER BY group.name DESC
245
+ # ) ORDER BY people_groups.category_id ASC)
246
+ # ```
247
+ #
248
+ def select_row_to_json(from = nil, **options, &block)
249
+ from.is_a?(Hash) ? options.merge!(from) : options.reverse_merge!(from: from)
250
+ options.compact!
251
+ raise ArgumentError.new("Required to provide a non-nilled from clause") unless options.key?(:from)
252
+
253
+ JsonChain.new(spawn).row_to_json!(**options, &block)
254
+ end
255
+
256
+ # Creates a json response object that will convert all subquery results into a json compatible response
257
+ #
258
+ # Arguments:
259
+ # key: [Symbol or String]: What should this response return as
260
+ # from: [String, Arel, or ActiveRecord::Relation] : A subquery that can be nested into the top-level from clause
261
+ #
262
+ # Options:
263
+ # - as: [Symbol or String] (default="results"): What the column will be aliased to
264
+ #
265
+ #
266
+ # - value: [Symbol or String] (defaults=[key]): How the response should handel the json value return
267
+ #
268
+ # Example:
269
+ #
270
+ # - Generic example:
271
+ #
272
+ # subquery = Group.select(:name, :category_id).where("user_id = users.id")
273
+ # User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_with: :array)
274
+ # #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
275
+ #
276
+ # - Setting a custom value:
277
+ #
278
+ # Before:
279
+ # subquery = User.select(:name).where(id: 100..110).group(:name)
280
+ # User.build_json_object(:gang_members, subquery).take.results["gang_members"] #=> nil
281
+ #
282
+ # After:
283
+ # User.build_json_object(:gang_members, subquery, value: "COALESCE(array_agg(\"gang_members\"), 'BANG!')")
284
+ # .take
285
+ # .results["gang_members"] #=> "BANG!"
286
+ #
287
+ def json_build_object(key, from, **options)
288
+ options[:key] = key
289
+ options[:from] = from
290
+ JsonChain.new(spawn).json_build_object!(options)
291
+ end
292
+
293
+ def jsonb_build_object(key, from, **options)
294
+ options[:key] = key
295
+ options[:from] = from
296
+ JsonChain.new(spawn).jsonb_build_object!(options)
297
+ end
298
+
299
+ # Appends a hash literal to the calling relations response
300
+ #
301
+ # Arguments: Requires an Array or Hash set of values
302
+ #
303
+ # Options:
304
+ #
305
+ # - as: [Symbol or String] (default="results"): What the column will be aliased to
306
+ #
307
+ # Example:
308
+ # - Supplying inputs as a Hash
309
+ # query = User.json_build_literal(number: 1, last_name: "json", pi: 3.14)
310
+ # query.take.results #=> { "number" => 1, "last_name" => "json", "pi" => 3.14 }
311
+ #
312
+ # - Supplying inputs as an Array
313
+ #
314
+ # query = User.json_build_literal(:number, 1, :last_name, "json", :pi, 3.14)
315
+ # query.take.results #=> { "number" => 1, "last_name" => "json", "pi" => 3.14 }
316
+ #
317
+
318
+ def json_build_literal(*args)
319
+ JsonChain.new(spawn).json_build_literal!(args)
320
+ end
321
+
322
+ def jsonb_build_literal(*args)
323
+ JsonChain.new(spawn).jsonb_build_literal!(args)
324
+ end
325
+ end
326
+ end
327
+ end
328
+
329
+ ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Json)