Skip to content

Commit

Permalink
Add paper_trail.update_columns (#1037)
Browse files Browse the repository at this point in the history
* Add paper_trail.update_columns

Add paper_trail.update_columns so you can record a version when using
update_columns (which skips all callbacks, including the after_update
callback).

* Change recordable_object_changes to not have a default for the changes
argument so that we don't need an exception to
Style/MethodCallWithoutArgsParentheses

* Add back the duplication between record_update and
record_update_columns, at @jaredbeck's request

* - Add Changelog entry for `paper_trail.update_columns`
- Use a guard in `record_update_columns`
- Use Timecop.freeze so that we can guarantee that an expectation will pass
- Add some comments
  • Loading branch information
TylerRick authored and jaredbeck committed Jan 23, 2018
1 parent febd62a commit 3960d7f
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -26,6 +26,7 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).

### Added

- [#1037](https://github.com/airblade/paper_trail/pull/1037) Add `paper_trail.update_columns`
- [#961](https://github.com/airblade/paper_trail/issues/961) - Instead of
crashing when misconfigured Custom Version Classes are used, an error will be
raised earlier, with a much more helpful message.
Expand Down
58 changes: 54 additions & 4 deletions lib/paper_trail/record_trail.rb
Expand Up @@ -226,7 +226,7 @@ def data_for_create
data[:created_at] = @record.updated_at
end
if record_object_changes? && changed_notably?
data[:object_changes] = recordable_object_changes
data[:object_changes] = recordable_object_changes(changes)
end
add_transaction_id_to(data)
merge_metadata_into(data)
Expand Down Expand Up @@ -284,7 +284,7 @@ def record_update(force)
@in_after_callback = false
end

# Returns data for record update
# Returns data for record_update
# @api private
def data_for_update
data = {
Expand All @@ -296,7 +296,35 @@ def data_for_update
data[:created_at] = @record.updated_at
end
if record_object_changes?
data[:object_changes] = recordable_object_changes
data[:object_changes] = recordable_object_changes(changes)
end
add_transaction_id_to(data)
merge_metadata_into(data)
end

# @api private
def record_update_columns(changes)
return unless enabled?
versions_assoc = @record.send(@record.class.versions_association_name)
version = versions_assoc.create(data_for_update_columns(changes))
if version.errors.any?
log_version_errors(version, :update)
else
update_transaction_id(version)
save_associations(version)
end
end

# Returns data for record_update_columns
# @api private
def data_for_update_columns(changes)
data = {
event: @record.paper_trail_event || "update",
object: recordable_object,
whodunnit: PaperTrail.whodunnit
}
if record_object_changes?
data[:object_changes] = recordable_object_changes(changes)
end
add_transaction_id_to(data)
merge_metadata_into(data)
Expand All @@ -322,7 +350,7 @@ def recordable_object
# otherwise the column is a `text` column, and we must perform the
# serialization here, using `PaperTrail.serializer`.
# @api private
def recordable_object_changes
def recordable_object_changes(changes)
if @record.class.paper_trail.version_class.object_changes_col_is_json?
changes
else
Expand Down Expand Up @@ -406,6 +434,28 @@ def touch_with_version(name = nil)
@record.save!(validate: false)
end

# Like the `update_column` method from `ActiveRecord::Persistence`, but also
# creates a version to record those changes.
# @api public
def update_column(name, value)
update_columns(name => value)
end

# Like the `update_columns` method from `ActiveRecord::Persistence`, but also
# creates a version to record those changes.
# @api public
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

# Returns the object (not a Version) as it was at the given timestamp.
def version_at(timestamp, reify_options = {})
# Because a version stores how its object looked *before* the change,
Expand Down
9 changes: 9 additions & 0 deletions spec/models/on/empty_array_spec.rb
Expand Up @@ -21,6 +21,15 @@ module On
end
end

describe ".paper_trail.update_columns" do
it "creates a version record" do
widget = Widget.create
assert_equal 1, widget.versions.length
widget.paper_trail.update_columns(name: "Bugle")
assert_equal 2, widget.versions.length
end
end

describe "#update_attributes" do
it "does not create any version records" do
record = described_class.create(name: "Alice")
Expand Down
14 changes: 14 additions & 0 deletions spec/models/widget_spec.rb
Expand Up @@ -285,6 +285,20 @@
end
end

describe ".paper_trail.update_columns", versioning: true do
it "creates a version record" do
widget = Widget.create
expect(widget.versions.count).to eq(1)
Timecop.freeze Time.now do
widget.paper_trail.update_columns(name: "Bugle")
expect(widget.versions.count).to eq(2)
expect(widget.versions.last.event).to(eq("update"))
expect(widget.versions.last.changeset[:name]).to eq([nil, "Bugle"])
expect(widget.versions.last.created_at.to_i).to eq(Time.now.to_i)
end
end
end

describe "#update", versioning: true do
it "creates a version record" do
widget = Widget.create
Expand Down

0 comments on commit 3960d7f

Please sign in to comment.