lib/left_joins.rb in left_joins-1.0.6 vs lib/left_joins.rb in left_joins-1.0.7

- old
+ new

@@ -1,187 +1,178 @@ -require "left_joins/version" -require 'active_record' -require 'active_record/relation' - -module LeftJoins - IS_RAILS3_FLAG = Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('4.0.0') - HAS_BUILT_IN_LEFT_JOINS_METHOD = ActiveRecord::QueryMethods.method_defined?(:left_outer_joins) - require 'left_joins_for_rails_3' if IS_RAILS3_FLAG - - class << self - def bind_values_of(relation) - return relation.bound_attributes if relation.respond_to?(:bound_attributes) # For Rails 5.0, 5.1, 5.2 - return relation.bind_values # For Rails 4.2 - end - end -end - -module ActiveRecord::QueryMethods - if not LeftJoins::HAS_BUILT_IN_LEFT_JOINS_METHOD - # ---------------------------------------------------------------- - # ● Storing left joins values into @left_outer_joins_values - # ---------------------------------------------------------------- - attr_writer :left_outer_joins_values - def left_outer_joins_values - @left_outer_joins_values ||= [] - end - - def left_outer_joins(*args) - check_if_method_has_arguments!(:left_outer_joins, args) - - args.compact! - args.flatten! - self.distinct_value = false - - return (LeftJoins::IS_RAILS3_FLAG ? clone : spawn).left_outer_joins!(*args) - end - - def left_outer_joins!(*args) - left_outer_joins_values.concat(args) - self - end - - # ---------------------------------------------------------------- - # ● Implement left joins when building arel - # ---------------------------------------------------------------- - alias_method :left_joins, :left_outer_joins - alias_method :build_arel_without_outer_joins, :build_arel - def build_arel(*args) - arel = build_arel_without_outer_joins(*args) - build_left_outer_joins(arel, @left_outer_joins_values) if @left_outer_joins_values - return arel - end - - alias_method :build_joins_without_join_type, :build_joins - def build_joins(manager, joins, join_type = nil) - Thread.current.thread_variable_set(:left_joins_join_type, join_type) - result = build_joins_without_join_type(manager, joins) - Thread.current.thread_variable_set(:left_joins_join_type, nil) - return result - end - - def build_left_outer_joins(manager, joins) - result = build_joins(manager, joins, Arel::Nodes::OuterJoin) - return result - end - - class << ::ActiveRecord::Base - def left_joins(*args) - self.where('').left_joins(*args) - end - alias_method :left_outer_joins, :left_joins - end - - class ::ActiveRecord::Associations::JoinDependency - if private_method_defined?(:make_constraints) - alias_method :make_constraints_without_hooking_join_type, :make_constraints - def make_constraints(*args, join_type) - join_type = Thread.current.thread_variable_get(:left_joins_join_type) || join_type - return make_constraints_without_hooking_join_type(*args, join_type) - end - else - alias_method :build_without_hooking_join_type, :build - def build(associations, parent = nil, join_type = Arel::Nodes::InnerJoin) - join_type = Thread.current.thread_variable_get(:left_joins_join_type) || join_type - return build_without_hooking_join_type(associations, parent, join_type) - end - end - end - - module ActiveRecord::Calculations - # This method is copied from activerecord-4.2.10/lib/active_record/relation/calculations.rb - # and modified this line `distinct = true` to `distinct = true if distinct == nil` - def perform_calculation(operation, column_name, options = {}) - # TODO: Remove options argument as soon we remove support to - # activerecord-deprecated_finders. - operation = operation.to_s.downcase - - # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) - distinct = options[:distinct] || self.distinct_value - - if operation == "count" - column_name ||= (select_for_count || :all) - - unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? - distinct = true if distinct == nil - end - - column_name = primary_key if column_name == :all && distinct - distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i - end - - if group_values.any? - execute_grouped_calculation(operation, column_name, distinct) - else - execute_simple_calculation(operation, column_name, distinct) - end - end - end - end -end - -# ---------------------------------------------------------------- -# ● Implement left joins when merging relations -# ---------------------------------------------------------------- -if not LeftJoins::IS_RAILS3_FLAG - require 'active_record/relation/merger' - class ActiveRecord::Relation - class Merger - alias_method :merge_without_left_joins, :merge - def merge - values = other.left_outer_joins_values - relation.left_outer_joins!(*values) if values.present? - return merge_without_left_joins - end - end - end - - module ActiveRecord - module SpawnMethods - - private - - alias_method :relation_with_without_left_joins, :relation_with - def relation_with(values) # :nodoc: - result = relation_with_without_left_joins(values) - result.left_outer_joins_values = self.left_outer_joins_values - return result - end - end - end -end - -# ---------------------------------------------------------------- -# ● Implement left joins in update statement -# ---------------------------------------------------------------- -module ActiveRecord - class Relation - if not LeftJoins::HAS_BUILT_IN_LEFT_JOINS_METHOD - def has_join_values? - joins_values.any? || left_outer_joins_values.any? - end - - alias_method :update_all_without_left_joins_values, :update_all - - def update_all(updates) - raise ArgumentError, "Empty list of attributes to change" if updates.blank? - - stmt = Arel::UpdateManager.new(arel.engine) - - stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)) - stmt.table(table) - stmt.key = table[primary_key] - - if has_join_values? - @klass.connection.join_to_update(stmt, arel) - else - stmt.take(arel.limit) - stmt.order(*arel.orders) - stmt.wheres = arel.constraints - end - - bvs = LeftJoins.bind_values_of(self) + bind_values - @klass.connection.update stmt, 'SQL', bvs - end - end - end -end +require "left_joins/version" +require 'active_record' +require 'active_record/relation' + +module LeftJoins + IS_RAILS3_FLAG = Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('4.0.0') + HAS_BUILT_IN_LEFT_JOINS_METHOD = ActiveRecord::QueryMethods.method_defined?(:left_outer_joins) + require 'left_joins_for_rails_3' if IS_RAILS3_FLAG + + class << self + def patch(target, method, as:) + return yield if target.singleton_methods.include?(method) + + target.define_singleton_method(method, &as) + result = yield + target.singleton_class.send(:remove_method, method) + + return result + end + end +end + +module ActiveRecord::QueryMethods + if not LeftJoins::HAS_BUILT_IN_LEFT_JOINS_METHOD + # ---------------------------------------------------------------- + # ● Storing left joins values into @left_outer_joins_values + # ---------------------------------------------------------------- + attr_writer :left_outer_joins_values + def left_outer_joins_values + @left_outer_joins_values ||= [] + end + + def left_outer_joins(*args) + check_if_method_has_arguments!(:left_outer_joins, args) + + args.compact! + args.flatten! + self.distinct_value = false + + return (LeftJoins::IS_RAILS3_FLAG ? clone : spawn).left_outer_joins!(*args) + end + + def left_outer_joins!(*args) + left_outer_joins_values.concat(args) + self + end + + # ---------------------------------------------------------------- + # ● Implement left joins when building arel + # ---------------------------------------------------------------- + alias_method :left_joins, :left_outer_joins + alias_method :build_arel_without_outer_joins, :build_arel + def build_arel(*args) + arel = build_arel_without_outer_joins(*args) + build_left_outer_joins(arel, @left_outer_joins_values) if @left_outer_joins_values + return arel + end + + alias_method :build_joins_without_join_type, :build_joins + def build_joins(manager, joins, join_type = nil) + Thread.current.thread_variable_set(:left_joins_join_type, join_type) + result = build_joins_without_join_type(manager, joins) + Thread.current.thread_variable_set(:left_joins_join_type, nil) + return result + end + + def build_left_outer_joins(manager, joins) + result = build_joins(manager, joins, Arel::Nodes::OuterJoin) + return result + end + + class << ::ActiveRecord::Base + def left_joins(*args) + self.where('').left_joins(*args) + end + alias_method :left_outer_joins, :left_joins + end + + class ::ActiveRecord::Associations::JoinDependency + if private_method_defined?(:make_constraints) + alias_method :make_constraints_without_hooking_join_type, :make_constraints + def make_constraints(*args, join_type) + join_type = Thread.current.thread_variable_get(:left_joins_join_type) || join_type + return make_constraints_without_hooking_join_type(*args, join_type) + end + else + alias_method :build_without_hooking_join_type, :build + def build(associations, parent = nil, join_type = Arel::Nodes::InnerJoin) + join_type = Thread.current.thread_variable_get(:left_joins_join_type) || join_type + return build_without_hooking_join_type(associations, parent, join_type) + end + end + end + + module ActiveRecord::Calculations + # This method is copied from activerecord-4.2.10/lib/active_record/relation/calculations.rb + # and modified this line `distinct = true` to `distinct = true if distinct == nil` + def perform_calculation(operation, column_name, options = {}) + # TODO: Remove options argument as soon we remove support to + # activerecord-deprecated_finders. + operation = operation.to_s.downcase + + # If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count) + distinct = options[:distinct] || self.distinct_value + + if operation == "count" + column_name ||= (select_for_count || :all) + + unless arel.ast.grep(Arel::Nodes::OuterJoin).empty? + distinct = true if distinct == nil + end + + column_name = primary_key if column_name == :all && distinct + distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i + end + + if group_values.any? + execute_grouped_calculation(operation, column_name, distinct) + else + execute_simple_calculation(operation, column_name, distinct) + end + end + end + end +end + +# ---------------------------------------------------------------- +# ● Implement left joins when merging relations +# ---------------------------------------------------------------- +if not LeftJoins::IS_RAILS3_FLAG + require 'active_record/relation/merger' + class ActiveRecord::Relation + class Merger + alias_method :merge_without_left_joins, :merge + def merge + values = other.left_outer_joins_values + relation.left_outer_joins!(*values) if values.present? + return merge_without_left_joins + end + end + end + + module ActiveRecord + module SpawnMethods + + private + + alias_method :relation_with_without_left_joins, :relation_with + def relation_with(values) # :nodoc: + result = relation_with_without_left_joins(values) + result.left_outer_joins_values = self.left_outer_joins_values + return result + end + end + end +end + +# ---------------------------------------------------------------- +# ● Implement left joins in update statement +# ---------------------------------------------------------------- +module ActiveRecord + class Relation + if not LeftJoins::HAS_BUILT_IN_LEFT_JOINS_METHOD + alias_method :update_all_without_left_joins_values, :update_all + + def update_all(*args) + local_joins_values = joins_values.clone + has_left_outer_joins = left_outer_joins_values.any? + + LeftJoins.patch(self, :joins_values, as: ->{ local_joins_values }) do + LeftJoins.patch(local_joins_values, :any?, as: ->{ super() || has_left_outer_joins }) do + update_all_without_left_joins_values(*args) + end + end + end + end + end +end