Class: PaperTrail::RecordTrail
- Inherits:
-
Object
- Object
- PaperTrail::RecordTrail
- Defined in:
- lib/paper_trail/record_trail.rb
Overview
Represents the “paper trail” for a single record.
Instance Method Summary collapse
-
#clear_rolled_back_versions ⇒ Object
Invoked after rollbacks to ensure versions records are not created for changes that never actually took place.
-
#clear_version_instance ⇒ Object
Invoked via`after_update` callback for when a previous version is reified and then saved.
-
#initialize(record) ⇒ RecordTrail
constructor
A new instance of RecordTrail.
-
#live? ⇒ Boolean
Returns true if this instance is the current, live one; returns false if this instance came from a previous version.
-
#next_version ⇒ Object
Returns the object (not a Version) as it became next.
-
#originator ⇒ Object
Returns who put ‘@record` into its current state.
-
#previous_version ⇒ Object
Returns the object (not a Version) as it was most recently.
- #record_create ⇒ Object
-
#record_destroy(recording_order) ⇒ Object
private
‘recording_order` is “after” or “before”.
-
#record_update(force:, in_after_callback:, is_touch:) ⇒ Object
private
paper_trail-association_tracking.
-
#reset_timestamp_attrs_for_update_if_needed ⇒ Object
Invoked via callback when a user attempts to persist a reified ‘Version`.
-
#save_version? ⇒ Boolean
private
AR callback.
-
#save_with_version(in_after_callback: false, **options) ⇒ Object
Save, and create a version record regardless of options such as ‘:on`, `:if`, or `:unless`.
- #source_version ⇒ Object
-
#update_column(name, value) ⇒ Object
Like the ‘update_column` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
-
#update_columns(attributes) ⇒ Object
Like the ‘update_columns` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
-
#version_at(timestamp, reify_options = {}) ⇒ Object
Returns the object (not a Version) as it was at the given timestamp.
-
#versions_between(start_time, end_time) ⇒ Object
Returns the objects (not Versions) as they were between the given times.
Constructor Details
#initialize(record) ⇒ RecordTrail
Returns a new instance of RecordTrail.
10 11 12 |
# File 'lib/paper_trail/record_trail.rb', line 10 def initialize(record) @record = record end |
Instance Method Details
#clear_rolled_back_versions ⇒ Object
Invoked after rollbacks to ensure versions records are not created for changes that never actually took place. Optimization: Use lazy ‘reset` instead of eager `reload` because, in many use cases, the association will not be used.
18 19 20 |
# File 'lib/paper_trail/record_trail.rb', line 18 def clear_rolled_back_versions versions.reset end |
#clear_version_instance ⇒ Object
Invoked via`after_update` callback for when a previous version is reified and then saved.
24 25 26 |
# File 'lib/paper_trail/record_trail.rb', line 24 def clear_version_instance @record.send("#{@record.class.version_association_name}=", nil) end |
#live? ⇒ Boolean
Returns true if this instance is the current, live one; returns false if this instance came from a previous version.
30 31 32 |
# File 'lib/paper_trail/record_trail.rb', line 30 def live? source_version.nil? end |
#next_version ⇒ Object
Returns the object (not a Version) as it became next. NOTE: if self (the item) was not reified from a version, i.e. it is the
"live" item, we return nil. Perhaps we should return self instead?
37 38 39 40 41 42 |
# File 'lib/paper_trail/record_trail.rb', line 37 def next_version subsequent_version = source_version.next subsequent_version ? subsequent_version.reify : @record.class.find(@record.id) rescue StandardError # TODO: Rescue something more specific nil end |
#originator ⇒ Object
Returns who put ‘@record` into its current state.
47 48 49 |
# File 'lib/paper_trail/record_trail.rb', line 47 def originator (source_version || versions.last).try(:whodunnit) end |
#previous_version ⇒ Object
Returns the object (not a Version) as it was most recently.
54 55 56 |
# File 'lib/paper_trail/record_trail.rb', line 54 def previous_version (source_version ? source_version.previous : versions.last).try(:reify) end |
#record_create ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/paper_trail/record_trail.rb', line 58 def record_create return unless enabled? build_version_on_create(in_after_callback: true).tap do |version| version.save! # Because the version object was created using version_class.new instead # of versions_assoc.build?, the association cache is unaware. So, we # invalidate the `versions` association cache with `reset`. versions.reset rescue StandardError => e handle_version_errors e, version, :create end end |
#record_destroy(recording_order) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
‘recording_order` is “after” or “before”. See ModelConfig#on_destroy.
paper_trail-association_tracking
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/paper_trail/record_trail.rb', line 77 def record_destroy(recording_order) return unless enabled? && !@record.new_record? in_after_callback = recording_order == "after" event = Events::Destroy.new(@record, in_after_callback) # Merge data from `Event` with data from PT-AT. We no longer use # `data_for_destroy` but PT-AT still does. data = event.data.merge(data_for_destroy) version = @record.class.paper_trail.version_class.new(data) begin version.save! assign_and_reset_version_association(version) version rescue StandardError => e handle_version_errors e, version, :destroy end end |
#record_update(force:, in_after_callback:, is_touch:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
paper_trail-association_tracking
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/paper_trail/record_trail.rb', line 104 def record_update(force:, in_after_callback:, is_touch:) return unless enabled? version = build_version_on_update( force: force, in_after_callback: in_after_callback, is_touch: is_touch ) return unless version begin version.save! # Because the version object was created using version_class.new instead # of versions_assoc.build?, the association cache is unaware. So, we # invalidate the `versions` association cache with `reset`. versions.reset version rescue StandardError => e handle_version_errors e, version, :update end end |
#reset_timestamp_attrs_for_update_if_needed ⇒ Object
Invoked via callback when a user attempts to persist a reified ‘Version`.
128 129 130 131 132 133 |
# File 'lib/paper_trail/record_trail.rb', line 128 def return if live? @record.send(:timestamp_attributes_for_update_in_model).each do |column| @record.send("restore_#{column}!") end end |
#save_version? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
AR callback.
137 138 139 140 141 |
# File 'lib/paper_trail/record_trail.rb', line 137 def save_version? if_condition = @record.[:if] unless_condition = @record.[:unless] (if_condition.blank? || if_condition.call(@record)) && !unless_condition.try(:call, @record) end |
#save_with_version(in_after_callback: false, **options) ⇒ Object
Save, and create a version record regardless of options such as ‘:on`, `:if`, or `:unless`.
‘in_after_callback`: Indicates if this method is being called within an
`after` callback. Defaults to `false`.
‘options`: Optional arguments passed to `save`.
This is an “update” event. That is, we record the same data we would in the case of a normal AR ‘update`.
156 157 158 159 160 161 |
# File 'lib/paper_trail/record_trail.rb', line 156 def save_with_version(in_after_callback: false, **) ::PaperTrail.request(enabled: false) do @record.save(**) end record_update(force: true, in_after_callback: in_after_callback, is_touch: false) end |
#source_version ⇒ Object
143 144 145 |
# File 'lib/paper_trail/record_trail.rb', line 143 def source_version version end |
#update_column(name, value) ⇒ Object
Like the ‘update_column` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
166 167 168 |
# File 'lib/paper_trail/record_trail.rb', line 166 def update_column(name, value) update_columns(name => value) end |
#update_columns(attributes) ⇒ Object
Like the ‘update_columns` method from `ActiveRecord::Persistence`, but also creates a version to record those changes.
173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/paper_trail/record_trail.rb', line 173 def update_columns(attributes) # `@record.update_columns` skips dirty-tracking, so we can't just use # `@record.changes` or @record.saved_changes` from `ActiveModel::Dirty`. # We need to build our own hash with the changes that will be made # directly to the database. changes = {} attributes.each do |k, v| changes[k] = [@record[k], v] end @record.update_columns(attributes) record_update_columns(changes) end |
#version_at(timestamp, reify_options = {}) ⇒ Object
Returns the object (not a Version) as it was at the given timestamp.
187 188 189 190 191 192 193 |
# File 'lib/paper_trail/record_trail.rb', line 187 def version_at(, = {}) # Because a version stores how its object looked *before* the change, # we need to look for the first version created *after* the timestamp. v = versions.subsequent(, true).first return v.reify() if v @record unless @record.destroyed? end |
#versions_between(start_time, end_time) ⇒ Object
Returns the objects (not Versions) as they were between the given times.
196 197 198 199 |
# File 'lib/paper_trail/record_trail.rb', line 196 def versions_between(start_time, end_time) versions = send(@record.class.versions_association_name).between(start_time, end_time) versions.collect { |version| version_at(version.created_at) } end |