Skip to content

Commit

Permalink
Global configuration for :ignore_log_data option
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitryTsepelev committed Jan 31, 2019
1 parent ec7f3b6 commit 26ea844
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 64 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## master

- PR [#111](https://github.com/palkan/logidze/pull/111) Global configuration for `:ignore_log_data` option ([@dmitrytsepelev][])

Now it's possible to avoid loading `log_data` from the DB by default with

```ruby
Logidze.ignore_log_data_by_default = true
```

In cases when `ignore_log_data: false` is explicitly passed to the `ignore_log_data` the default setting is being overriden. Also, it's possible to change it inside the block:

```ruby
Logidze.with_log_data do
Post.find(params[:id]).log_data
end
```

## 0.9.0 (2018-11-28)

- PR [#98](https://github.com/palkan/logidze/pull/98) Add `:ignore_log_data` option to `#has_logidze` ([@dmitrytsepelev][])
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,21 @@ class User < ActiveRecord::Base
end
```

After that, each time you use `User.all` (or any other relation method) `log_data` won't be loaded from the DB.
If you want Logidze to always behave this way - you can set up a global configuration option:

```ruby
Rails.application.config.logidze.ignore_log_data_by_default = true
```

However, you can override it by explicitly passing `ignore_log_data: false` to the `ignore_log_data`. Also, it's possible to change it temporary inside the block:

```ruby
Logidze.with_log_data do
Post.find(params[:id]).log_data
end
```

When `ignore_log_data` is turned on, each time you use `User.all` (or any other relation method) `log_data` won't be loaded from the DB.

The chart below shows the difference in PG query time before and after turning `ignore_log_data` on. (Special thanks to [@aderyabin](https://github.com/aderyabin) for sharing it.)

Expand Down
47 changes: 36 additions & 11 deletions lib/logidze.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,48 @@ module Logidze
class << self
# Determines if Logidze should append a version to the log after updating an old version.
attr_accessor :append_on_undo

attr_writer :associations_versioning

def associations_versioning
@associations_versioning || false
end
end

# Temporary disable DB triggers.
#
# @example
# Logidze.without_logging { Post.update_all(active: true) }
def self.without_logging
ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute "SET LOCAL logidze.disabled TO on;"
res = yield
ActiveRecord::Base.connection.execute "SET LOCAL logidze.disabled TO DEFAULT;"
res
# Determines if Logidze should exclude log data from SELECT statements
attr_writer :ignore_log_data_by_default

def ignore_log_data_by_default
@ignore_log_data_by_default || false
end

attr_writer :force_load_log_data

def force_load_log_data
@force_load_log_data || false
end

# Temporary disable DB triggers.
#
# @example
# Logidze.without_logging { Post.update_all(active: true) }
def without_logging
ActiveRecord::Base.transaction do
ActiveRecord::Base.connection.execute "SET LOCAL logidze.disabled TO on;"
res = yield
ActiveRecord::Base.connection.execute "SET LOCAL logidze.disabled TO DEFAULT;"
res
end
end

# Temporary turn off ignore_log_data_by_default config
#
# @example
# Logidze.with_log_data { Post.update_all(active: true) }
def with_log_data
Logidze.force_load_log_data = true
yield
ensure
Logidze.force_load_log_data = false
end
end
end
14 changes: 12 additions & 2 deletions lib/logidze/has_logidze.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ module ClassMethods # :nodoc:
# Include methods to work with history.
#
# rubocop:disable Naming/PredicateName
def has_logidze(ignore_log_data: false)
def has_logidze(ignore_log_data: nil)
include Logidze::Model
include Logidze::IgnoreLogData if ignore_log_data
include Logidze::IgnoreLogData

@ignore_log_data = ignore_log_data
end

def ignores_log_data?
if @ignore_log_data.nil? && Logidze.ignore_log_data_by_default
!Logidze.force_load_log_data
else
@ignore_log_data
end
end
end
end
Expand Down
14 changes: 9 additions & 5 deletions lib/logidze/ignore_log_data.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

module Logidze
# Add `has_logidze` method to AR::Base
module IgnoreLogData
module IgnoreLogData # :nodoc:
extend ActiveSupport::Concern

included do
Expand All @@ -16,14 +15,19 @@ module IgnoreLogData

require "logidze/ignore_log_data/missing_attribute_patch"
include MissingAttributePatch

require "logidze/ignore_log_data/default_scope_patch"
include DefaultScopePatch
end

self.ignored_columns += ["log_data"]

scope :with_log_data, -> { select(column_names + ["log_data"]) }
end

module ClassMethods # :nodoc:
def with_log_data
select(column_names + ["log_data"])
class_methods do
def self.default_scope
ignores_log_data? ? super : super.with_log_data
end
end
end
Expand Down
25 changes: 25 additions & 0 deletions lib/logidze/ignore_log_data/default_scope_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Logidze
module IgnoreLogData
# Since Rails caches ignored_columns, we have to patch .column_names and
# .unscoped methods to make conditional log_data loading possible
module DefaultScopePatch
extend ActiveSupport::Concern

class_methods do
def column_names
ignores_log_data? ? super : super + ["log_data"]
end

def unscoped
if ignores_log_data?
super
else
block_given? ? with_log_data.scoping { yield } : with_log_data
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/logidze/ignore_log_data/ignored_columns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module Relation # :nodoc:
private

def build_select(arel)
if select_values.blank? && klass.ignored_columns.any?
if select_values.blank? && klass.ignored_columns.any? && ignores_log_data?
arel.project(*arel_columns(klass.column_names - klass.ignored_columns))
else
super
Expand Down
4 changes: 3 additions & 1 deletion lib/logidze/ignore_log_data/missing_attribute_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ module IgnoreLogData
# from Rails 4 - raise ActiveModel::MissingAttributeError
module MissingAttributePatch
def log_data
raise ActiveModel::MissingAttributeError if attributes["log_data"].nil?
if self.class.ignores_log_data? && attributes["log_data"].nil?
raise ActiveModel::MissingAttributeError
end

super
end
Expand Down
5 changes: 5 additions & 0 deletions spec/dummy/app/models/always_logged_post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AlwaysLoggedPost < ActiveRecord::Base
has_logidze ignore_log_data: false

self.table_name = "posts"
end
2 changes: 1 addition & 1 deletion spec/dummy/app/models/comment.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Comment < ActiveRecord::Base
has_logidze
has_logidze ignore_log_data: false
belongs_to :article
end
4 changes: 3 additions & 1 deletion spec/dummy/app/models/not_logged_post.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
class NotLoggedPost < Post
class NotLoggedPost < ActiveRecord::Base
has_logidze ignore_log_data: true

self.table_name = "posts"
end
106 changes: 65 additions & 41 deletions spec/integrations/ignore_log_data_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,69 +14,93 @@
# Close active connections to handle db variables
ActiveRecord::Base.connection_pool.disconnect!
end

Post.reset_column_information
# For Rails 4
Post.instance_variable_set(:@attribute_names, nil)
end

let(:user) { User.create! }
let!(:source_post) { Post.create!(user: user) }

shared_context "test #log_data" do
context "#log_data" do
it "raises error when log_data is called" do
expect { post.log_data }.to raise_error(ActiveModel::MissingAttributeError)
end
describe "#update!" do
it "updates log_data" do
expect do
NotLoggedPost.find(source_post.id).update!(title: "new")
end.to change { Post.find(source_post.id).log_data.version }.by(1)
end
end

shared_context "test #reload_log_data" do
context "#reload_log_data" do
it "loads data from DB" do
expect(post.reload_log_data).not_to be_nil
expect(post.log_data).not_to be_nil
describe "#log_data" do
shared_examples "test loads #log_data" do
it "loads log_data" do
expect(model.log_data).not_to be_nil
expect(model.log_data).to be_a(Logidze::History)
end
end

it "deserializes log_data properly" do
expect(post.reload_log_data).to be_a(Logidze::History)
shared_examples "test raises error when #log_data is called" do
it "raises error when #log_data is called" do
expect { model.reload.log_data }.to raise_error(ActiveModel::MissingAttributeError)
end
end
end

context "with .all" do
subject(:post) { NotLoggedPost.find(source_post.id) }
context "when Logidze.ignore_log_data_by_default = true" do
before(:all) { Logidze.ignore_log_data_by_default = true }
after(:all) { Logidze.ignore_log_data_by_default = false }

include_context "test #log_data"
include_context "test #reload_log_data"
end
subject(:model) { Post.create! }

context "with .with_log_data" do
subject(:post) { NotLoggedPost.with_log_data.find(source_post.id) }
include_examples "test raises error when #log_data is called"

context "#log_data" do
it "loads log_data" do
expect(post.log_data).not_to be_nil
context "when inside Logidze.with_log_data block" do
it "loads log_data" do
Logidze.with_log_data do
expect(model.reload.log_data).not_to be_nil
end
end
end
end

it "deserializes log_data properly" do
expect(post.log_data).to be_a(Logidze::History)
context "when model is configured with has_logidze(ignore_log_data: false)" do
context "when Logidze.ignore_log_data_by_default = true" do
before(:all) { Logidze.ignore_log_data_by_default = true }
after(:all) { Logidze.ignore_log_data_by_default = false }

subject(:model) { AlwaysLoggedPost.find(source_post.id) }
include_examples "test loads #log_data"
end
end
end

context "#update!" do
it "updates log_data" do
expect do
NotLoggedPost.find(source_post.id).update!(title: "new")
end.to change { Post.find(source_post.id).log_data.version }.by(1)
end
end
context "when model is configured with has_logidze(ignore_log_data: true)" do
shared_context "test #reload_log_data" do
context "#reload_log_data" do
it "loads data from DB" do
expect(model.reload_log_data).not_to be_nil
expect(model.log_data).not_to be_nil
end

it "deserializes log_data properly" do
expect(model.reload_log_data).to be_a(Logidze::History)
end
end
end

context "with default scope" do
subject(:model) { NotLoggedPost.find(source_post.id) }

context "with .eager_load" do
subject(:post) { User.eager_load(:not_logged_posts).last.not_logged_posts.last }
include_examples "test raises error when #log_data is called"
include_context "test #reload_log_data"
end

context ".with_log_data" do
subject(:model) { NotLoggedPost.with_log_data.find(source_post.id) }

include_examples "test loads #log_data"
end

include_context "test #log_data"
include_context "test #reload_log_data"
describe ".eager_load" do
subject(:model) { User.eager_load(:not_logged_posts).last.not_logged_posts.last }

include_examples "test raises error when #log_data is called"
include_context "test #reload_log_data"
end
end
end
end

0 comments on commit 26ea844

Please sign in to comment.