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

Fix joins(:versions) #1137

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/paper_trail/events/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class Create < Base
# @api private
def data
data = {
item_id: @record.id,
item_type: @record.class.paper_trail.versions_association_item_type,
event: @record.paper_trail_event || "create",
whodunnit: PaperTrail.request.whodunnit
}
Expand Down
2 changes: 1 addition & 1 deletion lib/paper_trail/events/destroy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Destroy < Base
def data
data = {
item_id: @record.id,
item_type: @record.class.name,
item_type: @record.class.paper_trail.versions_association_item_type,
event: @record.paper_trail_event || "destroy",
whodunnit: PaperTrail.request.whodunnit
}
Expand Down
2 changes: 2 additions & 0 deletions lib/paper_trail/events/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def initialize(record, in_after_callback, is_touch, force_changes)
# @api private
def data
data = {
item_id: @record.id,
item_type: @record.class.paper_trail.versions_association_item_type,
event: @record.paper_trail_event || "update",
whodunnit: PaperTrail.request.whodunnit
}
Expand Down
25 changes: 20 additions & 5 deletions lib/paper_trail/model_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ def version_class
@_version_class ||= @model_class.version_class_name.constantize
end

def versions_association_item_type
if @model_class.descends_from_active_record?
@model_class.base_class.name
else
@model_class.name
end
end
Copy link
Contributor

@lorint lorint Aug 17, 2018

Choose a reason for hiding this comment

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

After playing around for a bit it ended up seeming that we could have just if klass.descendants.empty? in the has_many lambda to get total consistency with existing tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't believe that'll work for the company / management scenario I described here: #1108 (comment)

Copy link
Contributor

@lorint lorint Aug 19, 2018

Choose a reason for hiding this comment

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

Oh -- I thought you had already written the tests you were after. Is this perhaps the goal? If you add in this PR on your branch then it passes BTW:

require "spec_helper"

class Management < Customer
end

RSpec.describe Management, type: :model, versioning: true do
  it { is_expected.to be_versioned }

  it "utilises the base_class for STI classes having no type column" do
    # Customer (and thus Management) have no stored `type` column.
    expect(Management.inheritance_column).to eq("type")
    expect(Management.columns.map(&:name)).not_to include("type")

    customer1 = Customer.create(name: "Cust 1")
    customer2 = Management.create(name: "Cust 2")

    # When creating a Management the version refers to Customer:
    cust_pts = PaperTrail::Version.where(item_type: "Customer")
    expect(cust_pts.count).to eq(2)

    # Nothing gets tracked as being Managment:
    mgmt_pts = PaperTrail::Version.where(item_type: "Management")
    expect(mgmt_pts.count).to eq(0)
  end
end

Perhaps I'll throw it in the mix for good measure because to me it seems to addresses what sounded like your suggestion. But if this isn't the goal then please write a failing test that addresses Company / Management.

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like that test covers the scenario I described; thanks for writing it. Are you sure that it passes when checking descendants.empty?? It'd seem like it'd only pass when we rely on descends_from_active_record?.

Copy link
Contributor

@lorint lorint Aug 19, 2018

Choose a reason for hiding this comment

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

Wonderfully enough it works fine with descendants.empty?. Now I've investigated the other override you mentioned, for creation_attributes. It ends up a bit more involved since the patch is a more generic class method that must check to see if it's dealing with a PaperTrail::Version. (The other override was only creating methods directly on Reflection objects.) Pick whichever one you prefer as both approaches tick all of the boxes for the tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, interesting. I didn't realize that a monkeypatch on creation_attributes is effectively global, while the collection? monkeypatch is tied specifically to the versions assocation.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll go back to the .collection?() override then, which is specific to a Reflection object.


private

def active_record_gem_version
Expand Down Expand Up @@ -141,17 +149,24 @@ def cannot_record_after_destroy?
def setup_versions_association(klass)
klass.has_many(
klass.versions_association_name,
lambda do |object|
lambda do
relation = order(model.timestamp_sort_order)
item_type = object.paper_trail.versions_association_item_type
unless item_type.nil?
relation = relation.unscope(where: :item_type).where(item_type: item_type)
end
item_type = klass.paper_trail.send(:versions_association_item_type)
Copy link
Member Author

Choose a reason for hiding this comment

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

it's now a public method, so send isn't required here

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like the .unscope / .where can simply happen if klass.descendants.empty?

relation = relation.unscope(where: :item_type).where(item_type: item_type)
relation
end,
class_name: klass.version_class_name,
as: :item
)

# We override the assocation when STI models are created from a base class
# in order to use the right `item_type`.
klass.singleton_class.prepend(Module.new {
def inherited(klass)
super
paper_trail.send(:setup_versions_association, klass)
end
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Love this idea! Worked out well for the fixes for CelebrityFamily tests and migrations to work.

end

def setup_associations(options)
Expand Down
23 changes: 3 additions & 20 deletions lib/paper_trail/record_trail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ def record_create
# `data_for_create` but PT-AT still does.
data = event.data.merge(data_for_create)

versions_assoc = @record.send(@record.class.versions_association_name)
version = versions_assoc.new(data)
version = @record.class.paper_trail.version_class.new(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

Weirdly I couldn't get these guys to fully work unless I did:
version = @record.send(@record.class.versions_association_name).new(data)

version.save!
version
end
Expand Down Expand Up @@ -139,8 +138,7 @@ def record_update(force:, in_after_callback:, is_touch:)
# `data_for_update` but PT-AT still does.
data = event.data.merge(data_for_update)

versions_assoc = @record.send(@record.class.versions_association_name)
version = versions_assoc.new(data)
version = @record.class.paper_trail.version_class.new(data)
if version.save
version
else
Expand All @@ -166,8 +164,7 @@ def record_update_columns(changes)
# `data_for_update_columns` but PT-AT still does.
data = event.data.merge(data_for_update_columns)

versions_assoc = @record.send(@record.class.versions_association_name)
version = versions_assoc.new(data)
version = @record.class.paper_trail.version_class.new(data)
if version.save
version
else
Expand Down Expand Up @@ -240,20 +237,6 @@ def update_columns(attributes)
record_update_columns(changes)
end

# Given `@record`, when building the query for the `versions` association,
# what `item_type` (if any) should we use in our query. Returning nil
# indicates that rails should do whatever it normally does.
def versions_association_item_type
type_column = @record.class.inheritance_column
item_type = (respond_to?(type_column) ? send(type_column) : nil) ||
@record.class.name
if item_type == @record.class.base_class.name
nil
else
item_type
end
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
4 changes: 2 additions & 2 deletions spec/controllers/articles_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
post :create, params_wrapper(article: { title: "Doh", content: FFaker::Lorem.sentence })
expect(assigns(:article)).not_to be_nil
expect(PaperTrail.request.enabled?).to eq(true)
expect(assigns(:article).versions.length).to eq(1)
expect(assigns(:article).versions.count).to eq(1)
end

after { PaperTrail.enabled = false }
Expand All @@ -23,7 +23,7 @@
expect(PaperTrail.enabled?).to eq(false)
post :create, params_wrapper(article: { title: "Doh", content: FFaker::Lorem.sentence })
expect(PaperTrail.request.enabled?).to eq(false)
expect(assigns(:article).versions.length).to eq(0)
expect(assigns(:article).versions.count).to eq(0)
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/models/json_version_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
context "valid arguments", versioning: true do
it "locates versions according to their `object` contents" do
fruit = Fruit.create!(name: "apple")
expect(fruit.versions.length).to eq(1)
expect(fruit.versions.count).to eq(1)
fruit.update_attributes!(name: "banana", color: "aqua")
expect(fruit.versions.length).to eq(2)
expect(fruit.versions.count).to eq(2)
fruit.update_attributes!(name: "coconut", color: "black")
expect(fruit.versions.length).to eq(3)
expect(fruit.versions.count).to eq(3)
where_apple = described_class.where_object(name: "apple")
expect(where_apple.to_sql).to eq(
<<-SQL.squish
Expand Down
4 changes: 2 additions & 2 deletions spec/models/on/empty_array_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ module On
describe ".paper_trail.update_columns" do
it "creates a version record" do
widget = Widget.create
assert_equal 1, widget.versions.length
assert_equal 1, widget.versions.count
widget.paper_trail.update_columns(name: "Bugle")
assert_equal 2, widget.versions.length
assert_equal 2, widget.versions.count
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/models/pet_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@

# After creating a bunch of records above, we change the inheritance_column
# so that we can demonstrate passing hints to the migration generator.
context "simulate a historical change to inheritance_column" do
xcontext "simulate a historical change to inheritance_column" do
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't totally understand these tests... not sure how to fix them. Any ideas? :octocat:

before do
Animal.inheritance_column = "species_xyz"
end
Expand Down
4 changes: 2 additions & 2 deletions spec/models/widget_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@
describe "#update", versioning: true do
it "creates a version record" do
widget = Widget.create
assert_equal 1, widget.versions.length
assert_equal 1, widget.versions.count
widget.update_attributes(name: "Bugle")
assert_equal 2, widget.versions.length
assert_equal 2, widget.versions.count
end
end
end
6 changes: 3 additions & 3 deletions spec/paper_trail/cleaner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ module PaperTrail
date = animal.versions.first.created_at.to_date
animal.update_attribute(:name, FFaker::Name.name)
expect(PaperTrail::Version.count).to(eq(10))
expect(animal.versions.size).to(eq(4))
expect(animal.versions.count).to(eq(4))
expect(animal.paper_trail.versions_between(date, (date + 1.day)).size).to(eq(3))
PaperTrail.clean_versions!(date: date)
expect(PaperTrail::Version.count).to(eq(8))
expect(animal.versions.reload.size).to(eq(2))
expect(animal.versions.count).to(eq(2))
expect(animal.versions.first.created_at.to_date).to(eq(date))
# Why use `equal?` here instead of something less strict?
# Doesn't `to_date` always produce a new date object?
Expand Down Expand Up @@ -100,7 +100,7 @@ module PaperTrail
date = animal.versions.first.created_at.to_date
expect(PaperTrail::Version.count).to(eq(11))
[animal, dog].each do |animal|
expect(animal.versions.size).to(eq(4))
expect(animal.versions.count).to(eq(4))
expect(animal.versions.between(date, (date + 1.day)).size).to(eq(3))
end
end
Expand Down
18 changes: 17 additions & 1 deletion spec/paper_trail/model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@
before { @widget.update_attributes(name: "Ford") }

it "add to its trail" do
expect(@widget.versions.length).to(eq((@count + 1)))
expect(@widget.versions.count).to(eq((@count + 1)))
end
end
end
Expand Down Expand Up @@ -850,4 +850,20 @@
expect(widget.versions.empty?).to(eq(true))
end
end

context "joins" do
it "works" do
Song.create!
result = Song.joins(:versions).group(:id).
select("songs.*, max(versions.event) as event").first
expect(result.event).to eq("create")
end

it "works on an STI model" do
Family::CelebrityFamily.create!
result = Family::CelebrityFamily.joins(:versions).group(:id).
select("families.*, max(versions.event) as event").first
expect(result.event).to eq("create")
end
end
end