66 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			66 lines
		
	
	
		
			2.1 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| # Add support for writing recursive CTEs in ActiveRecord
 | |
| 
 | |
| # Initially from Lorin Thwaits (https://github.com/lorint) as per comment:
 | |
| # https://github.com/vlado/activerecord-cte/issues/16#issuecomment-1433043310
 | |
| 
 | |
| # Modified from the above code to change the signature to
 | |
| # `with_recursive(hash)` and extending CTE hash values to also includes arrays
 | |
| # of values that get turned into UNION ALL expressions.
 | |
| 
 | |
| # This implementation has been merged in Rails: https://github.com/rails/rails/pull/51601
 | |
| 
 | |
| module ActiveRecord
 | |
|   module QueryMethodsExtensions
 | |
|     def with_recursive(*args)
 | |
|       @with_is_recursive = true
 | |
|       check_if_method_has_arguments!(__callee__, args)
 | |
|       spawn.with_recursive!(*args)
 | |
|     end
 | |
| 
 | |
|     # Like #with_recursive but modifies the relation in place.
 | |
|     def with_recursive!(*args) # :nodoc:
 | |
|       self.with_values += args
 | |
|       @with_is_recursive = true
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     def build_with(arel)
 | |
|       return if with_values.empty?
 | |
| 
 | |
|       with_statements = with_values.map do |with_value|
 | |
|         raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
 | |
| 
 | |
|         build_with_value_from_hash(with_value)
 | |
|       end
 | |
| 
 | |
|       # Was:  arel.with(with_statements)
 | |
|       @with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
 | |
|     end
 | |
| 
 | |
|     def build_with_value_from_hash(hash)
 | |
|       hash.map do |name, value|
 | |
|         Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def build_with_expression_from_value(value)
 | |
|       case value
 | |
|       when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
 | |
|       when ActiveRecord::Relation then value.arel
 | |
|       when Arel::SelectManager then value
 | |
|       when Array then value.map { |e| build_with_expression_from_value(e) }.reduce { |result, value| Arel::Nodes::UnionAll.new(result, value) }
 | |
|       else
 | |
|         raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| ActiveSupport.on_load(:active_record) do
 | |
|   ActiveRecord::QueryMethods.prepend(ActiveRecord::QueryMethodsExtensions)
 | |
| end
 |