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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +39 -0
- data/Appraisals +15 -0
- data/CHANGELOG.md +330 -0
- data/CONTRIBUTING.md +57 -0
- data/Gemfile +11 -0
- data/Guardfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +555 -0
- data/Rakefile +21 -0
- data/UPGRADING.md +8 -0
- data/acts-as-taggable-on.gemspec +32 -0
- data/db/migrate/1_acts_as_taggable_on_migration.rb +36 -0
- data/db/migrate/2_add_missing_unique_indices.rb +25 -0
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +19 -0
- data/db/migrate/4_add_missing_taggable_index.rb +14 -0
- data/db/migrate/5_change_collation_for_tag_names.rb +14 -0
- data/db/migrate/6_add_missing_indexes_on_taggings.rb +22 -0
- data/gemfiles/activerecord_5.0.gemfile +21 -0
- data/gemfiles/activerecord_5.1.gemfile +21 -0
- data/gemfiles/activerecord_5.2.gemfile +21 -0
- data/gemfiles/activerecord_6.0.gemfile +21 -0
- data/lib/acts-as-taggable-on.rb +133 -0
- data/lib/acts_as_taggable_on.rb +6 -0
- data/lib/acts_as_taggable_on/default_parser.rb +79 -0
- data/lib/acts_as_taggable_on/engine.rb +4 -0
- data/lib/acts_as_taggable_on/generic_parser.rb +19 -0
- data/lib/acts_as_taggable_on/tag.rb +139 -0
- data/lib/acts_as_taggable_on/tag_list.rb +106 -0
- data/lib/acts_as_taggable_on/taggable.rb +101 -0
- data/lib/acts_as_taggable_on/taggable/cache.rb +90 -0
- data/lib/acts_as_taggable_on/taggable/collection.rb +183 -0
- data/lib/acts_as_taggable_on/taggable/core.rb +322 -0
- data/lib/acts_as_taggable_on/taggable/ownership.rb +136 -0
- data/lib/acts_as_taggable_on/taggable/related.rb +71 -0
- data/lib/acts_as_taggable_on/taggable/tag_list_type.rb +4 -0
- 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 +111 -0
- data/lib/acts_as_taggable_on/taggable/tagged_with_query/any_tags_query.rb +70 -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 +89 -0
- data/lib/acts_as_taggable_on/tagging.rb +36 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +15 -0
- data/lib/acts_as_taggable_on/utils.rb +37 -0
- data/lib/acts_as_taggable_on/version.rb +3 -0
- data/lib/tasks/tags_collate_utf8.rake +21 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +285 -0
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +112 -0
- data/spec/acts_as_taggable_on/caching_spec.rb +129 -0
- data/spec/acts_as_taggable_on/default_parser_spec.rb +47 -0
- data/spec/acts_as_taggable_on/dirty_spec.rb +142 -0
- data/spec/acts_as_taggable_on/generic_parser_spec.rb +14 -0
- data/spec/acts_as_taggable_on/related_spec.rb +99 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +231 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +176 -0
- data/spec/acts_as_taggable_on/tag_spec.rb +340 -0
- data/spec/acts_as_taggable_on/taggable_spec.rb +817 -0
- data/spec/acts_as_taggable_on/tagger_spec.rb +153 -0
- data/spec/acts_as_taggable_on/tagging_spec.rb +117 -0
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +45 -0
- data/spec/acts_as_taggable_on/utils_spec.rb +23 -0
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +5 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +11 -0
- data/spec/internal/app/models/columns_override_model.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +4 -0
- data/spec/internal/app/models/market.rb +2 -0
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
- data/spec/internal/app/models/other_cached_model.rb +3 -0
- data/spec/internal/app/models/other_taggable_model.rb +4 -0
- data/spec/internal/app/models/student.rb +4 -0
- data/spec/internal/app/models/taggable_model.rb +14 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/internal/config/database.yml.sample +19 -0
- data/spec/internal/db/schema.rb +110 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/0-helpers.rb +32 -0
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +36 -0
- data/spec/support/database_cleaner.rb +21 -0
- 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,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
|