torque-postgresql 2.0.2 → 2.1.0
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.
- checksums.yaml +4 -4
- data/lib/torque/postgresql.rb +1 -0
- data/lib/torque/postgresql/adapter.rb +23 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +2 -0
- data/lib/torque/postgresql/adapter/oid.rb +3 -1
- data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
- data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
- data/lib/torque/postgresql/adapter/quoting.rb +2 -0
- data/lib/torque/postgresql/adapter/schema_creation.rb +8 -1
- data/lib/torque/postgresql/adapter/schema_definitions.rb +2 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +7 -1
- data/lib/torque/postgresql/adapter/schema_statements.rb +2 -0
- data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
- data/lib/torque/postgresql/arel/join_source.rb +2 -0
- data/lib/torque/postgresql/arel/nodes.rb +2 -0
- data/lib/torque/postgresql/arel/operations.rb +2 -0
- data/lib/torque/postgresql/arel/select_manager.rb +2 -0
- data/lib/torque/postgresql/arel/visitors.rb +6 -3
- data/lib/torque/postgresql/associations/association.rb +14 -3
- data/lib/torque/postgresql/associations/association_scope.rb +2 -0
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +169 -47
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +8 -5
- data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
- data/lib/torque/postgresql/associations/preloader/association.rb +30 -1
- data/lib/torque/postgresql/attributes/builder.rb +3 -1
- data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
- data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
- data/lib/torque/postgresql/attributes/enum.rb +5 -10
- data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
- data/lib/torque/postgresql/attributes/lazy.rb +3 -1
- data/lib/torque/postgresql/attributes/period.rb +2 -0
- data/lib/torque/postgresql/autosave_association.rb +19 -16
- data/lib/torque/postgresql/auxiliary_statement.rb +2 -0
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
- data/lib/torque/postgresql/base.rb +11 -2
- data/lib/torque/postgresql/coder.rb +5 -3
- data/lib/torque/postgresql/collector.rb +2 -0
- data/lib/torque/postgresql/config.rb +5 -0
- data/lib/torque/postgresql/geometry_builder.rb +2 -0
- data/lib/torque/postgresql/i18n.rb +2 -0
- data/lib/torque/postgresql/inheritance.rb +2 -0
- data/lib/torque/postgresql/insert_all.rb +26 -0
- data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
- data/lib/torque/postgresql/railtie.rb +2 -0
- data/lib/torque/postgresql/reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +13 -28
- data/lib/torque/postgresql/reflection/association_reflection.rb +24 -0
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +18 -4
- data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
- data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
- data/lib/torque/postgresql/relation.rb +15 -11
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +6 -1
- data/lib/torque/postgresql/relation/distinct_on.rb +2 -0
- data/lib/torque/postgresql/relation/inheritance.rb +2 -0
- data/lib/torque/postgresql/relation/merger.rb +2 -0
- data/lib/torque/postgresql/schema_cache.rb +2 -0
- data/lib/torque/postgresql/version.rb +3 -1
- data/spec/schema.rb +3 -2
- data/spec/tests/arel_spec.rb +3 -1
- data/spec/tests/belongs_to_many_spec.rb +134 -13
- data/spec/tests/enum_set_spec.rb +1 -1
- data/spec/tests/has_many_spec.rb +25 -1
- data/spec/tests/insert_all_spec.rb +89 -0
- data/spec/tests/table_inheritance_spec.rb +1 -1
- metadata +9 -6
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: cd2096d27cb0b11fd4cd2048627bf0b12dcd905d4240ea0251366f580a0afcf2
         | 
| 4 | 
            +
              data.tar.gz: c6d948dd77768feff3d85284bc838b8fb03c91233cc33ecdc8b3c22e463038c3
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3f61ba4bf68f2519090bb23b259cf9d8fa2c59f5a9935c867fe0dd2629eeeddd21e7a9f68a0e168550f696564d9613a4617ed6619ac4fe1f3d7660bf3fd1fc9c
         | 
| 7 | 
            +
              data.tar.gz: dbb298a6df589aa9368fb70f933b5ece9984bfc3661d667c7e42b989e2bdda6159aafbf78a303f9926f3b17dbc762473454f4646e91e976a21a0fd439a57c9f9
         | 
    
        data/lib/torque/postgresql.rb
    CHANGED
    
    | @@ -22,6 +22,7 @@ require 'torque/postgresql/autosave_association' | |
| 22 22 | 
             
            require 'torque/postgresql/auxiliary_statement'
         | 
| 23 23 | 
             
            require 'torque/postgresql/base'
         | 
| 24 24 | 
             
            require 'torque/postgresql/inheritance'
         | 
| 25 | 
            +
            require 'torque/postgresql/insert_all'
         | 
| 25 26 | 
             
            require 'torque/postgresql/coder'
         | 
| 26 27 | 
             
            require 'torque/postgresql/migration'
         | 
| 27 28 | 
             
            require 'torque/postgresql/relation'
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require_relative 'adapter/database_statements'
         | 
| 2 4 | 
             
            require_relative 'adapter/oid'
         | 
| 3 5 | 
             
            require_relative 'adapter/quoting'
         | 
| @@ -13,12 +15,33 @@ module Torque | |
| 13 15 | 
             
                  include DatabaseStatements
         | 
| 14 16 | 
             
                  include SchemaStatements
         | 
| 15 17 |  | 
| 18 | 
            +
                  INJECT_WHERE_REGEX = /(DO UPDATE SET.*excluded\.[^ ]+) RETURNING/.freeze
         | 
| 19 | 
            +
             | 
| 16 20 | 
             
                  # Get the current PostgreSQL version as a Gem Version.
         | 
| 17 21 | 
             
                  def version
         | 
| 18 22 | 
             
                    @version ||= Gem::Version.new(
         | 
| 19 23 | 
             
                      select_value('SELECT version()').match(/#{Adapter::ADAPTER_NAME} ([\d\.]+)/)[1]
         | 
| 20 24 | 
             
                    )
         | 
| 21 25 | 
             
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  # Add `inherits` to the list of extracted table options
         | 
| 28 | 
            +
                  def extract_table_options!(options)
         | 
| 29 | 
            +
                    super.merge(options.extract!(:inherits))
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # Allow filtered bulk insert by adding the where clause. This method is only used by
         | 
| 33 | 
            +
                  # +InsertAll+, so it somewhat safe to override it
         | 
| 34 | 
            +
                  def build_insert_sql(insert)
         | 
| 35 | 
            +
                    super.tap do |sql|
         | 
| 36 | 
            +
                      if insert.update_duplicates? && insert.where_condition?
         | 
| 37 | 
            +
                        if insert.returning
         | 
| 38 | 
            +
                          sql.gsub!(INJECT_WHERE_REGEX, "\\1 WHERE #{insert.where} RETURNING")
         | 
| 39 | 
            +
                        else
         | 
| 40 | 
            +
                          sql << " WHERE #{insert.where}"
         | 
| 41 | 
            +
                        end
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 22 45 | 
             
                end
         | 
| 23 46 |  | 
| 24 47 | 
             
                ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
         | 
| @@ -17,9 +17,11 @@ module Torque | |
| 17 17 | 
             
                  ActiveRecord::Type.register(:circle,   OID::Circle,   adapter: :postgresql)
         | 
| 18 18 | 
             
                  ActiveRecord::Type.register(:enum,     OID::Enum,     adapter: :postgresql)
         | 
| 19 19 | 
             
                  ActiveRecord::Type.register(:enum_set, OID::EnumSet,  adapter: :postgresql)
         | 
| 20 | 
            -
                  ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
         | 
| 21 20 | 
             
                  ActiveRecord::Type.register(:line,     OID::Line,     adapter: :postgresql)
         | 
| 22 21 | 
             
                  ActiveRecord::Type.register(:segment,  OID::Segment,  adapter: :postgresql)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql) \
         | 
| 24 | 
            +
                    unless PostgreSQL::AR610
         | 
| 23 25 | 
             
                end
         | 
| 24 26 | 
             
              end
         | 
| 25 27 | 
             
            end
         | 
| @@ -24,10 +24,17 @@ module Torque | |
| 24 24 | 
             
                        end)
         | 
| 25 25 | 
             
                      end
         | 
| 26 26 |  | 
| 27 | 
            +
                      if respond_to?(:supports_check_constraints?) && supports_check_constraints?
         | 
| 28 | 
            +
                        statements.concat(o.check_constraints.map do |expression, options|
         | 
| 29 | 
            +
                          check_constraint_in_create(o.name, expression, options)
         | 
| 30 | 
            +
                        end)
         | 
| 31 | 
            +
                      end
         | 
| 32 | 
            +
             | 
| 27 33 | 
             
                      create_sql << "(#{statements.join(', ')})" \
         | 
| 28 34 | 
             
                        if statements.present? || o.inherits.present?
         | 
| 29 35 |  | 
| 30 | 
            -
                       | 
| 36 | 
            +
                      options = PostgreSQL::AR610 ? o : table_options(o)
         | 
| 37 | 
            +
                      add_table_options!(create_sql, options)
         | 
| 31 38 |  | 
| 32 39 | 
             
                      if o.inherits.present?
         | 
| 33 40 | 
             
                        tables = o.inherits.map(&method(:quote_table_name))
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Torque
         | 
| 2 4 | 
             
              module PostgreSQL
         | 
| 3 5 | 
             
                module Adapter
         | 
| @@ -39,7 +41,7 @@ module Torque | |
| 39 41 |  | 
| 40 42 | 
             
                      def tables(stream) # :nodoc:
         | 
| 41 43 | 
             
                        inherited_tables = @connection.inherited_tables
         | 
| 42 | 
            -
                        sorted_tables = @connection. | 
| 44 | 
            +
                        sorted_tables = @connection.tables.sort - @connection.views
         | 
| 43 45 |  | 
| 44 46 | 
             
                        stream.puts "  # These are the common tables managed"
         | 
| 45 47 | 
             
                        (sorted_tables - inherited_tables.keys).each do |table_name|
         | 
| @@ -76,6 +78,10 @@ module Torque | |
| 76 78 |  | 
| 77 79 | 
             
                        # Scenic integration
         | 
| 78 80 | 
             
                        views(stream) if defined?(::Scenic)
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                        # FX integration
         | 
| 83 | 
            +
                        functions(stream) if defined?(::Fx::SchemaDumper::Function)
         | 
| 84 | 
            +
                        triggers(stream) if defined?(::Fx::SchemaDumper::Trigger)
         | 
| 79 85 | 
             
                      end
         | 
| 80 86 |  | 
| 81 87 | 
             
                      # Dump user defined types like enum
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Torque
         | 
| 2 4 | 
             
              module PostgreSQL
         | 
| 3 5 | 
             
                module Arel
         | 
| @@ -22,6 +24,8 @@ module Torque | |
| 22 24 | 
             
                  }.freeze
         | 
| 23 25 |  | 
| 24 26 | 
             
                  INFLIX_OPERATION.each do |operator_name, operator|
         | 
| 27 | 
            +
                    next if nodes.const_defined?(operator_name)
         | 
| 28 | 
            +
             | 
| 25 29 | 
             
                    klass = Class.new(inflix)
         | 
| 26 30 | 
             
                    klass.send(:define_method, :initialize) { |*args| super(operator, *args) }
         | 
| 27 31 |  | 
| @@ -31,7 +35,7 @@ module Torque | |
| 31 35 | 
             
                    # Don't worry about quoting here, if the right side is something that
         | 
| 32 36 | 
             
                    # doesn't need quoting, it will leave it as it is
         | 
| 33 37 | 
             
                    Math.send(:define_method, operator_name.underscore) do |other|
         | 
| 34 | 
            -
                      klass.new(self,  | 
| 38 | 
            +
                      klass.new(self, other)
         | 
| 35 39 | 
             
                    end
         | 
| 36 40 | 
             
                  end
         | 
| 37 41 |  | 
| @@ -1,10 +1,12 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Torque
         | 
| 2 4 | 
             
              module PostgreSQL
         | 
| 3 5 | 
             
                module Arel
         | 
| 4 6 | 
             
                  module Visitors
         | 
| 5 7 | 
             
                    # Enclose select manager with parenthesis
         | 
| 6 8 | 
             
                    # :TODO: Remove when checking the new version of Arel
         | 
| 7 | 
            -
                    def visit_Arel_SelectManager | 
| 9 | 
            +
                    def visit_Arel_SelectManager(o, collector)
         | 
| 8 10 | 
             
                      collector << '('
         | 
| 9 11 | 
             
                      visit(o.ast, collector) << ')'
         | 
| 10 12 | 
             
                    end
         | 
| @@ -23,8 +25,9 @@ module Torque | |
| 23 25 |  | 
| 24 26 | 
             
                    # Allow quoted arrays to get here
         | 
| 25 27 | 
             
                    def visit_Arel_Nodes_Casted(o, collector)
         | 
| 26 | 
            -
                       | 
| 27 | 
            -
                       | 
| 28 | 
            +
                      value = o.respond_to?(:val) ? o.val : o.value
         | 
| 29 | 
            +
                      return super unless value.is_a?(::Enumerable)
         | 
| 30 | 
            +
                      quote_array(value, collector)
         | 
| 28 31 | 
             
                    end
         | 
| 29 32 |  | 
| 30 33 | 
             
                    ## TORQUE VISITORS
         | 
| @@ -1,8 +1,12 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Torque
         | 
| 2 4 | 
             
              module PostgreSQL
         | 
| 3 5 | 
             
                module Associations
         | 
| 4 6 | 
             
                  module Association
         | 
| 5 7 |  | 
| 8 | 
            +
                    # There is no problem of adding temporary items on target because
         | 
| 9 | 
            +
                    # CollectionProxy will handle memory and persisted relationship
         | 
| 6 10 | 
             
                    def inversed_from(record)
         | 
| 7 11 | 
             
                      return super unless reflection.connected_through_array?
         | 
| 8 12 |  | 
| @@ -11,20 +15,27 @@ module Torque | |
| 11 15 | 
             
                      @inversed = self.target.present?
         | 
| 12 16 | 
             
                    end
         | 
| 13 17 |  | 
| 18 | 
            +
                    # The binds and the cache are getting mixed and caching the wrong query
         | 
| 19 | 
            +
                    def skip_statement_cache?(*)
         | 
| 20 | 
            +
                      super || reflection.connected_through_array?
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 14 23 | 
             
                    private
         | 
| 15 24 |  | 
| 25 | 
            +
                      # This is mainly for the has many when connect through an array to add
         | 
| 26 | 
            +
                      # its id to the list of the inverse belongs to many association
         | 
| 16 27 | 
             
                      def set_owner_attributes(record)
         | 
| 17 28 | 
             
                        return super unless reflection.connected_through_array?
         | 
| 18 29 |  | 
| 19 30 | 
             
                        add_id = owner[reflection.active_record_primary_key]
         | 
| 20 | 
            -
                         | 
| 21 | 
            -
             | 
| 22 | 
            -
                        record[record_fk].push(add_id) unless (record[record_fk] ||= []).include?(add_id)
         | 
| 31 | 
            +
                        list = record[reflection.foreign_key] ||= []
         | 
| 32 | 
            +
                        list.push(add_id) unless list.include?(add_id)
         | 
| 23 33 | 
             
                      end
         | 
| 24 34 |  | 
| 25 35 | 
             
                  end
         | 
| 26 36 |  | 
| 27 37 | 
             
                  ::ActiveRecord::Associations::Association.prepend(Association)
         | 
| 38 | 
            +
                  ::ActiveRecord::Associations::HasManyAssociation.prepend(Association)
         | 
| 28 39 | 
             
                end
         | 
| 29 40 | 
             
              end
         | 
| 30 41 | 
             
            end
         | 
| @@ -1,4 +1,7 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require 'active_record/associations/collection_association'
         | 
| 4 | 
            +
             | 
| 2 5 | 
             
            # FIXME: build, create
         | 
| 3 6 | 
             
            module Torque
         | 
| 4 7 | 
             
              module PostgreSQL
         | 
| @@ -6,10 +9,69 @@ module Torque | |
| 6 9 | 
             
                  class BelongsToManyAssociation < ::ActiveRecord::Associations::CollectionAssociation
         | 
| 7 10 | 
             
                    include ::ActiveRecord::Associations::ForeignAssociation
         | 
| 8 11 |  | 
| 12 | 
            +
                    ## CUSTOM
         | 
| 13 | 
            +
                    def ids_reader
         | 
| 14 | 
            +
                      if loaded?
         | 
| 15 | 
            +
                        target.pluck(reflection.association_primary_key)
         | 
| 16 | 
            +
                      elsif !target.empty?
         | 
| 17 | 
            +
                        load_target.pluck(reflection.association_primary_key)
         | 
| 18 | 
            +
                      else
         | 
| 19 | 
            +
                        stale_state || column_default_value
         | 
| 20 | 
            +
                      end
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def ids_writer(ids)
         | 
| 24 | 
            +
                      ids = ids.presence || column_default_value
         | 
| 25 | 
            +
                      owner.write_attribute(source_attr, ids)
         | 
| 26 | 
            +
                      return unless owner.persisted? && owner.attribute_changed?(source_attr)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                      owner.update_attribute(source_attr, ids)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def size
         | 
| 32 | 
            +
                      if loaded?
         | 
| 33 | 
            +
                        target.size
         | 
| 34 | 
            +
                      elsif !target.empty?
         | 
| 35 | 
            +
                        unsaved_records = target.select(&:new_record?)
         | 
| 36 | 
            +
                        unsaved_records.size + stale_state.size
         | 
| 37 | 
            +
                      else
         | 
| 38 | 
            +
                        stale_state&.size || 0
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    def empty?
         | 
| 43 | 
            +
                      size.zero?
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    def include?(record)
         | 
| 47 | 
            +
                      return false unless record.is_a?(reflection.klass)
         | 
| 48 | 
            +
                      return include_in_memory?(record) if record.new_record?
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      (!target.empty? && target.include?(record)) ||
         | 
| 51 | 
            +
                        stale_state&.include?(record.read_attribute(klass_attr))
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    def load_target
         | 
| 55 | 
            +
                      if stale_target? || find_target?
         | 
| 56 | 
            +
                        @target = merge_target_lists(find_target, target)
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      loaded!
         | 
| 60 | 
            +
                      target
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def build_changes
         | 
| 64 | 
            +
                      @_building_changes = true
         | 
| 65 | 
            +
                      yield.tap { ids_writer(ids_reader) }
         | 
| 66 | 
            +
                    ensure
         | 
| 67 | 
            +
                      @_building_changes = nil
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    ## HAS MANY
         | 
| 9 71 | 
             
                    def handle_dependency
         | 
| 10 72 | 
             
                      case options[:dependent]
         | 
| 11 73 | 
             
                      when :restrict_with_exception
         | 
| 12 | 
            -
                        raise  | 
| 74 | 
            +
                        raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
         | 
| 13 75 |  | 
| 14 76 | 
             
                      when :restrict_with_error
         | 
| 15 77 | 
             
                        unless empty?
         | 
| @@ -19,88 +81,126 @@ module Torque | |
| 19 81 | 
             
                        end
         | 
| 20 82 |  | 
| 21 83 | 
             
                      when :destroy
         | 
| 22 | 
            -
                        # No point in executing the counter update since we're going to destroy the parent anyway
         | 
| 23 84 | 
             
                        load_target.each { |t| t.destroyed_by_association = reflection }
         | 
| 24 85 | 
             
                        destroy_all
         | 
| 86 | 
            +
                      when :destroy_async
         | 
| 87 | 
            +
                        load_target.each do |t|
         | 
| 88 | 
            +
                          t.destroyed_by_association = reflection
         | 
| 89 | 
            +
                        end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                        unless target.empty?
         | 
| 92 | 
            +
                          association_class = target.first.class
         | 
| 93 | 
            +
                          primary_key_column = association_class.primary_key.to_sym
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                          ids = target.collect do |assoc|
         | 
| 96 | 
            +
                            assoc.public_send(primary_key_column)
         | 
| 97 | 
            +
                          end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                          enqueue_destroy_association(
         | 
| 100 | 
            +
                            owner_model_name: owner.class.to_s,
         | 
| 101 | 
            +
                            owner_id: owner.id,
         | 
| 102 | 
            +
                            association_class: association_class.to_s,
         | 
| 103 | 
            +
                            association_ids: ids,
         | 
| 104 | 
            +
                            association_primary_key_column: primary_key_column,
         | 
| 105 | 
            +
                            ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
         | 
| 106 | 
            +
                          )
         | 
| 107 | 
            +
                        end
         | 
| 25 108 | 
             
                      else
         | 
| 26 109 | 
             
                        delete_all
         | 
| 27 110 | 
             
                      end
         | 
| 28 111 | 
             
                    end
         | 
| 29 112 |  | 
| 30 | 
            -
                    def ids_reader
         | 
| 31 | 
            -
                      owner[reflection.active_record_primary_key]
         | 
| 32 | 
            -
                    end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                    def ids_writer(new_ids)
         | 
| 35 | 
            -
                      column = reflection.active_record_primary_key
         | 
| 36 | 
            -
                      command = owner.persisted? ? :update_column : :write_attribute
         | 
| 37 | 
            -
                      owner.public_send(command, column, new_ids.presence)
         | 
| 38 | 
            -
                      @association_scope = nil
         | 
| 39 | 
            -
                    end
         | 
| 40 | 
            -
             | 
| 41 113 | 
             
                    def insert_record(record, *)
         | 
| 42 | 
            -
                      super
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                       | 
| 45 | 
            -
                      attribute.push(record[klass_fk])
         | 
| 46 | 
            -
                      record
         | 
| 47 | 
            -
                    end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                    def empty?
         | 
| 50 | 
            -
                      size.zero?
         | 
| 114 | 
            +
                      super.tap do |saved|
         | 
| 115 | 
            +
                        ids_rewriter(record.read_attribute(klass_attr), :<<) if saved
         | 
| 116 | 
            +
                      end
         | 
| 51 117 | 
             
                    end
         | 
| 52 118 |  | 
| 53 | 
            -
                     | 
| 54 | 
            -
             | 
| 55 | 
            -
                       | 
| 119 | 
            +
                    ## BELONGS TO
         | 
| 120 | 
            +
                    def default(&block)
         | 
| 121 | 
            +
                      writer(owner.instance_exec(&block)) if reader.nil?
         | 
| 56 122 | 
             
                    end
         | 
| 57 123 |  | 
| 58 124 | 
             
                    private
         | 
| 59 125 |  | 
| 60 | 
            -
                       | 
| 61 | 
            -
                       | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 126 | 
            +
                      ## CUSTOM
         | 
| 127 | 
            +
                      def _create_record(attributes, raises = false, &block)
         | 
| 128 | 
            +
                        if attributes.is_a?(Array)
         | 
| 129 | 
            +
                          attributes.collect { |attr| _create_record(attr, raises, &block) }
         | 
| 130 | 
            +
                        else
         | 
| 131 | 
            +
                          build_record(attributes, &block).tap do |record|
         | 
| 132 | 
            +
                            transaction do
         | 
| 133 | 
            +
                              result = nil
         | 
| 134 | 
            +
                              add_to_target(record) do
         | 
| 135 | 
            +
                                result = insert_record(record, true, raises) { @_was_loaded = loaded? }
         | 
| 136 | 
            +
                              end
         | 
| 137 | 
            +
                              raise ActiveRecord::Rollback unless result
         | 
| 138 | 
            +
                            end
         | 
| 139 | 
            +
                          end
         | 
| 140 | 
            +
                        end
         | 
| 64 141 | 
             
                      end
         | 
| 65 142 |  | 
| 66 | 
            -
                      # When the idea is to  | 
| 143 | 
            +
                      # When the idea is to nullify the association, then just set the owner
         | 
| 67 144 | 
             
                      # +primary_key+ as empty
         | 
| 68 | 
            -
                      def delete_count(method, scope, ids | 
| 69 | 
            -
                         | 
| 70 | 
            -
                         | 
| 71 | 
            -
                        remove_stash_records(ids)
         | 
| 145 | 
            +
                      def delete_count(method, scope, ids)
         | 
| 146 | 
            +
                        size_cache = scope.delete_all if method == :delete_all
         | 
| 147 | 
            +
                        (size_cache || ids.size).tap { ids_rewriter(ids, :-) }
         | 
| 72 148 | 
             
                      end
         | 
| 73 149 |  | 
| 74 150 | 
             
                      def delete_or_nullify_all_records(method)
         | 
| 75 | 
            -
                        delete_count(method, scope)
         | 
| 151 | 
            +
                        delete_count(method, scope, ids_reader)
         | 
| 76 152 | 
             
                      end
         | 
| 77 153 |  | 
| 78 154 | 
             
                      # Deletes the records according to the <tt>:dependent</tt> option.
         | 
| 79 155 | 
             
                      def delete_records(records, method)
         | 
| 80 | 
            -
                        ids =  | 
| 156 | 
            +
                        ids = read_records_ids(records)
         | 
| 81 157 |  | 
| 82 158 | 
             
                        if method == :destroy
         | 
| 83 159 | 
             
                          records.each(&:destroy!)
         | 
| 84 | 
            -
                           | 
| 160 | 
            +
                          ids_rewriter(ids, :-)
         | 
| 85 161 | 
             
                        else
         | 
| 86 | 
            -
                          scope = self.scope.where( | 
| 162 | 
            +
                          scope = self.scope.where(klass_attr => records)
         | 
| 87 163 | 
             
                          delete_count(method, scope, ids)
         | 
| 88 164 | 
             
                        end
         | 
| 89 165 | 
             
                      end
         | 
| 90 166 |  | 
| 91 | 
            -
                      def  | 
| 92 | 
            -
                         | 
| 93 | 
            -
                        ids_writer(ids_reader)
         | 
| 94 | 
            -
                        result
         | 
| 167 | 
            +
                      def source_attr
         | 
| 168 | 
            +
                        reflection.foreign_key
         | 
| 95 169 | 
             
                      end
         | 
| 96 170 |  | 
| 97 | 
            -
                      def  | 
| 98 | 
            -
                         | 
| 99 | 
            -
                        ids_writer(ids_reader - Array.wrap(ids))
         | 
| 171 | 
            +
                      def klass_attr
         | 
| 172 | 
            +
                        reflection.active_record_primary_key
         | 
| 100 173 | 
             
                      end
         | 
| 101 174 |  | 
| 102 | 
            -
                      def  | 
| 103 | 
            -
                         | 
| 175 | 
            +
                      def read_records_ids(records)
         | 
| 176 | 
            +
                        return unless records.present?
         | 
| 177 | 
            +
                        Array.wrap(records).each_with_object(klass_attr).map(&:read_attribute).presence
         | 
| 178 | 
            +
                      end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                      def ids_rewriter(ids, operator)
         | 
| 181 | 
            +
                        list = owner[source_attr] ||= []
         | 
| 182 | 
            +
                        list = list.public_send(operator, ids)
         | 
| 183 | 
            +
                        owner[source_attr] = list.uniq.compact.presence || column_default_value
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                        return if @_building_changes || !owner.persisted?
         | 
| 186 | 
            +
                        owner.update_attribute(source_attr, list)
         | 
| 187 | 
            +
                      end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                      def column_default_value
         | 
| 190 | 
            +
                        owner.class.columns_hash[source_attr].default
         | 
| 191 | 
            +
                      end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                      ## HAS MANY
         | 
| 194 | 
            +
                      def replace_records(*)
         | 
| 195 | 
            +
                        build_changes { super }
         | 
| 196 | 
            +
                      end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                      def concat_records(*)
         | 
| 199 | 
            +
                        build_changes { super }
         | 
| 200 | 
            +
                      end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                      def delete_or_destroy(*)
         | 
| 203 | 
            +
                        build_changes { super }
         | 
| 104 204 | 
             
                      end
         | 
| 105 205 |  | 
| 106 206 | 
             
                      def difference(a, b)
         | 
| @@ -110,6 +210,28 @@ module Torque | |
| 110 210 | 
             
                      def intersection(a, b)
         | 
| 111 211 | 
             
                        a & b
         | 
| 112 212 | 
             
                      end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                      ## BELONGS TO
         | 
| 215 | 
            +
                      def scope_for_create
         | 
| 216 | 
            +
                        super.except!(klass.primary_key)
         | 
| 217 | 
            +
                      end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                      def find_target?
         | 
| 220 | 
            +
                        !loaded? && foreign_key_present? && klass
         | 
| 221 | 
            +
                      end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                      def foreign_key_present?
         | 
| 224 | 
            +
                        stale_state.present?
         | 
| 225 | 
            +
                      end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                      def invertible_for?(record)
         | 
| 228 | 
            +
                        inverse = inverse_reflection_for(record)
         | 
| 229 | 
            +
                        inverse && (inverse.has_many? && inverse.connected_through_array?)
         | 
| 230 | 
            +
                      end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                      def stale_state
         | 
| 233 | 
            +
                        owner.read_attribute(source_attr)
         | 
| 234 | 
            +
                      end
         | 
| 113 235 | 
             
                  end
         | 
| 114 236 |  | 
| 115 237 | 
             
                  ::ActiveRecord::Associations.const_set(:BelongsToManyAssociation, BelongsToManyAssociation)
         |