Skip to content
This repository was archived by the owner on Feb 28, 2023. It is now read-only.
Open
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ u = User.new
u.first_name = 'Philippe'
u.save

u.first_name_history
u.history_of(:first_name)
# => [
{
"attribute_value" => "Philippe",
Expand All @@ -70,7 +70,7 @@ u.first_name_history

u.first_name = 'Jean-Philippe'
u.save
u.first_name_history
u.history_of(:first_name)
# => [
{
"attribute_value" => "Jean-Philippe",
Expand All @@ -90,7 +90,7 @@ It is not possible to directly query attribute values since model attributes tra
To overcome this limitation, Historyable also exposes the raw `ActiveRecord` polymorphic relation.

```ruby
u.first_name_history_raw
u.raw_history_of(:first_name)
# => #<ActiveRecord::Relation [#<Change id: nil, object_attribute_value: "Jean-Philippe", created_at: "2013-08-20 16:20:10">], [#<Change id: nil, object_attribute_value: "Philippe", created_at: "2013-08-20 16:20:00">]>
```

Expand Down
49 changes: 39 additions & 10 deletions lib/historyable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def has_history(*attributes)
define_historyable_association(historyable)

# Instance methods
define_historyable_history_raw(historyable)
define_historyable_history # Should be defined once only

define_historyable_attribute_history_raw(historyable)
define_historyable_attribute_history(historyable)
define_historyable_attribute_history?(historyable)
Expand All @@ -51,33 +54,33 @@ def define_historyable_association(historyable)
dependent: :destroy
end

# attribute_history_raw
# raw_history_of
#
# @example
#
# @user.name_history_raw
# @user.raw_history_of(:name)
#
# @return [ActiveRecord::Relation]
def define_historyable_attribute_history_raw(historyable)
define_method("#{historyable.attribute_name.to_s}_history_raw") do
def define_historyable_history_raw(historyable)
define_method("raw_history_of") do |attribute_name|
send(historyable.association_name)
.where(object_attribute: historyable.attribute_name)
.where(object_attribute: attribute_name)
.order('created_at DESC')
.select([:object_attribute_value, :created_at])
end
end

# attribute_history
# history_of
#
# @example
#
# @user.name_history
# @user.history_of(:name)
#
# @return [Array]
def define_historyable_attribute_history(historyable)
define_method("#{historyable.attribute_name.to_s}_history") do
def define_historyable_history
define_method("history_of") do |attribute_name|
self.historyable_cache ||= Hash.new
historyable_cache[historyable.attribute_name] ||= send("#{historyable.attribute_name}_history_raw").inject([]) do |memo, record|
historyable_cache[attribute_name] ||= send("raw_history_of", attribute_name).inject([]) do |memo, record|
entry = Entry.new
entry.attribute_value = record.object_attribute_value
entry.changed_at = record.created_at
Expand All @@ -87,6 +90,32 @@ def define_historyable_attribute_history(historyable)
end
end

# attribute_history_raw
#
# @example
#
# @user.name_history_raw
#
# @return [ActiveRecord::Relation]
def define_historyable_attribute_history_raw(historyable)
define_method("#{historyable.attribute_name.to_s}_history_raw") do
send("raw_history_of", historyable.attribute_name)
end
end

# attribute_history
#
# @example
#
# @user.name_history
#
# @return [Array]
def define_historyable_attribute_history(historyable)
define_method("#{historyable.attribute_name.to_s}_history") do
send("history_of", historyable.attribute_name)
end
end

# attribute_history?
#
# @example
Expand Down
53 changes: 50 additions & 3 deletions spec/historyable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,47 @@ class Dog < ActiveRecord::Base
it { expect(dog.name_history?).to be_false }
end

describe :history_of do
it { expect(cat.history_of(:name)).to be_an_instance_of(Array) }
it { expect(cat.history_of(:name).first).to be_a_kind_of(Historyable::Entry) }
it { expect(cat.history_of(:name).first.attribute_value).to eq('Garfield') }
it { expect(dog.history_of(:name)).to be_an_instance_of(Array) }
it { expect(dog.history_of(:name)).to be_empty }

describe :Caching do

describe "creation" do
context "with a cold cache" do
it "hits the database" do
expect(cat).to receive(:raw_history_of).and_call_original
cat.history_of(:name)
end
end

context "with a warm cache" do
before { cat.history_of(:name) }

it "doesn't hit the database" do
expect(cat).not_to receive(:raw_history_of).and_call_original
cat.history_of(:name)
end
end
end

describe "expiration" do
before do
cat.history_of(:name)
cat.update_attribute(:name, 'Amadeus')
end

it "hits the database" do
expect(cat).to receive(:raw_history_of).and_call_original
cat.history_of(:name)
end
end
end
end

describe :name_history do
it { expect(cat.name_history).to be_an_instance_of(Array) }
it { expect(cat.name_history.first).to be_a_kind_of(Historyable::Entry) }
Expand All @@ -57,7 +98,7 @@ class Dog < ActiveRecord::Base
describe "creation" do
context "with a cold cache" do
it "hits the database" do
expect(cat).to receive(:name_history_raw).and_call_original
expect(cat).to receive(:raw_history_of).and_call_original
cat.name_history
end
end
Expand All @@ -66,7 +107,7 @@ class Dog < ActiveRecord::Base
before { cat.name_history }

it "doesn't hit the database" do
expect(cat).not_to receive(:name_history_raw).and_call_original
expect(cat).not_to receive(:raw_history_of).and_call_original
cat.name_history
end
end
Expand All @@ -79,13 +120,19 @@ class Dog < ActiveRecord::Base
end

it "hits the database" do
expect(cat).to receive(:name_history_raw).and_call_original
expect(cat).to receive(:raw_history_of).and_call_original
cat.name_history
end
end
end
end

describe :raw_history_of do
it { expect(cat.raw_history_of(:name)).to be_a_kind_of(ActiveRecord::Relation) }
it { expect(cat.raw_history_of(:name).first).to be_an_instance_of(Historyable::Change) }
it { expect(cat.raw_history_of(:name).first[:object_attribute_value]).to eq('Garfield') }
end

describe :name_history_raw do
it { expect(cat.name_history_raw).to be_a_kind_of(ActiveRecord::Relation) }
it { expect(cat.name_history_raw.first).to be_an_instance_of(Historyable::Change) }
Expand Down