sb-acts-as-taggable-on 6.5.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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +39 -0
  5. data/Appraisals +15 -0
  6. data/CHANGELOG.md +330 -0
  7. data/CONTRIBUTING.md +57 -0
  8. data/Gemfile +11 -0
  9. data/Guardfile +5 -0
  10. data/LICENSE.md +20 -0
  11. data/README.md +555 -0
  12. data/Rakefile +21 -0
  13. data/UPGRADING.md +8 -0
  14. data/acts-as-taggable-on.gemspec +32 -0
  15. data/db/migrate/1_acts_as_taggable_on_migration.rb +36 -0
  16. data/db/migrate/2_add_missing_unique_indices.rb +25 -0
  17. data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +19 -0
  18. data/db/migrate/4_add_missing_taggable_index.rb +14 -0
  19. data/db/migrate/5_change_collation_for_tag_names.rb +14 -0
  20. data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
  21. data/gemfiles/activerecord_5.0.gemfile +21 -0
  22. data/gemfiles/activerecord_5.1.gemfile +21 -0
  23. data/gemfiles/activerecord_5.2.gemfile +21 -0
  24. data/gemfiles/activerecord_6.0.gemfile +21 -0
  25. data/lib/acts-as-taggable-on.rb +133 -0
  26. data/lib/acts_as_taggable_on.rb +6 -0
  27. data/lib/acts_as_taggable_on/default_parser.rb +79 -0
  28. data/lib/acts_as_taggable_on/engine.rb +4 -0
  29. data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
  30. data/lib/acts_as_taggable_on/tag.rb +139 -0
  31. data/lib/acts_as_taggable_on/tag_list.rb +106 -0
  32. data/lib/acts_as_taggable_on/taggable.rb +101 -0
  33. data/lib/acts_as_taggable_on/taggable/cache.rb +90 -0
  34. data/lib/acts_as_taggable_on/taggable/collection.rb +183 -0
  35. data/lib/acts_as_taggable_on/taggable/core.rb +322 -0
  36. data/lib/acts_as_taggable_on/taggable/ownership.rb +136 -0
  37. data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
  38. data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
  39. data/lib/acts_as_taggable_on/taggable/tagged_with_query.rb +16 -0
  40. data/lib/acts_as_taggable_on/taggable/tagged_with_query/all_tags_query.rb +111 -0
  41. data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +70 -0
  42. data/lib/acts_as_taggable_on/taggable/tagged_with_query/exclude_tags_query.rb +82 -0
  43. data/lib/acts_as_taggable_on/taggable/tagged_with_query/query_base.rb +61 -0
  44. data/lib/acts_as_taggable_on/tagger.rb +89 -0
  45. data/lib/acts_as_taggable_on/tagging.rb +36 -0
  46. data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
  47. data/lib/acts_as_taggable_on/utils.rb +37 -0
  48. data/lib/acts_as_taggable_on/version.rb +3 -0
  49. data/lib/tasks/tags_collate_utf8.rake +21 -0
  50. data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +285 -0
  51. data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +112 -0
  52. data/spec/acts_as_taggable_on/caching_spec.rb +129 -0
  53. data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
  54. data/spec/acts_as_taggable_on/dirty_spec.rb +142 -0
  55. data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
  56. data/spec/acts_as_taggable_on/related_spec.rb +99 -0
  57. data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +231 -0
  58. data/spec/acts_as_taggable_on/tag_list_spec.rb +176 -0
  59. data/spec/acts_as_taggable_on/tag_spec.rb +340 -0
  60. data/spec/acts_as_taggable_on/taggable_spec.rb +817 -0
  61. data/spec/acts_as_taggable_on/tagger_spec.rb +153 -0
  62. data/spec/acts_as_taggable_on/tagging_spec.rb +117 -0
  63. data/spec/acts_as_taggable_on/tags_helper_spec.rb +45 -0
  64. data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
  65. data/spec/internal/app/models/altered_inheriting_taggable_model.rb +5 -0
  66. data/spec/internal/app/models/cached_model.rb +3 -0
  67. data/spec/internal/app/models/cached_model_with_array.rb +11 -0
  68. data/spec/internal/app/models/columns_override_model.rb +5 -0
  69. data/spec/internal/app/models/company.rb +15 -0
  70. data/spec/internal/app/models/inheriting_taggable_model.rb +4 -0
  71. data/spec/internal/app/models/market.rb +2 -0
  72. data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
  73. data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
  74. data/spec/internal/app/models/other_cached_model.rb +3 -0
  75. data/spec/internal/app/models/other_taggable_model.rb +4 -0
  76. data/spec/internal/app/models/student.rb +4 -0
  77. data/spec/internal/app/models/taggable_model.rb +14 -0
  78. data/spec/internal/app/models/untaggable_model.rb +3 -0
  79. data/spec/internal/app/models/user.rb +3 -0
  80. data/spec/internal/config/database.yml.sample +19 -0
  81. data/spec/internal/db/schema.rb +110 -0
  82. data/spec/spec_helper.rb +20 -0
  83. data/spec/support/0-helpers.rb +32 -0
  84. data/spec/support/array.rb +9 -0
  85. data/spec/support/database.rb +36 -0
  86. data/spec/support/database_cleaner.rb +21 -0
  87. metadata +269 -0
@@ -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
+ matches_attribute.matches("%#{escaped_tag(tag)}%", "!")
33
+ else
34
+ matches_attribute.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
+ ActsAsTaggableOn::Utils.escape_like(tag)
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
@@ -0,0 +1,89 @@
1
+ module ActsAsTaggableOn
2
+ module Tagger
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ ##
9
+ # Make a model a tagger. This allows an instance of a model to claim ownership
10
+ # of tags.
11
+ #
12
+ # Example:
13
+ # class User < ActiveRecord::Base
14
+ # acts_as_tagger
15
+ # end
16
+ def acts_as_tagger(opts={})
17
+ class_eval do
18
+ owned_taggings_scope = opts.delete(:scope)
19
+
20
+ has_many :owned_taggings, owned_taggings_scope,
21
+ opts.merge(
22
+ as: :tagger,
23
+ class_name: '::ActsAsTaggableOn::Tagging',
24
+ dependent: :destroy
25
+ )
26
+
27
+ has_many :owned_tags, -> { distinct },
28
+ class_name: '::ActsAsTaggableOn::Tag',
29
+ source: :tag,
30
+ through: :owned_taggings
31
+ end
32
+
33
+ include ActsAsTaggableOn::Tagger::InstanceMethods
34
+ extend ActsAsTaggableOn::Tagger::SingletonMethods
35
+ end
36
+
37
+ def tagger?
38
+ false
39
+ end
40
+
41
+ def is_tagger?
42
+ tagger?
43
+ end
44
+ end
45
+
46
+ module InstanceMethods
47
+ ##
48
+ # Tag a taggable model with tags that are owned by the tagger.
49
+ #
50
+ # @param taggable The object that will be tagged
51
+ # @param [Hash] options An hash with options. Available options are:
52
+ # * <tt>:with</tt> - The tags that you want to
53
+ # * <tt>:on</tt> - The context on which you want to tag
54
+ #
55
+ # Example:
56
+ # @user.tag(@photo, :with => "paris, normandy", :on => :locations)
57
+ def tag(taggable, opts={})
58
+ opts.reverse_merge!(force: true)
59
+ skip_save = opts.delete(:skip_save)
60
+ return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
61
+
62
+ fail 'You need to specify a tag context using :on' unless opts.key?(:on)
63
+ fail 'You need to specify some tags using :with' unless opts.key?(:with)
64
+ fail "No context :#{opts[:on]} defined in #{taggable.class}" unless opts[:force] || taggable.tag_types.include?(opts[:on])
65
+
66
+ taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
67
+ taggable.save unless skip_save
68
+ end
69
+
70
+ def tagger?
71
+ self.class.is_tagger?
72
+ end
73
+
74
+ def is_tagger?
75
+ tagger?
76
+ end
77
+ end
78
+
79
+ module SingletonMethods
80
+ def tagger?
81
+ true
82
+ end
83
+
84
+ def is_tagger?
85
+ tagger?
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,36 @@
1
+ module ActsAsTaggableOn
2
+ class Tagging < ::ActiveRecord::Base #:nodoc:
3
+ self.table_name = ActsAsTaggableOn.taggings_table
4
+
5
+ DEFAULT_CONTEXT = 'tags'
6
+ belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
7
+ belongs_to :taggable, polymorphic: true
8
+
9
+ belongs_to :tagger, {polymorphic: true}.tap {|o| o.merge!(optional: true) }
10
+
11
+ scope :owned_by, ->(owner) { where(tagger: owner) }
12
+ scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
13
+
14
+ scope :by_contexts, ->(contexts) { where(context: (contexts || DEFAULT_CONTEXT)) }
15
+ scope :by_context, ->(context = DEFAULT_CONTEXT) { by_contexts(context.to_s) }
16
+
17
+ validates_presence_of :context
18
+ validates_presence_of :tag_id
19
+
20
+ validates_uniqueness_of :tag_id, scope: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type]
21
+
22
+ after_destroy :remove_unused_tags
23
+
24
+ private
25
+
26
+ def remove_unused_tags
27
+ if ActsAsTaggableOn.remove_unused_tags
28
+ if ActsAsTaggableOn.tags_counter
29
+ tag.destroy if tag.reload.taggings_count.zero?
30
+ else
31
+ tag.destroy if tag.reload.taggings.count.zero?
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ module ActsAsTaggableOn
2
+ module TagsHelper
3
+ # See the wiki for an example using tag_cloud.
4
+ def tag_cloud(tags, classes)
5
+ return [] if tags.empty?
6
+
7
+ max_count = tags.sort_by(&:taggings_count).last.taggings_count.to_f
8
+
9
+ tags.each do |tag|
10
+ index = ((tag.taggings_count / max_count) * (classes.size - 1))
11
+ yield tag, classes[index.nan? ? 0 : index.round]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ # This module is deprecated and will be removed in the incoming versions
2
+
3
+ module ActsAsTaggableOn
4
+ module Utils
5
+ class << self
6
+ # Use ActsAsTaggableOn::Tag connection
7
+ def connection
8
+ ActsAsTaggableOn::Tag.connection
9
+ end
10
+
11
+ def using_postgresql?
12
+ connection && connection.adapter_name == 'PostgreSQL'
13
+ end
14
+
15
+ def using_mysql?
16
+ connection && connection.adapter_name == 'Mysql2'
17
+ end
18
+
19
+ def sha_prefix(string)
20
+ Digest::SHA1.hexdigest(string)[0..6]
21
+ end
22
+
23
+ def like_operator
24
+ using_postgresql? ? 'ILIKE' : 'LIKE'
25
+ end
26
+
27
+ def legacy_activerecord?
28
+ ActiveRecord.version <= Gem::Version.new('5.3.0')
29
+ end
30
+
31
+ # escape _ and % characters in strings, since these are wildcards in SQL.
32
+ def escape_like(str)
33
+ str.gsub(/[!%_]/) { |x| '!' + x }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module ActsAsTaggableOn
2
+ VERSION = '6.5.0'
3
+ end
@@ -0,0 +1,21 @@
1
+ # These rake tasks are to be run by MySql users only, they fix the management of
2
+ # binary-encoded strings for tag 'names'. Issues:
3
+ # https://github.com/mbleigh/acts-as-taggable-on/issues/623
4
+
5
+ namespace :acts_as_taggable_on_engine do
6
+
7
+ namespace :tag_names do
8
+
9
+ desc "Forcing collate of tag names to utf8_bin"
10
+ task :collate_bin => [:environment] do |t, args|
11
+ ActsAsTaggableOn::Configuration.apply_binary_collation(true)
12
+ end
13
+
14
+ desc "Forcing collate of tag names to utf8_general_ci"
15
+ task :collate_ci => [:environment] do |t, args|
16
+ ActsAsTaggableOn::Configuration.apply_binary_collation(false)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,285 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe 'Acts As Taggable On' do
5
+
6
+ it "should provide a class method 'taggable?' that is false for untaggable models" do
7
+ expect(UntaggableModel).to_not be_taggable
8
+ end
9
+
10
+ describe 'Taggable Method Generation To Preserve Order' do
11
+ before(:each) do
12
+ TaggableModel.tag_types = []
13
+ TaggableModel.preserve_tag_order = false
14
+ TaggableModel.acts_as_ordered_taggable_on(:ordered_tags)
15
+ @taggable = TaggableModel.new(name: 'Bob Jones')
16
+ end
17
+
18
+ it "should respond 'true' to preserve_tag_order?" do
19
+ expect(@taggable.class.preserve_tag_order?).to be_truthy
20
+ end
21
+ end
22
+
23
+ describe 'Taggable Method Generation' do
24
+ before(:each) do
25
+ TaggableModel.tag_types = []
26
+ TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings)
27
+ @taggable = TaggableModel.new(name: 'Bob Jones')
28
+ end
29
+
30
+ it "should respond 'true' to taggable?" do
31
+ expect(@taggable.class).to be_taggable
32
+ end
33
+
34
+ it 'should create a class attribute for tag types' do
35
+ expect(@taggable.class).to respond_to(:tag_types)
36
+ end
37
+
38
+ it 'should create an instance attribute for tag types' do
39
+ expect(@taggable).to respond_to(:tag_types)
40
+ end
41
+
42
+ it 'should have all tag types' do
43
+ expect(@taggable.tag_types).to eq([:tags, :languages, :skills, :needs, :offerings])
44
+ end
45
+
46
+ it 'should create a class attribute for preserve tag order' do
47
+ expect(@taggable.class).to respond_to(:preserve_tag_order?)
48
+ end
49
+
50
+ it 'should create an instance attribute for preserve tag order' do
51
+ expect(@taggable).to respond_to(:preserve_tag_order?)
52
+ end
53
+
54
+ it "should respond 'false' to preserve_tag_order?" do
55
+ expect(@taggable.class.preserve_tag_order?).to be_falsy
56
+ end
57
+
58
+ it 'should generate an association for each tag type' do
59
+ expect(@taggable).to respond_to(:tags, :skills, :languages)
60
+ end
61
+
62
+ it 'should add tagged_with and tag_counts to singleton' do
63
+ expect(TaggableModel).to respond_to(:tagged_with, :tag_counts)
64
+ end
65
+
66
+ it 'should generate a tag_list accessor/setter for each tag type' do
67
+ expect(@taggable).to respond_to(:tag_list, :skill_list, :language_list)
68
+ expect(@taggable).to respond_to(:tag_list=, :skill_list=, :language_list=)
69
+ end
70
+
71
+ it 'should generate a tag_list accessor, that includes owned tags, for each tag type' do
72
+ expect(@taggable).to respond_to(:all_tags_list, :all_skills_list, :all_languages_list)
73
+ end
74
+ end
75
+
76
+ describe 'Reloading' do
77
+ it 'should save a model instantiated by Model.find' do
78
+ taggable = TaggableModel.create!(name: 'Taggable')
79
+ found_taggable = TaggableModel.find(taggable.id)
80
+ found_taggable.save
81
+ end
82
+ end
83
+
84
+ describe 'Matching Contexts' do
85
+ it 'should find objects with tags of matching contexts' do
86
+ taggable1 = TaggableModel.create!(name: 'Taggable 1')
87
+ taggable2 = TaggableModel.create!(name: 'Taggable 2')
88
+ taggable3 = TaggableModel.create!(name: 'Taggable 3')
89
+
90
+ taggable1.offering_list = 'one, two'
91
+ taggable1.save!
92
+
93
+ taggable2.need_list = 'one, two'
94
+ taggable2.save!
95
+
96
+ taggable3.offering_list = 'one, two'
97
+ taggable3.save!
98
+
99
+ expect(taggable1.find_matching_contexts(:offerings, :needs)).to include(taggable2)
100
+ expect(taggable1.find_matching_contexts(:offerings, :needs)).to_not include(taggable3)
101
+ end
102
+
103
+ it 'should find other related objects with tags of matching contexts' do
104
+ taggable1 = TaggableModel.create!(name: 'Taggable 1')
105
+ taggable2 = OtherTaggableModel.create!(name: 'Taggable 2')
106
+ taggable3 = OtherTaggableModel.create!(name: 'Taggable 3')
107
+
108
+ taggable1.offering_list = 'one, two'
109
+ taggable1.save
110
+
111
+ taggable2.need_list = 'one, two'
112
+ taggable2.save
113
+
114
+ taggable3.offering_list = 'one, two'
115
+ taggable3.save
116
+
117
+ expect(taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs)).to include(taggable2)
118
+ expect(taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs)).to_not include(taggable3)
119
+ end
120
+
121
+ it 'should not include the object itself in the list of related objects with tags of matching contexts' do
122
+ taggable1 = TaggableModel.create!(name: 'Taggable 1')
123
+ taggable2 = TaggableModel.create!(name: 'Taggable 2')
124
+
125
+ taggable1.offering_list = 'one, two'
126
+ taggable1.need_list = 'one, two'
127
+ taggable1.save
128
+
129
+ taggable2.need_list = 'one, two'
130
+ taggable2.save
131
+
132
+ expect(taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to include(taggable2)
133
+ expect(taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(taggable1)
134
+ end
135
+
136
+ it 'should ensure joins to multiple taggings maintain their contexts when aliasing' do
137
+ taggable1 = TaggableModel.create!(name: 'Taggable 1')
138
+
139
+ taggable1.offering_list = 'one'
140
+ taggable1.need_list = 'two'
141
+
142
+ taggable1.save
143
+
144
+ column = TaggableModel.connection.quote_column_name("context")
145
+ offer_alias = TaggableModel.connection.quote_table_name(ActsAsTaggableOn.taggings_table)
146
+ need_alias = TaggableModel.connection.quote_table_name("need_taggings_taggable_models_join")
147
+
148
+ expect(TaggableModel.joins(:offerings, :needs).to_sql).to include "#{offer_alias}.#{column}"
149
+ expect(TaggableModel.joins(:offerings, :needs).to_sql).to include "#{need_alias}.#{column}"
150
+ end
151
+
152
+ end
153
+
154
+ describe 'Tagging Contexts' do
155
+ it 'should eliminate duplicate tagging contexts ' do
156
+ TaggableModel.acts_as_taggable_on(:skills, :skills)
157
+ expect(TaggableModel.tag_types.freq[:skills]).to eq(1)
158
+ end
159
+
160
+ it 'should not contain embedded/nested arrays' do
161
+ TaggableModel.acts_as_taggable_on([:array], [:array])
162
+ expect(TaggableModel.tag_types.freq[[:array]]).to eq(0)
163
+ end
164
+
165
+ it 'should _flatten_ the content of arrays' do
166
+ TaggableModel.acts_as_taggable_on([:array], [:array])
167
+ expect(TaggableModel.tag_types.freq[:array]).to eq(1)
168
+ end
169
+
170
+ it 'should not raise an error when passed nil' do
171
+ expect(-> {
172
+ TaggableModel.acts_as_taggable_on
173
+ }).to_not raise_error
174
+ end
175
+
176
+ it 'should not raise an error when passed [nil]' do
177
+ expect(-> {
178
+ TaggableModel.acts_as_taggable_on([nil])
179
+ }).to_not raise_error
180
+ end
181
+
182
+ it 'should include dynamic contexts in tagging_contexts' do
183
+ taggable = TaggableModel.create!(name: 'Dynamic Taggable')
184
+ taggable.set_tag_list_on :colors, 'tag1, tag2, tag3'
185
+ expect(taggable.tagging_contexts).to eq(%w(tags languages skills needs offerings array colors))
186
+ taggable.save
187
+ taggable = TaggableModel.where(name: 'Dynamic Taggable').first
188
+ expect(taggable.tagging_contexts).to eq(%w(tags languages skills needs offerings array colors))
189
+ end
190
+ end
191
+
192
+ context 'when tagging context ends in an "s" when singular (ex. "status", "glass", etc.)' do
193
+ describe 'caching' do
194
+ before { @taggable = OtherCachedModel.new(name: 'John Smith') }
195
+ subject { @taggable }
196
+
197
+ it { should respond_to(:save_cached_tag_list) }
198
+ its(:cached_language_list) { should be_blank }
199
+ its(:cached_status_list) { should be_blank }
200
+ its(:cached_glass_list) { should be_blank }
201
+
202
+ context 'language taggings cache after update' do
203
+ before { @taggable.update(language_list: 'ruby, .net') }
204
+ subject { @taggable }
205
+
206
+ its(:language_list) { should == ['ruby', '.net']}
207
+ its(:cached_language_list) { should == 'ruby, .net' } # passes
208
+ its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@language_list' : :@language_list)) }
209
+ end
210
+
211
+ context 'status taggings cache after update' do
212
+ before { @taggable.update(status_list: 'happy, married') }
213
+ subject { @taggable }
214
+
215
+ its(:status_list) { should == ['happy', 'married'] }
216
+ its(:cached_status_list) { should == 'happy, married' } # fails
217
+ its(:cached_status_list) { should_not == '' } # fails, is blank
218
+ its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@status_list' : :@status_list)) }
219
+ its(:instance_variables) { should_not include((RUBY_VERSION < '1.9' ? '@statu_list' : :@statu_list)) } # fails, note: one "s"
220
+
221
+ end
222
+
223
+ context 'glass taggings cache after update' do
224
+ before do
225
+ @taggable.update(glass_list: 'rectangle, aviator')
226
+ end
227
+
228
+ subject { @taggable }
229
+ its(:glass_list) { should == ['rectangle', 'aviator'] }
230
+ its(:cached_glass_list) { should == 'rectangle, aviator' } # fails
231
+ its(:cached_glass_list) { should_not == '' } # fails, is blank
232
+ if RUBY_VERSION < '1.9'
233
+ its(:instance_variables) { should include('@glass_list') }
234
+ its(:instance_variables) { should_not include('@glas_list') } # fails, note: one "s"
235
+ else
236
+ its(:instance_variables) { should include(:@glass_list) }
237
+ its(:instance_variables) { should_not include(:@glas_list) } # fails, note: one "s"
238
+ end
239
+
240
+ end
241
+ end
242
+ end
243
+
244
+ describe 'taggings' do
245
+ before(:each) do
246
+ @taggable = TaggableModel.new(name: 'Art Kram')
247
+ end
248
+
249
+ it 'should return no taggings' do
250
+ expect(@taggable.taggings).to be_empty
251
+ end
252
+ end
253
+
254
+ describe '@@remove_unused_tags' do
255
+ before do
256
+ @taggable = TaggableModel.create(name: 'Bob Jones')
257
+ @tag = ActsAsTaggableOn::Tag.create(name: 'awesome')
258
+
259
+ @tagging = ActsAsTaggableOn::Tagging.create(taggable: @taggable, tag: @tag, context: 'tags')
260
+ end
261
+
262
+ context 'if set to true' do
263
+ before do
264
+ ActsAsTaggableOn.remove_unused_tags = true
265
+ end
266
+
267
+ it 'should remove unused tags after removing taggings' do
268
+ @tagging.destroy
269
+ expect(ActsAsTaggableOn::Tag.find_by_name('awesome')).to be_nil
270
+ end
271
+ end
272
+
273
+ context 'if set to false' do
274
+ before do
275
+ ActsAsTaggableOn.remove_unused_tags = false
276
+ end
277
+
278
+ it 'should not remove unused tags after removing taggings' do
279
+ @tagging.destroy
280
+ expect(ActsAsTaggableOn::Tag.find_by_name('awesome')).to eq(@tag)
281
+ end
282
+ end
283
+ end
284
+
285
+ end