mongo_trails 10.3.1

Sign up to get free protection for your applications and to get access to all the features.
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