acts-as-taggable-on 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +4 -11
- data/Appraisals +7 -13
- data/CHANGELOG.md +82 -0
- data/CONTRIBUTING.md +13 -0
- data/Gemfile +1 -1
- data/README.md +42 -10
- data/acts-as-taggable-on.gemspec +2 -2
- data/db/migrate/1_acts_as_taggable_on_migration.rb +6 -1
- data/db/migrate/2_add_missing_unique_indices.rb +6 -1
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +6 -1
- data/db/migrate/4_add_missing_taggable_index.rb +6 -1
- data/db/migrate/5_change_collation_for_tag_names.rb +6 -1
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
- data/gemfiles/activerecord_4.2.gemfile +3 -3
- data/gemfiles/activerecord_5.0.gemfile +3 -3
- data/gemfiles/{activerecord_4.0.gemfile → activerecord_5.1.gemfile} +3 -4
- data/lib/acts_as_taggable_on/tag.rb +10 -7
- data/lib/acts_as_taggable_on/tag_list.rb +1 -0
- data/lib/acts_as_taggable_on/taggable/core.rb +10 -157
- data/lib/acts_as_taggable_on/taggable/ownership.rb +16 -5
- data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +113 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +75 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
- data/lib/acts_as_taggable_on/tagger.rb +2 -2
- data/lib/acts_as_taggable_on/tagging.rb +3 -2
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/spec/acts_as_taggable_on/caching_spec.rb +18 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +16 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +1 -1
- data/spec/internal/db/schema.rb +3 -0
- metadata +13 -9
- data/db/migrate/6_add_missing_indexes.rb +0 -12
- data/gemfiles/activerecord_4.1.gemfile +0 -16
@@ -0,0 +1,82 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable::TaggedWithQuery
|
2
|
+
class ExcludeTagsQuery < QueryBase
|
3
|
+
def build
|
4
|
+
taggable_model.joins(owning_to_tagger)
|
5
|
+
.where(tags_not_in_list)
|
6
|
+
.having(tags_that_matches_count)
|
7
|
+
.readonly(false)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def tags_not_in_list
|
13
|
+
return taggable_arel_table[:id].not_in(
|
14
|
+
tagging_arel_table
|
15
|
+
.project(tagging_arel_table[:taggable_id])
|
16
|
+
.join(tag_arel_table)
|
17
|
+
.on(
|
18
|
+
tagging_arel_table[:tag_id].eq(tag_arel_table[:id])
|
19
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
20
|
+
.and(tags_match_type)
|
21
|
+
)
|
22
|
+
)
|
23
|
+
|
24
|
+
# FIXME: missing time scope, this is also missing in the original implementation
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def owning_to_tagger
|
29
|
+
return [] unless options[:owned_by].present?
|
30
|
+
|
31
|
+
owner = options[:owned_by]
|
32
|
+
|
33
|
+
arel_join = taggable_arel_table
|
34
|
+
.join(tagging_arel_table)
|
35
|
+
.on(
|
36
|
+
tagging_arel_table[:tagger_id].eq(owner.id)
|
37
|
+
.and(tagging_arel_table[:tagger_type].eq(owner.class.base_class.to_s))
|
38
|
+
.and(tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key]))
|
39
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
40
|
+
)
|
41
|
+
|
42
|
+
if options[:match_all].present?
|
43
|
+
arel_join = arel_join
|
44
|
+
.join(tagging_arel_table, Arel::Nodes::OuterJoin)
|
45
|
+
.on(
|
46
|
+
match_all_on_conditions
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
return arel_join.join_sources
|
51
|
+
end
|
52
|
+
|
53
|
+
def match_all_on_conditions
|
54
|
+
on_condition = tagging_arel_table[:taggable_id].eq(taggable_arel_table[taggable_model.primary_key])
|
55
|
+
.and(tagging_arel_table[:taggable_type].eq(taggable_model.base_class.name))
|
56
|
+
|
57
|
+
if options[:start_at].present?
|
58
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].gteq(options[:start_at]))
|
59
|
+
end
|
60
|
+
|
61
|
+
if options[:end_at].present?
|
62
|
+
on_condition = on_condition.and(tagging_arel_table[:created_at].lteq(options[:end_at]))
|
63
|
+
end
|
64
|
+
|
65
|
+
if options[:on].present?
|
66
|
+
on_condition = on_condition.and(tagging_arel_table[:context].lteq(options[:on]))
|
67
|
+
end
|
68
|
+
|
69
|
+
on_condition
|
70
|
+
end
|
71
|
+
|
72
|
+
def tags_that_matches_count
|
73
|
+
return [] unless options[:match_all].present?
|
74
|
+
|
75
|
+
taggable_model.find_by_sql(tag_arel_table.project(Arel.star.count).where(tags_match_type).to_sql)
|
76
|
+
|
77
|
+
tagging_arel_table[:taggable_id].count.eq(
|
78
|
+
tag_arel_table.project(Arel.star.count).where(tags_match_type)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ActsAsTaggableOn::Taggable::TaggedWithQuery
|
2
|
+
class QueryBase
|
3
|
+
def initialize(taggable_model, tag_model, tagging_model, tag_list, options)
|
4
|
+
@taggable_model = taggable_model
|
5
|
+
@tag_model = tag_model
|
6
|
+
@tagging_model = tagging_model
|
7
|
+
@tag_list = tag_list
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :taggable_model, :tag_model, :tagging_model, :tag_list, :options
|
14
|
+
|
15
|
+
def taggable_arel_table
|
16
|
+
@taggable_arel_table ||= taggable_model.arel_table
|
17
|
+
end
|
18
|
+
|
19
|
+
def tag_arel_table
|
20
|
+
@tag_arel_table ||= tag_model.arel_table
|
21
|
+
end
|
22
|
+
|
23
|
+
def tagging_arel_table
|
24
|
+
@tagging_arel_table ||=tagging_model.arel_table
|
25
|
+
end
|
26
|
+
|
27
|
+
def tag_match_type(tag)
|
28
|
+
matches_attribute = tag_arel_table[:name]
|
29
|
+
matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
|
30
|
+
|
31
|
+
if options[:wild].present?
|
32
|
+
tag_arel_table[:name].matches("%#{escaped_tag(tag)}%", "!")
|
33
|
+
else
|
34
|
+
tag_arel_table[:name].matches(escaped_tag(tag), "!")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def tags_match_type
|
39
|
+
matches_attribute = tag_arel_table[:name]
|
40
|
+
matches_attribute = matches_attribute.lower unless ActsAsTaggableOn.strict_case_match
|
41
|
+
|
42
|
+
if options[:wild].present?
|
43
|
+
matches_attribute.matches_any(tag_list.map{|tag| "%#{escaped_tag(tag)}%"}, "!")
|
44
|
+
else
|
45
|
+
matches_attribute.matches_any(tag_list.map{|tag| "#{escaped_tag(tag)}"}, "!")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def escaped_tag(tag)
|
50
|
+
tag = tag.downcase unless ActsAsTaggableOn.strict_case_match
|
51
|
+
tag.gsub(/[!%_]/) { |x| '!' + x }
|
52
|
+
end
|
53
|
+
|
54
|
+
def adjust_taggings_alias(taggings_alias)
|
55
|
+
if taggings_alias.size > 75
|
56
|
+
taggings_alias = 'taggings_alias_' + Digest::SHA1.hexdigest(taggings_alias)
|
57
|
+
end
|
58
|
+
taggings_alias
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -20,12 +20,12 @@ module ActsAsTaggableOn
|
|
20
20
|
has_many :owned_taggings, owned_taggings_scope,
|
21
21
|
opts.merge(
|
22
22
|
as: :tagger,
|
23
|
-
class_name: ::ActsAsTaggableOn::Tagging,
|
23
|
+
class_name: '::ActsAsTaggableOn::Tagging',
|
24
24
|
dependent: :destroy
|
25
25
|
)
|
26
26
|
|
27
27
|
has_many :owned_tags, -> { distinct },
|
28
|
-
class_name: ::ActsAsTaggableOn::Tag,
|
28
|
+
class_name: '::ActsAsTaggableOn::Tag',
|
29
29
|
source: :tag,
|
30
30
|
through: :owned_taggings
|
31
31
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module ActsAsTaggableOn
|
2
2
|
class Tagging < ::ActiveRecord::Base #:nodoc:
|
3
|
+
DEFAULT_CONTEXT = 'tags'
|
3
4
|
belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
|
4
5
|
belongs_to :taggable, polymorphic: true
|
5
6
|
|
@@ -8,8 +9,8 @@ module ActsAsTaggableOn
|
|
8
9
|
scope :owned_by, ->(owner) { where(tagger: owner) }
|
9
10
|
scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
|
10
11
|
|
11
|
-
scope :by_contexts, ->(contexts) { where(context: (contexts ||
|
12
|
-
scope :by_context, ->(context =
|
12
|
+
scope :by_contexts, ->(contexts) { where(context: (contexts || DEFAULT_CONTEXT)) }
|
13
|
+
scope :by_context, ->(context = DEFAULT_CONTEXT) { by_contexts(context.to_s) }
|
13
14
|
|
14
15
|
validates_presence_of :context
|
15
16
|
validates_presence_of :tag_id
|
@@ -99,6 +99,24 @@ describe 'Acts As Taggable On' do
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
describe 'Cache methods initialization on new models' do
|
103
|
+
before(:all) do
|
104
|
+
ActiveRecord::Base.connection.execute(
|
105
|
+
'INSERT INTO cache_methods_injected_models (cached_tag_list) VALUES (\'ciao\')'
|
106
|
+
)
|
107
|
+
class CacheMethodsInjectedModel < ActiveRecord::Base
|
108
|
+
acts_as_taggable
|
109
|
+
end
|
110
|
+
end
|
111
|
+
after(:all) { Object.send(:remove_const, :CacheMethodsInjectedModel) }
|
112
|
+
|
113
|
+
it 'cached_tag_list_on? get injected correctly' do
|
114
|
+
expect do
|
115
|
+
CacheMethodsInjectedModel.first.tag_list
|
116
|
+
end.not_to raise_error
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
102
120
|
describe 'CachingWithArray' do
|
103
121
|
pending '#TODO'
|
104
122
|
end
|
@@ -170,11 +170,27 @@ describe 'Single Table Inheritance' do
|
|
170
170
|
expect(taggable.tags_from(student)).to eq(%w(ruby scheme))
|
171
171
|
end
|
172
172
|
|
173
|
+
it 'returns all owner tags on the taggable' do
|
174
|
+
student.tag(taggable, with: 'ruby, scheme', on: :tags)
|
175
|
+
student.tag(taggable, with: 'skill_one', on: :skills)
|
176
|
+
student.tag(taggable, with: 'english, spanish', on: :language)
|
177
|
+
expect(taggable.owner_tags(student).count).to eq(5)
|
178
|
+
expect(taggable.owner_tags(student).sort == %w(english ruby scheme skill_one spanish))
|
179
|
+
end
|
180
|
+
|
181
|
+
|
173
182
|
it 'returns owner tags on the tagger' do
|
174
183
|
student.tag(taggable, with: 'ruby, scheme', on: :tags)
|
175
184
|
expect(taggable.owner_tags_on(student, :tags).count).to eq(2)
|
176
185
|
end
|
177
186
|
|
187
|
+
it 'returns owner tags on the taggable for an array of contexts' do
|
188
|
+
student.tag(taggable, with: 'ruby, scheme', on: :tags)
|
189
|
+
student.tag(taggable, with: 'skill_one, skill_two', on: :skills)
|
190
|
+
expect(taggable.owner_tags_on(student, [:tags, :skills]).count).to eq(4)
|
191
|
+
expect(taggable.owner_tags_on(student, [:tags, :skills]).sort == %w(ruby scheme skill_one skill_two))
|
192
|
+
end
|
193
|
+
|
178
194
|
it 'should scope objects returned by tagged_with by owners' do
|
179
195
|
student.tag(taggable, with: 'ruby, scheme', on: :tags)
|
180
196
|
expect(TaggableModel.tagged_with(%w(ruby scheme), owned_by: student).count).to eq(1)
|
@@ -208,4 +224,3 @@ describe 'Single Table Inheritance' do
|
|
208
224
|
end
|
209
225
|
end
|
210
226
|
end
|
211
|
-
|
@@ -247,7 +247,7 @@ describe 'Taggable' do
|
|
247
247
|
expect(TaggableModel.tagged_with("ruby", :start_at => today, :end_at => tomorrow).count).to eq(1)
|
248
248
|
end
|
249
249
|
|
250
|
-
|
250
|
+
it "shouldn't be able to find a tag outside date range" do
|
251
251
|
@taggable.skill_list = "ruby"
|
252
252
|
@taggable.save
|
253
253
|
|
data/spec/internal/db/schema.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts-as-taggable-on
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Bleigh
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-05-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: 4.2.8
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
27
|
+
version: 4.2.8
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: sqlite3
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,11 +164,10 @@ files:
|
|
164
164
|
- db/migrate/3_add_taggings_counter_cache_to_tags.rb
|
165
165
|
- db/migrate/4_add_missing_taggable_index.rb
|
166
166
|
- db/migrate/5_change_collation_for_tag_names.rb
|
167
|
-
- db/migrate/
|
168
|
-
- gemfiles/activerecord_4.0.gemfile
|
169
|
-
- gemfiles/activerecord_4.1.gemfile
|
167
|
+
- db/migrate/6_add_missing_indexes_on_taggings.rb
|
170
168
|
- gemfiles/activerecord_4.2.gemfile
|
171
169
|
- gemfiles/activerecord_5.0.gemfile
|
170
|
+
- gemfiles/activerecord_5.1.gemfile
|
172
171
|
- lib/acts-as-taggable-on.rb
|
173
172
|
- lib/acts_as_taggable_on.rb
|
174
173
|
- lib/acts_as_taggable_on/default_parser.rb
|
@@ -183,6 +182,11 @@ files:
|
|
183
182
|
- lib/acts_as_taggable_on/taggable/dirty.rb
|
184
183
|
- lib/acts_as_taggable_on/taggable/ownership.rb
|
185
184
|
- lib/acts_as_taggable_on/taggable/related.rb
|
185
|
+
- lib/acts_as_taggable_on/taggable/tagged_with_query.rb
|
186
|
+
- lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb
|
187
|
+
- lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb
|
188
|
+
- lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb
|
189
|
+
- lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb
|
186
190
|
- lib/acts_as_taggable_on/tagger.rb
|
187
191
|
- lib/acts_as_taggable_on/tagging.rb
|
188
192
|
- lib/acts_as_taggable_on/tags_helper.rb
|
@@ -246,7 +250,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
246
250
|
requirements:
|
247
251
|
- - ">="
|
248
252
|
- !ruby/object:Gem::Version
|
249
|
-
version: 2.
|
253
|
+
version: 2.2.7
|
250
254
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
251
255
|
requirements:
|
252
256
|
- - ">="
|
@@ -254,7 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
258
|
version: '0'
|
255
259
|
requirements: []
|
256
260
|
rubyforge_project:
|
257
|
-
rubygems_version: 2.
|
261
|
+
rubygems_version: 2.6.8
|
258
262
|
signing_key:
|
259
263
|
specification_version: 4
|
260
264
|
summary: Advanced tagging for Rails.
|
@@ -1,12 +0,0 @@
|
|
1
|
-
class AddMissingIndexes < ActiveRecord::Migration
|
2
|
-
def change
|
3
|
-
add_index :taggings, :tag_id
|
4
|
-
add_index :taggings, :taggable_id
|
5
|
-
add_index :taggings, :taggable_type
|
6
|
-
add_index :taggings, :tagger_id
|
7
|
-
add_index :taggings, :context
|
8
|
-
|
9
|
-
add_index :taggings, [:tagger_id, :tagger_type]
|
10
|
-
add_index :taggings, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy'
|
11
|
-
end
|
12
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 4.1.0"
|
6
|
-
gem "mysql2", "~> 0.3.21"
|
7
|
-
|
8
|
-
group :local_development do
|
9
|
-
gem "guard"
|
10
|
-
gem "guard-rspec"
|
11
|
-
gem "appraisal"
|
12
|
-
gem "rake"
|
13
|
-
gem "byebug", :platforms => [:mri_21, :mri_22, :mri_23]
|
14
|
-
end
|
15
|
-
|
16
|
-
gemspec :path => "../"
|