acts-as-taggable-on 2.2.0 → 2.3.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.
- data/.gitignore +3 -1
- data/README.rdoc +59 -7
- data/acts-as-taggable-on.gemspec +3 -3
- data/lib/acts-as-taggable-on/version.rb +1 -1
- data/lib/acts-as-taggable-on.rb +30 -3
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +7 -19
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +86 -21
- data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +37 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +33 -12
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +1 -1
- data/lib/acts_as_taggable_on/tag.rb +5 -8
- data/lib/acts_as_taggable_on/tag_list.rb +13 -12
- data/lib/acts_as_taggable_on/taggable.rb +102 -0
- data/lib/acts_as_taggable_on/{acts_as_tagger.rb → tagger.rb} +3 -3
- data/lib/acts_as_taggable_on/tagging.rb +12 -0
- data/lib/acts_as_taggable_on/tags_helper.rb +2 -2
- data/lib/acts_as_taggable_on/utils.rb +4 -0
- data/lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb +3 -1
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +58 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +84 -65
- data/spec/acts_as_taggable_on/tag_spec.rb +32 -10
- data/spec/acts_as_taggable_on/taggable_spec.rb +147 -14
- data/spec/acts_as_taggable_on/tagger_spec.rb +23 -16
- data/spec/acts_as_taggable_on/tagging_spec.rb +2 -5
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +16 -0
- data/spec/models.rb +5 -0
- data/spec/schema.rb +5 -0
- data/spec/spec_helper.rb +3 -1
- metadata +26 -25
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -54
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module ActsAsTaggableOn
|
|
2
|
+
module Taggable
|
|
3
|
+
def taggable?
|
|
4
|
+
false
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# This is an alias for calling <tt>acts_as_taggable_on :tags</tt>.
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
# class Book < ActiveRecord::Base
|
|
12
|
+
# acts_as_taggable
|
|
13
|
+
# end
|
|
14
|
+
def acts_as_taggable
|
|
15
|
+
acts_as_taggable_on :tags
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# This is an alias for calling <tt>acts_as_ordered_taggable_on :tags</tt>.
|
|
20
|
+
#
|
|
21
|
+
# Example:
|
|
22
|
+
# class Book < ActiveRecord::Base
|
|
23
|
+
# acts_as_ordered_taggable
|
|
24
|
+
# end
|
|
25
|
+
def acts_as_ordered_taggable
|
|
26
|
+
acts_as_ordered_taggable_on :tags
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Make a model taggable on specified contexts.
|
|
31
|
+
#
|
|
32
|
+
# @param [Array] tag_types An array of taggable contexts
|
|
33
|
+
#
|
|
34
|
+
# Example:
|
|
35
|
+
# class User < ActiveRecord::Base
|
|
36
|
+
# acts_as_taggable_on :languages, :skills
|
|
37
|
+
# end
|
|
38
|
+
def acts_as_taggable_on(*tag_types)
|
|
39
|
+
taggable_on(false, tag_types)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Make a model taggable on specified contexts
|
|
45
|
+
# and preserves the order in which tags are created
|
|
46
|
+
#
|
|
47
|
+
# @param [Array] tag_types An array of taggable contexts
|
|
48
|
+
#
|
|
49
|
+
# Example:
|
|
50
|
+
# class User < ActiveRecord::Base
|
|
51
|
+
# acts_as_ordered_taggable_on :languages, :skills
|
|
52
|
+
# end
|
|
53
|
+
def acts_as_ordered_taggable_on(*tag_types)
|
|
54
|
+
taggable_on(true, tag_types)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Make a model taggable on specified contexts
|
|
60
|
+
# and optionally preserves the order in which tags are created
|
|
61
|
+
#
|
|
62
|
+
# Seperate methods used above for backwards compatibility
|
|
63
|
+
# so that the original acts_as_taggable_on method is unaffected
|
|
64
|
+
# as it's not possible to add another arguement to the method
|
|
65
|
+
# without the tag_types being enclosed in square brackets
|
|
66
|
+
#
|
|
67
|
+
# NB: method overridden in core module in order to create tag type
|
|
68
|
+
# associations and methods after this logic has executed
|
|
69
|
+
#
|
|
70
|
+
def taggable_on(preserve_tag_order, *tag_types)
|
|
71
|
+
tag_types = tag_types.to_a.flatten.compact.map(&:to_sym)
|
|
72
|
+
|
|
73
|
+
if taggable?
|
|
74
|
+
self.tag_types = (self.tag_types + tag_types).uniq
|
|
75
|
+
self.preserve_tag_order = preserve_tag_order
|
|
76
|
+
else
|
|
77
|
+
class_attribute :tag_types
|
|
78
|
+
self.tag_types = tag_types
|
|
79
|
+
class_attribute :preserve_tag_order
|
|
80
|
+
self.preserve_tag_order = preserve_tag_order
|
|
81
|
+
|
|
82
|
+
class_eval do
|
|
83
|
+
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging"
|
|
84
|
+
has_many :base_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
|
|
85
|
+
|
|
86
|
+
def self.taggable?
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
include ActsAsTaggableOn::Utils
|
|
91
|
+
include ActsAsTaggableOn::Taggable::Core
|
|
92
|
+
include ActsAsTaggableOn::Taggable::Collection
|
|
93
|
+
include ActsAsTaggableOn::Taggable::Cache
|
|
94
|
+
include ActsAsTaggableOn::Taggable::Ownership
|
|
95
|
+
include ActsAsTaggableOn::Taggable::Related
|
|
96
|
+
include ActsAsTaggableOn::Taggable::Dirty
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -31,7 +31,7 @@ module ActsAsTaggableOn
|
|
|
31
31
|
|
|
32
32
|
module InstanceMethods
|
|
33
33
|
##
|
|
34
|
-
# Tag a taggable model with tags that are owned by the tagger.
|
|
34
|
+
# Tag a taggable model with tags that are owned by the tagger.
|
|
35
35
|
#
|
|
36
36
|
# @param taggable The object that will be tagged
|
|
37
37
|
# @param [Hash] options An hash with options. Available options are:
|
|
@@ -42,7 +42,7 @@ module ActsAsTaggableOn
|
|
|
42
42
|
# @user.tag(@photo, :with => "paris, normandy", :on => :locations)
|
|
43
43
|
def tag(taggable, opts={})
|
|
44
44
|
opts.reverse_merge!(:force => true)
|
|
45
|
-
|
|
45
|
+
skip_save = opts.delete(:skip_save)
|
|
46
46
|
return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable?
|
|
47
47
|
|
|
48
48
|
raise "You need to specify a tag context using :on" unless opts.has_key?(:on)
|
|
@@ -50,7 +50,7 @@ module ActsAsTaggableOn
|
|
|
50
50
|
raise "No context :#{opts[:on]} defined in #{taggable.class.to_s}" unless (opts[:force] || taggable.tag_types.include?(opts[:on]))
|
|
51
51
|
|
|
52
52
|
taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with])
|
|
53
|
-
taggable.save
|
|
53
|
+
taggable.save unless skip_save
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def is_tagger?
|
|
@@ -18,5 +18,17 @@ module ActsAsTaggableOn
|
|
|
18
18
|
validates_presence_of :tag_id
|
|
19
19
|
|
|
20
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 tag.taggings.count.zero?
|
|
29
|
+
tag.destroy
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
21
33
|
end
|
|
22
34
|
end
|
|
@@ -9,8 +9,8 @@ module ActsAsTaggableOn
|
|
|
9
9
|
max_count = tags.sort_by(&:count).last.count.to_f
|
|
10
10
|
|
|
11
11
|
tags.each do |tag|
|
|
12
|
-
index = ((tag.count / max_count) * (classes.size - 1))
|
|
13
|
-
yield tag, classes[index]
|
|
12
|
+
index = ((tag.count / max_count) * (classes.size - 1))
|
|
13
|
+
yield tag, classes[index.nan? ? 0 : index.round]
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -15,6 +15,10 @@ module ActsAsTaggableOn
|
|
|
15
15
|
::ActiveRecord::Base.connection && ::ActiveRecord::Base.connection.adapter_name == 'SQLite'
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def sha_prefix(string)
|
|
19
|
+
Digest::SHA1.hexdigest("#{string}#{rand}")[0..6]
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
private
|
|
19
23
|
def like_operator
|
|
20
24
|
using_postgresql? ? 'ILIKE' : 'LIKE'
|
|
@@ -12,7 +12,9 @@ class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
|
|
12
12
|
t.references :taggable, :polymorphic => true
|
|
13
13
|
t.references :tagger, :polymorphic => true
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# limit is created to prevent mysql error o index lenght for myisam table type.
|
|
16
|
+
# http://bit.ly/vgW2Ql
|
|
17
|
+
t.string :context, :limit => 128
|
|
16
18
|
|
|
17
19
|
t.datetime :created_at
|
|
18
20
|
end
|
|
@@ -8,6 +8,20 @@ describe "Acts As Taggable On" do
|
|
|
8
8
|
it "should provide a class method 'taggable?' that is false for untaggable models" do
|
|
9
9
|
UntaggableModel.should_not be_taggable
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
describe "Taggable Method Generation To Preserve Order" do
|
|
13
|
+
before(:each) do
|
|
14
|
+
clean_database!
|
|
15
|
+
TaggableModel.tag_types = []
|
|
16
|
+
TaggableModel.preserve_tag_order = false
|
|
17
|
+
TaggableModel.acts_as_ordered_taggable_on(:ordered_tags)
|
|
18
|
+
@taggable = TaggableModel.new(:name => "Bob Jones")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should respond 'true' to preserve_tag_order?" do
|
|
22
|
+
@taggable.class.preserve_tag_order?.should be_true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
11
25
|
|
|
12
26
|
describe "Taggable Method Generation" do
|
|
13
27
|
before(:each) do
|
|
@@ -32,6 +46,18 @@ describe "Acts As Taggable On" do
|
|
|
32
46
|
it "should have all tag types" do
|
|
33
47
|
@taggable.tag_types.should == [:tags, :languages, :skills, :needs, :offerings]
|
|
34
48
|
end
|
|
49
|
+
|
|
50
|
+
it "should create a class attribute for preserve tag order" do
|
|
51
|
+
@taggable.class.should respond_to(:preserve_tag_order?)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "should create an instance attribute for preserve tag order" do
|
|
55
|
+
@taggable.should respond_to(:preserve_tag_order?)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should respond 'false' to preserve_tag_order?" do
|
|
59
|
+
@taggable.class.preserve_tag_order?.should be_false
|
|
60
|
+
end
|
|
35
61
|
|
|
36
62
|
it "should generate an association for each tag type" do
|
|
37
63
|
@taggable.should respond_to(:tags, :skills, :languages)
|
|
@@ -453,4 +479,36 @@ describe "Acts As Taggable On" do
|
|
|
453
479
|
@taggable.taggings.should == []
|
|
454
480
|
end
|
|
455
481
|
end
|
|
482
|
+
|
|
483
|
+
describe "@@remove_unused_tags" do
|
|
484
|
+
before do
|
|
485
|
+
@taggable = TaggableModel.create(:name => "Bob Jones")
|
|
486
|
+
@tag = ActsAsTaggableOn::Tag.create(:name => "awesome")
|
|
487
|
+
|
|
488
|
+
@tagging = ActsAsTaggableOn::Tagging.create(:taggable => @taggable, :tag => @tag, :context => 'tags')
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
context "if set to true" do
|
|
492
|
+
before do
|
|
493
|
+
ActsAsTaggableOn.remove_unused_tags = true
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
it "should remove unused tags after removing taggings" do
|
|
497
|
+
@tagging.destroy
|
|
498
|
+
ActsAsTaggableOn::Tag.find_by_name("awesome").should be_nil
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
context "if set to false" do
|
|
503
|
+
before do
|
|
504
|
+
ActsAsTaggableOn.remove_unused_tags = false
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
it "should not remove unused tags after removing taggings" do
|
|
508
|
+
@tagging.destroy
|
|
509
|
+
ActsAsTaggableOn::Tag.find_by_name("awesome").should == @tag
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
456
514
|
end
|
|
@@ -1,74 +1,93 @@
|
|
|
1
1
|
require File.expand_path('../../spec_helper', __FILE__)
|
|
2
2
|
|
|
3
3
|
describe ActsAsTaggableOn::TagList do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
it "should
|
|
9
|
-
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it "should be able to be add a new tag word" do
|
|
13
|
-
@tag_list.add("cool")
|
|
14
|
-
@tag_list.include?("cool").should be_true
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it "should be able to add delimited lists of words" do
|
|
18
|
-
@tag_list.add("cool, wicked", :parse => true)
|
|
19
|
-
@tag_list.include?("cool").should be_true
|
|
20
|
-
@tag_list.include?("wicked").should be_true
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "should be able to add delimited list of words with quoted delimiters" do
|
|
24
|
-
@tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
|
|
25
|
-
@tag_list.include?("cool, wicked").should be_true
|
|
26
|
-
@tag_list.include?("really cool, really wicked").should be_true
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
it "should be able to handle other uses of quotation marks correctly" do
|
|
30
|
-
@tag_list.add("john's cool car, mary's wicked toy", :parse => true)
|
|
31
|
-
@tag_list.include?("john's cool car").should be_true
|
|
32
|
-
@tag_list.include?("mary's wicked toy").should be_true
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "should be able to add an array of words" do
|
|
36
|
-
@tag_list.add(["cool", "wicked"], :parse => true)
|
|
37
|
-
@tag_list.include?("cool").should be_true
|
|
38
|
-
@tag_list.include?("wicked").should be_true
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
it "should be able to remove words" do
|
|
42
|
-
@tag_list.remove("awesome")
|
|
43
|
-
@tag_list.include?("awesome").should be_false
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it "should be able to remove delimited lists of words" do
|
|
47
|
-
@tag_list.remove("awesome, radical", :parse => true)
|
|
48
|
-
@tag_list.should be_empty
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it "should be able to remove an array of words" do
|
|
52
|
-
@tag_list.remove(["awesome", "radical"], :parse => true)
|
|
53
|
-
@tag_list.should be_empty
|
|
4
|
+
let(:tag_list) { ActsAsTaggableOn::TagList.new("awesome","radical") }
|
|
5
|
+
|
|
6
|
+
it { should be_kind_of Array }
|
|
7
|
+
|
|
8
|
+
it "#from should return empty array if empty array is passed" do
|
|
9
|
+
ActsAsTaggableOn::TagList.from([]).should be_empty
|
|
54
10
|
end
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
11
|
+
|
|
12
|
+
describe "#add" do
|
|
13
|
+
it "should be able to be add a new tag word" do
|
|
14
|
+
tag_list.add("cool")
|
|
15
|
+
tag_list.include?("cool").should be_true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should be able to add delimited lists of words" do
|
|
19
|
+
tag_list.add("cool, wicked", :parse => true)
|
|
20
|
+
tag_list.should include("cool", "wicked")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should be able to add delimited list of words with quoted delimiters" do
|
|
24
|
+
tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
|
|
25
|
+
tag_list.should include("cool, wicked", "really cool, really wicked")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should be able to handle other uses of quotation marks correctly" do
|
|
29
|
+
tag_list.add("john's cool car, mary's wicked toy", :parse => true)
|
|
30
|
+
tag_list.should include("john's cool car", "mary's wicked toy")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should be able to add an array of words" do
|
|
34
|
+
tag_list.add(["cool", "wicked"], :parse => true)
|
|
35
|
+
tag_list.should include("cool", "wicked")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "should quote escape tags with commas in them" do
|
|
39
|
+
tag_list.add("cool","rad,bodacious")
|
|
40
|
+
tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
|
|
41
|
+
end
|
|
42
|
+
|
|
58
43
|
end
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
|
|
45
|
+
describe "#remove" do
|
|
46
|
+
it "should be able to remove words" do
|
|
47
|
+
tag_list.remove("awesome")
|
|
48
|
+
tag_list.should_not include("awesome")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should be able to remove delimited lists of words" do
|
|
52
|
+
tag_list.remove("awesome, radical", :parse => true)
|
|
53
|
+
tag_list.should be_empty
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should be able to remove an array of words" do
|
|
57
|
+
tag_list.remove(["awesome", "radical"], :parse => true)
|
|
58
|
+
tag_list.should be_empty
|
|
59
|
+
end
|
|
63
60
|
end
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
|
|
62
|
+
describe "#to_s" do
|
|
63
|
+
it "should give a delimited list of words when converted to string" do
|
|
64
|
+
tag_list.to_s.should == "awesome, radical"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "should be able to call to_s on a frozen tag list" do
|
|
68
|
+
tag_list.freeze
|
|
69
|
+
lambda { tag_list.add("cool","rad,bodacious") }.should raise_error
|
|
70
|
+
lambda { tag_list.to_s }.should_not raise_error
|
|
71
|
+
end
|
|
67
72
|
end
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
|
|
74
|
+
describe "cleaning" do
|
|
75
|
+
it "should parameterize if force_parameterize is set to true" do
|
|
76
|
+
ActsAsTaggableOn.force_parameterize = true
|
|
77
|
+
tag_list = ActsAsTaggableOn::TagList.new("awesome()","radical)(cc")
|
|
78
|
+
|
|
79
|
+
tag_list.to_s.should == "awesome, radical-cc"
|
|
80
|
+
ActsAsTaggableOn.force_parameterize = false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should lowercase if force_lowercase is set to true" do
|
|
84
|
+
ActsAsTaggableOn.force_lowercase = true
|
|
85
|
+
|
|
86
|
+
tag_list = ActsAsTaggableOn::TagList.new("aweSomE","RaDicaL")
|
|
87
|
+
tag_list.to_s.should == "awesome, radical"
|
|
88
|
+
|
|
89
|
+
ActsAsTaggableOn.force_lowercase = false
|
|
90
|
+
end
|
|
91
|
+
|
|
73
92
|
end
|
|
74
93
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#encoding: utf-8
|
|
2
|
+
|
|
1
3
|
require File.expand_path('../../spec_helper', __FILE__)
|
|
2
4
|
|
|
3
5
|
describe ActsAsTaggableOn::Tag do
|
|
@@ -40,6 +42,23 @@ describe ActsAsTaggableOn::Tag do
|
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
unless ActsAsTaggableOn::Tag.using_sqlite?
|
|
46
|
+
describe "find or create by unicode name" do
|
|
47
|
+
before(:each) do
|
|
48
|
+
@tag.name = "привет"
|
|
49
|
+
@tag.save
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should find by name" do
|
|
53
|
+
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("привет").should == @tag
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "should find by name case insensitive" do
|
|
57
|
+
ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("ПРИВЕТ").should == @tag
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
43
62
|
describe "find or create all by any name" do
|
|
44
63
|
before(:each) do
|
|
45
64
|
@tag.name = "awesome"
|
|
@@ -74,20 +93,22 @@ describe ActsAsTaggableOn::Tag do
|
|
|
74
93
|
it "should require a name" do
|
|
75
94
|
@tag.valid?
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
@tag.errors[:name].should == ["can't be blank"]
|
|
79
|
-
else
|
|
80
|
-
@tag.errors[:name].should == "can't be blank"
|
|
81
|
-
end
|
|
96
|
+
@tag.errors[:name].should == ["can't be blank"]
|
|
82
97
|
|
|
83
98
|
@tag.name = "something"
|
|
84
99
|
@tag.valid?
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
@tag.errors[:name].should == []
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "should limit the name length to 255 or less characters" do
|
|
105
|
+
@tag.name = "fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr"
|
|
106
|
+
@tag.valid?
|
|
107
|
+
@tag.errors[:name].should == ["is too long (maximum is 255 characters)"]
|
|
108
|
+
|
|
109
|
+
@tag.name = "fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran"
|
|
110
|
+
@tag.valid?
|
|
111
|
+
@tag.errors[:name].should == []
|
|
91
112
|
end
|
|
92
113
|
|
|
93
114
|
it "should equal a tag with the same name" do
|
|
@@ -128,4 +149,5 @@ describe ActsAsTaggableOn::Tag do
|
|
|
128
149
|
end
|
|
129
150
|
|
|
130
151
|
end
|
|
152
|
+
|
|
131
153
|
end
|
|
@@ -1,5 +1,114 @@
|
|
|
1
1
|
require File.expand_path('../../spec_helper', __FILE__)
|
|
2
2
|
|
|
3
|
+
describe "Taggable To Preserve Order" do
|
|
4
|
+
before(:each) do
|
|
5
|
+
clean_database!
|
|
6
|
+
@taggable = OrderedTaggableModel.new(:name => "Bob Jones")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "should have tag types" do
|
|
10
|
+
[:tags, :colours].each do |type|
|
|
11
|
+
OrderedTaggableModel.tag_types.should include type
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@taggable.tag_types.should == OrderedTaggableModel.tag_types
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "should have tag associations" do
|
|
18
|
+
[:tags, :colours].each do |type|
|
|
19
|
+
@taggable.respond_to?(type).should be_true
|
|
20
|
+
@taggable.respond_to?("#{type.to_s.singularize}_taggings").should be_true
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "should have tag associations ordered by id" do
|
|
25
|
+
[:tags, :colours].each do |type|
|
|
26
|
+
OrderedTaggableModel.reflect_on_association(type).options[:order].should include('id')
|
|
27
|
+
OrderedTaggableModel.reflect_on_association("#{type.to_s.singularize}_taggings".to_sym).options[:order].should include('id')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should have tag methods" do
|
|
32
|
+
[:tags, :colours].each do |type|
|
|
33
|
+
@taggable.respond_to?("#{type.to_s.singularize}_list").should be_true
|
|
34
|
+
@taggable.respond_to?("#{type.to_s.singularize}_list=").should be_true
|
|
35
|
+
@taggable.respond_to?("all_#{type.to_s}_list").should be_true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should return tag list in the order the tags were created" do
|
|
40
|
+
# create
|
|
41
|
+
@taggable.tag_list = "rails, ruby, css"
|
|
42
|
+
@taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
|
|
43
|
+
|
|
44
|
+
lambda {
|
|
45
|
+
@taggable.save
|
|
46
|
+
}.should change(ActsAsTaggableOn::Tag, :count).by(3)
|
|
47
|
+
|
|
48
|
+
@taggable.reload
|
|
49
|
+
@taggable.tag_list.should == %w(rails ruby css)
|
|
50
|
+
|
|
51
|
+
# update
|
|
52
|
+
@taggable.tag_list = "pow, ruby, rails"
|
|
53
|
+
@taggable.save
|
|
54
|
+
|
|
55
|
+
@taggable.reload
|
|
56
|
+
@taggable.tag_list.should == %w(pow ruby rails)
|
|
57
|
+
|
|
58
|
+
# update with no change
|
|
59
|
+
@taggable.tag_list = "pow, ruby, rails"
|
|
60
|
+
@taggable.save
|
|
61
|
+
|
|
62
|
+
@taggable.reload
|
|
63
|
+
@taggable.tag_list.should == %w(pow ruby rails)
|
|
64
|
+
|
|
65
|
+
# update to clear tags
|
|
66
|
+
@taggable.tag_list = ""
|
|
67
|
+
@taggable.save
|
|
68
|
+
|
|
69
|
+
@taggable.reload
|
|
70
|
+
@taggable.tag_list.should == []
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should return tag objects in the order the tags were created" do
|
|
74
|
+
# create
|
|
75
|
+
@taggable.tag_list = "pow, ruby, rails"
|
|
76
|
+
@taggable.instance_variable_get("@tag_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
|
|
77
|
+
|
|
78
|
+
lambda {
|
|
79
|
+
@taggable.save
|
|
80
|
+
}.should change(ActsAsTaggableOn::Tag, :count).by(3)
|
|
81
|
+
|
|
82
|
+
@taggable.reload
|
|
83
|
+
@taggable.tags.map{|t| t.name}.should == %w(pow ruby rails)
|
|
84
|
+
|
|
85
|
+
# update
|
|
86
|
+
@taggable.tag_list = "rails, ruby, css, pow"
|
|
87
|
+
@taggable.save
|
|
88
|
+
|
|
89
|
+
@taggable.reload
|
|
90
|
+
@taggable.tags.map{|t| t.name}.should == %w(rails ruby css pow)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should return tag objects in tagging id order" do
|
|
94
|
+
# create
|
|
95
|
+
@taggable.tag_list = "pow, ruby, rails"
|
|
96
|
+
@taggable.save
|
|
97
|
+
|
|
98
|
+
@taggable.reload
|
|
99
|
+
ids = @taggable.tags.map{|t| t.taggings.first.id}
|
|
100
|
+
ids.should == ids.sort
|
|
101
|
+
|
|
102
|
+
# update
|
|
103
|
+
@taggable.tag_list = "rails, ruby, css, pow"
|
|
104
|
+
@taggable.save
|
|
105
|
+
|
|
106
|
+
@taggable.reload
|
|
107
|
+
ids = @taggable.tags.map{|t| t.taggings.first.id}
|
|
108
|
+
ids.should == ids.sort
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
3
112
|
describe "Taggable" do
|
|
4
113
|
before(:each) do
|
|
5
114
|
clean_database!
|
|
@@ -142,20 +251,9 @@ describe "Taggable" do
|
|
|
142
251
|
TaggableModel.tagged_with(['rails, css'], :on => :needs, :any => true).tagged_with(['c++', 'java'], :on => :offerings, :any => true).to_a.should == [bob]
|
|
143
252
|
end
|
|
144
253
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
TaggableModel.tagged_with("ruby").first.should_not be_readonly
|
|
149
|
-
end
|
|
150
|
-
else
|
|
151
|
-
xit "should not return read-only records" do
|
|
152
|
-
# apparantly, there is no way to set readonly to false in a scope if joins are made
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
it "should be possible to return writable records" do
|
|
156
|
-
TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
157
|
-
TaggableModel.tagged_with("ruby").first(:readonly => false).should_not be_readonly
|
|
158
|
-
end
|
|
254
|
+
it "should not return read-only records" do
|
|
255
|
+
TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
|
|
256
|
+
TaggableModel.tagged_with("ruby").first.should_not be_readonly
|
|
159
257
|
end
|
|
160
258
|
|
|
161
259
|
it "should be able to get scoped tag counts" do
|
|
@@ -229,6 +327,19 @@ describe "Taggable" do
|
|
|
229
327
|
TaggableModel.tagged_with(["depressed", "css"], :order => 'taggable_models.name', :any => true).to_a.should == [bob, frank]
|
|
230
328
|
end
|
|
231
329
|
|
|
330
|
+
context "wild: true" do
|
|
331
|
+
it "should use params as wildcards" do
|
|
332
|
+
bob = TaggableModel.create(:name => "Bob", :tag_list => "bob, tricia")
|
|
333
|
+
frank = TaggableModel.create(:name => "Frank", :tag_list => "bobby, jim")
|
|
334
|
+
steve = TaggableModel.create(:name => "Steve", :tag_list => "john, patricia")
|
|
335
|
+
jim = TaggableModel.create(:name => "Jim", :tag_list => "jim, steve")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :any => true).to_a.sort_by{|o| o.id}.should == [bob, frank, steve]
|
|
339
|
+
TaggableModel.tagged_with(["bob", "tricia"], :wild => true, :exclude => true).to_a.should == [jim]
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
232
343
|
it "should be able to find tagged on a custom tag context" do
|
|
233
344
|
bob = TaggableModel.create(:name => "Bob")
|
|
234
345
|
bob.set_tag_list_on(:rotors, "spinning, jumping")
|
|
@@ -405,6 +516,28 @@ describe "Taggable" do
|
|
|
405
516
|
@taggable.tag_list_on(:test).should == ["hello"]
|
|
406
517
|
end
|
|
407
518
|
end
|
|
519
|
+
|
|
520
|
+
describe "Dirty Objects" do
|
|
521
|
+
before(:each) do
|
|
522
|
+
@taggable = TaggableModel.create(:tag_list => "awesome, epic")
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
it 'should show changes of dirty object' do
|
|
526
|
+
@taggable.changes.should == {}
|
|
527
|
+
@taggable.tag_list = 'one'
|
|
528
|
+
@taggable.changes.should == {"tag_list"=>["awesome, epic", ["one"]]}
|
|
529
|
+
|
|
530
|
+
@taggable.tag_list_changed?.should be_true
|
|
531
|
+
@taggable.tag_list_was.should == "awesome, epic"
|
|
532
|
+
@taggable.tag_list_change.should == ["awesome, epic", ["one"]]
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
it 'should show no changes if the same tag_list' do
|
|
536
|
+
@taggable.tag_list = "awesome, epic"
|
|
537
|
+
@taggable.tag_list_changed?.should be_false
|
|
538
|
+
@taggable.changes.should == {}
|
|
539
|
+
end
|
|
540
|
+
end
|
|
408
541
|
end
|
|
409
542
|
|
|
410
543
|
|