mongo_trails 10.3.1

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +13 -0
  5. data/Appraisals +7 -0
  6. data/Gemfile +7 -0
  7. data/Gemfile.lock +62 -0
  8. data/LICENSE +20 -0
  9. data/README.md +36 -0
  10. data/Rakefile +13 -0
  11. data/gemfiles/rails_5.gemfile +9 -0
  12. data/gemfiles/rails_5.gemfile.lock +63 -0
  13. data/gemfiles/rails_6.gemfile +9 -0
  14. data/gemfiles/rails_6.gemfile.lock +63 -0
  15. data/lib/mongo_trails.rb +154 -0
  16. data/lib/mongo_trails/attribute_serializers/README.md +10 -0
  17. data/lib/mongo_trails/attribute_serializers/attribute_serializer_factory.rb +27 -0
  18. data/lib/mongo_trails/attribute_serializers/cast_attribute_serializer.rb +51 -0
  19. data/lib/mongo_trails/attribute_serializers/object_attribute.rb +41 -0
  20. data/lib/mongo_trails/attribute_serializers/object_changes_attribute.rb +44 -0
  21. data/lib/mongo_trails/cleaner.rb +60 -0
  22. data/lib/mongo_trails/compatibility.rb +51 -0
  23. data/lib/mongo_trails/config.rb +41 -0
  24. data/lib/mongo_trails/events/base.rb +323 -0
  25. data/lib/mongo_trails/events/create.rb +32 -0
  26. data/lib/mongo_trails/events/destroy.rb +42 -0
  27. data/lib/mongo_trails/events/update.rb +60 -0
  28. data/lib/mongo_trails/frameworks/cucumber.rb +33 -0
  29. data/lib/mongo_trails/frameworks/rails.rb +4 -0
  30. data/lib/mongo_trails/frameworks/rails/controller.rb +109 -0
  31. data/lib/mongo_trails/frameworks/rails/engine.rb +43 -0
  32. data/lib/mongo_trails/frameworks/rspec.rb +43 -0
  33. data/lib/mongo_trails/frameworks/rspec/helpers.rb +29 -0
  34. data/lib/mongo_trails/has_paper_trail.rb +86 -0
  35. data/lib/mongo_trails/model_config.rb +249 -0
  36. data/lib/mongo_trails/mongo_support/config.rb +9 -0
  37. data/lib/mongo_trails/mongo_support/version.rb +56 -0
  38. data/lib/mongo_trails/queries/versions/where_object.rb +65 -0
  39. data/lib/mongo_trails/queries/versions/where_object_changes.rb +75 -0
  40. data/lib/mongo_trails/record_history.rb +51 -0
  41. data/lib/mongo_trails/record_trail.rb +304 -0
  42. data/lib/mongo_trails/reifier.rb +130 -0
  43. data/lib/mongo_trails/request.rb +166 -0
  44. data/lib/mongo_trails/serializers/json.rb +46 -0
  45. data/lib/mongo_trails/serializers/yaml.rb +43 -0
  46. data/lib/mongo_trails/type_serializers/postgres_array_serializer.rb +48 -0
  47. data/lib/mongo_trails/version_concern.rb +336 -0
  48. data/lib/mongo_trails/version_number.rb +23 -0
  49. data/mongo_trails.gemspec +38 -0
  50. metadata +180 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mongo_trails/events/base"
4
+
5
+ module PaperTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Create < Base
11
+ # Return attributes of nascent `Version` record.
12
+ #
13
+ # @api private
14
+ def data
15
+ data = {
16
+ item: @record,
17
+ event: @record.paper_trail_event || "create",
18
+ whodunnit: PaperTrail.request.whodunnit
19
+ }
20
+ if @record.respond_to?(:updated_at)
21
+ data[:created_at] = @record.updated_at
22
+ end
23
+ if record_object_changes? && changed_notably?
24
+ changes = notable_changes
25
+ data[:object_changes] = prepare_object_changes(changes)
26
+ end
27
+ merge_item_subtype_into(data)
28
+ merge_metadata_into(data)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mongo_trails/events/base"
4
+
5
+ module PaperTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Destroy < Base
11
+ # Return attributes of nascent `Version` record.
12
+ #
13
+ # @api private
14
+ def data
15
+ data = {
16
+ item_id: @record.id,
17
+ item_type: @record.class.base_class.name,
18
+ event: @record.paper_trail_event || "destroy",
19
+ whodunnit: PaperTrail.request.whodunnit
20
+ }
21
+ if record_object?
22
+ data[:object] = recordable_object(false)
23
+ end
24
+ if record_object_changes?
25
+ data[:object_changes] = prepare_object_changes(notable_changes)
26
+ end
27
+ merge_item_subtype_into(data)
28
+ merge_metadata_into(data)
29
+ end
30
+
31
+ private
32
+
33
+ # Rails' implementation (eg. `@record.saved_changes`) returns nothing on
34
+ # destroy, so we have to build the hash we want.
35
+ #
36
+ # @override
37
+ def changes_in_latest_version
38
+ @record.attributes.map { |attr, value| [attr, [value, nil]] }.to_h
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mongo_trails/events/base"
4
+
5
+ module PaperTrail
6
+ module Events
7
+ # See docs in `Base`.
8
+ #
9
+ # @api private
10
+ class Update < Base
11
+ # - is_touch - [boolean] - Used in the two situations that are touch-like:
12
+ # - `after_touch` we call `RecordTrail#record_update`
13
+ # - force_changes - [Hash] - Only used by `RecordTrail#update_columns`,
14
+ # because there dirty-tracking is off, so it has to track its own changes.
15
+ #
16
+ # @api private
17
+ def initialize(record, in_after_callback, is_touch, force_changes)
18
+ super(record, in_after_callback)
19
+ @is_touch = is_touch
20
+ @force_changes = force_changes
21
+ end
22
+
23
+ # Return attributes of nascent `Version` record.
24
+ #
25
+ # @api private
26
+ def data
27
+ data = {
28
+ item: @record,
29
+ event: @record.paper_trail_event || "update",
30
+ whodunnit: PaperTrail.request.whodunnit
31
+ }
32
+ if @record.respond_to?(:updated_at)
33
+ data[:created_at] = @record.updated_at
34
+ end
35
+ if record_object?
36
+ data[:object] = recordable_object(@is_touch)
37
+ end
38
+ if record_object_changes?
39
+ changes = @force_changes.nil? ? notable_changes : @force_changes
40
+ data[:object_changes] = prepare_object_changes(changes)
41
+ end
42
+ merge_item_subtype_into(data)
43
+ merge_metadata_into(data)
44
+ end
45
+
46
+ private
47
+
48
+ # `touch` cannot record `object_changes` because rails' `touch` does not
49
+ # perform dirty-tracking. Specifically, methods from `Dirty`, like
50
+ # `saved_changes`, return the same values before and after `touch`.
51
+ #
52
+ # See https://github.com/rails/rails/issues/33429
53
+ #
54
+ # @api private
55
+ def record_object_changes?
56
+ !@is_touch && super
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # before hook for Cucumber
4
+ Before do
5
+ PaperTrail.enabled = false
6
+ PaperTrail.request.enabled = true
7
+ PaperTrail.request.whodunnit = nil
8
+ PaperTrail.request.controller_info = {} if defined?(::Rails)
9
+ end
10
+
11
+ module PaperTrail
12
+ module Cucumber
13
+ # Helper method for enabling PT in Cucumber features.
14
+ module Extensions
15
+ # :call-seq:
16
+ # with_versioning
17
+ #
18
+ # enable versioning for specific blocks
19
+
20
+ def with_versioning
21
+ was_enabled = ::PaperTrail.enabled?
22
+ ::PaperTrail.enabled = true
23
+ begin
24
+ yield
25
+ ensure
26
+ ::PaperTrail.enabled = was_enabled
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ World PaperTrail::Cucumber::Extensions
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mongo_trails/frameworks/rails/controller"
4
+ require "mongo_trails/frameworks/rails/engine"
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Rails
5
+ # Extensions to rails controllers. Provides convenient ways to pass certain
6
+ # information to the model layer, with `controller_info` and `whodunnit`.
7
+ # Also includes a convenient on/off switch,
8
+ # `paper_trail_enabled_for_controller`.
9
+ module Controller
10
+ def self.included(controller)
11
+ controller.before_action(
12
+ :set_paper_trail_enabled_for_controller,
13
+ :set_paper_trail_controller_info
14
+ )
15
+ end
16
+
17
+ protected
18
+
19
+ # Returns the user who is responsible for any changes that occur.
20
+ # By default this calls `current_user` and returns the result.
21
+ #
22
+ # Override this method in your controller to call a different
23
+ # method, e.g. `current_person`, or anything you like.
24
+ #
25
+ # @api public
26
+ def user_for_paper_trail
27
+ return unless defined?(current_user)
28
+ current_user.try(:id) || current_user
29
+ end
30
+
31
+ # Returns any information about the controller or request that you
32
+ # want PaperTrail to store alongside any changes that occur. By
33
+ # default this returns an empty hash.
34
+ #
35
+ # Override this method in your controller to return a hash of any
36
+ # information you need. The hash's keys must correspond to columns
37
+ # in your `versions` table, so don't forget to add any new columns
38
+ # you need.
39
+ #
40
+ # For example:
41
+ #
42
+ # {:ip => request.remote_ip, :user_agent => request.user_agent}
43
+ #
44
+ # The columns `ip` and `user_agent` must exist in your `versions` # table.
45
+ #
46
+ # Use the `:meta` option to
47
+ # `PaperTrail::Model::ClassMethods.has_paper_trail` to store any extra
48
+ # model-level data you need.
49
+ #
50
+ # @api public
51
+ def info_for_paper_trail
52
+ {}
53
+ end
54
+
55
+ # Returns `true` (default) or `false` depending on whether PaperTrail
56
+ # should be active for the current request.
57
+ #
58
+ # Override this method in your controller to specify when PaperTrail
59
+ # should be off.
60
+ #
61
+ # ```
62
+ # def paper_trail_enabled_for_controller
63
+ # # Don't omit `super` without a good reason.
64
+ # super && request.user_agent != 'Disable User-Agent'
65
+ # end
66
+ # ```
67
+ #
68
+ # @api public
69
+ def paper_trail_enabled_for_controller
70
+ ::PaperTrail.enabled?
71
+ end
72
+
73
+ private
74
+
75
+ # Tells PaperTrail whether versions should be saved in the current
76
+ # request.
77
+ #
78
+ # @api public
79
+ def set_paper_trail_enabled_for_controller
80
+ ::PaperTrail.request.enabled = paper_trail_enabled_for_controller
81
+ end
82
+
83
+ # Tells PaperTrail who is responsible for any changes that occur.
84
+ #
85
+ # @api public
86
+ def set_paper_trail_whodunnit
87
+ if ::PaperTrail.request.enabled?
88
+ ::PaperTrail.request.whodunnit = user_for_paper_trail
89
+ end
90
+ end
91
+
92
+ # Tells PaperTrail any information from the controller you want to store
93
+ # alongside any changes that occur.
94
+ #
95
+ # @api public
96
+ def set_paper_trail_controller_info
97
+ if ::PaperTrail.request.enabled?
98
+ ::PaperTrail.request.controller_info = info_for_paper_trail
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ if defined?(::ActionController)
106
+ ::ActiveSupport.on_load(:action_controller) do
107
+ include ::PaperTrail::Rails::Controller
108
+ end
109
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module Rails
5
+ # See http://guides.rubyonrails.org/engines.html
6
+ class Engine < ::Rails::Engine
7
+ DPR_CONFIG_ENABLED = <<~EOS.squish.freeze
8
+ The rails configuration option config.paper_trail.enabled is deprecated.
9
+ Please use PaperTrail.enabled= instead. People were getting confused
10
+ that PT has both, specifically regarding *when* each was happening. If
11
+ you'd like to keep config.paper_trail, join the discussion at
12
+ https://github.com/paper-trail-gem/mongo_trails/pull/1176
13
+ EOS
14
+ private_constant :DPR_CONFIG_ENABLED
15
+ DPR_RUDELY_ENABLING = <<~EOS.squish.freeze
16
+ At some point early in the rails boot process, you have set
17
+ PaperTrail.enabled = false. PT's rails engine is now overriding your
18
+ setting, and setting it to true. We're not sure why, but this is how PT
19
+ has worked since 5.0, when the config.paper_trail.enabled option was
20
+ introduced. This is now deprecated. In the future, PT will not override
21
+ your setting. See
22
+ https://github.com/paper-trail-gem/mongo_trails/pull/1176 for discussion.
23
+ EOS
24
+ private_constant :DPR_RUDELY_ENABLING
25
+
26
+ # --- Begin deprecated section ---
27
+ config.paper_trail = ActiveSupport::OrderedOptions.new
28
+ initializer "paper_trail.initialisation" do |app|
29
+ enable = app.config.paper_trail[:enabled]
30
+ if enable.nil?
31
+ unless PaperTrail.enabled?
32
+ ::ActiveSupport::Deprecation.warn(DPR_RUDELY_ENABLING)
33
+ PaperTrail.enabled = true
34
+ end
35
+ else
36
+ ::ActiveSupport::Deprecation.warn(DPR_CONFIG_ENABLED)
37
+ PaperTrail.enabled = enable
38
+ end
39
+ end
40
+ # --- End deprecated section ---
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core"
4
+ require "rspec/matchers"
5
+ require "mongo_trails/frameworks/rspec/helpers"
6
+
7
+ RSpec.configure do |config|
8
+ config.include ::PaperTrail::RSpec::Helpers::InstanceMethods
9
+ config.extend ::PaperTrail::RSpec::Helpers::ClassMethods
10
+
11
+ config.before(:each) do
12
+ ::Mongoid.purge!
13
+ ::PaperTrail.enabled = false
14
+ ::PaperTrail.request.enabled = true
15
+ ::PaperTrail.request.whodunnit = nil
16
+ ::PaperTrail.request.controller_info = {} if defined?(::Rails) && defined?(::RSpec::Rails)
17
+ end
18
+
19
+ config.before(:each, versioning: true) do
20
+ ::PaperTrail.enabled = true
21
+ end
22
+ end
23
+
24
+ RSpec::Matchers.define :be_versioned do
25
+ # check to see if the model has `has_paper_trail` declared on it
26
+ match { |actual| actual.is_a?(::PaperTrail::Model::InstanceMethods) }
27
+ end
28
+
29
+ RSpec::Matchers.define :have_a_version_with do |attributes|
30
+ # check if the model has a version with the specified attributes
31
+ match do |actual|
32
+ versions_association = actual.class.versions_association_name
33
+ actual.send(versions_association).where_object(attributes).any?
34
+ end
35
+ end
36
+
37
+ RSpec::Matchers.define :have_a_version_with_changes do |attributes|
38
+ # check if the model has a version changes with the specified attributes
39
+ match do |actual|
40
+ versions_association = actual.class.versions_association_name
41
+ actual.send(versions_association).where_object_changes(attributes).any?
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ module RSpec
5
+ module Helpers
6
+ # Included in the RSpec configuration in `frameworks/rspec.rb`
7
+ module InstanceMethods
8
+ # enable versioning for specific blocks (at instance-level)
9
+ def with_versioning
10
+ was_enabled = ::PaperTrail.enabled?
11
+ ::PaperTrail.enabled = true
12
+ yield
13
+ ensure
14
+ ::PaperTrail.enabled = was_enabled
15
+ end
16
+ end
17
+
18
+ # Extended by the RSpec configuration in `frameworks/rspec.rb`
19
+ module ClassMethods
20
+ # enable versioning for specific blocks (at class-level)
21
+ def with_versioning(&block)
22
+ context "with versioning", versioning: true do
23
+ class_exec(&block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mongo_trails/attribute_serializers/object_attribute"
4
+ require "mongo_trails/attribute_serializers/object_changes_attribute"
5
+ require "mongo_trails/model_config"
6
+ require "mongo_trails/record_trail"
7
+
8
+ module PaperTrail
9
+ # Extensions to `ActiveRecord::Base`. See `frameworks/active_record.rb`.
10
+ # It is our goal to have the smallest possible footprint here, because
11
+ # `ActiveRecord::Base` is a very crowded namespace! That is why we introduced
12
+ # `.paper_trail` and `#paper_trail`.
13
+ module Model
14
+ def self.included(base)
15
+ base.send :extend, ClassMethods
16
+ end
17
+
18
+ # :nodoc:
19
+ module ClassMethods
20
+ # Declare this in your model to track every create, update, and destroy.
21
+ # Each version of the model is available in the `versions` association.
22
+ #
23
+ # Options:
24
+ #
25
+ # - :on - The events to track (optional; defaults to all of them). Set
26
+ # to an array of `:create`, `:update`, `:destroy` and `:touch` as desired.
27
+ # - :class_name (deprecated) - The name of a custom Version class that
28
+ # includes `PaperTrail::VersionConcern`.
29
+ # - :ignore - An array of attributes for which a new `Version` will not be
30
+ # created if only they change. It can also accept a Hash as an
31
+ # argument where the key is the attribute to ignore (a `String` or
32
+ # `Symbol`), which will only be ignored if the value is a `Proc` which
33
+ # returns truthily.
34
+ # - :if, :unless - Procs that allow to specify conditions when to save
35
+ # versions for an object.
36
+ # - :only - Inverse of `ignore`. A new `Version` will be created only
37
+ # for these attributes if supplied it can also accept a Hash as an
38
+ # argument where the key is the attribute to track (a `String` or
39
+ # `Symbol`), which will only be counted if the value is a `Proc` which
40
+ # returns truthily.
41
+ # - :skip - Fields to ignore completely. As with `ignore`, updates to
42
+ # these fields will not create a new `Version`. In addition, these
43
+ # fields will not be included in the serialized versions of the object
44
+ # whenever a new `Version` is created.
45
+ # - :meta - A hash of extra data to store. You must add a column to the
46
+ # `versions` table for each key. Values are objects or procs (which
47
+ # are called with `self`, i.e. the model with the paper trail). See
48
+ # `PaperTrail::Controller.info_for_paper_trail` for how to store data
49
+ # from the controller.
50
+ # - :versions - Either,
51
+ # - A String (deprecated) - The name to use for the versions
52
+ # association. Default is `:versions`.
53
+ # - A Hash - options passed to `has_many`, plus `name:` and `scope:`.
54
+ # - :version - The name to use for the method which returns the version
55
+ # the instance was reified from. Default is `:version`.
56
+ #
57
+ # Plugins like the experimental `paper_trail-association_tracking` gem
58
+ # may accept additional options.
59
+ #
60
+ # You can define a default set of options via the configurable
61
+ # `PaperTrail.config.has_paper_trail_defaults` hash in your applications
62
+ # initializer. The hash can contain any of the following options and will
63
+ # provide an overridable default for all models.
64
+ #
65
+ # @api public
66
+ def has_paper_trail(options = {})
67
+ defaults = PaperTrail.config.has_paper_trail_defaults
68
+ paper_trail.setup(defaults.merge(options))
69
+ end
70
+
71
+ # @api public
72
+ def paper_trail
73
+ ::PaperTrail::ModelConfig.new(self)
74
+ end
75
+ end
76
+
77
+ # Wrap the following methods in a module so we can include them only in the
78
+ # ActiveRecord models that declare `has_paper_trail`.
79
+ module InstanceMethods
80
+ # @api public
81
+ def paper_trail
82
+ ::PaperTrail::RecordTrail.new(self)
83
+ end
84
+ end
85
+ end
86
+ end