mongo_trails 10.3.1 → 14.0.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +53 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +19 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +10 -4
  7. data/Gemfile +8 -1
  8. data/README.md +28 -6
  9. data/Rakefile +17 -0
  10. data/bin/bump-version +67 -0
  11. data/gemfiles/rails_7.gemfile +15 -0
  12. data/gemfiles/rails_7.gemfile.lock +212 -0
  13. data/gemfiles/rails_8.gemfile +15 -0
  14. data/gemfiles/rails_8.gemfile.lock +213 -0
  15. data/gemfiles/rails_8_1.gemfile +15 -0
  16. data/gemfiles/rails_8_1.gemfile.lock +143 -0
  17. data/lib/mongo_trails/callback_propagation.rb +77 -0
  18. data/lib/mongo_trails/config.rb +7 -24
  19. data/lib/mongo_trails/events/base.rb +5 -308
  20. data/lib/mongo_trails/model_config.rb +1 -232
  21. data/lib/mongo_trails/mongo_support/config.rb +7 -1
  22. data/lib/mongo_trails/mongo_support/criteria.rb +7 -0
  23. data/lib/mongo_trails/mongo_support/version.rb +249 -19
  24. data/lib/mongo_trails/mongo_support/version_commit_wrap.rb +37 -0
  25. data/lib/mongo_trails/mongo_support/write_version_worker.rb +11 -0
  26. data/lib/mongo_trails/record_trail.rb +21 -262
  27. data/lib/mongo_trails/version.rb +5 -0
  28. data/lib/mongo_trails/version_concern.rb +4 -293
  29. data/lib/mongo_trails.rb +13 -149
  30. data/mongo_trails.gemspec +19 -20
  31. metadata +51 -85
  32. data/.travis.yml +0 -13
  33. data/Gemfile.lock +0 -62
  34. data/gemfiles/rails_5.gemfile +0 -9
  35. data/gemfiles/rails_5.gemfile.lock +0 -63
  36. data/gemfiles/rails_6.gemfile +0 -9
  37. data/gemfiles/rails_6.gemfile.lock +0 -63
  38. data/lib/mongo_trails/attribute_serializers/README.md +0 -10
  39. data/lib/mongo_trails/attribute_serializers/attribute_serializer_factory.rb +0 -27
  40. data/lib/mongo_trails/attribute_serializers/cast_attribute_serializer.rb +0 -51
  41. data/lib/mongo_trails/attribute_serializers/object_attribute.rb +0 -41
  42. data/lib/mongo_trails/attribute_serializers/object_changes_attribute.rb +0 -44
  43. data/lib/mongo_trails/cleaner.rb +0 -60
  44. data/lib/mongo_trails/compatibility.rb +0 -51
  45. data/lib/mongo_trails/events/create.rb +0 -32
  46. data/lib/mongo_trails/events/destroy.rb +0 -42
  47. data/lib/mongo_trails/events/update.rb +0 -60
  48. data/lib/mongo_trails/frameworks/cucumber.rb +0 -33
  49. data/lib/mongo_trails/frameworks/rails/controller.rb +0 -109
  50. data/lib/mongo_trails/frameworks/rails/engine.rb +0 -43
  51. data/lib/mongo_trails/frameworks/rails.rb +0 -4
  52. data/lib/mongo_trails/frameworks/rspec/helpers.rb +0 -29
  53. data/lib/mongo_trails/frameworks/rspec.rb +0 -43
  54. data/lib/mongo_trails/has_paper_trail.rb +0 -86
  55. data/lib/mongo_trails/queries/versions/where_object.rb +0 -65
  56. data/lib/mongo_trails/queries/versions/where_object_changes.rb +0 -75
  57. data/lib/mongo_trails/record_history.rb +0 -51
  58. data/lib/mongo_trails/reifier.rb +0 -130
  59. data/lib/mongo_trails/request.rb +0 -166
  60. data/lib/mongo_trails/serializers/json.rb +0 -46
  61. data/lib/mongo_trails/serializers/yaml.rb +0 -43
  62. data/lib/mongo_trails/type_serializers/postgres_array_serializer.rb +0 -48
  63. data/lib/mongo_trails/version_number.rb +0 -23
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- # Represents the history of a single record.
5
- # @api private
6
- class RecordHistory
7
- # @param versions - ActiveRecord::Relation - All versions of the record.
8
- # @param version_class - Class - Usually PaperTrail::Version,
9
- # but it could also be a custom version class.
10
- # @api private
11
- def initialize(versions, version_class)
12
- @versions = versions
13
- @version_class = version_class
14
- end
15
-
16
- # Returns ordinal position of `version` in `sequence`.
17
- # @api private
18
- def index(version)
19
- sequence.to_a.index(version)
20
- end
21
-
22
- private
23
-
24
- # Returns `@versions` in chronological order.
25
- # @api private
26
- def sequence
27
- if @version_class.primary_key_is_int?
28
- @versions.select(primary_key).order(primary_key.asc)
29
- else
30
- @versions.
31
- select([table[:created_at], primary_key]).
32
- order(@version_class.timestamp_sort_order)
33
- end
34
- end
35
-
36
- # @return - Arel::Attribute - Attribute representing the primary key
37
- # of the version table. The column's data type is usually a serial
38
- # integer (the rails convention) but not always.
39
- # @api private
40
- def primary_key
41
- table[@version_class.primary_key]
42
- end
43
-
44
- # @return - Arel::Table - The version table, usually named `versions`, but
45
- # not always.
46
- # @api private
47
- def table
48
- @version_class.arel_table
49
- end
50
- end
51
- end
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "mongo_trails/attribute_serializers/object_attribute"
4
-
5
- module PaperTrail
6
- # Given a version record and some options, builds a new model object.
7
- # @api private
8
- module Reifier
9
- class << self
10
- # See `VersionConcern#reify` for documentation.
11
- # @api private
12
- def reify(version, options)
13
- options = apply_defaults_to(options, version)
14
- attrs = version.object_deserialized
15
- model = init_model(attrs, options, version)
16
- reify_attributes(model, version, attrs)
17
- model.send "#{model.class.version_association_name}=", version
18
- model
19
- end
20
-
21
- private
22
-
23
- # Given a hash of `options` for `.reify`, return a new hash with default
24
- # values applied.
25
- # @api private
26
- def apply_defaults_to(options, version)
27
- {
28
- version_at: version.created_at,
29
- mark_for_destruction: false,
30
- has_one: false,
31
- has_many: false,
32
- belongs_to: false,
33
- has_and_belongs_to_many: false,
34
- unversioned_attributes: :nil
35
- }.merge(options)
36
- end
37
-
38
- # Initialize a model object suitable for reifying `version` into. Does
39
- # not perform reification, merely instantiates the appropriate model
40
- # class and, if specified by `options[:unversioned_attributes]`, sets
41
- # unversioned attributes to `nil`.
42
- #
43
- # Normally a polymorphic belongs_to relationship allows us to get the
44
- # object we belong to by calling, in this case, `item`. However this
45
- # returns nil if `item` has been destroyed, and we need to be able to
46
- # retrieve destroyed objects.
47
- #
48
- # In this situation we constantize the `item_type` to get hold of the
49
- # class...except when the stored object's attributes include a `type`
50
- # key. If this is the case, the object we belong to is using single
51
- # table inheritance (STI) and the `item_type` will be the base class,
52
- # not the actual subclass. If `type` is present but empty, the class is
53
- # the base class.
54
- def init_model(attrs, options, version)
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
- find_cond = { klass.primary_key => version.item_id }
64
-
65
- version.item || klass.unscoped.where(find_cond).first || klass.new
66
- end
67
-
68
- if options[:unversioned_attributes] == :nil && !model.new_record?
69
- init_unversioned_attrs(attrs, model)
70
- end
71
-
72
- model
73
- end
74
-
75
- # Look for attributes that exist in `model` and not in this version.
76
- # These attributes should be set to nil. Modifies `attrs`.
77
- # @api private
78
- def init_unversioned_attrs(attrs, model)
79
- (model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
80
- end
81
-
82
- # Reify onto `model` an attribute named `k` with value `v` from `version`.
83
- #
84
- # `ObjectAttribute#deserialize` will return the mapped enum value and in
85
- # Rails < 5, the []= uses the integer type caster from the column
86
- # definition (in general) and thus will turn a (usually) string to 0
87
- # instead of the correct value.
88
- #
89
- # @api private
90
- def reify_attribute(k, v, model, version)
91
- if model.has_attribute?(k)
92
- model[k.to_sym] = v
93
- elsif model.respond_to?("#{k}=")
94
- model.send("#{k}=", v)
95
- elsif version.logger
96
- version.logger.warn(
97
- "Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
98
- )
99
- end
100
- end
101
-
102
- # Reify onto `model` all the attributes of `version`.
103
- # @api private
104
- def reify_attributes(model, version, attrs)
105
- AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
106
- attrs.each do |k, v|
107
- reify_attribute(k, v, model, version)
108
- end
109
- end
110
-
111
- # Given a `version`, return the class to reify. This method supports
112
- # Single Table Inheritance (STI) with custom inheritance columns.
113
- #
114
- # For example, imagine a `version` whose `item_type` is "Animal". The
115
- # `animals` table is an STI table (it has cats and dogs) and it has a
116
- # custom inheritance column, `species`. If `attrs["species"]` is "Dog",
117
- # this method returns the constant `Dog`. If `attrs["species"]` is blank,
118
- # this method returns the constant `Animal`. You can see this particular
119
- # example in action in `spec/models/animal_spec.rb`.
120
- #
121
- # TODO: Duplication: similar `constantize` in VersionConcern#version_limit
122
- def version_reification_class(version, attrs)
123
- inheritance_column_name = version.item_type.constantize.inheritance_column
124
- inher_col_value = attrs[inheritance_column_name]
125
- class_name = inher_col_value.blank? ? version.item_type : inher_col_value
126
- class_name.constantize
127
- end
128
- end
129
- end
130
- end
@@ -1,166 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "request_store"
4
-
5
- module PaperTrail
6
- # Manages variables that affect the current HTTP request, such as `whodunnit`.
7
- #
8
- # Please do not use `PaperTrail::Request` directly, use `PaperTrail.request`.
9
- # Currently, `Request` is a `Module`, but in the future it is quite possible
10
- # we may make it a `Class`. If we make such a choice, we will not provide any
11
- # warning and will not treat it as a breaking change. You've been warned :)
12
- #
13
- # @api private
14
- module Request
15
- class InvalidOption < RuntimeError
16
- end
17
-
18
- class << self
19
- # Sets any data from the controller that you want PaperTrail to store.
20
- # See also `PaperTrail::Rails::Controller#info_for_paper_trail`.
21
- #
22
- # PaperTrail.request.controller_info = { ip: request_user_ip }
23
- # PaperTrail.request.controller_info # => { ip: '127.0.0.1' }
24
- #
25
- # @api public
26
- def controller_info=(value)
27
- store[:controller_info] = value
28
- end
29
-
30
- # Returns the data from the controller that you want PaperTrail to store.
31
- # See also `PaperTrail::Rails::Controller#info_for_paper_trail`.
32
- #
33
- # PaperTrail.request.controller_info = { ip: request_user_ip }
34
- # PaperTrail.request.controller_info # => { ip: '127.0.0.1' }
35
- #
36
- # @api public
37
- def controller_info
38
- store[:controller_info]
39
- end
40
-
41
- # Switches PaperTrail off for the given model.
42
- # @api public
43
- def disable_model(model_class)
44
- enabled_for_model(model_class, false)
45
- end
46
-
47
- # Switches PaperTrail on for the given model.
48
- # @api public
49
- def enable_model(model_class)
50
- enabled_for_model(model_class, true)
51
- end
52
-
53
- # Sets whether PaperTrail is enabled or disabled for the current request.
54
- # @api public
55
- def enabled=(value)
56
- store[:enabled] = value
57
- end
58
-
59
- # Returns `true` if PaperTrail is enabled for the request, `false` otherwise.
60
- # See `PaperTrail::Rails::Controller#paper_trail_enabled_for_controller`.
61
- # @api public
62
- def enabled?
63
- !!store[:enabled]
64
- end
65
-
66
- # Sets whether PaperTrail is enabled or disabled for this model in the
67
- # current request.
68
- # @api public
69
- def enabled_for_model(model, value)
70
- store[:"enabled_for_#{model}"] = value
71
- end
72
-
73
- # Returns `true` if PaperTrail is enabled for this model in the current
74
- # request, `false` otherwise.
75
- # @api public
76
- def enabled_for_model?(model)
77
- model.include?(::PaperTrail::Model::InstanceMethods) &&
78
- !!store.fetch(:"enabled_for_#{model}", true)
79
- end
80
-
81
- # @api private
82
- def merge(options)
83
- options.to_h.each do |k, v|
84
- store[k] = v
85
- end
86
- end
87
-
88
- # @api private
89
- def set(options)
90
- store.clear
91
- merge(options)
92
- end
93
-
94
- # Returns a deep copy of the internal hash from our RequestStore. Keys are
95
- # all symbols. Values are mostly primitives, but whodunnit can be a Proc.
96
- # We cannot use Marshal.dump here because it doesn't support Proc. It is
97
- # unclear exactly how `deep_dup` handles a Proc, but it doesn't complain.
98
- # @api private
99
- def to_h
100
- store.deep_dup
101
- end
102
-
103
- # Temporarily set `options` and execute a block.
104
- # @api private
105
- def with(options)
106
- return unless block_given?
107
- validate_public_options(options)
108
- before = to_h
109
- merge(options)
110
- yield
111
- ensure
112
- set(before)
113
- end
114
-
115
- # Sets who is responsible for any changes that occur during request. You
116
- # would normally use this in a migration or on the console, when working
117
- # with models directly.
118
- #
119
- # `value` is usually a string, the name of a person, but you can set
120
- # anything that responds to `to_s`. You can also set a Proc, which will
121
- # not be evaluated until `whodunnit` is called later, usually right before
122
- # inserting a `Version` record.
123
- #
124
- # @api public
125
- def whodunnit=(value)
126
- store[:whodunnit] = value
127
- end
128
-
129
- # Returns who is reponsible for any changes that occur during request.
130
- #
131
- # @api public
132
- def whodunnit
133
- who = store[:whodunnit]
134
- who.respond_to?(:call) ? who.call : who
135
- end
136
-
137
- private
138
-
139
- # Returns a Hash, initializing with default values if necessary.
140
- # @api private
141
- def store
142
- RequestStore.store[:paper_trail] ||= {
143
- enabled: true
144
- }
145
- end
146
-
147
- # Provide a helpful error message if someone has a typo in one of their
148
- # option keys. We don't validate option values here. That's traditionally
149
- # been handled with casting (`to_s`, `!!`) in the accessor method.
150
- # @api private
151
- def validate_public_options(options)
152
- options.each do |k, _v|
153
- case k
154
- when :controller_info,
155
- /enabled_for_/,
156
- :enabled,
157
- :whodunnit
158
- next
159
- else
160
- raise InvalidOption, "Invalid option: #{k}"
161
- end
162
- end
163
- end
164
- end
165
- end
166
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- module Serializers
5
- # An alternate serializer for, e.g. `versions.object`.
6
- module JSON
7
- extend self # makes all instance methods become module methods as well
8
-
9
- def load(string)
10
- ActiveSupport::JSON.decode string
11
- end
12
-
13
- def dump(object)
14
- ActiveSupport::JSON.encode object
15
- end
16
-
17
- # Returns a SQL LIKE condition to be used to match the given field and
18
- # value in the serialized object.
19
- def where_object_condition(arel_field, field, value)
20
- # Convert to JSON to handle strings and nulls correctly.
21
- json_value = value.to_json
22
-
23
- # If the value is a number, we need to ensure that we find the next
24
- # character too, which is either `,` or `}`, to ensure that searching
25
- # for the value 12 doesn't yield false positives when the value is
26
- # 123.
27
- if value.is_a? Numeric
28
- arel_field.matches("%\"#{field}\":#{json_value},%").
29
- or(arel_field.matches("%\"#{field}\":#{json_value}}%"))
30
- else
31
- arel_field.matches("%\"#{field}\":#{json_value}%")
32
- end
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
- end
45
- end
46
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- module PaperTrail
6
- module Serializers
7
- # The default serializer for, e.g. `versions.object`.
8
- module YAML
9
- extend self # makes all instance methods become module methods as well
10
-
11
- def load(string)
12
- ::YAML.load string
13
- end
14
-
15
- # @param object (Hash | HashWithIndifferentAccess) - Coming from
16
- # `recordable_object` `object` will be a plain `Hash`. However, due to
17
- # recent [memory optimizations](https://git.io/fjeYv), when coming from
18
- # `recordable_object_changes`, it will be a `HashWithIndifferentAccess`.
19
- def dump(object)
20
- object = object.to_hash if object.is_a?(HashWithIndifferentAccess)
21
- ::YAML.dump object
22
- end
23
-
24
- # Returns a SQL LIKE condition to be used to match the given field and
25
- # value in the serialized object.
26
- def where_object_condition(arel_field, field, value)
27
- arel_field.matches("%\n#{field}: #{value}\n%")
28
- end
29
-
30
- # Returns a SQL LIKE condition to be used to match the given field and
31
- # value in the serialized `object_changes`.
32
- def where_object_changes_condition(*)
33
- raise <<-STR.squish.freeze
34
- where_object_changes no longer supports reading YAML from a text
35
- column. The old implementation was inaccurate, returning more records
36
- than you wanted. This feature was deprecated in 8.1.0 and removed in
37
- 9.0.0. The json and jsonb datatypes are still supported. See
38
- discussion at https://github.com/paper-trail-gem/paper_trail/pull/997
39
- STR
40
- end
41
- end
42
- end
43
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- module TypeSerializers
5
- # Provides an alternative method of serialization
6
- # and deserialization of PostgreSQL array columns.
7
- class PostgresArraySerializer
8
- def initialize(subtype, delimiter)
9
- @subtype = subtype
10
- @delimiter = delimiter
11
- end
12
-
13
- def serialize(array)
14
- return serialize_with_ar(array) if active_record_pre_502?
15
- array
16
- end
17
-
18
- def deserialize(array)
19
- return deserialize_with_ar(array) if active_record_pre_502?
20
-
21
- case array
22
- # Needed for legacy reasons. If serialized array is a string
23
- # then it was serialized with Rails < 5.0.2.
24
- when ::String then deserialize_with_ar(array)
25
- else array
26
- end
27
- end
28
-
29
- private
30
-
31
- def active_record_pre_502?
32
- ::ActiveRecord.gem_version < Gem::Version.new("5.0.2")
33
- end
34
-
35
- def serialize_with_ar(array)
36
- ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
37
- new(@subtype, @delimiter).
38
- serialize(array)
39
- end
40
-
41
- def deserialize_with_ar(array)
42
- ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.
43
- new(@subtype, @delimiter).
44
- deserialize(array)
45
- end
46
- end
47
- end
48
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module PaperTrail
4
- # The version number of the paper_trail gem. Not to be confused with
5
- # `PaperTrail::Version`. Ruby constants are case-sensitive, apparently,
6
- # and they are two different modules! It would be nice to remove `VERSION`,
7
- # because of this confusion, but it's not worth the breaking change.
8
- # People are encouraged to use `PaperTrail.gem_version` instead.
9
- module VERSION
10
- MAJOR = 10
11
- MINOR = 3
12
- TINY = 1
13
-
14
- # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
15
- PRE = nil
16
-
17
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
18
-
19
- def self.to_s
20
- STRING
21
- end
22
- end
23
- end