paper_trail 10.3.1 → 13.0.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/LICENSE +20 -0
- data/lib/generators/paper_trail/install/install_generator.rb +25 -7
- data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +4 -2
- data/lib/generators/paper_trail/migration_generator.rb +5 -4
- data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +4 -2
- data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
- data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +14 -46
- data/lib/paper_trail/compatibility.rb +3 -3
- data/lib/paper_trail/config.rb +0 -33
- data/lib/paper_trail/errors.rb +33 -0
- data/lib/paper_trail/events/base.rb +68 -68
- data/lib/paper_trail/events/destroy.rb +1 -1
- data/lib/paper_trail/events/update.rb +23 -4
- data/lib/paper_trail/frameworks/active_record.rb +9 -2
- data/lib/paper_trail/frameworks/rails/controller.rb +1 -9
- data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -2
- data/lib/paper_trail/has_paper_trail.rb +1 -1
- data/lib/paper_trail/model_config.rb +49 -46
- data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
- data/lib/paper_trail/queries/versions/where_object.rb +1 -1
- data/lib/paper_trail/queries/versions/where_object_changes.rb +8 -13
- data/lib/paper_trail/queries/versions/where_object_changes_from.rb +57 -0
- data/lib/paper_trail/queries/versions/where_object_changes_to.rb +57 -0
- data/lib/paper_trail/record_trail.rb +7 -7
- data/lib/paper_trail/reifier.rb +41 -26
- data/lib/paper_trail/request.rb +0 -3
- data/lib/paper_trail/serializers/json.rb +0 -10
- data/lib/paper_trail/serializers/yaml.rb +19 -13
- data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -14
- data/lib/paper_trail/version_concern.rb +86 -41
- data/lib/paper_trail/version_number.rb +3 -3
- data/lib/paper_trail.rb +17 -40
- metadata +104 -43
- data/lib/paper_trail/frameworks/rails/engine.rb +0 -45
| @@ -25,9 +25,7 @@ module PaperTrail | |
| 25 25 | 
             
                  # @api public
         | 
| 26 26 | 
             
                  def user_for_paper_trail
         | 
| 27 27 | 
             
                    return unless defined?(current_user)
         | 
| 28 | 
            -
                     | 
| 29 | 
            -
                  rescue NoMethodError
         | 
| 30 | 
            -
                    current_user
         | 
| 28 | 
            +
                    current_user.try(:id) || current_user
         | 
| 31 29 | 
             
                  end
         | 
| 32 30 |  | 
| 33 31 | 
             
                  # Returns any information about the controller or request that you
         | 
| @@ -103,9 +101,3 @@ module PaperTrail | |
| 103 101 | 
             
                end
         | 
| 104 102 | 
             
              end
         | 
| 105 103 | 
             
            end
         | 
| 106 | 
            -
             | 
| 107 | 
            -
            if defined?(::ActionController)
         | 
| 108 | 
            -
              ::ActiveSupport.on_load(:action_controller) do
         | 
| 109 | 
            -
                include ::PaperTrail::Rails::Controller
         | 
| 110 | 
            -
              end
         | 
| 111 | 
            -
            end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PaperTrail
         | 
| 4 | 
            +
              # Represents code to load within Rails framework. See documentation in
         | 
| 5 | 
            +
              # `railties/lib/rails/railtie.rb`.
         | 
| 6 | 
            +
              # @api private
         | 
| 7 | 
            +
              class Railtie < ::Rails::Railtie
         | 
| 8 | 
            +
                # PaperTrail only has one initializer.
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # We specify `before: "load_config_initializers"` to ensure that the PT
         | 
| 11 | 
            +
                # initializer happens before "app initializers" (those defined in
         | 
| 12 | 
            +
                # the app's `config/initalizers`).
         | 
| 13 | 
            +
                initializer "paper_trail", before: "load_config_initializers" do
         | 
| 14 | 
            +
                  # `on_load` is a "lazy load hook". It "declares a block that will be
         | 
| 15 | 
            +
                  # executed when a Rails component is fully loaded". (See
         | 
| 16 | 
            +
                  # `active_support/lazy_load_hooks.rb`)
         | 
| 17 | 
            +
                  ActiveSupport.on_load(:action_controller) do
         | 
| 18 | 
            +
                    require "paper_trail/frameworks/rails/controller"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    # Mix our extensions into `ActionController::Base`, which is `self`
         | 
| 21 | 
            +
                    # because of the `class_eval` in `lazy_load_hooks.rb`.
         | 
| 22 | 
            +
                    include PaperTrail::Rails::Controller
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  ActiveSupport.on_load(:active_record) do
         | 
| 26 | 
            +
                    require "paper_trail/frameworks/active_record"
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -18,11 +18,6 @@ module PaperTrail | |
| 18 18 | 
             
                  `abstract_class`. This is fine, but all application models must be
         | 
| 19 19 | 
             
                  configured to use concrete (not abstract) version models.
         | 
| 20 20 | 
             
                STR
         | 
| 21 | 
            -
                E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE = <<~STR.squish.freeze
         | 
| 22 | 
            -
                  To use PaperTrail's per-model limit in your %s model, you must have an
         | 
| 23 | 
            -
                  item_subtype column in your versions table. See documentation sections
         | 
| 24 | 
            -
                  2.e.1 Per-model limit, and 4.b.1 The optional item_subtype column.
         | 
| 25 | 
            -
                STR
         | 
| 26 21 | 
             
                DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION = <<~STR.squish
         | 
| 27 22 | 
             
                  Passing versions association name as `has_paper_trail versions: %{versions_name}`
         | 
| 28 23 | 
             
                  is deprecated. Use `has_paper_trail versions: {name: %{versions_name}}` instead.
         | 
| @@ -45,22 +40,14 @@ module PaperTrail | |
| 45 40 | 
             
                  @model_class.after_create { |r|
         | 
| 46 41 | 
             
                    r.paper_trail.record_create if r.paper_trail.save_version?
         | 
| 47 42 | 
             
                  }
         | 
| 48 | 
            -
                   | 
| 49 | 
            -
                  @model_class.paper_trail_options[:on] << :create
         | 
| 43 | 
            +
                  append_option_uniquely(:on, :create)
         | 
| 50 44 | 
             
                end
         | 
| 51 45 |  | 
| 52 46 | 
             
                # Adds a callback that records a version before or after a "destroy" event.
         | 
| 53 47 | 
             
                #
         | 
| 54 48 | 
             
                # @api public
         | 
| 55 49 | 
             
                def on_destroy(recording_order = "before")
         | 
| 56 | 
            -
                   | 
| 57 | 
            -
                    raise ArgumentError, 'recording order can only be "after" or "before"'
         | 
| 58 | 
            -
                  end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                  if recording_order.to_s == "after" && cannot_record_after_destroy?
         | 
| 61 | 
            -
                    raise E_CANNOT_RECORD_AFTER_DESTROY
         | 
| 62 | 
            -
                  end
         | 
| 63 | 
            -
             | 
| 50 | 
            +
                  assert_valid_recording_order_for_on_destroy(recording_order)
         | 
| 64 51 | 
             
                  @model_class.send(
         | 
| 65 52 | 
             
                    "#{recording_order}_destroy",
         | 
| 66 53 | 
             
                    lambda do |r|
         | 
| @@ -68,9 +55,7 @@ module PaperTrail | |
| 68 55 | 
             
                      r.paper_trail.record_destroy(recording_order)
         | 
| 69 56 | 
             
                    end
         | 
| 70 57 | 
             
                  )
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                  return if @model_class.paper_trail_options[:on].include?(:destroy)
         | 
| 73 | 
            -
                  @model_class.paper_trail_options[:on] << :destroy
         | 
| 58 | 
            +
                  append_option_uniquely(:on, :destroy)
         | 
| 74 59 | 
             
                end
         | 
| 75 60 |  | 
| 76 61 | 
             
                # Adds a callback that records a version after an "update" event.
         | 
| @@ -92,19 +77,27 @@ module PaperTrail | |
| 92 77 | 
             
                  @model_class.after_update { |r|
         | 
| 93 78 | 
             
                    r.paper_trail.clear_version_instance
         | 
| 94 79 | 
             
                  }
         | 
| 95 | 
            -
                   | 
| 96 | 
            -
                  @model_class.paper_trail_options[:on] << :update
         | 
| 80 | 
            +
                  append_option_uniquely(:on, :update)
         | 
| 97 81 | 
             
                end
         | 
| 98 82 |  | 
| 99 83 | 
             
                # Adds a callback that records a version after a "touch" event.
         | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                # Rails < 6.0 has a bug where dirty-tracking does not occur during
         | 
| 86 | 
            +
                # a `touch`. (https://github.com/rails/rails/issues/33429) See also:
         | 
| 87 | 
            +
                # https://github.com/paper-trail-gem/paper_trail/issues/1121
         | 
| 88 | 
            +
                # https://github.com/paper-trail-gem/paper_trail/issues/1161
         | 
| 89 | 
            +
                # https://github.com/paper-trail-gem/paper_trail/pull/1285
         | 
| 90 | 
            +
                #
         | 
| 100 91 | 
             
                # @api public
         | 
| 101 92 | 
             
                def on_touch
         | 
| 102 93 | 
             
                  @model_class.after_touch { |r|
         | 
| 103 | 
            -
                    r.paper_trail. | 
| 104 | 
            -
                       | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 94 | 
            +
                    if r.paper_trail.save_version?
         | 
| 95 | 
            +
                      r.paper_trail.record_update(
         | 
| 96 | 
            +
                        force: RAILS_LT_6_0,
         | 
| 97 | 
            +
                        in_after_callback: true,
         | 
| 98 | 
            +
                        is_touch: true
         | 
| 99 | 
            +
                      )
         | 
| 100 | 
            +
                    end
         | 
| 108 101 | 
             
                  }
         | 
| 109 102 | 
             
                end
         | 
| 110 103 |  | 
| @@ -117,42 +110,48 @@ module PaperTrail | |
| 117 110 | 
             
                  @model_class.send :include, ::PaperTrail::Model::InstanceMethods
         | 
| 118 111 | 
             
                  setup_options(options)
         | 
| 119 112 | 
             
                  setup_associations(options)
         | 
| 120 | 
            -
                  check_presence_of_item_subtype_column(options)
         | 
| 121 113 | 
             
                  @model_class.after_rollback { paper_trail.clear_rolled_back_versions }
         | 
| 122 114 | 
             
                  setup_callbacks_from_options options[:on]
         | 
| 123 115 | 
             
                end
         | 
| 124 116 |  | 
| 117 | 
            +
                # @api private
         | 
| 125 118 | 
             
                def version_class
         | 
| 126 | 
            -
                  @ | 
| 119 | 
            +
                  @version_class ||= @model_class.version_class_name.constantize
         | 
| 127 120 | 
             
                end
         | 
| 128 121 |  | 
| 129 122 | 
             
                private
         | 
| 130 123 |  | 
| 131 | 
            -
                 | 
| 132 | 
            -
             | 
| 124 | 
            +
                RAILS_LT_6_0 = ::ActiveRecord.gem_version < ::Gem::Version.new("6.0.0")
         | 
| 125 | 
            +
                private_constant :RAILS_LT_6_0
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                # @api private
         | 
| 128 | 
            +
                def append_option_uniquely(option, value)
         | 
| 129 | 
            +
                  collection = @model_class.paper_trail_options.fetch(option)
         | 
| 130 | 
            +
                  return if collection.include?(value)
         | 
| 131 | 
            +
                  collection << value
         | 
| 133 132 | 
             
                end
         | 
| 134 133 |  | 
| 135 134 | 
             
                # Raises an error if the provided class is an `abstract_class`.
         | 
| 136 135 | 
             
                # @api private
         | 
| 137 136 | 
             
                def assert_concrete_activerecord_class(class_name)
         | 
| 138 137 | 
             
                  if class_name.constantize.abstract_class?
         | 
| 139 | 
            -
                    raise format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
         | 
| 138 | 
            +
                    raise Error, format(E_HPT_ABSTRACT_CLASS, @model_class, class_name)
         | 
| 140 139 | 
             
                  end
         | 
| 141 140 | 
             
                end
         | 
| 142 141 |  | 
| 143 | 
            -
                 | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 142 | 
            +
                # @api private
         | 
| 143 | 
            +
                def assert_valid_recording_order_for_on_destroy(recording_order)
         | 
| 144 | 
            +
                  unless %w[after before].include?(recording_order.to_s)
         | 
| 145 | 
            +
                    raise ArgumentError, 'recording order can only be "after" or "before"'
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  if recording_order.to_s == "after" && cannot_record_after_destroy?
         | 
| 149 | 
            +
                    raise Error, E_CANNOT_RECORD_AFTER_DESTROY
         | 
| 150 | 
            +
                  end
         | 
| 146 151 | 
             
                end
         | 
| 147 152 |  | 
| 148 | 
            -
                 | 
| 149 | 
            -
             | 
| 150 | 
            -
                #
         | 
| 151 | 
            -
                # @api private
         | 
| 152 | 
            -
                def check_presence_of_item_subtype_column(options)
         | 
| 153 | 
            -
                  return unless options.key?(:limit)
         | 
| 154 | 
            -
                  return if version_class.item_subtype_column_present?
         | 
| 155 | 
            -
                  raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
         | 
| 153 | 
            +
                def cannot_record_after_destroy?
         | 
| 154 | 
            +
                  ::ActiveRecord::Base.belongs_to_required_by_default
         | 
| 156 155 | 
             
                end
         | 
| 157 156 |  | 
| 158 157 | 
             
                def check_version_class_name(options)
         | 
| @@ -210,6 +209,14 @@ module PaperTrail | |
| 210 209 | 
             
                  options
         | 
| 211 210 | 
             
                end
         | 
| 212 211 |  | 
| 212 | 
            +
                # Process an `ignore`, `skip`, or `only` option.
         | 
| 213 | 
            +
                def event_attribute_option(option_name)
         | 
| 214 | 
            +
                  [@model_class.paper_trail_options[option_name]].
         | 
| 215 | 
            +
                    flatten.
         | 
| 216 | 
            +
                    compact.
         | 
| 217 | 
            +
                    map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
             | 
| 213 220 | 
             
                def get_versions_scope(options)
         | 
| 214 221 | 
             
                  options[:versions][:scope] || -> { order(model.timestamp_sort_order) }
         | 
| 215 222 | 
             
                end
         | 
| @@ -244,12 +251,8 @@ module PaperTrail | |
| 244 251 | 
             
                  @model_class.paper_trail_options = options.dup
         | 
| 245 252 |  | 
| 246 253 | 
             
                  %i[ignore skip only].each do |k|
         | 
| 247 | 
            -
                    @model_class.paper_trail_options[k] =  | 
| 248 | 
            -
                      flatten.
         | 
| 249 | 
            -
                      compact.
         | 
| 250 | 
            -
                      map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
         | 
| 254 | 
            +
                    @model_class.paper_trail_options[k] = event_attribute_option(k)
         | 
| 251 255 | 
             
                  end
         | 
| 252 | 
            -
             | 
| 253 256 | 
             
                  @model_class.paper_trail_options[:meta] ||= {}
         | 
| 254 257 | 
             
                end
         | 
| 255 258 | 
             
              end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PaperTrail
         | 
| 4 | 
            +
              module Queries
         | 
| 5 | 
            +
                module Versions
         | 
| 6 | 
            +
                  # For public API documentation, see `where_attribute_changes` in
         | 
| 7 | 
            +
                  # `paper_trail/version_concern.rb`.
         | 
| 8 | 
            +
                  # @api private
         | 
| 9 | 
            +
                  class WhereAttributeChanges
         | 
| 10 | 
            +
                    # - version_model_class - The class that VersionConcern was mixed into.
         | 
| 11 | 
            +
                    # - attribute - An attribute that changed. See the public API
         | 
| 12 | 
            +
                    #   documentation for details.
         | 
| 13 | 
            +
                    # @api private
         | 
| 14 | 
            +
                    def initialize(version_model_class, attribute)
         | 
| 15 | 
            +
                      @version_model_class = version_model_class
         | 
| 16 | 
            +
                      @attribute = attribute
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    # @api private
         | 
| 20 | 
            +
                    def execute
         | 
| 21 | 
            +
                      if PaperTrail.config.object_changes_adapter.respond_to?(:where_attribute_changes)
         | 
| 22 | 
            +
                        return PaperTrail.config.object_changes_adapter.where_attribute_changes(
         | 
| 23 | 
            +
                          @version_model_class, @attribute
         | 
| 24 | 
            +
                        )
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                      column_type = @version_model_class.columns_hash["object_changes"].type
         | 
| 27 | 
            +
                      case column_type
         | 
| 28 | 
            +
                      when :jsonb, :json
         | 
| 29 | 
            +
                        json
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        raise UnsupportedColumnType.new(
         | 
| 32 | 
            +
                          method: "where_attribute_changes",
         | 
| 33 | 
            +
                          expected: "json or jsonb",
         | 
| 34 | 
            +
                          actual: column_type
         | 
| 35 | 
            +
                        )
         | 
| 36 | 
            +
                      end
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    # @api private
         | 
| 42 | 
            +
                    def json
         | 
| 43 | 
            +
                      sql = "object_changes -> ? IS NOT NULL"
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      @version_model_class.where(sql, @attribute)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -19,7 +19,7 @@ module PaperTrail | |
| 19 19 | 
             
                    # @api private
         | 
| 20 20 | 
             
                    def execute
         | 
| 21 21 | 
             
                      column = @version_model_class.columns_hash["object"]
         | 
| 22 | 
            -
                      raise "where_object  | 
| 22 | 
            +
                      raise Error, "where_object requires an object column" unless column
         | 
| 23 23 |  | 
| 24 24 | 
             
                      case column.type
         | 
| 25 25 | 
             
                      when :jsonb
         | 
| @@ -23,18 +23,23 @@ module PaperTrail | |
| 23 23 |  | 
| 24 24 | 
             
                    # @api private
         | 
| 25 25 | 
             
                    def execute
         | 
| 26 | 
            -
                      if PaperTrail.config.object_changes_adapter | 
| 26 | 
            +
                      if PaperTrail.config.object_changes_adapter.respond_to?(:where_object_changes)
         | 
| 27 27 | 
             
                        return PaperTrail.config.object_changes_adapter.where_object_changes(
         | 
| 28 28 | 
             
                          @version_model_class, @attributes
         | 
| 29 29 | 
             
                        )
         | 
| 30 30 | 
             
                      end
         | 
| 31 | 
            -
                       | 
| 31 | 
            +
                      column_type = @version_model_class.columns_hash["object_changes"].type
         | 
| 32 | 
            +
                      case column_type
         | 
| 32 33 | 
             
                      when :jsonb
         | 
| 33 34 | 
             
                        jsonb
         | 
| 34 35 | 
             
                      when :json
         | 
| 35 36 | 
             
                        json
         | 
| 36 37 | 
             
                      else
         | 
| 37 | 
            -
                         | 
| 38 | 
            +
                        raise UnsupportedColumnType.new(
         | 
| 39 | 
            +
                          method: "where_object_changes",
         | 
| 40 | 
            +
                          expected: "json or jsonb",
         | 
| 41 | 
            +
                          actual: column_type
         | 
| 42 | 
            +
                        )
         | 
| 38 43 | 
             
                      end
         | 
| 39 44 | 
             
                    end
         | 
| 40 45 |  | 
| @@ -59,16 +64,6 @@ module PaperTrail | |
| 59 64 | 
             
                      @attributes.each { |field, value| @attributes[field] = [value] }
         | 
| 60 65 | 
             
                      @version_model_class.where("object_changes @> ?", @attributes.to_json)
         | 
| 61 66 | 
             
                    end
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                    # @api private
         | 
| 64 | 
            -
                    def text
         | 
| 65 | 
            -
                      arel_field = @version_model_class.arel_table[:object_changes]
         | 
| 66 | 
            -
                      where_conditions = @attributes.map { |field, value|
         | 
| 67 | 
            -
                        ::PaperTrail.serializer.where_object_changes_condition(arel_field, field, value)
         | 
| 68 | 
            -
                      }
         | 
| 69 | 
            -
                      where_conditions = where_conditions.reduce { |a, e| a.and(e) }
         | 
| 70 | 
            -
                      @version_model_class.where(where_conditions)
         | 
| 71 | 
            -
                    end
         | 
| 72 67 | 
             
                  end
         | 
| 73 68 | 
             
                end
         | 
| 74 69 | 
             
              end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PaperTrail
         | 
| 4 | 
            +
              module Queries
         | 
| 5 | 
            +
                module Versions
         | 
| 6 | 
            +
                  # For public API documentation, see `where_object_changes_from` in
         | 
| 7 | 
            +
                  # `paper_trail/version_concern.rb`.
         | 
| 8 | 
            +
                  # @api private
         | 
| 9 | 
            +
                  class WhereObjectChangesFrom
         | 
| 10 | 
            +
                    # - version_model_class - The class that VersionConcern was mixed into.
         | 
| 11 | 
            +
                    # - attributes - A `Hash` of attributes and values. See the public API
         | 
| 12 | 
            +
                    #   documentation for details.
         | 
| 13 | 
            +
                    # @api private
         | 
| 14 | 
            +
                    def initialize(version_model_class, attributes)
         | 
| 15 | 
            +
                      @version_model_class = version_model_class
         | 
| 16 | 
            +
                      @attributes = attributes
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    # @api private
         | 
| 20 | 
            +
                    def execute
         | 
| 21 | 
            +
                      if PaperTrail.config.object_changes_adapter.respond_to?(:where_object_changes_from)
         | 
| 22 | 
            +
                        return PaperTrail.config.object_changes_adapter.where_object_changes_from(
         | 
| 23 | 
            +
                          @version_model_class, @attributes
         | 
| 24 | 
            +
                        )
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                      column_type = @version_model_class.columns_hash["object_changes"].type
         | 
| 27 | 
            +
                      case column_type
         | 
| 28 | 
            +
                      when :jsonb, :json
         | 
| 29 | 
            +
                        json
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        raise UnsupportedColumnType.new(
         | 
| 32 | 
            +
                          method: "where_object_changes_from",
         | 
| 33 | 
            +
                          expected: "json or jsonb",
         | 
| 34 | 
            +
                          actual: column_type
         | 
| 35 | 
            +
                        )
         | 
| 36 | 
            +
                      end
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    # @api private
         | 
| 42 | 
            +
                    def json
         | 
| 43 | 
            +
                      predicates = []
         | 
| 44 | 
            +
                      values = []
         | 
| 45 | 
            +
                      @attributes.each do |field, value|
         | 
| 46 | 
            +
                        predicates.push(
         | 
| 47 | 
            +
                          "(object_changes->>? ILIKE ?)"
         | 
| 48 | 
            +
                        )
         | 
| 49 | 
            +
                        values.concat([field, "[#{value.to_json},%"])
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
                      sql = predicates.join(" and ")
         | 
| 52 | 
            +
                      @version_model_class.where(sql, *values)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module PaperTrail
         | 
| 4 | 
            +
              module Queries
         | 
| 5 | 
            +
                module Versions
         | 
| 6 | 
            +
                  # For public API documentation, see `where_object_changes_to` in
         | 
| 7 | 
            +
                  # `paper_trail/version_concern.rb`.
         | 
| 8 | 
            +
                  # @api private
         | 
| 9 | 
            +
                  class WhereObjectChangesTo
         | 
| 10 | 
            +
                    # - version_model_class - The class that VersionConcern was mixed into.
         | 
| 11 | 
            +
                    # - attributes - A `Hash` of attributes and values. See the public API
         | 
| 12 | 
            +
                    #   documentation for details.
         | 
| 13 | 
            +
                    # @api private
         | 
| 14 | 
            +
                    def initialize(version_model_class, attributes)
         | 
| 15 | 
            +
                      @version_model_class = version_model_class
         | 
| 16 | 
            +
                      @attributes = attributes
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    # @api private
         | 
| 20 | 
            +
                    def execute
         | 
| 21 | 
            +
                      if PaperTrail.config.object_changes_adapter.respond_to?(:where_object_changes_to)
         | 
| 22 | 
            +
                        return PaperTrail.config.object_changes_adapter.where_object_changes_to(
         | 
| 23 | 
            +
                          @version_model_class, @attributes
         | 
| 24 | 
            +
                        )
         | 
| 25 | 
            +
                      end
         | 
| 26 | 
            +
                      column_type = @version_model_class.columns_hash["object_changes"].type
         | 
| 27 | 
            +
                      case column_type
         | 
| 28 | 
            +
                      when :jsonb, :json
         | 
| 29 | 
            +
                        json
         | 
| 30 | 
            +
                      else
         | 
| 31 | 
            +
                        raise UnsupportedColumnType.new(
         | 
| 32 | 
            +
                          method: "where_object_changes_to",
         | 
| 33 | 
            +
                          expected: "json or jsonb",
         | 
| 34 | 
            +
                          actual: column_type
         | 
| 35 | 
            +
                        )
         | 
| 36 | 
            +
                      end
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    # @api private
         | 
| 42 | 
            +
                    def json
         | 
| 43 | 
            +
                      predicates = []
         | 
| 44 | 
            +
                      values = []
         | 
| 45 | 
            +
                      @attributes.each do |field, value|
         | 
| 46 | 
            +
                        predicates.push(
         | 
| 47 | 
            +
                          "(object_changes->>? ILIKE ?)"
         | 
| 48 | 
            +
                        )
         | 
| 49 | 
            +
                        values.concat([field, "[%#{value.to_json}]"])
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
                      sql = predicates.join(" and ")
         | 
| 52 | 
            +
                      @version_model_class.where(sql, *values)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -7,8 +7,6 @@ require "paper_trail/events/update" | |
| 7 7 | 
             
            module PaperTrail
         | 
| 8 8 | 
             
              # Represents the "paper trail" for a single record.
         | 
| 9 9 | 
             
              class RecordTrail
         | 
| 10 | 
            -
                RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
         | 
| 11 | 
            -
             | 
| 12 10 | 
             
                def initialize(record)
         | 
| 13 11 | 
             
                  @record = record
         | 
| 14 12 | 
             
                end
         | 
| @@ -196,15 +194,17 @@ module PaperTrail | |
| 196 194 | 
             
                # Save, and create a version record regardless of options such as `:on`,
         | 
| 197 195 | 
             
                # `:if`, or `:unless`.
         | 
| 198 196 | 
             
                #
         | 
| 199 | 
            -
                #  | 
| 197 | 
            +
                # `in_after_callback`: Indicates if this method is being called within an
         | 
| 198 | 
            +
                #                      `after` callback. Defaults to `false`.
         | 
| 199 | 
            +
                # `options`: Optional arguments passed to `save`.
         | 
| 200 200 | 
             
                #
         | 
| 201 201 | 
             
                # This is an "update" event. That is, we record the same data we would in
         | 
| 202 202 | 
             
                # the case of a normal AR `update`.
         | 
| 203 | 
            -
                def save_with_version( | 
| 203 | 
            +
                def save_with_version(in_after_callback: false, **options)
         | 
| 204 204 | 
             
                  ::PaperTrail.request(enabled: false) do
         | 
| 205 | 
            -
                    @record.save( | 
| 205 | 
            +
                    @record.save(**options)
         | 
| 206 206 | 
             
                  end
         | 
| 207 | 
            -
                  record_update(force: true, in_after_callback:  | 
| 207 | 
            +
                  record_update(force: true, in_after_callback: in_after_callback, is_touch: false)
         | 
| 208 208 | 
             
                end
         | 
| 209 209 |  | 
| 210 210 | 
             
                # Like the `update_column` method from `ActiveRecord::Persistence`, but also
         | 
| @@ -285,7 +285,7 @@ module PaperTrail | |
| 285 285 | 
             
                def log_version_errors(version, action)
         | 
| 286 286 | 
             
                  version.logger&.warn(
         | 
| 287 287 | 
             
                    "Unable to create version for #{action} of #{@record.class.name}" \
         | 
| 288 | 
            -
             | 
| 288 | 
            +
                    "##{@record.id}: " + version.errors.full_messages.join(", ")
         | 
| 289 289 | 
             
                  )
         | 
| 290 290 | 
             
                end
         | 
| 291 291 |  | 
    
        data/lib/paper_trail/reifier.rb
    CHANGED
    
    | @@ -52,26 +52,29 @@ module PaperTrail | |
| 52 52 | 
             
                  # not the actual subclass. If `type` is present but empty, the class is
         | 
| 53 53 | 
             
                  # the base class.
         | 
| 54 54 | 
             
                  def init_model(attrs, options, version)
         | 
| 55 | 
            -
                     | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
                     | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
                       | 
| 68 | 
            -
                        model = item_found
         | 
| 69 | 
            -
                        init_unversioned_attrs(attrs, model)
         | 
| 70 | 
            -
                      end
         | 
| 55 | 
            +
                    klass = version_reification_class(version, attrs)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    # The `dup` option and destroyed version always returns a new object,
         | 
| 58 | 
            +
                    # otherwise we should attempt to load item or to look for the item
         | 
| 59 | 
            +
                    # outside of default scope(s).
         | 
| 60 | 
            +
                    model = if options[:dup] == true || version.event == "destroy"
         | 
| 61 | 
            +
                              klass.new
         | 
| 62 | 
            +
                            else
         | 
| 63 | 
            +
                              version.item || init_model_by_finding_item_id(klass, version) || klass.new
         | 
| 64 | 
            +
                            end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    if options[:unversioned_attributes] == :nil && !model.new_record?
         | 
| 67 | 
            +
                      init_unversioned_attrs(attrs, model)
         | 
| 71 68 | 
             
                    end
         | 
| 69 | 
            +
             | 
| 72 70 | 
             
                    model
         | 
| 73 71 | 
             
                  end
         | 
| 74 72 |  | 
| 73 | 
            +
                  # @api private
         | 
| 74 | 
            +
                  def init_model_by_finding_item_id(klass, version)
         | 
| 75 | 
            +
                    klass.unscoped.where(klass.primary_key => version.item_id).first
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 75 78 | 
             
                  # Look for attributes that exist in `model` and not in this version.
         | 
| 76 79 | 
             
                  # These attributes should be set to nil. Modifies `attrs`.
         | 
| 77 80 | 
             
                  # @api private
         | 
| @@ -88,9 +91,7 @@ module PaperTrail | |
| 88 91 | 
             
                  #
         | 
| 89 92 | 
             
                  # @api private
         | 
| 90 93 | 
             
                  def reify_attribute(k, v, model, version)
         | 
| 91 | 
            -
                     | 
| 92 | 
            -
                    is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)
         | 
| 93 | 
            -
                    if model.has_attribute?(k) && !is_enum_without_type_caster
         | 
| 94 | 
            +
                    if model.has_attribute?(k)
         | 
| 94 95 | 
             
                      model[k.to_sym] = v
         | 
| 95 96 | 
             
                    elsif model.respond_to?("#{k}=")
         | 
| 96 97 | 
             
                      model.send("#{k}=", v)
         | 
| @@ -111,21 +112,35 @@ module PaperTrail | |
| 111 112 | 
             
                  end
         | 
| 112 113 |  | 
| 113 114 | 
             
                  # Given a `version`, return the class to reify. This method supports
         | 
| 114 | 
            -
                  # Single Table Inheritance (STI) with custom inheritance columns | 
| 115 | 
            +
                  # Single Table Inheritance (STI) with custom inheritance columns and
         | 
| 116 | 
            +
                  # custom inheritance column values.
         | 
| 115 117 | 
             
                  #
         | 
| 116 118 | 
             
                  # For example, imagine a `version` whose `item_type` is "Animal". The
         | 
| 117 119 | 
             
                  # `animals` table is an STI table (it has cats and dogs) and it has a
         | 
| 118 120 | 
             
                  # custom inheritance column, `species`. If `attrs["species"]` is "Dog",
         | 
| 119 121 | 
             
                  # this method returns the constant `Dog`. If `attrs["species"]` is blank,
         | 
| 120 | 
            -
                  # this method returns the constant `Animal`. | 
| 121 | 
            -
                  # | 
| 122 | 
            +
                  # this method returns the constant `Animal`.
         | 
| 123 | 
            +
                  #
         | 
| 124 | 
            +
                  # The values contained in the inheritance columns may be non-camelized
         | 
| 125 | 
            +
                  # strings (e.g. 'dog' instead of 'Dog'). To reify classes in this case
         | 
| 126 | 
            +
                  # we need to call the parents class `sti_class_for` method to retrieve
         | 
| 127 | 
            +
                  # the correct record class.
         | 
| 122 128 | 
             
                  #
         | 
| 123 | 
            -
                  #  | 
| 129 | 
            +
                  # You can see these particular examples in action in
         | 
| 130 | 
            +
                  # `spec/models/animal_spec.rb` and `spec/models/plant_spec.rb`
         | 
| 124 131 | 
             
                  def version_reification_class(version, attrs)
         | 
| 125 | 
            -
                     | 
| 132 | 
            +
                    clazz = version.item_type.constantize
         | 
| 133 | 
            +
                    inheritance_column_name = clazz.inheritance_column
         | 
| 126 134 | 
             
                    inher_col_value = attrs[inheritance_column_name]
         | 
| 127 | 
            -
                     | 
| 128 | 
            -
             | 
| 135 | 
            +
                    return clazz if inher_col_value.blank?
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                    # Rails 6.1 adds a public method for clients to use to customize STI classes. If that
         | 
| 138 | 
            +
                    # method is not available, fall back to using the private one
         | 
| 139 | 
            +
                    if clazz.public_methods.include?(:sti_class_for)
         | 
| 140 | 
            +
                      return clazz.sti_class_for(inher_col_value)
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    clazz.send(:find_sti_class, inher_col_value)
         | 
| 129 144 | 
             
                  end
         | 
| 130 145 | 
             
                end
         | 
| 131 146 | 
             
              end
         | 
    
        data/lib/paper_trail/request.rb
    CHANGED
    
    | @@ -12,9 +12,6 @@ module PaperTrail | |
| 12 12 | 
             
              #
         | 
| 13 13 | 
             
              # @api private
         | 
| 14 14 | 
             
              module Request
         | 
| 15 | 
            -
                class InvalidOption < RuntimeError
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
             | 
| 18 15 | 
             
                class << self
         | 
| 19 16 | 
             
                  # Sets any data from the controller that you want PaperTrail to store.
         | 
| 20 17 | 
             
                  # See also `PaperTrail::Rails::Controller#info_for_paper_trail`.
         | 
| @@ -31,16 +31,6 @@ module PaperTrail | |
| 31 31 | 
             
                      arel_field.matches("%\"#{field}\":#{json_value}%")
         | 
| 32 32 | 
             
                    end
         | 
| 33 33 | 
             
                  end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                  def where_object_changes_condition(*)
         | 
| 36 | 
            -
                    raise <<-STR.squish.freeze
         | 
| 37 | 
            -
                      where_object_changes no longer supports reading JSON from a text
         | 
| 38 | 
            -
                      column. The old implementation was inaccurate, returning more records
         | 
| 39 | 
            -
                      than you wanted. This feature was deprecated in 7.1.0 and removed in
         | 
| 40 | 
            -
                      8.0.0. The json and jsonb datatypes are still supported. See the
         | 
| 41 | 
            -
                      discussion at https://github.com/paper-trail-gem/paper_trail/issues/803
         | 
| 42 | 
            -
                    STR
         | 
| 43 | 
            -
                  end
         | 
| 44 34 | 
             
                end
         | 
| 45 35 | 
             
              end
         | 
| 46 36 | 
             
            end
         |