Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add paper_trail.update_columns #1037

Merged
merged 5 commits into from Jan 23, 2018

Conversation

TylerRick
Copy link
Contributor

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

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

Hi Tyler. I don't have time to review this right now, but a few quick thoughts:

  • Please don't change linter config
  • Can this be done without a method extraction, or was that necessary because of AbcSize cop? Generally, if you can put refactoring in a separate PR, it makes the review easier.

Sorry, that's not much of a review, but it's all I have time for right now.

argument so that we don't need an exception to
Style/MethodCallWithoutArgsParentheses
@TylerRick
Copy link
Contributor Author

TylerRick commented Jan 17, 2018

Can this be done without a method extraction, or was that necessary because of AbcSize cop?

As you wish. I've added back the duplication. No, it was only necessary to allow reuse without duplication.

Copy link
Member

@jaredbeck jaredbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is close, nice work. Please also add an entry to the changelog under Unreleased -> Added.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't guarantee that this expectation will pass. The operation could take more than a second, or cross a second-boundary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True... should I just use a Timecop.freeze to keep Time.now fixed? (That's what I did.)

Looks like there's at least one other expectation with the same problem:

        expect(widget.paper_trail.version_at(Time.now)).to be_nil

expect(widget.versions.count).to eq(1)
Timecop.travel 1.second.since # because MySQL lacks fractional seconds precision
widget.paper_trail.update_columns(name: "Bugle")
# widget.update_attributes(name: "Bugle")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please delete all commented-out code.

# widget.update_attributes(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"])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, detailed test.

widget = Widget.create
assert_equal 1, widget.versions.length
widget.paper_trail.update_columns(name: "Bugle")
assert_equal 2, widget.versions.length
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI, there is a nice way to "expect a change" in RSpec. You don't have to use it, what you have is fine, just thought I'd share.

expect {
  widget.paper_trail.update_columns(name: "Bugle")
}.to(change { widget.versions.length }).from(1).to(2)

end

def record_update_columns(changes)
if enabled?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer a "guard clause" here.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job including details like this error logging, and the enabled? check. 👍

object: recordable_object,
whodunnit: PaperTrail.whodunnit
}
data[:created_at] = Time.now
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to set created_at? Won't AR do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember. I think it may have been necessary in an earlier version I had but it appears to work fine without it, so I'll remove it. Good catch.

changes = {}
attributes.each do |k, v|
changes[k] = [@record[k], v]
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment somewhere here explaining that we are constructing a hash that is compatible with the hash returned by ActiveModel::Dirty#changes.

@TylerRick
Copy link
Contributor Author

Thanks for the detailed review, @jaredbeck. I've pushed up another commit to fix the things you suggested.

- Use a guard in `record_update_columns`
- Use Timecop.freeze so that we can guarantee that an expectation will pass
- Add some comments
Copy link
Member

@jaredbeck jaredbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I think we can merge this as is, but maybe we should follow up with some documentation in the readme also, or else people will not know this feature exists.

@jaredbeck jaredbeck merged commit 3960d7f into paper-trail-gem:master Jan 23, 2018
@jaredbeck
Copy link
Member

Merged. Thanks for your contribution, Tyler!

.. maybe we should follow up with some documentation in the readme also, or else people will not know this feature exists.

Please follow up with a docs PR, so other people can discover this feature. Thanks!

@Jakanapes
Copy link

This is excellent, we were just looking at how to solve this issue cleanly.

@thejosk
Copy link

thejosk commented Feb 12, 2018

@jaredbeck Can you please provide documentation for this?

@jaredbeck
Copy link
Member

@jaredbeck Can you please provide documentation for this?

As I said above, contributions of documentation are welcome, thanks!

@TylerRick TylerRick deleted the add_record_update_columns branch February 15, 2018 17:15
aried3r pushed a commit to aried3r/paper_trail that referenced this pull request Dec 14, 2020
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants