diff --git a/README.md b/README.md index 3be4713..07e0907 100644 --- a/README.md +++ b/README.md @@ -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", @@ -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", @@ -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) # => #], [#]> ``` diff --git a/lib/historyable.rb b/lib/historyable.rb index 169eac8..5b24d7f 100644 --- a/lib/historyable.rb +++ b/lib/historyable.rb @@ -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) @@ -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 @@ -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 diff --git a/spec/historyable_spec.rb b/spec/historyable_spec.rb index 325b197..fc3da58 100644 --- a/spec/historyable_spec.rb +++ b/spec/historyable_spec.rb @@ -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) } @@ -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 @@ -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 @@ -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) }