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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -11
  3. data/Appraisals +7 -13
  4. data/CHANGELOG.md +82 -0
  5. data/CONTRIBUTING.md +13 -0
  6. data/Gemfile +1 -1
  7. data/README.md +42 -10
  8. data/acts-as-taggable-on.gemspec +2 -2
  9. data/db/migrate/1_acts_as_taggable_on_migration.rb +6 -1
  10. data/db/migrate/2_add_missing_unique_indices.rb +6 -1
  11. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +6 -1
  12. data/db/migrate/4_add_missing_taggable_index.rb +6 -1
  13. data/db/migrate/5_change_collation_for_tag_names.rb +6 -1
  14. data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
  15. data/gemfiles/activerecord_4.2.gemfile +3 -3
  16. data/gemfiles/activerecord_5.0.gemfile +3 -3
  17. data/gemfiles/{activerecord_4.0.gemfile → activerecord_5.1.gemfile} +3 -4
  18. data/lib/acts_as_taggable_on/tag.rb +10 -7
  19. data/lib/acts_as_taggable_on/tag_list.rb +1 -0
  20. data/lib/acts_as_taggable_on/taggable/core.rb +10 -157
  21. data/lib/acts_as_taggable_on/taggable/ownership.rb +16 -5
  22. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
  23. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +113 -0
  24. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +75 -0
  25. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
  26. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
  27. data/lib/acts_as_taggable_on/tagger.rb +2 -2
  28. data/lib/acts_as_taggable_on/tagging.rb +3 -2
  29. data/lib/acts_as_taggable_on/version.rb +1 -1
  30. data/spec/acts_as_taggable_on/caching_spec.rb +18 -0
  31. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +16 -1
  32. data/spec/acts_as_taggable_on/taggable_spec.rb +1 -1
  33. data/spec/internal/db/schema.rb +3 -0
  34. metadata +13 -9
  35. data/db/migrate/6_add_missing_indexes.rb +0 -12
  36. 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 || 'tags')) }
12
- scope :by_context, ->(context = 'tags') { by_contexts(context.to_s) }
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
@@ -1,4 +1,4 @@
1
1
  module ActsAsTaggableOn
2
- VERSION = '4.0.0'
2
+ VERSION = '5.0.0'
3
3
  end
4
4
 
@@ -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
- it "shouldn't be able to find a tag outside date range" do
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
 
@@ -78,6 +78,9 @@ ActiveRecord::Schema.define version: 0 do
78
78
  t.column :type, :string
79
79
  end
80
80
 
81
+ create_table :cache_methods_injected_models, force: true do |t|
82
+ t.column :cached_tag_list, :string
83
+ end
81
84
 
82
85
  # Special cases for postgresql
83
86
  if using_postgresql?
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.0.0
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: 2016-08-01 00:00:00.000000000 Z
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: '4.0'
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: '4.0'
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/6_add_missing_indexes.rb
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.0.0
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.4.5.1
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 => "../"