Skip to content

Commit dfb34e0

Browse files
committed
Avoid overriding user-defined columns cache methods
Previously, ActsAsTaggableOn::Taggable::Cache intercepts calls to columns by using instance_eval. Unfortunately, if the user had defined columns before invoking acts-as-taggable-on, their method would be overridden. This commit ensures the intercept happens as part of the inheritance ancestor walk-up, before any user-defined methods.
1 parent 0f54d1e commit dfb34e0

File tree

6 files changed

+57
-35
lines changed

6 files changed

+57
-35
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Each change should fall into categories that would affect whether the release is
55
As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch. And _misc_ is either minor or patch, the difference being kind of fuzzy for the purposes of history. Adding tests would be patch level.
66

77
### [Master / Unreleased](https://github.com/mbleigh/acts-as-taggable-on/compare/v6.0.1...master)
8+
* Fixes
9+
* [@tonyta Avoid overriding user-defined columns cache methods](https://github.com/mbleigh/acts-as-taggable-on/pull/911)
810
* Misc
911
* [@gssbzn Remove legacy code for an empty query and replace it with ` ActiveRecord::none`](https://github.com/mbleigh/acts-as-taggable-on/pull/906)
1012

lib/acts_as_taggable_on/taggable/cache.rb

+38-34
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,51 @@ module Cache
33
def self.included(base)
44
# When included, conditionally adds tag caching methods when the model
55
# has any "cached_#{tag_type}_list" column
6-
base.instance_eval do
7-
# @private
8-
def _has_tags_cache_columns?(db_columns)
9-
db_column_names = db_columns.map(&:name)
10-
tag_types.any? do |context|
11-
db_column_names.include?("cached_#{context.to_s.singularize}_list")
12-
end
6+
base.extend Columns
7+
end
8+
9+
module Columns
10+
# ActiveRecord::Base.columns makes a database connection and caches the
11+
# calculated columns hash for the record as @columns. Since we don't
12+
# want to add caching methods until we confirm the presence of a
13+
# caching column, and we don't want to force opening a database
14+
# connection when the class is loaded, here we intercept and cache
15+
# the call to :columns as @acts_as_taggable_on_cache_columns
16+
# to mimic the underlying behavior. While processing this first
17+
# call to columns, we do the caching column check and dynamically add
18+
# the class and instance methods
19+
# FIXME: this method cannot compile in rubinius
20+
def columns
21+
@acts_as_taggable_on_cache_columns ||= begin
22+
db_columns = super
23+
_add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
24+
db_columns
1325
end
26+
end
1427

15-
# @private
16-
def _add_tags_caching_methods
17-
send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
18-
extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
28+
def reset_column_information
29+
super
30+
@acts_as_taggable_on_cache_columns = nil
31+
end
1932

20-
before_save :save_cached_tag_list
33+
private
2134

22-
initialize_tags_cache
35+
# @private
36+
def _has_tags_cache_columns?(db_columns)
37+
db_column_names = db_columns.map(&:name)
38+
tag_types.any? do |context|
39+
db_column_names.include?("cached_#{context.to_s.singularize}_list")
2340
end
41+
end
2442

25-
# ActiveRecord::Base.columns makes a database connection and caches the
26-
# calculated columns hash for the record as @columns. Since we don't
27-
# want to add caching methods until we confirm the presence of a
28-
# caching column, and we don't want to force opening a database
29-
# connection when the class is loaded, here we intercept and cache
30-
# the call to :columns as @acts_as_taggable_on_cache_columns
31-
# to mimic the underlying behavior. While processing this first
32-
# call to columns, we do the caching column check and dynamically add
33-
# the class and instance methods
34-
# FIXME: this method cannot compile in rubinius
35-
def columns
36-
@acts_as_taggable_on_cache_columns ||= begin
37-
db_columns = super
38-
_add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
39-
db_columns
40-
end
41-
end
43+
# @private
44+
def _add_tags_caching_methods
45+
send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
46+
extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
4247

43-
def reset_column_information
44-
super
45-
@acts_as_taggable_on_cache_columns = nil
46-
end
48+
before_save :save_cached_tag_list
49+
50+
initialize_tags_cache
4751
end
4852
end
4953

spec/acts_as_taggable_on/caching_spec.rb

+6
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@
7575
CachedModel.reset_column_information
7676
expect(CachedModel.instance_variable_get(:@acts_as_taggable_on_cache_columns)).to eql(nil)
7777
end
78+
79+
it 'should not override a user-defined columns method' do
80+
expect(ColumnsOverrideModel.columns.map(&:name)).not_to include('ignored_column')
81+
ColumnsOverrideModel.acts_as_taggable
82+
expect(ColumnsOverrideModel.columns.map(&:name)).not_to include('ignored_column')
83+
end
7884
end
7985

8086
describe 'with a custom delimiter' do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class ColumnsOverrideModel < ActiveRecord::Base
2+
def self.columns
3+
super.reject { |c| c.name == 'ignored_column' }
4+
end
5+
end

spec/internal/db/schema.rb

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
t.column :type, :string
3737
end
3838

39+
create_table :columns_override_models, force: true do |t|
40+
t.column :name, :string
41+
t.column :type, :string
42+
t.column :ignored_column, :string
43+
end
44+
3945
create_table :non_standard_id_taggable_models, primary_key: 'an_id', force: true do |t|
4046
t.column :name, :string
4147
t.column :type, :string

spec/spec_helper.rb

-1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,3 @@
1818
RSpec.configure do |config|
1919
config.raise_errors_for_deprecations!
2020
end
21-

0 commit comments

Comments
 (0)