active_record_extended 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -7
  3. data/lib/active_record_extended/arel/aggregate_function_name.rb +0 -0
  4. data/lib/active_record_extended/arel/nodes.rb +1 -1
  5. data/lib/active_record_extended/arel/predications.rb +0 -0
  6. data/lib/active_record_extended/arel/{sql_literal.rb → sql_literal_patch.rb} +2 -2
  7. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +16 -12
  8. data/lib/active_record_extended/arel.rb +1 -1
  9. data/lib/active_record_extended/patch/array_handler_patch.rb +22 -0
  10. data/lib/active_record_extended/patch/relation_patch.rb +67 -0
  11. data/lib/active_record_extended/patch/where_clause_patch.rb +13 -0
  12. data/lib/active_record_extended/query_methods/any_of.rb +1 -1
  13. data/lib/active_record_extended/query_methods/either.rb +3 -3
  14. data/lib/active_record_extended/query_methods/{select.rb → foster_select.rb} +4 -4
  15. data/lib/active_record_extended/query_methods/inet.rb +0 -0
  16. data/lib/active_record_extended/query_methods/json.rb +2 -2
  17. data/lib/active_record_extended/query_methods/unionize.rb +5 -5
  18. data/lib/active_record_extended/query_methods/where_chain.rb +96 -92
  19. data/lib/active_record_extended/query_methods/window.rb +3 -3
  20. data/lib/active_record_extended/query_methods/with_cte.rb +3 -3
  21. data/lib/active_record_extended/utilities/order_by.rb +1 -1
  22. data/lib/active_record_extended/utilities/support.rb +1 -1
  23. data/lib/active_record_extended/version.rb +1 -1
  24. data/lib/active_record_extended.rb +55 -4
  25. metadata +21 -70
  26. data/lib/active_record_extended/active_record/relation_patch.rb +0 -50
  27. data/lib/active_record_extended/active_record.rb +0 -25
  28. data/lib/active_record_extended/patch/5_1/where_clause.rb +0 -11
  29. data/lib/active_record_extended/patch/5_2/where_clause.rb +0 -11
  30. data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +0 -20
  31. data/spec/active_record_extended_spec.rb +0 -7
  32. data/spec/query_methods/any_of_spec.rb +0 -131
  33. data/spec/query_methods/array_query_spec.rb +0 -64
  34. data/spec/query_methods/either_spec.rb +0 -70
  35. data/spec/query_methods/hash_query_spec.rb +0 -45
  36. data/spec/query_methods/inet_query_spec.rb +0 -112
  37. data/spec/query_methods/json_spec.rb +0 -157
  38. data/spec/query_methods/select_spec.rb +0 -115
  39. data/spec/query_methods/unionize_spec.rb +0 -165
  40. data/spec/query_methods/window_spec.rb +0 -51
  41. data/spec/query_methods/with_cte_spec.rb +0 -50
  42. data/spec/spec_helper.rb +0 -28
  43. data/spec/sql_inspections/any_of_sql_spec.rb +0 -41
  44. data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +0 -41
  45. data/spec/sql_inspections/arel/array_spec.rb +0 -63
  46. data/spec/sql_inspections/arel/inet_spec.rb +0 -66
  47. data/spec/sql_inspections/contains_sql_queries_spec.rb +0 -47
  48. data/spec/sql_inspections/either_sql_spec.rb +0 -71
  49. data/spec/sql_inspections/json_sql_spec.rb +0 -82
  50. data/spec/sql_inspections/unionize_sql_spec.rb +0 -124
  51. data/spec/sql_inspections/window_sql_spec.rb +0 -98
  52. data/spec/sql_inspections/with_cte_sql_spec.rb +0 -95
  53. data/spec/support/database_cleaner.rb +0 -15
  54. data/spec/support/models.rb +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f3f7041cc4869183bc20b38f8387bd8f3f216225990a62ec0c4e0fba5f847dd
4
- data.tar.gz: c61e5ad1c2fae32a5dbfe8660bb888d85f255f285a2df8b1f02206f51cbd71b7
3
+ metadata.gz: b59ef27069d870b734f0494db168936d5536493fd1346469f7a85d9b43bc7655
4
+ data.tar.gz: f0e25e53250ec9d562bb75ec06e7d02deda48a9996b8a03e4fb5afac2d113abc
5
5
  SHA512:
6
- metadata.gz: c27cf6541f3e11a3beb00263be7c1478e8f4ddb64b0121ed141347ca995252453864bd3cf0992038b7ea2aa1c409f50792800fdf909d8e544edd470af35231fa
7
- data.tar.gz: d0426b00cca79e3d0d145fb87fdd05c25611ff5bfde95c8c1845a281786bd4c7e6bfbd866df46cb8ce1ccd45ab5b4bd91aa83e6c21db879721436574f90087cf
6
+ metadata.gz: ffad0b652cbaac828d25ed1dbbd6e5e5479f6905bf776785090f9b76fe33fefb750fcbce7c329bbcaf077f1f5401b9817e830d89efab130feca3500bbc97c1f8
7
+ data.tar.gz: b0f77a0248242cc50e7f87ebd813e90c4e57bf1cfd28e35190ee93fa4ce2ae50878dd6e7f153dc017a692f13d147a9a9f8378ba9583d7705c2b71566fb20e29c
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/active_record_extended.svg)](https://badge.fury.io/rb/active_record_extended)
2
- [![Build Status](https://travis-ci.com/GeorgeKaraszi/ActiveRecordExtended.svg?branch=master)](https://travis-ci.com/GeorgeKaraszi/ActiveRecordExtended)
2
+ [![Build Status](https://github.com/GeorgeKaraszi/ActiveRecordExtended/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/GeorgeKaraszi/ActiveRecordExtended/actions/workflows/test.yml?query=branch%3Amaster+)
3
3
  [![Maintainability](https://api.codeclimate.com/v1/badges/98ecffc0239417098cbc/maintainability)](https://codeclimate.com/github/GeorgeKaraszi/active_record_extended/maintainability)
4
4
  [![Test Coverage](https://api.codeclimate.com/v1/badges/f22154211bb3a8feb89f/test_coverage)](https://codeclimate.com/github/GeorgeKaraszi/ActiveRecordExtended/test_coverage)
5
5
  ## Index
@@ -52,12 +52,12 @@ Active Record Extended is essentially providing users with the other half of Pos
52
52
  ## Compatibility
53
53
 
54
54
  This package is designed align and work with any officially supported Ruby and Rails versions.
55
- - Minimum Ruby Version: 2.4.x **(EOL warning!)**
56
- - Minimum Rails Version: 5.1.x **(EOL warning!)**
57
- - Minimum Postgres Version: 9.6.x **(EOL warning!)**
58
- - Latest Ruby supported: 3.0.x
55
+ - Minimum Ruby Version: 2.5.x **(EOL warning!)**
56
+ - Minimum Rails Version: 5.2.x **(EOL warning!)**
57
+ - Minimum Postgres Version: 10.x **(EOL warning!)**
58
+ - Latest Ruby supported: 3.1.x
59
59
  - Latest Rails supported: 7.0.x
60
- - Postgres: 9.6-current(13) (probably works with most older versions to a certain point)
60
+ - Postgres: 10-current(14) (probably works with most older versions to a certain point)
61
61
 
62
62
  ## Installation
63
63
 
@@ -412,12 +412,27 @@ If any or all of your json sub-queries include a CTE, read the [Subquery CTE Got
412
412
  #### Row To JSON
413
413
  [Postgres 'ROW_TO_JSON' function](https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE)
414
414
 
415
- The implementation of the`.row_to_json/2` method is designed to be used with sub-queries. As a means for taking complex
415
+ The implementation of the`.select_row_to_json/2` method is designed to be used with sub-queries. As a means for taking complex
416
416
  query logic and transform them into a single or multiple json responses. These responses are required to be assigned
417
417
  to an aliased column on the parent(callee) level.
418
418
 
419
419
  While quite the mouthful of an explanation. The implementation of combining unrelated or semi-related queries is quite smooth(imo).
420
420
 
421
+ **Arguments:**
422
+ - `from` [String, Arel, or ActiveRecord::Relation]: A subquery that can be nested into a `ROW_TO_JSON` clause
423
+
424
+ **Options:**
425
+ - `as` [Symbol or String] (default="results"): What the column will be aliased to
426
+ - `key` [Symbol or String] (default=[random letter]): Internal query alias name.
427
+ * This is useful if you would like to add additional mid-level predicate clauses
428
+ - `cast_with` [Symbol or Array\<Symbol>]:
429
+ * `:to_jsonb`
430
+ * `:array`
431
+ * `:array_agg`
432
+ * `:distinct` (auto applies `:array_agg` & `:to_jsonb`)
433
+ - `order_by` [Symbol or Hash]: Applies an ordering operation (similar to ActiveRecord #order)
434
+ * **Note**: this option will be ignored if you need to order a DISTINCT Aggregated Array.
435
+
421
436
  ```ruby
422
437
  physical_cat = Category.create!(name: "Physical")
423
438
  products = 3.times.map { Product.create! }
@@ -5,7 +5,7 @@ require "arel/nodes/function"
5
5
 
6
6
  module Arel
7
7
  module Nodes
8
- if Gem::Requirement.new("< 6.1").satisfied_by?(ActiveRecord.gem_version)
8
+ unless ActiveRecordExtended::AR_VERSION_GTE_6_1
9
9
  ["Contains", "Overlaps"].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
10
10
  end
11
11
 
File without changes
@@ -5,7 +5,7 @@ require "arel/nodes/sql_literal"
5
5
  # CTE alias fix for Rails 6.1
6
6
  module Arel
7
7
  module Nodes
8
- module SqlLiteralDecorator
8
+ module SqlLiteralPatch
9
9
  def name
10
10
  self
11
11
  end
@@ -13,4 +13,4 @@ module Arel
13
13
  end
14
14
  end
15
15
 
16
- Arel::Nodes::SqlLiteral.prepend(Arel::Nodes::SqlLiteralDecorator)
16
+ Arel::Nodes::SqlLiteral.prepend(Arel::Nodes::SqlLiteralPatch)
@@ -9,21 +9,25 @@ module ActiveRecordExtended
9
9
 
10
10
  # rubocop:disable Naming/MethodName
11
11
 
12
- def visit_Arel_Nodes_Overlaps(object, collector)
13
- infix_value object, collector, " && "
12
+ unless ActiveRecordExtended::AR_VERSION_GTE_6_1
13
+ def visit_Arel_Nodes_Overlaps(object, collector)
14
+ infix_value object, collector, " && "
15
+ end
14
16
  end
15
17
 
16
- def visit_Arel_Nodes_Contains(object, collector)
17
- left_column = object.left.relation.name.classify.constantize.columns.detect do |col|
18
- matchable_column?(col, object)
19
- end
18
+ unless ActiveRecordExtended::AR_VERSION_GTE_6_1
19
+ def visit_Arel_Nodes_Contains(object, collector)
20
+ left_column = object.left.relation.name.classify.constantize.columns.detect do |col|
21
+ matchable_column?(col, object)
22
+ end
20
23
 
21
- if [:hstore, :jsonb].include?(left_column&.type)
22
- visit_Arel_Nodes_ContainsHStore(object, collector)
23
- elsif left_column.try(:array)
24
- visit_Arel_Nodes_ContainsArray(object, collector)
25
- else
26
- visit_Arel_Nodes_Inet_Contains(object, collector)
24
+ if [:hstore, :jsonb].include?(left_column&.type)
25
+ visit_Arel_Nodes_ContainsHStore(object, collector)
26
+ elsif left_column.try(:array)
27
+ visit_Arel_Nodes_ContainsArray(object, collector)
28
+ else
29
+ visit_Arel_Nodes_Inet_Contains(object, collector)
30
+ end
27
31
  end
28
32
  end
29
33
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record_extended/arel/nodes"
4
- require "active_record_extended/arel/sql_literal"
4
+ require "active_record_extended/arel/sql_literal_patch"
5
5
  require "active_record_extended/arel/aggregate_function_name"
6
6
  require "active_record_extended/arel/predications"
7
7
  require "active_record_extended/arel/visitors/postgresql_decorator"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/predicate_builder"
4
+ require "active_record/relation/predicate_builder/array_handler"
5
+
6
+ module ActiveRecordExtended
7
+ module Patch
8
+ module ArrayHandlerPatch
9
+ def call(attribute, value)
10
+ cache = ActiveRecord::Base.connection.schema_cache
11
+ if cache.data_source_exists?(attribute.relation.name)
12
+ column = cache.columns(attribute.relation.name).detect { |col| col.name.to_s == attribute.name.to_s }
13
+ return attribute.eq(value) if column.try(:array)
14
+ end
15
+
16
+ super(attribute, value)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ ActiveRecord::PredicateBuilder::ArrayHandler.prepend(ActiveRecordExtended::Patch::ArrayHandlerPatch)
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module Patch
5
+ module RelationPatch
6
+ module QueryDelegation
7
+ AR_EX_QUERY_METHODS = (
8
+ [
9
+ :with, :define_window, :select_window, :foster_select,
10
+ :either_join, :either_joins, :either_order, :either_orders
11
+ ] +
12
+ ActiveRecordExtended::QueryMethods::Unionize::UNIONIZE_METHODS +
13
+ ActiveRecordExtended::QueryMethods::Json::JSON_QUERY_METHODS
14
+ ).freeze
15
+
16
+ delegate(*AR_EX_QUERY_METHODS, to: :all)
17
+ end
18
+
19
+ module Merger
20
+ def merge
21
+ merge_ctes!
22
+ merge_union!
23
+ merge_windows!
24
+ super
25
+ end
26
+
27
+ def merge_union!
28
+ return if other.unionize_storage.empty?
29
+
30
+ relation.union_values += other.union_values
31
+ relation.union_operations += other.union_operations
32
+ relation.union_ordering_values += other.union_ordering_values
33
+ end
34
+
35
+ def merge_windows!
36
+ return unless other.window_values?
37
+
38
+ relation.window_values |= other.window_values
39
+ end
40
+
41
+ def merge_ctes!
42
+ return unless other.with_values?
43
+
44
+ if other.recursive_value? && !relation.recursive_value?
45
+ relation.with!.recursive(other.cte)
46
+ else
47
+ relation.with!(other.cte)
48
+ end
49
+ end
50
+ end
51
+
52
+ module ArelBuildPatch
53
+ def build_arel(*aliases)
54
+ super.tap do |arel|
55
+ build_windows(arel) if window_values?
56
+ build_unions(arel) if union_values?
57
+ build_with(arel) if with_values?
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ ActiveRecord::Relation.prepend(ActiveRecordExtended::Patch::RelationPatch::ArelBuildPatch)
66
+ ActiveRecord::Relation::Merger.prepend(ActiveRecordExtended::Patch::RelationPatch::Merger)
67
+ ActiveRecord::Base.extend(ActiveRecordExtended::Patch::RelationPatch::QueryDelegation)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module Patch
5
+ module WhereClausePatch
6
+ def modified_predicates(&block)
7
+ ActiveRecord::Relation::WhereClause.new(predicates.map(&block))
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ ActiveRecord::Relation::WhereClause.prepend(ActiveRecordExtended::Patch::WhereClausePatch)
@@ -29,7 +29,7 @@ module ActiveRecordExtended
29
29
 
30
30
  def hash_map_queries(queries)
31
31
  if queries.size == 1 && queries.first.is_a?(Hash)
32
- queries.first.each_pair.map { |attr, predicate| Hash[attr, predicate] }
32
+ queries.first.each_pair.map { |attr, predicate| { attr => predicate } }
33
33
  else
34
34
  queries
35
35
  end
@@ -26,7 +26,7 @@ module ActiveRecordExtended
26
26
  private
27
27
 
28
28
  def xor_field_sql(options)
29
- XOR_FIELD_SQL % Hash[xor_field_options(options)]
29
+ XOR_FIELD_SQL % xor_field_options(options).to_h
30
30
  end
31
31
 
32
32
  def sort_order_sql(dir)
@@ -35,7 +35,7 @@ module ActiveRecordExtended
35
35
 
36
36
  def xor_field_options(options)
37
37
  str_args = options.flatten.take(XOR_FIELD_KEYS.size).map(&:to_s)
38
- Hash[XOR_FIELD_KEYS.zip(str_args)]
38
+ XOR_FIELD_KEYS.zip(str_args).to_h
39
39
  end
40
40
 
41
41
  def map_columns_to_tables(associations_and_columns)
@@ -60,4 +60,4 @@ module ActiveRecordExtended
60
60
  end
61
61
  end
62
62
 
63
- ActiveRecord::Base.extend(ActiveRecordExtended::QueryMethods::Either)
63
+ ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Either)
@@ -2,10 +2,10 @@
2
2
 
3
3
  module ActiveRecordExtended
4
4
  module QueryMethods
5
- module Select
5
+ module FosterSelect
6
6
  class SelectHelper
7
- include ::ActiveRecordExtended::Utilities::Support
8
- include ::ActiveRecordExtended::Utilities::OrderBy
7
+ include ActiveRecordExtended::Utilities::Support
8
+ include ActiveRecordExtended::Utilities::OrderBy
9
9
 
10
10
  AGGREGATE_ONE_LINERS = /^(exists|sum|max|min|avg|count|jsonb?_agg|(bit|bool)_(and|or)|xmlagg|array_agg)$/.freeze
11
11
 
@@ -115,4 +115,4 @@ module ActiveRecordExtended
115
115
  end
116
116
  end
117
117
 
118
- ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Select)
118
+ ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::FosterSelect)
File without changes
@@ -12,8 +12,8 @@ module ActiveRecordExtended
12
12
  ].freeze
13
13
 
14
14
  class JsonChain
15
- include ::ActiveRecordExtended::Utilities::Support
16
- include ::ActiveRecordExtended::Utilities::OrderBy
15
+ include ActiveRecordExtended::Utilities::Support
16
+ include ActiveRecordExtended::Utilities::OrderBy
17
17
 
18
18
  DEFAULT_ALIAS = '"results"'
19
19
  TO_JSONB_OPTIONS = [:array_agg, :distinct, :to_jsonb].to_set.freeze
@@ -7,8 +7,8 @@ module ActiveRecordExtended
7
7
  UNIONIZE_METHODS = [:union, :union_all, :union_except, :union_intersect].freeze
8
8
 
9
9
  class UnionChain
10
- include ::ActiveRecordExtended::Utilities::Support
11
- include ::ActiveRecordExtended::Utilities::OrderBy
10
+ include ActiveRecordExtended::Utilities::Support
11
+ include ActiveRecordExtended::Utilities::OrderBy
12
12
 
13
13
  def initialize(scope)
14
14
  @scope = scope
@@ -107,7 +107,7 @@ module ActiveRecordExtended
107
107
  end
108
108
 
109
109
  def union(opts = :chain, *args)
110
- return UnionChain.new(spawn) if :chain == opts
110
+ return UnionChain.new(spawn) if opts == :chain
111
111
 
112
112
  opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
113
113
  end
@@ -121,7 +121,7 @@ module ActiveRecordExtended
121
121
  def union!(opts = :chain, *args, chain_method: :union)
122
122
  union_chain = UnionChain.new(self)
123
123
  chain_method ||= :union
124
- return union_chain if :chain == opts
124
+ return union_chain if opts == :chain
125
125
 
126
126
  union_chain.public_send(chain_method, *([opts] + args))
127
127
  end
@@ -178,7 +178,7 @@ module ActiveRecordExtended
178
178
  def build_union_nodes!(raise_error = true)
179
179
  unionize_error_or_warn!(raise_error)
180
180
  union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
181
- next resolve_relation_node(relation_node) if union_node.nil?
181
+ next resolve_relation_node(relation_node) if union_node.nil? # rubocop:disable Lint/UnmodifiedReduceAccumulator
182
182
 
183
183
  operation = union_operations.fetch(index - 1, :union)
184
184
  left = union_node
@@ -1,117 +1,121 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordExtended
4
- module WhereChain
5
- AR_VERSION_AT_LEAST_6_1 = ActiveRecord.version >= Gem::Version.new('6.1')
6
-
7
- # Finds Records that have an array column that contain any a set of values
8
- # User.where.overlap(tags: [1,2])
9
- # # SELECT * FROM users WHERE tags && {1,2}
10
- def overlaps(opts, *rest)
11
- substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
12
- end
13
- alias overlap overlaps
4
+ module QueryMethods
5
+ module WhereChain
6
+ # Finds Records that have an array column that contain any a set of values
7
+ # User.where.overlap(tags: [1,2])
8
+ # # SELECT * FROM users WHERE tags && {1,2}
9
+ def overlaps(opts, *rest)
10
+ substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
11
+ end
12
+ alias overlap overlaps
14
13
 
15
- # Finds Records that contain an element in an array column
16
- # User.where.any(tags: 3)
17
- # # SELECT user.* FROM user WHERE 3 = ANY(user.tags)
18
- def any(opts, *rest)
19
- equality_to_function("ANY", opts, rest)
20
- end
14
+ # Finds Records that contain an element in an array column
15
+ # User.where.any(tags: 3)
16
+ # # SELECT user.* FROM user WHERE 3 = ANY(user.tags)
17
+ def any(opts, *rest)
18
+ equality_to_function("ANY", opts, rest)
19
+ end
21
20
 
22
- # Finds Records that contain a single matchable array element
23
- # User.where.all(tags: 3)
24
- # # SELECT user.* FROM user WHERE 3 = ALL(user.tags)
25
- def all(opts, *rest)
26
- equality_to_function("ALL", opts, rest)
27
- end
21
+ # Finds Records that contain a single matchable array element
22
+ # User.where.all(tags: 3)
23
+ # # SELECT user.* FROM user WHERE 3 = ALL(user.tags)
24
+ def all(opts, *rest)
25
+ equality_to_function("ALL", opts, rest)
26
+ end
28
27
 
29
- # Finds Records that contains a nested set elements
30
- #
31
- # Array Column Type:
32
- # User.where.contains(tags: [1, 3])
33
- # # SELECT user.* FROM user WHERE user.tags @> {1,3}
34
- #
35
- # HStore Column Type:
36
- # User.where.contains(data: { nickname: 'chainer' })
37
- # # SELECT user.* FROM user WHERE user.data @> 'nickname' => 'chainer'
38
- #
39
- # JSONB Column Type:
40
- # User.where.contains(data: { nickname: 'chainer' })
41
- # # SELECT user.* FROM user WHERE user.data @> {'nickname': 'chainer'}
42
- #
43
- # This can also be used along side joined tables
44
- #
45
- # JSONB Column Type Example:
46
- # Tag.joins(:user).where.contains(user: { data: { nickname: 'chainer' } })
47
- # # SELECT tags.* FROM tags INNER JOIN user on user.id = tags.user_id WHERE user.data @> { nickname: 'chainer' }
48
- #
49
- def contains(opts, *rest)
50
- build_where_chain(opts, rest) do |arel|
51
- case arel
52
- when Arel::Nodes::In, Arel::Nodes::Equality
53
- column = left_column(arel) || column_from_association(arel)
54
-
55
- if [:hstore, :jsonb].include?(column.type)
56
- Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
57
- elsif column.try(:array)
58
- Arel::Nodes::ContainsArray.new(arel.left, arel.right)
28
+ # Finds Records that contains a nested set elements
29
+ #
30
+ # Array Column Type:
31
+ # User.where.contains(tags: [1, 3])
32
+ # # SELECT user.* FROM user WHERE user.tags @> {1,3}
33
+ #
34
+ # HStore Column Type:
35
+ # User.where.contains(data: { nickname: 'chainer' })
36
+ # # SELECT user.* FROM user WHERE user.data @> 'nickname' => 'chainer'
37
+ #
38
+ # JSONB Column Type:
39
+ # User.where.contains(data: { nickname: 'chainer' })
40
+ # # SELECT user.* FROM user WHERE user.data @> {'nickname': 'chainer'}
41
+ #
42
+ # This can also be used along side joined tables
43
+ #
44
+ # JSONB Column Type Example:
45
+ # Tag.joins(:user).where.contains(user: { data: { nickname: 'chainer' } })
46
+ # # SELECT tags.* FROM tags INNER JOIN user on user.id = tags.user_id WHERE user.data @> { nickname: 'chainer' }
47
+ #
48
+ def contains(opts, *rest)
49
+ if ActiveRecordExtended::AR_VERSION_GTE_6_1
50
+ return substitute_comparisons(opts, rest, Arel::Nodes::Contains, "contains")
51
+ end
52
+
53
+ build_where_chain(opts, rest) do |arel|
54
+ case arel
55
+ when Arel::Nodes::In, Arel::Nodes::Equality
56
+ column = left_column(arel) || column_from_association(arel)
57
+
58
+ if [:hstore, :jsonb].include?(column.type)
59
+ Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
60
+ elsif column.try(:array)
61
+ Arel::Nodes::ContainsArray.new(arel.left, arel.right)
62
+ else
63
+ raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
64
+ end
59
65
  else
60
66
  raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
61
67
  end
62
- else
63
- raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
64
68
  end
65
69
  end
66
- end
67
70
 
68
- private
71
+ private
69
72
 
70
- def matchable_column?(col, arel)
71
- col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
72
- end
73
+ def matchable_column?(col, arel)
74
+ col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
75
+ end
73
76
 
74
- def column_from_association(arel)
75
- assoc = assoc_from_related_table(arel)
76
- assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
77
- end
77
+ def column_from_association(arel)
78
+ assoc = assoc_from_related_table(arel)
79
+ assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
80
+ end
78
81
 
79
- def assoc_from_related_table(arel)
80
- @scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
81
- @scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
82
- end
82
+ def assoc_from_related_table(arel)
83
+ @scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
84
+ @scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
85
+ end
83
86
 
84
- def left_column(arel)
85
- @scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
86
- end
87
+ def left_column(arel)
88
+ @scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
89
+ end
87
90
 
88
- def equality_to_function(function_name, opts, rest)
89
- build_where_chain(opts, rest) do |arel|
90
- case arel
91
- when Arel::Nodes::Equality
92
- Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
93
- else
94
- raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
91
+ def equality_to_function(function_name, opts, rest)
92
+ build_where_chain(opts, rest) do |arel|
93
+ case arel
94
+ when Arel::Nodes::Equality
95
+ Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
96
+ else
97
+ raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
98
+ end
95
99
  end
96
100
  end
97
- end
98
101
 
99
- def substitute_comparisons(opts, rest, arel_node_class, method)
100
- build_where_chain(opts, rest) do |arel|
101
- case arel
102
- when Arel::Nodes::In, Arel::Nodes::Equality
103
- arel_node_class.new(arel.left, arel.right)
104
- else
105
- raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
102
+ def substitute_comparisons(opts, rest, arel_node_class, method)
103
+ build_where_chain(opts, rest) do |arel|
104
+ case arel
105
+ when Arel::Nodes::In, Arel::Nodes::Equality
106
+ arel_node_class.new(arel.left, arel.right)
107
+ else
108
+ raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
109
+ end
106
110
  end
107
111
  end
108
- end
109
112
 
110
- def build_where_clause_for(scope, opts, rest)
111
- if AR_VERSION_AT_LEAST_6_1
112
- scope.send(:build_where_clause, opts, rest)
113
- else
114
- scope.send(:where_clause_factory).build(opts, rest)
113
+ def build_where_clause_for(scope, opts, rest)
114
+ if ActiveRecordExtended::AR_VERSION_GTE_6_1
115
+ scope.send(:build_where_clause, opts, rest)
116
+ else
117
+ scope.send(:where_clause_factory).build(opts, rest)
118
+ end
115
119
  end
116
120
  end
117
121
  end
@@ -120,7 +124,7 @@ end
120
124
  module ActiveRecord
121
125
  module QueryMethods
122
126
  class WhereChain
123
- prepend ActiveRecordExtended::WhereChain
127
+ prepend ActiveRecordExtended::QueryMethods::WhereChain
124
128
 
125
129
  def build_where_chain(opts, rest, &block)
126
130
  where_clause = build_where_clause_for(@scope, opts, rest)
@@ -4,8 +4,8 @@ module ActiveRecordExtended
4
4
  module QueryMethods
5
5
  module Window
6
6
  class DefineWindowChain
7
- include ::ActiveRecordExtended::Utilities::Support
8
- include ::ActiveRecordExtended::Utilities::OrderBy
7
+ include ActiveRecordExtended::Utilities::Support
8
+ include ActiveRecordExtended::Utilities::OrderBy
9
9
 
10
10
  def initialize(scope, window_name)
11
11
  @scope = scope
@@ -24,7 +24,7 @@ module ActiveRecordExtended
24
24
  end
25
25
 
26
26
  class WindowSelectBuilder
27
- include ::ActiveRecordExtended::Utilities::Support
27
+ include ActiveRecordExtended::Utilities::Support
28
28
 
29
29
  def initialize(window_function, args, window_name)
30
30
  @window_function = window_function
@@ -4,7 +4,7 @@ module ActiveRecordExtended
4
4
  module QueryMethods
5
5
  module WithCTE
6
6
  class WithCTE
7
- include ::ActiveRecordExtended::Utilities::Support
7
+ include ActiveRecordExtended::Utilities::Support
8
8
  include Enumerable
9
9
  extend Forwardable
10
10
 
@@ -113,14 +113,14 @@ module ActiveRecordExtended
113
113
 
114
114
  # @param [Hash, WithCTE] opts
115
115
  def with(opts = :chain, *rest)
116
- return WithChain.new(spawn) if :chain == opts
116
+ return WithChain.new(spawn) if opts == :chain
117
117
 
118
118
  opts.blank? ? self : spawn.with!(opts, *rest)
119
119
  end
120
120
 
121
121
  # @param [Hash, WithCTE] opts
122
122
  def with!(opts = :chain, *_rest)
123
- return WithChain.new(self) if :chain == opts
123
+ return WithChain.new(self) if opts == :chain
124
124
 
125
125
  tap do |scope|
126
126
  scope.cte ||= WithCTE.new(self)
@@ -49,7 +49,7 @@ module ActiveRecordExtended
49
49
  obj.each_pair do |o_key, o_value|
50
50
  new_hash["#{tbl_or_col}.#{o_key}"] = o_value
51
51
  end
52
- elsif ::ActiveRecord::QueryMethods::VALID_DIRECTIONS.include?(obj)
52
+ elsif ActiveRecord::QueryMethods::VALID_DIRECTIONS.include?(obj)
53
53
  new_hash[tbl_or_col] = obj
54
54
  elsif obj.nil?
55
55
  new_hash[tbl_or_col.to_s] = :asc
@@ -113,7 +113,7 @@ module ActiveRecordExtended
113
113
  case value.to_s
114
114
  # Ignore keys that contain double quotes or a Arel.star (*)[all columns]
115
115
  # or if a table has already been explicitly declared (ex: users.id)
116
- when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+))/
116
+ when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+)|\(.+\))/
117
117
  value
118
118
  else
119
119
  PG::Connection.quote_ident(value.to_s)