acts-as-taggable-on 3.4.2 → 4.0.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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +22 -29
- data/Appraisals +8 -13
- data/CHANGELOG.md +16 -10
- data/Gemfile +2 -2
- data/README.md +88 -20
- data/Rakefile +2 -0
- data/UPGRADING.md +2 -7
- data/acts-as-taggable-on.gemspec +3 -3
- data/db/migrate/2_add_missing_unique_indices.rb +3 -2
- data/db/migrate/5_change_collation_for_tag_names.rb +9 -0
- data/db/migrate/6_add_missing_indexes.rb +12 -0
- data/gemfiles/activerecord_4.0.gemfile +2 -0
- data/gemfiles/activerecord_4.1.gemfile +2 -0
- data/gemfiles/activerecord_4.2.gemfile +2 -3
- data/gemfiles/{activerecord_3.2.gemfile → activerecord_5.0.gemfile} +2 -1
- data/lib/acts-as-taggable-on.rb +40 -5
- data/lib/acts_as_taggable_on/engine.rb +0 -1
- data/lib/acts_as_taggable_on/tag.rb +7 -3
- data/lib/acts_as_taggable_on/tag_list.rb +2 -13
- data/lib/acts_as_taggable_on/taggable/cache.rb +5 -1
- data/lib/acts_as_taggable_on/taggable/collection.rb +6 -3
- data/lib/acts_as_taggable_on/taggable/core.rb +21 -16
- data/lib/acts_as_taggable_on/taggable/ownership.rb +3 -3
- data/lib/acts_as_taggable_on/taggable/related.rb +1 -1
- data/lib/acts_as_taggable_on/taggable.rb +2 -2
- data/lib/acts_as_taggable_on/tagger.rb +12 -11
- data/lib/acts_as_taggable_on/tagging.rb +5 -15
- data/lib/acts_as_taggable_on/utils.rb +3 -4
- data/lib/acts_as_taggable_on/version.rb +1 -1
- data/lib/tasks/tags_collate_utf8.rake +21 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +10 -1
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +1 -1
- data/spec/acts_as_taggable_on/caching_spec.rb +28 -0
- data/spec/acts_as_taggable_on/related_spec.rb +9 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +27 -1
- data/spec/acts_as_taggable_on/tag_spec.rb +18 -1
- data/spec/acts_as_taggable_on/taggable_spec.rb +19 -5
- data/spec/acts_as_taggable_on/tagging_spec.rb +64 -10
- data/spec/acts_as_taggable_on/utils_spec.rb +7 -0
- data/spec/internal/db/schema.rb +7 -3
- data/spec/support/database.rb +1 -7
- metadata +14 -28
- data/gemfiles/activerecord_edge.gemfile +0 -15
- data/lib/acts_as_taggable_on/compatibility.rb +0 -35
- data/lib/acts_as_taggable_on/tag_list_parser.rb +0 -21
- data/spec/acts_as_taggable_on/tag_list_parser_spec.rb +0 -46
|
@@ -40,6 +40,10 @@ module ActsAsTaggableOn::Taggable
|
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def reset_column_information
|
|
44
|
+
super
|
|
45
|
+
@acts_as_taggable_on_cache_columns = nil
|
|
46
|
+
end
|
|
43
47
|
end
|
|
44
48
|
end
|
|
45
49
|
|
|
@@ -69,7 +73,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
69
73
|
tag_types.map(&:to_s).each do |tag_type|
|
|
70
74
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
|
71
75
|
if tag_list_cache_set_on(tag_type)
|
|
72
|
-
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join(
|
|
76
|
+
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join("#{ActsAsTaggableOn.delimiter} ")
|
|
73
77
|
self["cached_#{tag_type.singularize}_list"] = list
|
|
74
78
|
end
|
|
75
79
|
end
|
|
@@ -138,7 +138,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
138
138
|
scoped_ids = pluck(table_name_pkey)
|
|
139
139
|
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
|
|
140
140
|
else
|
|
141
|
-
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})")
|
|
141
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(except(:select).select(table_name_pkey))})")
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
tagging_scope
|
|
@@ -169,9 +169,12 @@ module ActsAsTaggableOn::Taggable
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
module CalculationMethods
|
|
172
|
-
|
|
172
|
+
# Rails 5 TODO: Remove options argument as soon we remove support to
|
|
173
|
+
# activerecord-deprecated_finders.
|
|
174
|
+
# See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation/calculations.rb#L38
|
|
175
|
+
def count(column_name = :all, options = {})
|
|
173
176
|
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
|
174
|
-
super
|
|
177
|
+
ActsAsTaggableOn::Utils.active_record5? ? super(column_name) : super(column_name, options)
|
|
175
178
|
end
|
|
176
179
|
end
|
|
177
180
|
end
|
|
@@ -23,18 +23,15 @@ module ActsAsTaggableOn::Taggable
|
|
|
23
23
|
class_eval do
|
|
24
24
|
# when preserving tag order, include order option so that for a 'tags' context
|
|
25
25
|
# the associations tag_taggings & tags are always returned in created order
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class_name: 'ActsAsTaggableOn::Tag',
|
|
36
|
-
order: taggings_order
|
|
37
|
-
|
|
26
|
+
has_many context_taggings, -> { includes(:tag).order(taggings_order).where(context: tags_type) },
|
|
27
|
+
as: :taggable,
|
|
28
|
+
class_name: ActsAsTaggableOn::Tagging,
|
|
29
|
+
dependent: :destroy
|
|
30
|
+
|
|
31
|
+
has_many context_tags, -> { order(taggings_order) },
|
|
32
|
+
class_name: ActsAsTaggableOn::Tag,
|
|
33
|
+
through: context_taggings,
|
|
34
|
+
source: :tag
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
taggable_mixin.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
@@ -246,6 +243,14 @@ module ActsAsTaggableOn::Taggable
|
|
|
246
243
|
def taggable_mixin
|
|
247
244
|
@taggable_mixin ||= Module.new
|
|
248
245
|
end
|
|
246
|
+
|
|
247
|
+
private
|
|
248
|
+
|
|
249
|
+
# Rails 5 has merged sanitize and quote_value
|
|
250
|
+
# See https://github.com/rails/rails/blob/master/activerecord/lib/active_record/sanitization.rb#L10
|
|
251
|
+
def quote_value(value, column = nil)
|
|
252
|
+
ActsAsTaggableOn::Utils.active_record5? ? super(value) : super(value, column)
|
|
253
|
+
end
|
|
249
254
|
end
|
|
250
255
|
|
|
251
256
|
# all column names are necessary for PostgreSQL group clause
|
|
@@ -254,7 +259,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
254
259
|
end
|
|
255
260
|
|
|
256
261
|
def custom_contexts
|
|
257
|
-
@custom_contexts ||=
|
|
262
|
+
@custom_contexts ||= taggings.map(&:context).uniq
|
|
258
263
|
end
|
|
259
264
|
|
|
260
265
|
def is_taggable?
|
|
@@ -333,7 +338,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
333
338
|
end
|
|
334
339
|
|
|
335
340
|
def tagging_contexts
|
|
336
|
-
|
|
341
|
+
self.class.tag_types.map(&:to_s) + custom_contexts
|
|
337
342
|
end
|
|
338
343
|
|
|
339
344
|
def process_dirty_object(context, new_list)
|
|
@@ -407,7 +412,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
407
412
|
|
|
408
413
|
# Destroy old taggings:
|
|
409
414
|
if old_tags.present?
|
|
410
|
-
taggings.not_owned.by_context(context).
|
|
415
|
+
taggings.not_owned.by_context(context).where(tag_id: old_tags).destroy_all
|
|
411
416
|
end
|
|
412
417
|
|
|
413
418
|
# Create new taggings:
|
|
@@ -432,7 +437,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
432
437
|
tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"}
|
|
433
438
|
super.delete_if {|attr| tag_lists.include? attr }
|
|
434
439
|
end
|
|
435
|
-
|
|
440
|
+
|
|
436
441
|
##
|
|
437
442
|
# Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} --
|
|
438
443
|
# context is provided so that you may conditionally use a Tag subclass
|
|
@@ -108,9 +108,9 @@ module ActsAsTaggableOn::Taggable
|
|
|
108
108
|
|
|
109
109
|
# Find all taggings that belong to the taggable (self), are owned by the owner,
|
|
110
110
|
# have the correct context, and are removed from the list.
|
|
111
|
-
ActsAsTaggableOn::Tagging.
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
ActsAsTaggableOn::Tagging.where(taggable_id: id, taggable_type: self.class.base_class.to_s,
|
|
112
|
+
tagger_type: owner.class.base_class.to_s, tagger_id: owner.id,
|
|
113
|
+
tag_id: old_tags, context: context).destroy_all if old_tags.present?
|
|
114
114
|
|
|
115
115
|
# Create new taggings:
|
|
116
116
|
new_tags.each do |tag|
|
|
@@ -43,7 +43,7 @@ module ActsAsTaggableOn::Taggable
|
|
|
43
43
|
def related_tags_for(context, klass, options = {})
|
|
44
44
|
tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || []
|
|
45
45
|
tags_to_find = tags_on(context).map { |t| t.name }.reject { |t| tags_to_ignore.include? t }
|
|
46
|
-
related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find])
|
|
46
|
+
related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, context])
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
private
|
|
@@ -80,8 +80,8 @@ module ActsAsTaggableOn
|
|
|
80
80
|
self.preserve_tag_order = preserve_tag_order
|
|
81
81
|
|
|
82
82
|
class_eval do
|
|
83
|
-
has_many :taggings, as: :taggable, dependent: :destroy, class_name: 'ActsAsTaggableOn::Tagging'
|
|
84
|
-
has_many :base_tags, through: :taggings, source: :tag, class_name: 'ActsAsTaggableOn::Tag'
|
|
83
|
+
has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging'
|
|
84
|
+
has_many :base_tags, through: :taggings, source: :tag, class_name: '::ActsAsTaggableOn::Tag'
|
|
85
85
|
|
|
86
86
|
def self.taggable?
|
|
87
87
|
true
|
|
@@ -15,18 +15,19 @@ module ActsAsTaggableOn
|
|
|
15
15
|
# end
|
|
16
16
|
def acts_as_tagger(opts={})
|
|
17
17
|
class_eval do
|
|
18
|
-
|
|
19
|
-
opts.merge(
|
|
20
|
-
as: :tagger,
|
|
21
|
-
dependent: :destroy,
|
|
22
|
-
class_name: 'ActsAsTaggableOn::Tagging'
|
|
23
|
-
)
|
|
18
|
+
owned_taggings_scope = opts.delete(:scope)
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
include ActsAsTaggableOn::Tagger::InstanceMethods
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
module ActsAsTaggableOn
|
|
2
2
|
class Tagging < ::ActiveRecord::Base #:nodoc:
|
|
3
|
-
|
|
4
|
-
attr_accessible :tag,
|
|
5
|
-
:tag_id,
|
|
6
|
-
:context,
|
|
7
|
-
:taggable,
|
|
8
|
-
:taggable_type,
|
|
9
|
-
:taggable_id,
|
|
10
|
-
:tagger,
|
|
11
|
-
:tagger_type,
|
|
12
|
-
:tagger_id if defined?(ActiveModel::MassAssignmentSecurity)
|
|
13
|
-
|
|
14
|
-
belongs_to :tag, class_name: 'ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
|
|
3
|
+
belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter
|
|
15
4
|
belongs_to :taggable, polymorphic: true
|
|
16
|
-
|
|
5
|
+
|
|
6
|
+
belongs_to :tagger, {polymorphic: true}.tap {|o| o.merge!(optional: true) if ActsAsTaggableOn::Utils.active_record5? }
|
|
17
7
|
|
|
18
8
|
scope :owned_by, ->(owner) { where(tagger: owner) }
|
|
19
9
|
scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) }
|
|
20
10
|
|
|
21
|
-
scope :by_contexts, ->(contexts
|
|
22
|
-
scope :by_context, ->(context= 'tags') { by_contexts(context.to_s) }
|
|
11
|
+
scope :by_contexts, ->(contexts) { where(context: (contexts || 'tags')) }
|
|
12
|
+
scope :by_context, ->(context = 'tags') { by_contexts(context.to_s) }
|
|
23
13
|
|
|
24
14
|
validates_presence_of :context
|
|
25
15
|
validates_presence_of :tag_id
|
|
@@ -13,16 +13,15 @@ module ActsAsTaggableOn
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def using_mysql?
|
|
16
|
-
#We should probably use regex for mysql to support prehistoric adapters
|
|
17
16
|
connection && connection.adapter_name == 'Mysql2'
|
|
18
17
|
end
|
|
19
18
|
|
|
20
19
|
def sha_prefix(string)
|
|
21
|
-
Digest::SHA1.hexdigest(
|
|
20
|
+
Digest::SHA1.hexdigest(string)[0..6]
|
|
22
21
|
end
|
|
23
22
|
|
|
24
|
-
def
|
|
25
|
-
::ActiveRecord::VERSION::MAJOR ==
|
|
23
|
+
def active_record5?
|
|
24
|
+
::ActiveRecord::VERSION::MAJOR == 5
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def like_operator
|
|
@@ -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
|
|
@@ -154,7 +154,7 @@ describe 'Acts As Taggable On' do
|
|
|
154
154
|
describe 'Tagging Contexts' do
|
|
155
155
|
it 'should eliminate duplicate tagging contexts ' do
|
|
156
156
|
TaggableModel.acts_as_taggable_on(:skills, :skills)
|
|
157
|
-
expect(TaggableModel.tag_types.freq[:skills]).
|
|
157
|
+
expect(TaggableModel.tag_types.freq[:skills]).to eq(1)
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
it 'should not contain embedded/nested arrays' do
|
|
@@ -178,6 +178,15 @@ describe 'Acts As Taggable On' do
|
|
|
178
178
|
TaggableModel.acts_as_taggable_on([nil])
|
|
179
179
|
}).to_not raise_error
|
|
180
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
|
|
181
190
|
end
|
|
182
191
|
|
|
183
192
|
context 'when tagging context ends in an "s" when singular (ex. "status", "glass", etc.)' do
|
|
@@ -72,7 +72,7 @@ describe 'acts_as_tagger' do
|
|
|
72
72
|
expect(@taggable.tag_list_on(:foo_boo)).to be_empty
|
|
73
73
|
expect(-> {
|
|
74
74
|
@tagger.tag(@taggable, with: 'this, and, that', on: :foo_boo, force: false)
|
|
75
|
-
}).to raise_error
|
|
75
|
+
}).to raise_error(RuntimeError)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
it 'should not create the tag context on-the-fly when the default is over-ridden' do
|
|
@@ -69,6 +69,34 @@ describe 'Acts As Taggable On' do
|
|
|
69
69
|
@taggable.save!
|
|
70
70
|
expect(@taggable.tag_list.sort).to eq(%w(awesome epic).sort)
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
it 'should clear the cache on reset_column_information' do
|
|
74
|
+
CachedModel.column_names
|
|
75
|
+
CachedModel.reset_column_information
|
|
76
|
+
expect(CachedModel.instance_variable_get(:@acts_as_taggable_on_cache_columns)).to eql(nil)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe 'with a custom delimiter' do
|
|
81
|
+
before(:each) do
|
|
82
|
+
@taggable = CachedModel.new(name: 'Bob Jones')
|
|
83
|
+
@another_taggable = OtherCachedModel.new(name: 'John Smith')
|
|
84
|
+
ActsAsTaggableOn.delimiter = ';'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
after(:all) do
|
|
88
|
+
ActsAsTaggableOn.delimiter = ','
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'should cache tags with custom delimiter' do
|
|
92
|
+
@taggable.update_attributes(tag_list: 'awesome; epic')
|
|
93
|
+
expect(@taggable.tag_list).to eq(['awesome', 'epic'])
|
|
94
|
+
expect(@taggable.cached_tag_list).to eq('awesome; epic')
|
|
95
|
+
|
|
96
|
+
@taggable = CachedModel.find_by_name('Bob Jones')
|
|
97
|
+
expect(@taggable.tag_list).to eq(['awesome', 'epic'])
|
|
98
|
+
expect(@taggable.cached_tag_list).to eq('awesome; epic')
|
|
99
|
+
end
|
|
72
100
|
end
|
|
73
101
|
|
|
74
102
|
describe 'CachingWithArray' do
|
|
@@ -42,6 +42,15 @@ describe 'Acts As Taggable On' do
|
|
|
42
42
|
expect(taggable1.find_related_tags_for(OtherTaggableModel)).to_not include(taggable2)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
it 'should find other related objects based on tags only from particular context' do
|
|
46
|
+
taggable1 = TaggableModel.create!(name: 'Taggable 1',tag_list: 'one, two')
|
|
47
|
+
taggable2 = TaggableModel.create!(name: 'Taggable 2',tag_list: 'three, four', skill_list: 'one, two')
|
|
48
|
+
taggable3 = TaggableModel.create!(name: 'Taggable 3',tag_list: 'one, four')
|
|
49
|
+
|
|
50
|
+
expect(taggable1.find_related_tags).to include(taggable3)
|
|
51
|
+
expect(taggable1.find_related_tags).to_not include(taggable2)
|
|
52
|
+
end
|
|
53
|
+
|
|
45
54
|
|
|
46
55
|
shared_examples "a collection" do
|
|
47
56
|
it do
|
|
@@ -83,6 +83,18 @@ describe ActsAsTaggableOn::TagList do
|
|
|
83
83
|
new_tag_list = tag_list.concat(another_tag_list)
|
|
84
84
|
expect(new_tag_list.class).to eq(ActsAsTaggableOn::TagList)
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
context 'without duplicates' do
|
|
88
|
+
let(:arr) { ['crazy', 'alien'] }
|
|
89
|
+
let(:another_tag_list) { ActsAsTaggableOn::TagList.new(*arr) }
|
|
90
|
+
it 'adds other list' do
|
|
91
|
+
expect(tag_list.concat(another_tag_list)).to eq(%w[awesome radical crazy alien])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'adds other array' do
|
|
95
|
+
expect(tag_list.concat(arr)).to eq(%w[awesome radical crazy alien])
|
|
96
|
+
end
|
|
97
|
+
end
|
|
86
98
|
end
|
|
87
99
|
|
|
88
100
|
describe '#to_s' do
|
|
@@ -92,7 +104,7 @@ describe ActsAsTaggableOn::TagList do
|
|
|
92
104
|
|
|
93
105
|
it 'should be able to call to_s on a frozen tag list' do
|
|
94
106
|
tag_list.freeze
|
|
95
|
-
expect(-> { tag_list.add('cool', 'rad,bodacious') }).to raise_error
|
|
107
|
+
expect(-> { tag_list.add('cool', 'rad,bodacious') }).to raise_error(RuntimeError)
|
|
96
108
|
expect(-> { tag_list.to_s }).to_not raise_error
|
|
97
109
|
end
|
|
98
110
|
end
|
|
@@ -114,6 +126,20 @@ describe ActsAsTaggableOn::TagList do
|
|
|
114
126
|
|
|
115
127
|
ActsAsTaggableOn.force_lowercase = false
|
|
116
128
|
end
|
|
129
|
+
|
|
130
|
+
it 'should ignore case when removing duplicates if strict_case_match is false' do
|
|
131
|
+
tag_list = ActsAsTaggableOn::TagList.new('Junglist', 'JUNGLIST', 'Junglist', 'Massive', 'MASSIVE', 'MASSIVE')
|
|
132
|
+
|
|
133
|
+
expect(tag_list.to_s).to eq('Junglist, Massive')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'should not ignore case when removing duplicates if strict_case_match is true' do
|
|
137
|
+
ActsAsTaggableOn.strict_case_match = true
|
|
138
|
+
tag_list = ActsAsTaggableOn::TagList.new('Junglist', 'JUNGLIST', 'Junglist', 'Massive', 'MASSIVE', 'MASSIVE')
|
|
139
|
+
|
|
140
|
+
expect(tag_list.to_s).to eq('Junglist, JUNGLIST, Massive, MASSIVE')
|
|
141
|
+
ActsAsTaggableOn.strict_case_match = false
|
|
142
|
+
end
|
|
117
143
|
end
|
|
118
144
|
|
|
119
145
|
describe 'custom parser' do
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
require 'spec_helper'
|
|
3
3
|
require 'db/migrate/2_add_missing_unique_indices.rb'
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
shared_examples_for 'without unique index' do
|
|
6
7
|
prepend_before(:all) { AddMissingUniqueIndices.down }
|
|
7
8
|
append_after(:all) do
|
|
@@ -54,6 +55,21 @@ describe ActsAsTaggableOn::Tag do
|
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
describe 'for context' do
|
|
59
|
+
before(:each) do
|
|
60
|
+
@user.skill_list.add('ruby')
|
|
61
|
+
@user.save
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'should return tags that have been used in the given context' do
|
|
65
|
+
expect(ActsAsTaggableOn::Tag.for_context('skills').pluck(:name)).to include('ruby')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'should not return tags that have been used in other contexts' do
|
|
69
|
+
expect(ActsAsTaggableOn::Tag.for_context('needs').pluck(:name)).to_not include('ruby')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
describe 'find or create by name' do
|
|
58
74
|
before(:each) do
|
|
59
75
|
@tag.name = 'awesome'
|
|
@@ -309,7 +325,7 @@ describe ActsAsTaggableOn::Tag do
|
|
|
309
325
|
tag.save!
|
|
310
326
|
end
|
|
311
327
|
end
|
|
312
|
-
|
|
328
|
+
|
|
313
329
|
it 'should find the most popular tags' do
|
|
314
330
|
expect(ActsAsTaggableOn::Tag.most_used(3).first.name).to eq("golden_syrup")
|
|
315
331
|
expect(ActsAsTaggableOn::Tag.most_used(3).length).to eq(3)
|
|
@@ -320,4 +336,5 @@ describe ActsAsTaggableOn::Tag do
|
|
|
320
336
|
expect(ActsAsTaggableOn::Tag.least_used(3).length).to eq(3)
|
|
321
337
|
end
|
|
322
338
|
end
|
|
339
|
+
|
|
323
340
|
end
|
|
@@ -119,6 +119,21 @@ describe 'Taggable' do
|
|
|
119
119
|
expect(@taggable.tag_counts_on(:tags).length).to eq(2)
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
context 'tag_counts on a collection' do
|
|
123
|
+
context 'a select clause is specified on the collection' do
|
|
124
|
+
it 'should return tag counts without raising an error' do
|
|
125
|
+
expect(TaggableModel.tag_counts_on(:tags)).to be_empty
|
|
126
|
+
|
|
127
|
+
@taggable.tag_list = %w(awesome epic)
|
|
128
|
+
@taggable.save
|
|
129
|
+
|
|
130
|
+
expect {
|
|
131
|
+
expect(TaggableModel.select(:name).tag_counts_on(:tags).length).to eq(2)
|
|
132
|
+
}.not_to raise_error
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
122
137
|
it 'should have tags_on' do
|
|
123
138
|
expect(TaggableModel.tags_on(:tags)).to be_empty
|
|
124
139
|
|
|
@@ -226,8 +241,10 @@ describe 'Taggable' do
|
|
|
226
241
|
it "should be able to find a tag using dates" do
|
|
227
242
|
@taggable.skill_list = "ruby"
|
|
228
243
|
@taggable.save
|
|
244
|
+
today = Date.today.to_time.utc
|
|
245
|
+
tomorrow = Date.tomorrow.to_time.utc
|
|
229
246
|
|
|
230
|
-
expect(TaggableModel.tagged_with("ruby", :start_at =>
|
|
247
|
+
expect(TaggableModel.tagged_with("ruby", :start_at => today, :end_at => tomorrow).count).to eq(1)
|
|
231
248
|
end
|
|
232
249
|
|
|
233
250
|
it "shouldn't be able to find a tag outside date range" do
|
|
@@ -380,7 +397,6 @@ describe 'Taggable' do
|
|
|
380
397
|
# Test specific join syntaxes:
|
|
381
398
|
frank.untaggable_models.create!
|
|
382
399
|
expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tag_counts.size).to eq(2)
|
|
383
|
-
expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tag_counts.size).to eq(2)
|
|
384
400
|
expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tag_counts.size).to eq(2)
|
|
385
401
|
end
|
|
386
402
|
|
|
@@ -395,7 +411,6 @@ describe 'Taggable' do
|
|
|
395
411
|
# Test specific join syntaxes:
|
|
396
412
|
frank.untaggable_models.create!
|
|
397
413
|
expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tags.size).to eq(2)
|
|
398
|
-
expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tags.size).to eq(2)
|
|
399
414
|
expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tags.size).to eq(2)
|
|
400
415
|
end
|
|
401
416
|
|
|
@@ -459,7 +474,7 @@ describe 'Taggable' do
|
|
|
459
474
|
|
|
460
475
|
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank, steve])
|
|
461
476
|
expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, exclude: true).to_a).to eq([jim])
|
|
462
|
-
expect(TaggableModel.tagged_with('ji', wild: true, any: true).to_a
|
|
477
|
+
expect(TaggableModel.tagged_with('ji', wild: true, any: true).to_a =~ [frank, jim])
|
|
463
478
|
end
|
|
464
479
|
end
|
|
465
480
|
|
|
@@ -743,7 +758,6 @@ describe 'Taggable' do
|
|
|
743
758
|
|
|
744
759
|
context 'Model.limit(x).tag_counts.sum(:tags_count)' do
|
|
745
760
|
it 'should not break on Mysql' do
|
|
746
|
-
# Activerecord 3.2 return a string
|
|
747
761
|
expect(TaggableModel.limit(2).tag_counts.sum('tags_count').to_i).to eq(5)
|
|
748
762
|
end
|
|
749
763
|
end
|
|
@@ -49,15 +49,69 @@ describe ActsAsTaggableOn::Tagging do
|
|
|
49
49
|
ActsAsTaggableOn.remove_unused_tags = previous_setting
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
describe 'context scopes' do
|
|
53
|
+
before do
|
|
54
|
+
@tagging_2 = ActsAsTaggableOn::Tagging.new
|
|
55
|
+
@tagging_3 = ActsAsTaggableOn::Tagging.new
|
|
56
|
+
|
|
57
|
+
@tagger = User.new
|
|
58
|
+
@tagger_2 = User.new
|
|
59
|
+
|
|
60
|
+
@tagging.taggable = TaggableModel.create(name: "Black holes")
|
|
61
|
+
@tagging.tag = ActsAsTaggableOn::Tag.create(name: "Physics")
|
|
62
|
+
@tagging.tagger = @tagger
|
|
63
|
+
@tagging.context = 'Science'
|
|
64
|
+
@tagging.save
|
|
65
|
+
|
|
66
|
+
@tagging_2.taggable = TaggableModel.create(name: "Satellites")
|
|
67
|
+
@tagging_2.tag = ActsAsTaggableOn::Tag.create(name: "Technology")
|
|
68
|
+
@tagging_2.tagger = @tagger_2
|
|
69
|
+
@tagging_2.context = 'Science'
|
|
70
|
+
@tagging_2.save
|
|
71
|
+
|
|
72
|
+
@tagging_3.taggable = TaggableModel.create(name: "Satellites")
|
|
73
|
+
@tagging_3.tag = ActsAsTaggableOn::Tag.create(name: "Engineering")
|
|
74
|
+
@tagging_3.tagger = @tagger_2
|
|
75
|
+
@tagging_3.context = 'Astronomy'
|
|
76
|
+
@tagging_3.save
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '.owned_by' do
|
|
81
|
+
it "should belong to a specific user" do
|
|
82
|
+
expect(ActsAsTaggableOn::Tagging.owned_by(@tagger).first).to eq(@tagging)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe '.by_context' do
|
|
87
|
+
it "should be found by context" do
|
|
88
|
+
expect(ActsAsTaggableOn::Tagging.by_context('Science').length).to eq(2);
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '.by_contexts' do
|
|
93
|
+
it "should find taggings by contexts" do
|
|
94
|
+
expect(ActsAsTaggableOn::Tagging.by_contexts(['Science', 'Astronomy']).first).to eq(@tagging);
|
|
95
|
+
expect(ActsAsTaggableOn::Tagging.by_contexts(['Science', 'Astronomy']).second).to eq(@tagging_2);
|
|
96
|
+
expect(ActsAsTaggableOn::Tagging.by_contexts(['Science', 'Astronomy']).third).to eq(@tagging_3);
|
|
97
|
+
expect(ActsAsTaggableOn::Tagging.by_contexts(['Science', 'Astronomy']).length).to eq(3);
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe '.not_owned' do
|
|
102
|
+
before do
|
|
103
|
+
@tagging_4 = ActsAsTaggableOn::Tagging.new
|
|
104
|
+
@tagging_4.taggable = TaggableModel.create(name: "Gravity")
|
|
105
|
+
@tagging_4.tag = ActsAsTaggableOn::Tag.create(name: "Space")
|
|
106
|
+
@tagging_4.context = "Science"
|
|
107
|
+
@tagging_4.save
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "should found the taggings that do not have owner" do
|
|
111
|
+
expect(ActsAsTaggableOn::Tagging.all.length).to eq(4)
|
|
112
|
+
expect(ActsAsTaggableOn::Tagging.not_owned.length).to eq(1)
|
|
113
|
+
expect(ActsAsTaggableOn::Tagging.not_owned.first).to eq(@tagging_4)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
61
116
|
end
|
|
62
|
-
|
|
63
117
|
end
|
|
@@ -13,4 +13,11 @@ describe ActsAsTaggableOn::Utils do
|
|
|
13
13
|
expect(ActsAsTaggableOn::Utils.like_operator).to eq('LIKE')
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
describe '#sha_prefix' do
|
|
18
|
+
it 'should return a consistent prefix for a given word' do
|
|
19
|
+
expect(ActsAsTaggableOn::Utils.sha_prefix('kittens')).to eq(ActsAsTaggableOn::Utils.sha_prefix('kittens'))
|
|
20
|
+
expect(ActsAsTaggableOn::Utils.sha_prefix('puppies')).not_to eq(ActsAsTaggableOn::Utils.sha_prefix('kittens'))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
16
23
|
end
|
data/spec/internal/db/schema.rb
CHANGED
|
@@ -7,12 +7,15 @@ ActiveRecord::Schema.define version: 0 do
|
|
|
7
7
|
add_index 'tags', ['name'], name: 'index_tags_on_name', unique: true
|
|
8
8
|
|
|
9
9
|
create_table :taggings, force: true do |t|
|
|
10
|
-
t.
|
|
10
|
+
t.integer :tag_id
|
|
11
11
|
|
|
12
12
|
# You should make sure that the column created is
|
|
13
13
|
# long enough to store the required class names.
|
|
14
|
-
t.
|
|
15
|
-
t.
|
|
14
|
+
t.string :taggable_type
|
|
15
|
+
t.integer :taggable_id
|
|
16
|
+
|
|
17
|
+
t.string :tagger_type
|
|
18
|
+
t.integer :tagger_id
|
|
16
19
|
|
|
17
20
|
# Limit is created to prevent MySQL error on index
|
|
18
21
|
# length for MyISAM table type: http://bit.ly/vgW2Ql
|
|
@@ -23,6 +26,7 @@ ActiveRecord::Schema.define version: 0 do
|
|
|
23
26
|
add_index 'taggings',
|
|
24
27
|
['tag_id', 'taggable_id', 'taggable_type', 'context', 'tagger_id', 'tagger_type'],
|
|
25
28
|
unique: true, name: 'taggings_idx'
|
|
29
|
+
add_index 'taggings', :tag_id , name: 'index_taggings_on_tag_id'
|
|
26
30
|
|
|
27
31
|
# above copied from
|
|
28
32
|
# generators/acts_as_taggable_on/migration/migration_generator
|
data/spec/support/database.rb
CHANGED
|
@@ -13,13 +13,7 @@ if File.exist?(database_yml)
|
|
|
13
13
|
config = ActiveRecord::Base.configurations[db_name]
|
|
14
14
|
|
|
15
15
|
begin
|
|
16
|
-
|
|
17
|
-
#TODO, remove when activerecord 3 support is dropped
|
|
18
|
-
if ActsAsTaggableOn::Utils.active_record4?
|
|
19
|
-
ActiveRecord::Base.establish_connection(db_name.to_sym)
|
|
20
|
-
else
|
|
21
|
-
ActiveRecord::Base.establish_connection(db_name)
|
|
22
|
-
end
|
|
16
|
+
ActiveRecord::Base.establish_connection(db_name.to_sym)
|
|
23
17
|
ActiveRecord::Base.connection
|
|
24
18
|
rescue
|
|
25
19
|
case db_name
|