Module: ActiveRecordExtended::Utilities::Support
- Included in:
- QueryMethods::Json::JsonChain, QueryMethods::Select::SelectHelper, QueryMethods::Unionize::UnionChain, QueryMethods::Window::DefineWindowChain, QueryMethods::Window::WindowSelectBuilder, QueryMethods::WithCTE::WithCTE
- Defined in:
- lib/active_record_extended/utilities/support.rb
Constant Summary collapse
- A_TO_Z_KEYS =
("a".."z").to_a.freeze
Instance Method Summary collapse
-
#double_quote(value) ⇒ Object
Ensures the given value is properly double quoted.
- #flatten_safely(values, &block) ⇒ Object
-
#flatten_to_sql(*values) ⇒ Object
(also: #to_sql_array)
We need to ensure we can flatten nested ActiveRecord::Relations that might have been nested due to the (splat)*args parameters.
-
#from_clause_constructor(from, reference_key) ⇒ Object
Will attempt to digest and resolve the from clause.
- #generate_grouping(expr) ⇒ Object
- #generate_named_function(function_name, *args) ⇒ Object
- #group_when_needed(arel_or_rel_query) ⇒ Object
- #key_generator ⇒ Object
-
#literal_key(key) ⇒ Object
Ensures the key is properly single quoted and treated as a actual PG key reference.
- #needs_to_be_grouped?(query) ⇒ Boolean
-
#nested_alias_escape(query, alias_name) ⇒ Object
Applies aliases to the given query Ex: `SELECT * FROM users` => `(SELECT * FROM users) AS “members”`.
-
#pipe_cte_with!(subquery) ⇒ Object
Will carry defined CTE tables from the nested sub-query and gradually pushes it up to the parents query stack I.E: It pushes `WITH [:cte_name:] AS(…), ..` to the top of the query structure tree.
-
#to_arel_sql(value) ⇒ Object
Converts a potential subquery into a compatible Arel SQL node.
-
#wrap_with_agg_array(arel_or_rel_query, alias_name, order_by: false, distinct: false) ⇒ Object
Wraps query into an aggregated array EX: `(ARRAY_AGG((SELECT * FROM users)) AS “members”` `(ARRAY_AGG(DISTINCT (SELECT * FROM users)) AS “members”` `SELECT ARRAY_AGG((id)) AS “ids” FROM users` `SELECT ARRAY_AGG(DISTINCT (id)) AS “ids” FROM users`.
-
#wrap_with_array(arel_or_rel_query, alias_name, order_by: false) ⇒ Object
Wraps subquery into an Aliased ARRAY Ex: `SELECT * FROM users` => (ARRAY(SELECT * FROM users)) AS “members”.
Instance Method Details
#double_quote(value) ⇒ Object
Ensures the given value is properly double quoted. This also ensures we don't have conflicts with reversed keywords.
IE: `user` is a reserved keyword in PG. But `“user”` is allowed and works the same
when used as an column/tbl alias.
110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/active_record_extended/utilities/support.rb', line 110 def double_quote(value) return if value.nil? case value.to_s # Ignore keys that contain double quotes or a Arel.star (*)[all columns] # or if a table has already been explicitly declared (ex: users.id) when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+))/ value else PG::Connection.quote_ident(value.to_s) end end |
#flatten_safely(values, &block) ⇒ Object
21 22 23 24 25 26 27 28 |
# File 'lib/active_record_extended/utilities/support.rb', line 21 def flatten_safely(values, &block) unless values.is_a?(Array) values = yield values if block return [values] end values.map { |value| flatten_safely(value, &block) }.reduce(:+) end |
#flatten_to_sql(*values) ⇒ Object Also known as: to_sql_array
We need to ensure we can flatten nested ActiveRecord::Relations that might have been nested due to the (splat)*args parameters
Note: calling `Array.flatten/1` will actually remove all AR relations from the array.
13 14 15 16 17 18 |
# File 'lib/active_record_extended/utilities/support.rb', line 13 def flatten_to_sql(*values) flatten_safely(values) do |value| value = yield value if block_given? to_arel_sql(value) end end |
#from_clause_constructor(from, reference_key) ⇒ Object
Will attempt to digest and resolve the from clause
If the from clause is a String, it will check to see if a table reference key has been assigned.
- If one cannot be detected, one will be appended.
- Rails does not allow assigning table references using the `.from/2` method, when its a string / sym type.
If the from clause is an AR relation; it will duplicate the object.
- Ensures any memorizers are reset (ex: `.to_sql` sets a memorizer on the instance)
- Key's can be assigned using the `.from/2` method.
75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/active_record_extended/utilities/support.rb', line 75 def from_clause_constructor(from, reference_key) case from when /\s.?#{reference_key}.?$/ # The from clause is a string and has the tbl reference key @scope.unscoped.from(from) when String, Symbol @scope.unscoped.from("#{from} #{reference_key}") else replicate_klass = from.respond_to?(:unscoped) ? from.unscoped : @scope.unscoped replicate_klass.from(from.dup, reference_key) end.unscope(:where) end |
#generate_grouping(expr) ⇒ Object
163 164 165 |
# File 'lib/active_record_extended/utilities/support.rb', line 163 def generate_grouping(expr) ::Arel::Nodes::Grouping.new(to_arel_sql(expr)) end |
#generate_named_function(function_name, *args) ⇒ Object
167 168 169 170 171 |
# File 'lib/active_record_extended/utilities/support.rb', line 167 def generate_named_function(function_name, *args) args.map! { |arg| to_arel_sql(arg) } function_name = function_name.to_s.upcase ::Arel::Nodes::NamedFunction.new(to_arel_sql(function_name), args) end |
#group_when_needed(arel_or_rel_query) ⇒ Object
153 154 155 156 157 |
# File 'lib/active_record_extended/utilities/support.rb', line 153 def group_when_needed(arel_or_rel_query) return arel_or_rel_query unless needs_to_be_grouped?(arel_or_rel_query) generate_grouping(arel_or_rel_query) end |
#key_generator ⇒ Object
173 174 175 |
# File 'lib/active_record_extended/utilities/support.rb', line 173 def key_generator A_TO_Z_KEYS.sample end |
#literal_key(key) ⇒ Object
Ensures the key is properly single quoted and treated as a actual PG key reference.
124 125 126 127 128 129 130 131 132 133 |
# File 'lib/active_record_extended/utilities/support.rb', line 124 def literal_key(key) case key when TrueClass then "'t'" when FalseClass then "'f'" when Numeric then key else key = key.to_s key.start_with?("'") && key.end_with?("'") ? key : "'#{key}'" end end |
#needs_to_be_grouped?(query) ⇒ Boolean
159 160 161 |
# File 'lib/active_record_extended/utilities/support.rb', line 159 def needs_to_be_grouped?(query) query.respond_to?(:to_sql) || (query.is_a?(String) && /^SELECT.+/i.match?(query)) end |
#nested_alias_escape(query, alias_name) ⇒ Object
Applies aliases to the given query Ex: `SELECT * FROM users` => `(SELECT * FROM users) AS “members”`
32 33 34 35 |
# File 'lib/active_record_extended/utilities/support.rb', line 32 def nested_alias_escape(query, alias_name) sql_query = generate_grouping(query) Arel::Nodes::As.new(sql_query, to_arel_sql(double_quote(alias_name))) end |
#pipe_cte_with!(subquery) ⇒ Object
Will carry defined CTE tables from the nested sub-query and gradually pushes it up to the parents query stack I.E: It pushes `WITH [:cte_name:] AS(…), ..` to the top of the query structure tree
SPECIAL GOTCHA NOTE: (if duplicate keys are found) This will favor the parents query `with's` over nested ones!
91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/active_record_extended/utilities/support.rb', line 91 def pipe_cte_with!(subquery) return self unless subquery.try(:with_values?) # Add subquery CTE's to the parents query stack. (READ THE SPECIAL NOTE ABOVE!) if @scope.with_values? @scope.cte.pipe_cte_with!(subquery.cte) else # Top level has no with values @scope.with!(subquery.cte) end self end |
#to_arel_sql(value) ⇒ Object
Converts a potential subquery into a compatible Arel SQL node.
Note: We convert relations to SQL to maintain compatibility with Rails 5.1. Only Rails 5.2+ maintains bound attributes in Arel, so its better to be safe then sorry. When we drop support for Rails 5.1, we then can then drop the '.to_sql' conversation
142 143 144 145 146 147 148 149 150 151 |
# File 'lib/active_record_extended/utilities/support.rb', line 142 def to_arel_sql(value) case value when Arel::Nodes::Node, Arel::Nodes::SqlLiteral, nil value when ActiveRecord::Relation Arel.sql(value.spawn.to_sql) else Arel.sql(value.respond_to?(:to_sql) ? value.to_sql : value.to_s) end end |
#wrap_with_agg_array(arel_or_rel_query, alias_name, order_by: false, distinct: false) ⇒ Object
Wraps query into an aggregated array EX: `(ARRAY_AGG((SELECT * FROM users)) AS “members”`
`(ARRAY_AGG(DISTINCT (SELECT * FROM users)) AS "members"`
`SELECT ARRAY_AGG((id)) AS "ids" FROM users`
`SELECT ARRAY_AGG(DISTINCT (id)) AS "ids" FROM users`
53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/active_record_extended/utilities/support.rb', line 53 def wrap_with_agg_array(arel_or_rel_query, alias_name, order_by: false, distinct: false) distinct = !(!distinct) order_exp = distinct ? nil : order_by # Can't order a distinct agg query = group_when_needed(arel_or_rel_query) query = Arel::Nodes::AggregateFunctionName .new("ARRAY_AGG", to_sql_array(query), distinct) .order_by(order_exp) nested_alias_escape(query, alias_name) end |
#wrap_with_array(arel_or_rel_query, alias_name, order_by: false) ⇒ Object
Wraps subquery into an Aliased ARRAY Ex: `SELECT * FROM users` => (ARRAY(SELECT * FROM users)) AS “members”
39 40 41 42 43 44 45 46 |
# File 'lib/active_record_extended/utilities/support.rb', line 39 def wrap_with_array(arel_or_rel_query, alias_name, order_by: false) if order_by && arel_or_rel_query.is_a?(ActiveRecord::Relation) arel_or_rel_query = arel_or_rel_query.order(order_by) end query = Arel::Nodes::Array.new(to_sql_array(arel_or_rel_query)) nested_alias_escape(query, alias_name) end |