acts-as-taggable-on 2.3.3 → 2.4.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.
@@ -11,17 +11,30 @@ module ActsAsTaggableOn
11
11
  ### VALIDATIONS:
12
12
 
13
13
  validates_presence_of :name
14
- validates_uniqueness_of :name
14
+ validates_uniqueness_of :name, :if => :validates_name_uniqueness?
15
15
  validates_length_of :name, :maximum => 255
16
16
 
17
+ # monkey patch this method if don't need name uniqueness validation
18
+ def validates_name_uniqueness?
19
+ true
20
+ end
21
+
17
22
  ### SCOPES:
18
23
 
19
24
  def self.named(name)
20
- where(["lower(name) = ?", name.downcase])
25
+ if ActsAsTaggableOn.strict_case_match
26
+ where(["name = #{binary}?", name])
27
+ else
28
+ where(["lower(name) = ?", name.downcase])
29
+ end
21
30
  end
22
31
 
23
32
  def self.named_any(list)
24
- where(list.map { |tag| sanitize_sql(["lower(name) = ?", tag.to_s.mb_chars.downcase]) }.join(" OR "))
33
+ if ActsAsTaggableOn.strict_case_match
34
+ where(list.map { |tag| sanitize_sql(["name = #{binary}?", tag.to_s.mb_chars]) }.join(" OR "))
35
+ else
36
+ where(list.map { |tag| sanitize_sql(["lower(name) = ?", tag.to_s.mb_chars.downcase]) }.join(" OR "))
37
+ end
25
38
  end
26
39
 
27
40
  def self.named_like(name)
@@ -35,7 +48,11 @@ module ActsAsTaggableOn
35
48
  ### CLASS METHODS:
36
49
 
37
50
  def self.find_or_create_with_like_by_name(name)
38
- named_like(name).first || create(:name => name)
51
+ if (ActsAsTaggableOn.strict_case_match)
52
+ self.find_or_create_all_with_like_by_name([name]).first
53
+ else
54
+ named_like(name).first || create(:name => name)
55
+ end
39
56
  end
40
57
 
41
58
  def self.find_or_create_all_with_like_by_name(*list)
@@ -45,9 +62,9 @@ module ActsAsTaggableOn
45
62
 
46
63
  existing_tags = Tag.named_any(list).all
47
64
  new_tag_names = list.reject do |name|
48
- name = comparable_name(name)
49
- existing_tags.any? { |tag| comparable_name(tag.name) == name }
50
- end
65
+ name = comparable_name(name)
66
+ existing_tags.any? { |tag| comparable_name(tag.name) == name }
67
+ end
51
68
  created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
52
69
 
53
70
  existing_tags + created_tags
@@ -69,9 +86,14 @@ module ActsAsTaggableOn
69
86
 
70
87
  class << self
71
88
  private
72
- def comparable_name(str)
73
- str.mb_chars.downcase.to_s
74
- end
89
+
90
+ def comparable_name(str)
91
+ str.mb_chars.downcase.to_s
92
+ end
93
+
94
+ def binary
95
+ /mysql/ === ActiveRecord::Base.connection_config[:adapter] ? "BINARY " : nil
96
+ end
75
97
  end
76
98
  end
77
99
  end
@@ -58,7 +58,7 @@ module ActsAsTaggableOn
58
58
  end
59
59
 
60
60
  ##
61
- # Transform the tag_list into a tag string suitable for edting in a form.
61
+ # Transform the tag_list into a tag string suitable for editing in a form.
62
62
  # The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
63
63
  #
64
64
  # Example:
@@ -81,7 +81,7 @@ module ActsAsTaggableOn
81
81
  def clean!
82
82
  reject!(&:blank?)
83
83
  map!(&:strip)
84
- map!(&:downcase) if ActsAsTaggableOn.force_lowercase
84
+ map!{ |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase
85
85
  map!(&:parameterize) if ActsAsTaggableOn.force_parameterize
86
86
 
87
87
  uniq!
@@ -185,6 +185,39 @@ describe "Acts As Taggable On" do
185
185
  taggable1.find_related_tags.should_not include(taggable1)
186
186
  end
187
187
 
188
+ context "Ignored Tags" do
189
+ let(:taggable1) { TaggableModel.create!(:name => "Taggable 1") }
190
+ let(:taggable2) { TaggableModel.create!(:name => "Taggable 2") }
191
+ let(:taggable3) { TaggableModel.create!(:name => "Taggable 3") }
192
+ before(:each) do
193
+ taggable1.tag_list = "one, two, four"
194
+ taggable1.save
195
+
196
+ taggable2.tag_list = "two, three"
197
+ taggable2.save
198
+
199
+ taggable3.tag_list = "one, three"
200
+ taggable3.save
201
+ end
202
+ it "should not include ignored tags in related search" do
203
+ taggable1.find_related_tags(:ignore => 'two').should_not include(taggable2)
204
+ taggable1.find_related_tags(:ignore => 'two').should include(taggable3)
205
+ end
206
+
207
+ it "should accept array of ignored tags" do
208
+ taggable4 = TaggableModel.create!(:name => "Taggable 4")
209
+ taggable4.tag_list = "four"
210
+ taggable4.save
211
+
212
+ taggable1.find_related_tags(:ignore => ['two', 'four']).should_not include(taggable2)
213
+ taggable1.find_related_tags(:ignore => ['two', 'four']).should_not include(taggable4)
214
+ end
215
+
216
+ it "should accept symbols as ignored tags" do
217
+ taggable1.find_related_tags(:ignore => :two).should_not include(taggable2)
218
+ end
219
+ end
220
+
188
221
  context "Inherited Models" do
189
222
  before do
190
223
  @taggable1 = InheritingTaggableModel.create!(:name => "InheritingTaggable 1")
@@ -84,8 +84,8 @@ describe ActsAsTaggableOn::TagList do
84
84
  it "should lowercase if force_lowercase is set to true" do
85
85
  ActsAsTaggableOn.force_lowercase = true
86
86
 
87
- tag_list = ActsAsTaggableOn::TagList.new("aweSomE","RaDicaL")
88
- tag_list.to_s.should == "awesome, radical"
87
+ tag_list = ActsAsTaggableOn::TagList.new("aweSomE","RaDicaL","Entrée")
88
+ tag_list.to_s.should == "awesome, radical, entrée"
89
89
 
90
90
  ActsAsTaggableOn.force_lowercase = false
91
91
  end
@@ -150,4 +150,63 @@ describe ActsAsTaggableOn::Tag do
150
150
 
151
151
  end
152
152
 
153
- end
153
+ describe "when using strict_case_match" do
154
+ before do
155
+ ActsAsTaggableOn.strict_case_match = true
156
+ @tag.name = "awesome"
157
+ @tag.save!
158
+ end
159
+
160
+ after do
161
+ ActsAsTaggableOn.strict_case_match = false
162
+ end
163
+
164
+ it "should find by name" do
165
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("awesome").should == @tag
166
+ end
167
+
168
+ it "should find by name case sensitively" do
169
+ expect {
170
+ ActsAsTaggableOn::Tag.find_or_create_with_like_by_name("AWESOME")
171
+ }.to change(ActsAsTaggableOn::Tag, :count)
172
+
173
+ ActsAsTaggableOn::Tag.last.name.should == "AWESOME"
174
+ end
175
+
176
+ it "should have a named_scope named(something) that matches exactly" do
177
+ uppercase_tag = ActsAsTaggableOn::Tag.create(:name => "Cool")
178
+ @tag.name = "cool"
179
+ @tag.save!
180
+
181
+ ActsAsTaggableOn::Tag.named('cool').should include(@tag)
182
+ ActsAsTaggableOn::Tag.named('cool').should_not include(uppercase_tag)
183
+ end
184
+ end
185
+
186
+ describe "name uniqeness validation" do
187
+ let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(:name => 'ror') }
188
+
189
+ before { ActsAsTaggableOn::Tag.create(:name => 'ror') }
190
+
191
+ context "when don't need unique names" do
192
+ it "should not run uniqueness validation" do
193
+ duplicate_tag.stub(:validates_name_uniqueness?).and_return(false)
194
+ duplicate_tag.save
195
+ duplicate_tag.should be_persisted
196
+ end
197
+ end
198
+
199
+ context "when do need unique names" do
200
+ it "should run uniqueness validation" do
201
+ duplicate_tag.should_not be_valid
202
+ end
203
+
204
+ it "add error to name" do
205
+ duplicate_tag.save
206
+
207
+ duplicate_tag.should have(1).errors
208
+ duplicate_tag.errors.messages[:name].should include('has already been taken')
209
+ end
210
+ end
211
+ end
212
+ end
@@ -134,6 +134,16 @@ describe "Taggable" do
134
134
  @taggable.tag_counts_on(:tags).length.should == 2
135
135
  end
136
136
 
137
+ it "should have tags_on" do
138
+ TaggableModel.tags_on(:tags).all.should be_empty
139
+
140
+ @taggable.tag_list = ["awesome", "epic"]
141
+ @taggable.save
142
+
143
+ TaggableModel.tags_on(:tags).length.should == 2
144
+ @taggable.tags_on(:tags).length.should == 2
145
+ end
146
+
137
147
  it "should return [] right after create" do
138
148
  blank_taggable = TaggableModel.new(:name => "Bob Jones")
139
149
  blank_taggable.tag_list.should == []
@@ -242,6 +252,15 @@ describe "Taggable" do
242
252
  TaggableModel.all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
243
253
  end
244
254
 
255
+ it "should be able to get all tags on model as whole" do
256
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
257
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
258
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
259
+
260
+ TaggableModel.all_tags.all.should_not be_empty
261
+ TaggableModel.all_tags(:order => 'tags.id').first.name.should == "ruby"
262
+ end
263
+
245
264
  it "should be able to use named scopes to chain tag finds by any tags by context" do
246
265
  bob = TaggableModel.create(:name => "Bob", :need_list => "rails", :offering_list => "c++")
247
266
  frank = TaggableModel.create(:name => "Frank", :need_list => "css", :offering_list => "css")
@@ -273,6 +292,14 @@ describe "Taggable" do
273
292
  TaggableModel.tagged_with("ruby").all_tag_counts(:order => 'tags.id').first.count.should == 3 # ruby
274
293
  end
275
294
 
295
+ it "should be able to get all scoped tags" do
296
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
297
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
298
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby")
299
+
300
+ TaggableModel.tagged_with("ruby").all_tags(:order => 'tags.id').first.name.should == "ruby"
301
+ end
302
+
276
303
  it 'should only return tag counts for the available scope' do
277
304
  bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
278
305
  frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
@@ -288,6 +315,21 @@ describe "Taggable" do
288
315
  TaggableModel.tagged_with('rails').scoped(:joins => [:untaggable_models]).all_tag_counts.should have(2).items
289
316
  end
290
317
 
318
+ it 'should only return tags for the available scope' do
319
+ bob = TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
320
+ frank = TaggableModel.create(:name => "Frank", :tag_list => "ruby, rails")
321
+ charlie = TaggableModel.create(:name => "Charlie", :skill_list => "ruby, java")
322
+
323
+ TaggableModel.tagged_with('rails').all_tags.should have(3).items
324
+ TaggableModel.tagged_with('rails').all_tags.any? { |tag| tag.name == 'java' }.should be_false
325
+
326
+ # Test specific join syntaxes:
327
+ frank.untaggable_models.create!
328
+ TaggableModel.tagged_with('rails').scoped(:joins => :untaggable_models).all_tags.should have(2).items
329
+ TaggableModel.tagged_with('rails').scoped(:joins => { :untaggable_models => :taggable_model }).all_tags.should have(2).items
330
+ TaggableModel.tagged_with('rails').scoped(:joins => [:untaggable_models]).all_tags.should have(2).items
331
+ end
332
+
291
333
  it "should be able to set a custom tag context list" do
292
334
  bob = TaggableModel.create(:name => "Bob")
293
335
  bob.set_tag_list_on(:rotors, "spinning, jumping")
@@ -456,6 +498,17 @@ describe "Taggable" do
456
498
  TaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso fork spoon)
457
499
  end
458
500
 
501
+ it "should have different tags_on for inherited models" do
502
+ @inherited_same.tag_list = "bob, kelso"
503
+ @inherited_same.save!
504
+ @inherited_different.tag_list = "fork, spoon"
505
+ @inherited_different.save!
506
+
507
+ InheritingTaggableModel.tags_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso)
508
+ AlteredInheritingTaggableModel.tags_on(:tags, :order => 'tags.id').map(&:name).should == %w(fork spoon)
509
+ TaggableModel.tags_on(:tags, :order => 'tags.id').map(&:name).should == %w(bob kelso fork spoon)
510
+ end
511
+
459
512
  it 'should store same tag without validation conflict' do
460
513
  @taggable.tag_list = 'one'
461
514
  @taggable.save!
@@ -492,6 +545,16 @@ describe "Taggable" do
492
545
  @taggable.tag_counts_on(:tags).length.should == 2
493
546
  end
494
547
 
548
+ it "should have tags_on" do
549
+ NonStandardIdTaggableModel.tags_on(:tags).all.should be_empty
550
+
551
+ @taggable.tag_list = ["awesome", "epic"]
552
+ @taggable.save
553
+
554
+ NonStandardIdTaggableModel.tags_on(:tags).length.should == 2
555
+ @taggable.tags_on(:tags).length.should == 2
556
+ end
557
+
495
558
  it "should be able to create tags" do
496
559
  @taggable.skill_list = "ruby, rails, css"
497
560
  @taggable.instance_variable_get("@skill_list").instance_of?(ActsAsTaggableOn::TagList).should be_true
@@ -538,6 +601,12 @@ describe "Taggable" do
538
601
  @taggable.changes.should == {}
539
602
  end
540
603
  end
604
+
605
+ describe "Autogenerated methods" do
606
+ it "should be overridable" do
607
+ TaggableModel.create(:tag_list=>'woo').tag_list_submethod_called.should be_true
608
+ end
609
+ end
541
610
  end
542
611
 
543
612
 
data/spec/models.rb CHANGED
@@ -4,6 +4,12 @@ class TaggableModel < ActiveRecord::Base
4
4
  acts_as_taggable_on :skills
5
5
  acts_as_taggable_on :needs, :offerings
6
6
  has_many :untaggable_models
7
+
8
+ attr_reader :tag_list_submethod_called
9
+ def tag_list=v
10
+ @tag_list_submethod_called = true
11
+ super
12
+ end
7
13
  end
8
14
 
9
15
  class CachedModel < ActiveRecord::Base
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts-as-taggable-on
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.3
5
- prerelease:
4
+ version: 2.4.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Michael Bleigh
@@ -13,92 +12,116 @@ date: 2012-07-16 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rails
16
- requirement: &70163811858760 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
19
  version: '3.0'
22
20
  type: :runtime
23
21
  prerelease: false
24
- version_requirements: *70163811858760
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
25
27
  - !ruby/object:Gem::Dependency
26
28
  name: rspec
27
- requirement: &70163811858260 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - ~>
31
32
  - !ruby/object:Gem::Version
32
33
  version: '2.6'
33
34
  type: :development
34
35
  prerelease: false
35
- version_requirements: *70163811858260
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.6'
36
41
  - !ruby/object:Gem::Dependency
37
42
  name: ammeter
38
- requirement: &70163811857800 !ruby/object:Gem::Requirement
39
- none: false
43
+ requirement: !ruby/object:Gem::Requirement
40
44
  requirements:
41
45
  - - ~>
42
46
  - !ruby/object:Gem::Version
43
47
  version: 0.1.3
44
48
  type: :development
45
49
  prerelease: false
46
- version_requirements: *70163811857800
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.3
47
55
  - !ruby/object:Gem::Dependency
48
56
  name: sqlite3
49
- requirement: &70163811875300 !ruby/object:Gem::Requirement
50
- none: false
57
+ requirement: !ruby/object:Gem::Requirement
51
58
  requirements:
52
- - - ! '>='
59
+ - - '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '0'
55
62
  type: :development
56
63
  prerelease: false
57
- version_requirements: *70163811875300
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: mysql2
60
- requirement: &70163811874540 !ruby/object:Gem::Requirement
61
- none: false
71
+ requirement: !ruby/object:Gem::Requirement
62
72
  requirements:
63
73
  - - ~>
64
74
  - !ruby/object:Gem::Version
65
75
  version: 0.3.7
66
76
  type: :development
67
77
  prerelease: false
68
- version_requirements: *70163811874540
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 0.3.7
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: pg
71
- requirement: &70163811873760 !ruby/object:Gem::Requirement
72
- none: false
85
+ requirement: !ruby/object:Gem::Requirement
73
86
  requirements:
74
- - - ! '>='
87
+ - - '>='
75
88
  - !ruby/object:Gem::Version
76
89
  version: '0'
77
90
  type: :development
78
91
  prerelease: false
79
- version_requirements: *70163811873760
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
80
97
  - !ruby/object:Gem::Dependency
81
98
  name: guard
82
- requirement: &70163811872980 !ruby/object:Gem::Requirement
83
- none: false
99
+ requirement: !ruby/object:Gem::Requirement
84
100
  requirements:
85
- - - ! '>='
101
+ - - '>='
86
102
  - !ruby/object:Gem::Version
87
103
  version: '0'
88
104
  type: :development
89
105
  prerelease: false
90
- version_requirements: *70163811872980
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
91
111
  - !ruby/object:Gem::Dependency
92
112
  name: guard-rspec
93
- requirement: &70163811872440 !ruby/object:Gem::Requirement
94
- none: false
113
+ requirement: !ruby/object:Gem::Requirement
95
114
  requirements:
96
- - - ! '>='
115
+ - - '>='
97
116
  - !ruby/object:Gem::Version
98
117
  version: '0'
99
118
  type: :development
100
119
  prerelease: false
101
- version_requirements: *70163811872440
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
102
125
  description: With ActsAsTaggableOn, you can tag a single model on several contexts,
103
126
  such as skills, interests, and awards. It also provides other advanced functionality.
104
127
  email: michael@intridea.com
@@ -109,12 +132,13 @@ files:
109
132
  - .gitignore
110
133
  - .rspec
111
134
  - .travis.yml
112
- - CHANGELOG
135
+ - CHANGELOG.md
113
136
  - Gemfile
114
137
  - Guardfile
115
- - MIT-LICENSE
116
- - README.rdoc
138
+ - MIT-LICENSE.md
139
+ - README.md
117
140
  - Rakefile
141
+ - UPGRADING
118
142
  - acts-as-taggable-on.gemspec
119
143
  - lib/acts-as-taggable-on.rb
120
144
  - lib/acts-as-taggable-on/version.rb
@@ -152,33 +176,40 @@ files:
152
176
  - uninstall.rb
153
177
  homepage: ''
154
178
  licenses: []
155
- post_install_message:
179
+ metadata: {}
180
+ post_install_message: |2+
181
+
182
+ ** acts-as-taggable-on version 2.4.0
183
+
184
+ Hello! This version is the first one released by me (@tilsammans),
185
+ a new maintainer on the project. It's been a while since the last
186
+ release and I am not 100% sure if any breaking changes have been
187
+ introduced. Please test your tagging functionality carefully.
188
+
189
+ From now on things should stabilize. Please check the project's
190
+ milestones and compatibilities stated in the readme. If you have
191
+ any questions, feel free to create an issue on github.
192
+
193
+ https://github.com/mbleigh/acts-as-taggable-on
194
+
156
195
  rdoc_options: []
157
196
  require_paths:
158
197
  - lib
159
198
  required_ruby_version: !ruby/object:Gem::Requirement
160
- none: false
161
199
  requirements:
162
- - - ! '>='
200
+ - - '>='
163
201
  - !ruby/object:Gem::Version
164
202
  version: '0'
165
- segments:
166
- - 0
167
- hash: -1816807933576177649
168
203
  required_rubygems_version: !ruby/object:Gem::Requirement
169
- none: false
170
204
  requirements:
171
- - - ! '>='
205
+ - - '>='
172
206
  - !ruby/object:Gem::Version
173
207
  version: '0'
174
- segments:
175
- - 0
176
- hash: -1816807933576177649
177
208
  requirements: []
178
209
  rubyforge_project:
179
- rubygems_version: 1.8.10
210
+ rubygems_version: 2.0.0
180
211
  signing_key:
181
- specification_version: 3
212
+ specification_version: 4
182
213
  summary: Advanced tagging for Rails.
183
214
  test_files:
184
215
  - spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb