acts-as-taggable-on 4.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 => "../"
|