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.
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 => "../"