acts_as_taggable3 2.0.beta2
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/CHANGELOG +212 -0
- data/MIT-LICENSE +20 -0
- data/README +157 -0
- data/lib/acts_as_taggable.rb +246 -0
- data/lib/generators/acts_as_taggable_migration/acts_as_taggable_migration_generator.rb +24 -0
- data/lib/generators/acts_as_taggable_migration/templates/migration.rb +22 -0
- data/lib/tag.rb +59 -0
- data/lib/tag_list.rb +109 -0
- data/lib/tagging.rb +16 -0
- data/lib/tags_helper.rb +13 -0
- data/test/abstract_unit.rb +106 -0
- data/test/acts_as_taggable_test.rb +384 -0
- data/test/fixtures/magazine.rb +3 -0
- data/test/fixtures/photo.rb +8 -0
- data/test/fixtures/post.rb +7 -0
- data/test/fixtures/special_post.rb +2 -0
- data/test/fixtures/subscription.rb +4 -0
- data/test/fixtures/user.rb +7 -0
- data/test/schema.rb +48 -0
- data/test/tag_list_test.rb +119 -0
- data/test/tag_test.rb +63 -0
- data/test/tagging_test.rb +11 -0
- data/test/tags_helper_test.rb +25 -0
- metadata +121 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class ActsAsTaggableMigrationGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
def self.source_root
|
8
|
+
@source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.next_migration_number(dirname)
|
12
|
+
Time.now.strftime("%Y%m%d%H%M%S")
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_migration
|
16
|
+
migration_template "migration.rb", File.join("db/migrate", "#{file_name}.rb")
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def file_name
|
22
|
+
"acts_as_taggable_migration"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ActsAsTaggableMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :tags do |t|
|
4
|
+
t.string :name
|
5
|
+
end
|
6
|
+
|
7
|
+
create_table :taggings do |t|
|
8
|
+
t.references :tag
|
9
|
+
t.references :taggable, :polymorphic => true
|
10
|
+
t.datetime :created_at
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :tags, :name
|
14
|
+
add_index :taggings, :tag_id
|
15
|
+
add_index :taggings, [:taggable_id, :taggable_type]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
drop_table :taggings
|
20
|
+
drop_table :tags
|
21
|
+
end
|
22
|
+
end
|
data/lib/tag.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'active_support/core_ext/module/deprecation'
|
2
|
+
|
3
|
+
class Tag < ActiveRecord::Base
|
4
|
+
cattr_accessor :destroy_unused
|
5
|
+
self.destroy_unused = false
|
6
|
+
|
7
|
+
has_many :taggings, :dependent => :delete_all
|
8
|
+
|
9
|
+
validates_presence_of :name
|
10
|
+
validates_uniqueness_of :name
|
11
|
+
|
12
|
+
def ==(object)
|
13
|
+
super || (object.is_a?(Tag) && name == object.name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
name
|
18
|
+
end
|
19
|
+
|
20
|
+
def count
|
21
|
+
read_attribute(:count).to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def find_or_create_with_like_by_name(name)
|
26
|
+
where(arel_table[:name].matches(name)).first || create(:name => name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Calculate the tag counts for all tags.
|
30
|
+
#
|
31
|
+
# - +:start_at+ - restrict the tags to those created after a certain time
|
32
|
+
# - +:end_at+ - restrict the tags to those created before a certain time
|
33
|
+
# - +:at_least+ - exclude tags with a frequency less than the given value
|
34
|
+
# - +:at_most+ - exclude tags with a frequency greater than the given value
|
35
|
+
#
|
36
|
+
# Deprecated:
|
37
|
+
#
|
38
|
+
# - +:conditions+
|
39
|
+
# - +:limit+
|
40
|
+
# - +:order+
|
41
|
+
#
|
42
|
+
def counts(options = {})
|
43
|
+
options.assert_valid_keys :start_at, :end_at, :at_least, :at_most, :conditions, :limit, :order
|
44
|
+
|
45
|
+
tags = joins(:taggings).group(:name)
|
46
|
+
tags = tags.having(['count >= ?', options[:at_least]]) if options[:at_least]
|
47
|
+
tags = tags.having(['count <= ?', options[:at_most]]) if options[:at_most]
|
48
|
+
tags = tags.where("#{Tagging.quoted_table_name}.created_at >= ?", options[:start_at]) if options[:start_at]
|
49
|
+
tags = tags.where("#{Tagging.quoted_table_name}.created_at <= ?", options[:end_at]) if options[:end_at]
|
50
|
+
|
51
|
+
# TODO: deprecation warning
|
52
|
+
tags = tags.where(options[:conditions]) if options[:conditions]
|
53
|
+
tags = tags.limit(options[:limit]) if options[:limit]
|
54
|
+
tags = tags.order(options[:order]) if options[:order]
|
55
|
+
|
56
|
+
tags.select("#{quoted_table_name}.*, COUNT(#{quoted_table_name}.id) AS count")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/tag_list.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
class TagList < Array
|
2
|
+
cattr_accessor :delimiter
|
3
|
+
self.delimiter = ','
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
add(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Add tags to the tag_list. Duplicate or blank tags will be ignored.
|
10
|
+
#
|
11
|
+
# tag_list.add("Fun", "Happy")
|
12
|
+
#
|
13
|
+
# Use the <tt>:parse</tt> option to add an unparsed tag string.
|
14
|
+
#
|
15
|
+
# tag_list.add("Fun, Happy", :parse => true)
|
16
|
+
def add(*names)
|
17
|
+
extract_and_apply_options!(names)
|
18
|
+
concat(names)
|
19
|
+
clean!
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Remove specific tags from the tag_list.
|
24
|
+
#
|
25
|
+
# tag_list.remove("Sad", "Lonely")
|
26
|
+
#
|
27
|
+
# Like #add, the <tt>:parse</tt> option can be used to remove multiple tags in a string.
|
28
|
+
#
|
29
|
+
# tag_list.remove("Sad, Lonely", :parse => true)
|
30
|
+
def remove(*names)
|
31
|
+
extract_and_apply_options!(names)
|
32
|
+
delete_if { |name| names.include?(name) }
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Toggle the presence of the given tags.
|
37
|
+
# If a tag is already in the list it is removed, otherwise it is added.
|
38
|
+
def toggle(*names)
|
39
|
+
extract_and_apply_options!(names)
|
40
|
+
|
41
|
+
names.each do |name|
|
42
|
+
include?(name) ? delete(name) : push(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
clean!
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Transform the tag_list into a tag string suitable for edting in a form.
|
50
|
+
# The tags are joined with <tt>TagList.delimiter</tt> and quoted if necessary.
|
51
|
+
#
|
52
|
+
# tag_list = TagList.new("Round", "Square,Cube")
|
53
|
+
# tag_list.to_s # 'Round, "Square,Cube"'
|
54
|
+
def to_s
|
55
|
+
clean!
|
56
|
+
|
57
|
+
map do |name|
|
58
|
+
name.include?(delimiter) ? "\"#{name}\"" : name
|
59
|
+
end.join(delimiter.ends_with?(" ") ? delimiter : "#{delimiter} ")
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
# Remove whitespace, duplicates, and blanks.
|
64
|
+
def clean!
|
65
|
+
reject!(&:blank?)
|
66
|
+
map!(&:strip)
|
67
|
+
uniq!
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_and_apply_options!(args)
|
71
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
72
|
+
options.assert_valid_keys :parse
|
73
|
+
|
74
|
+
if options[:parse]
|
75
|
+
args.map! { |a| self.class.from(a) }
|
76
|
+
end
|
77
|
+
|
78
|
+
args.flatten!
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
# Returns a new TagList using the given tag string.
|
83
|
+
#
|
84
|
+
# tag_list = TagList.from("One , Two, Three")
|
85
|
+
# tag_list # ["One", "Two", "Three"]
|
86
|
+
def from(source)
|
87
|
+
tag_list = new
|
88
|
+
|
89
|
+
case source
|
90
|
+
when Array
|
91
|
+
tag_list.add(source)
|
92
|
+
else
|
93
|
+
string = source.to_s.dup
|
94
|
+
|
95
|
+
# Parse the quoted tags
|
96
|
+
[
|
97
|
+
/\s*#{delimiter}\s*(['"])(.*?)\1\s*/,
|
98
|
+
/^\s*(['"])(.*?)\1\s*#{delimiter}?/
|
99
|
+
].each do |re|
|
100
|
+
string.gsub!(re) { tag_list << $2; "" }
|
101
|
+
end
|
102
|
+
|
103
|
+
tag_list.add(string.split(delimiter))
|
104
|
+
end
|
105
|
+
|
106
|
+
tag_list
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/tagging.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Tagging < ActiveRecord::Base #:nodoc:
|
2
|
+
belongs_to :tag
|
3
|
+
belongs_to :taggable, :polymorphic => true
|
4
|
+
|
5
|
+
after_destroy :destroy_tag_if_unused
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def destroy_tag_if_unused
|
10
|
+
if Tag.destroy_unused
|
11
|
+
if tag.taggings.count.zero?
|
12
|
+
tag.destroy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/tags_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module TagsHelper
|
2
|
+
# See the README for an example using tag_cloud.
|
3
|
+
def tag_cloud(tags, classes)
|
4
|
+
return if tags.all.empty?
|
5
|
+
|
6
|
+
max_count = tags.sort_by(&:count).last.count.to_f
|
7
|
+
|
8
|
+
tags.each do |tag|
|
9
|
+
index = ((tag.count / max_count) * (classes.size - 1)).round
|
10
|
+
yield tag, classes[index]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require File.dirname(__FILE__) + '/../../../../config/environment'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
gem 'activesupport'
|
8
|
+
gem 'activerecord'
|
9
|
+
gem 'actionpack'
|
10
|
+
require 'active_support/dependencies'
|
11
|
+
require 'active_record'
|
12
|
+
require 'action_controller'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Search for fixtures first
|
16
|
+
fixture_path = File.dirname(__FILE__) + '/fixtures/'
|
17
|
+
ActiveSupport::Dependencies.autoload_paths.insert(0, fixture_path)
|
18
|
+
|
19
|
+
require "active_record/test_case"
|
20
|
+
require "active_record/fixtures"
|
21
|
+
|
22
|
+
require File.dirname(__FILE__) + '/../lib/acts_as_taggable'
|
23
|
+
require_dependency File.dirname(__FILE__) + '/../lib/tag_list'
|
24
|
+
require_dependency File.dirname(__FILE__) + '/../lib/tags_helper'
|
25
|
+
|
26
|
+
ENV['DB'] ||= 'sqlite3'
|
27
|
+
|
28
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
|
29
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
30
|
+
ActiveRecord::Base.establish_connection(ENV['DB'])
|
31
|
+
|
32
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
33
|
+
|
34
|
+
class ActiveSupport::TestCase #:nodoc:
|
35
|
+
include ActiveRecord::TestFixtures
|
36
|
+
|
37
|
+
self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
38
|
+
|
39
|
+
self.use_transactional_fixtures = true
|
40
|
+
self.use_instantiated_fixtures = false
|
41
|
+
|
42
|
+
fixtures :all
|
43
|
+
|
44
|
+
def assert_equivalent(expected, actual, message = nil)
|
45
|
+
if expected.first.is_a?(ActiveRecord::Base)
|
46
|
+
assert_equal expected.sort_by(&:id), actual.sort_by(&:id), message
|
47
|
+
else
|
48
|
+
assert_equal expected.sort, actual.sort, message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def assert_tag_counts(tags, expected_values)
|
53
|
+
# Map the tag fixture names to real tag names
|
54
|
+
expected_values = expected_values.inject({}) do |hash, (tag, count)|
|
55
|
+
hash[tags(tag).name] = count
|
56
|
+
hash
|
57
|
+
end
|
58
|
+
|
59
|
+
tags.each do |tag|
|
60
|
+
value = expected_values.delete(tag.name)
|
61
|
+
|
62
|
+
assert_not_nil value, "Expected count for #{tag.name} was not provided"
|
63
|
+
assert_equal value, tag.count, "Expected value of #{value} for #{tag.name}, but was #{tag.count}"
|
64
|
+
end
|
65
|
+
|
66
|
+
unless expected_values.empty?
|
67
|
+
assert false, "The following tag counts were not present: #{expected_values.inspect}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_queries(num = 1)
|
72
|
+
$query_count = 0
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
|
76
|
+
end
|
77
|
+
|
78
|
+
def assert_no_queries(&block)
|
79
|
+
assert_queries(0, &block)
|
80
|
+
end
|
81
|
+
|
82
|
+
# From Rails trunk
|
83
|
+
def assert_difference(expressions, difference = 1, message = nil, &block)
|
84
|
+
expression_evaluations = [expressions].flatten.collect{|expression| lambda { eval(expression, block.binding) } }
|
85
|
+
|
86
|
+
original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call }
|
87
|
+
yield
|
88
|
+
expression_evaluations.each_with_index do |expression, i|
|
89
|
+
assert_equal original_values[i] + difference, expression.call, message
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def assert_no_difference(expressions, message = nil, &block)
|
94
|
+
assert_difference expressions, 0, message, &block
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
ActiveRecord::Base.connection.class.class_eval do
|
99
|
+
def execute_with_counting(sql, name = nil, &block)
|
100
|
+
$query_count ||= 0
|
101
|
+
$query_count += 1
|
102
|
+
execute_without_counting(sql, name, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
alias_method_chain :execute, :counting
|
106
|
+
end
|
@@ -0,0 +1,384 @@
|
|
1
|
+
require File.expand_path('../abstract_unit', __FILE__)
|
2
|
+
|
3
|
+
class ActsAsTaggableOnSteroidsTest < ActiveSupport::TestCase
|
4
|
+
def test_find_related_tags_with
|
5
|
+
assert_equivalent [tags(:good), tags(:bad), tags(:question)], Post.find_related_tags("nature")
|
6
|
+
assert_equivalent [tags(:nature)], Post.find_related_tags([tags(:good)])
|
7
|
+
assert_equivalent [tags(:bad), tags(:question)], Post.find_related_tags(["Very Good", "Nature"])
|
8
|
+
assert_equivalent [tags(:bad), tags(:question)], Post.find_related_tags([tags(:good), tags(:nature)])
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_find_tagged_with_include_and_order
|
12
|
+
assert_equal photos(:sam_sky, :sam_flower, :jonathan_dog), Photo.find_tagged_with("Nature", :order => "photos.title DESC", :include => :user)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_find_related_tags_with_non_existent_tags
|
16
|
+
assert_equal [], Post.find_related_tags("ABCDEFG")
|
17
|
+
assert_equal [], Post.find_related_tags(['HIJKLM'])
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_find_related_tags_with_nothing
|
21
|
+
assert_equal [], Post.find_related_tags("")
|
22
|
+
assert_equal [], Post.find_related_tags([])
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_find_tagged_with
|
26
|
+
assert_equivalent [posts(:jonathan_sky), posts(:sam_flowers)], Post.find_tagged_with('"Very good"')
|
27
|
+
assert_equal Post.find_tagged_with('"Very good"'), Post.find_tagged_with(['Very good'])
|
28
|
+
assert_equal Post.find_tagged_with('"Very good"'), Post.find_tagged_with([tags(:good)])
|
29
|
+
|
30
|
+
assert_equivalent [photos(:jonathan_dog), photos(:sam_flower), photos(:sam_sky)], Photo.find_tagged_with('Nature')
|
31
|
+
assert_equal Photo.find_tagged_with('Nature'), Photo.find_tagged_with(['Nature'])
|
32
|
+
assert_equal Photo.find_tagged_with('Nature'), Photo.find_tagged_with([tags(:nature)])
|
33
|
+
|
34
|
+
assert_equivalent [photos(:jonathan_bad_cat), photos(:jonathan_dog), photos(:jonathan_questioning_dog)], Photo.find_tagged_with('"Crazy animal" Bad')
|
35
|
+
assert_equal Photo.find_tagged_with('"Crazy animal" Bad'), Photo.find_tagged_with(['Crazy animal', 'Bad'])
|
36
|
+
assert_equal Photo.find_tagged_with('"Crazy animal" Bad'), Photo.find_tagged_with([tags(:animal), tags(:bad)])
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_find_tagged_with_nothing
|
40
|
+
assert_equal [], Post.find_tagged_with("")
|
41
|
+
assert_equal [], Post.find_tagged_with([])
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_find_tagged_with_nonexistant_tags
|
45
|
+
assert_equal [], Post.find_tagged_with('ABCDEFG')
|
46
|
+
assert_equal [], Photo.find_tagged_with(['HIJKLM'])
|
47
|
+
assert_equal [], Photo.find_tagged_with([Tag.new(:name => 'unsaved tag')])
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_find_tagged_with_match_all
|
51
|
+
assert_equivalent [photos(:jonathan_dog)], Photo.find_tagged_with('Crazy animal, "Nature"', :match_all => true)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_find_tagged_with_match_all_and_include
|
55
|
+
assert_equivalent [posts(:jonathan_sky), posts(:sam_flowers)], Post.find_tagged_with(['Very good', 'Nature'], :match_all => true, :include => :tags)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_find_tagged_with_conditions
|
59
|
+
assert_equal [], Post.find_tagged_with('"Very good", Nature', :conditions => '1=0')
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_find_tagged_with_duplicates_options_hash
|
63
|
+
options = { :conditions => '1=1' }.freeze
|
64
|
+
assert_nothing_raised { Post.find_tagged_with("Nature", options) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_find_tagged_with_exclusions
|
68
|
+
assert_equivalent [photos(:jonathan_questioning_dog), photos(:jonathan_bad_cat)], Photo.find_tagged_with("Nature", :exclude => true)
|
69
|
+
assert_equivalent [posts(:jonathan_grass), posts(:jonathan_rain), posts(:jonathan_cloudy), posts(:jonathan_still_cloudy)], Post.find_tagged_with("'Very good', Bad", :exclude => true)
|
70
|
+
end
|
71
|
+
|
72
|
+
# def test_find_options_for_find_tagged_with_no_tags_returns_empty_hash
|
73
|
+
# assert_equal Hash.new, Post.find_options_for_find_tagged_with("")
|
74
|
+
# assert_equal Hash.new, Post.find_options_for_find_tagged_with([nil])
|
75
|
+
# end
|
76
|
+
|
77
|
+
# def test_find_options_for_find_tagged_with_leaves_arguments_unchanged
|
78
|
+
# original_tags = photos(:jonathan_questioning_dog).tags.dup
|
79
|
+
# Photo.find_options_for_find_tagged_with(photos(:jonathan_questioning_dog).tags)
|
80
|
+
# assert_equal original_tags, photos(:jonathan_questioning_dog).tags
|
81
|
+
# end
|
82
|
+
|
83
|
+
# def test_find_options_for_find_tagged_with_respects_custom_table_name
|
84
|
+
# Tagging.table_name = "categorisations"
|
85
|
+
# Tag.table_name = "categories"
|
86
|
+
#
|
87
|
+
# options = Photo.find_options_for_find_tagged_with("Hello")
|
88
|
+
#
|
89
|
+
# assert_no_match(/ taggings /, options[:joins])
|
90
|
+
# assert_no_match(/ tags /, options[:joins])
|
91
|
+
#
|
92
|
+
# assert_match(/ categorisations /, options[:joins])
|
93
|
+
# assert_match(/ categories /, options[:joins])
|
94
|
+
# ensure
|
95
|
+
# Tagging.table_name = "taggings"
|
96
|
+
# Tag.table_name = "tags"
|
97
|
+
# end
|
98
|
+
|
99
|
+
def test_include_tags_on_find_tagged_with
|
100
|
+
assert_nothing_raised do
|
101
|
+
Photo.find_tagged_with('Nature', :include => :tags)
|
102
|
+
Photo.find_tagged_with("Nature", :include => { :taggings => :tag })
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_basic_tag_counts_on_class
|
107
|
+
assert_tag_counts Post.tag_counts, :good => 2, :nature => 7, :question => 1, :bad => 1
|
108
|
+
assert_tag_counts Photo.tag_counts, :good => 1, :nature => 3, :question => 1, :bad => 1, :animal => 3
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_tag_counts_on_class_with_date_conditions
|
112
|
+
assert_tag_counts Post.tag_counts(:start_at => Date.new(2006, 8, 4)), :good => 1, :nature => 5, :question => 1, :bad => 1
|
113
|
+
assert_tag_counts Post.tag_counts(:end_at => Date.new(2006, 8, 6)), :good => 1, :nature => 4, :question => 1
|
114
|
+
assert_tag_counts Post.tag_counts(:start_at => Date.new(2006, 8, 5), :end_at => Date.new(2006, 8, 10)), :good => 1, :nature => 4, :bad => 1
|
115
|
+
|
116
|
+
assert_tag_counts Photo.tag_counts(:start_at => Date.new(2006, 8, 12), :end_at => Date.new(2006, 8, 19)), :good => 1, :nature => 2, :bad => 1, :question => 1, :animal => 3
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_tag_counts_on_class_with_frequencies
|
120
|
+
assert_tag_counts Photo.tag_counts(:at_least => 2), :nature => 3, :animal => 3
|
121
|
+
assert_tag_counts Photo.tag_counts(:at_most => 2), :good => 1, :question => 1, :bad => 1
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_tag_counts_on_class_with_frequencies_and_conditions
|
125
|
+
assert_tag_counts Photo.tag_counts(:at_least => 2, :conditions => '1=1'), :nature => 3, :animal => 3
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_tag_counts_duplicates_options_hash
|
129
|
+
options = { :at_least => 2, :conditions => '1=1' }.freeze
|
130
|
+
assert_nothing_raised { Photo.tag_counts(options) }
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_tag_counts_with_limit
|
134
|
+
assert_equal 2, Photo.tag_counts(:limit => 2).all.size
|
135
|
+
assert_equal 1, Post.tag_counts(:at_least => 4, :limit => 2).all.size
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_tag_counts_with_limit_and_order
|
139
|
+
assert_equal [tags(:nature), tags(:good)], Post.tag_counts(:order => 'count desc', :limit => 2)
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_tag_counts_on_association
|
143
|
+
assert_tag_counts users(:jonathan).posts.tag_counts, :good => 1, :nature => 5, :question => 1
|
144
|
+
assert_tag_counts users(:sam).posts.tag_counts, :good => 1, :nature => 2, :bad => 1
|
145
|
+
|
146
|
+
assert_tag_counts users(:jonathan).photos.tag_counts, :animal => 3, :nature => 1, :question => 1, :bad => 1
|
147
|
+
assert_tag_counts users(:sam).photos.tag_counts, :nature => 2, :good => 1
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_tag_counts_on_association_with_options
|
151
|
+
assert_equal [], users(:jonathan).posts.tag_counts(:conditions => '1=0')
|
152
|
+
assert_tag_counts users(:jonathan).posts.tag_counts(:at_most => 2), :good => 1, :question => 1
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_tag_counts_on_has_many_through
|
156
|
+
assert_tag_counts users(:jonathan).magazines.tag_counts, :good => 1
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_tag_counts_on_model_instance
|
160
|
+
assert_tag_counts photos(:jonathan_dog).tag_counts, :animal => 3, :nature => 3
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_tag_counts_on_model_instance_merges_conditions
|
164
|
+
assert_tag_counts photos(:jonathan_dog).tag_counts(:conditions => "tags.name = 'Crazy animal'"), :animal => 3
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_tag_counts_on_model_instance_with_no_tags
|
168
|
+
photo = Photo.create!
|
169
|
+
|
170
|
+
assert_tag_counts photo.tag_counts, {}
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_tag_counts_should_sanitize_scope_conditions
|
174
|
+
Photo.send :with_scope, :find => { :conditions => ["tags.id = ?", tags(:animal).id] } do
|
175
|
+
assert_tag_counts Photo.tag_counts, :animal => 3
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# # NOTE: the SQL FROM statement won't change when changing table_name dynamically...
|
180
|
+
# def test_tag_counts_respects_custom_table_names
|
181
|
+
# Tagging.table_name = "categorisations"
|
182
|
+
# Tag.table_name = "categories"
|
183
|
+
#
|
184
|
+
# sql = Photo.tag_counts(:start_at => 2.weeks.ago, :end_at => Date.today).to_sql
|
185
|
+
#
|
186
|
+
# assert_no_match /taggings/, sql
|
187
|
+
# assert_no_match /tags/, sql
|
188
|
+
#
|
189
|
+
# assert_match /categorisations/, sql
|
190
|
+
# assert_match /categories/, sql
|
191
|
+
# ensure
|
192
|
+
# Tagging.table_name = "taggings"
|
193
|
+
# Tag.table_name = "tags"
|
194
|
+
# end
|
195
|
+
|
196
|
+
def test_tag_list_reader
|
197
|
+
assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
|
198
|
+
assert_equivalent ["Bad", "Crazy animal"], photos(:jonathan_bad_cat).tag_list
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_reassign_tag_list
|
202
|
+
assert_equivalent ["Nature", "Question"], posts(:jonathan_rain).tag_list
|
203
|
+
posts(:jonathan_rain).taggings.reload
|
204
|
+
|
205
|
+
assert_queries ENV['DB'] == 'sqlite3' ? 1 : 3 do
|
206
|
+
posts(:jonathan_rain).update_attributes!(:tag_list => posts(:jonathan_rain).tag_list.to_s)
|
207
|
+
end
|
208
|
+
|
209
|
+
assert_equivalent ["Nature", "Question"], posts(:jonathan_rain).tag_list
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_new_tags
|
213
|
+
assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
|
214
|
+
posts(:jonathan_sky).update_attributes!(:tag_list => "#{posts(:jonathan_sky).tag_list}, One, Two")
|
215
|
+
assert_equivalent ["Very good", "Nature", "One", "Two"], posts(:jonathan_sky).tag_list
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_remove_tag
|
219
|
+
assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
|
220
|
+
posts(:jonathan_sky).update_attributes!(:tag_list => "Nature")
|
221
|
+
assert_equivalent ["Nature"], posts(:jonathan_sky).tag_list
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_change_case_of_tags
|
225
|
+
original_tag_names = photos(:jonathan_questioning_dog).tag_list
|
226
|
+
photos(:jonathan_questioning_dog).update_attributes!(:tag_list => photos(:jonathan_questioning_dog).tag_list.to_s.upcase)
|
227
|
+
|
228
|
+
# The new tag list is not uppercase becuase the AR finders are not case-sensitive
|
229
|
+
# and find the old tags when re-tagging with the uppercase tags.
|
230
|
+
assert_equivalent original_tag_names, photos(:jonathan_questioning_dog).reload.tag_list
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_remove_and_add_tag
|
234
|
+
assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
|
235
|
+
posts(:jonathan_sky).update_attributes!(:tag_list => "Nature, Beautiful")
|
236
|
+
assert_equivalent ["Nature", "Beautiful"], posts(:jonathan_sky).tag_list
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_tags_not_saved_if_validation_fails
|
240
|
+
assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
|
241
|
+
assert !posts(:jonathan_sky).update_attributes(:tag_list => "One, Two", :text => "")
|
242
|
+
assert_equivalent ["Very good", "Nature"], Post.find(posts(:jonathan_sky).id).tag_list
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_tag_list_accessors_on_new_record
|
246
|
+
p = Post.new(:text => 'Test')
|
247
|
+
|
248
|
+
assert p.tag_list.blank?
|
249
|
+
p.tag_list = "One, Two"
|
250
|
+
assert_equal "One, Two", p.tag_list.to_s
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_clear_tag_list_with_nil
|
254
|
+
p = photos(:jonathan_questioning_dog)
|
255
|
+
|
256
|
+
assert !p.tag_list.blank?
|
257
|
+
assert p.update_attributes(:tag_list => nil)
|
258
|
+
assert p.tag_list.blank?
|
259
|
+
|
260
|
+
assert p.reload.tag_list.blank?
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_clear_tag_list_with_string
|
264
|
+
p = photos(:jonathan_questioning_dog)
|
265
|
+
|
266
|
+
assert !p.tag_list.blank?
|
267
|
+
assert p.update_attributes(:tag_list => ' ')
|
268
|
+
assert p.tag_list.blank?
|
269
|
+
|
270
|
+
assert p.reload.tag_list.blank?
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_tag_list_reset_on_reload
|
274
|
+
p = photos(:jonathan_questioning_dog)
|
275
|
+
assert !p.tag_list.blank?
|
276
|
+
p.tag_list = nil
|
277
|
+
assert p.tag_list.blank?
|
278
|
+
assert !p.reload.tag_list.blank?
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_instance_tag_counts
|
282
|
+
assert_tag_counts posts(:jonathan_sky).tag_counts, :good => 2, :nature => 7
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_tag_list_populated_when_cache_nil
|
286
|
+
assert_nil posts(:jonathan_sky).cached_tag_list
|
287
|
+
posts(:jonathan_sky).save!
|
288
|
+
assert_equal posts(:jonathan_sky).tag_list.to_s, posts(:jonathan_sky).cached_tag_list
|
289
|
+
end
|
290
|
+
|
291
|
+
def test_cached_tag_list_used
|
292
|
+
posts(:jonathan_sky).save!
|
293
|
+
posts(:jonathan_sky).reload
|
294
|
+
|
295
|
+
assert_no_queries do
|
296
|
+
assert_equivalent ["Very good", "Nature"], posts(:jonathan_sky).tag_list
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_cached_tag_list_not_used
|
301
|
+
# Load fixture and column information
|
302
|
+
posts(:jonathan_sky).taggings(:reload)
|
303
|
+
|
304
|
+
assert_queries 1 do
|
305
|
+
# Tags association will be loaded
|
306
|
+
posts(:jonathan_sky).tag_list
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def test_cached_tag_list_updated
|
311
|
+
assert_nil posts(:jonathan_sky).cached_tag_list
|
312
|
+
posts(:jonathan_sky).save!
|
313
|
+
assert_equivalent ["Very good", "Nature"], TagList.from(posts(:jonathan_sky).cached_tag_list)
|
314
|
+
posts(:jonathan_sky).update_attributes!(:tag_list => "None")
|
315
|
+
|
316
|
+
assert_equal 'None', posts(:jonathan_sky).cached_tag_list
|
317
|
+
assert_equal 'None', posts(:jonathan_sky).reload.cached_tag_list
|
318
|
+
end
|
319
|
+
|
320
|
+
def test_clearing_cached_tag_list
|
321
|
+
# Generate the cached tag list
|
322
|
+
posts(:jonathan_sky).save!
|
323
|
+
|
324
|
+
posts(:jonathan_sky).update_attributes!(:tag_list => "")
|
325
|
+
assert_equal "", posts(:jonathan_sky).cached_tag_list
|
326
|
+
end
|
327
|
+
|
328
|
+
def test_find_tagged_with_using_sti
|
329
|
+
special_post = SpecialPost.create!(:text => "Test", :tag_list => "Random")
|
330
|
+
|
331
|
+
assert_equal [special_post], SpecialPost.find_tagged_with("Random")
|
332
|
+
assert Post.find_tagged_with("Random").include?(special_post)
|
333
|
+
end
|
334
|
+
|
335
|
+
def test_tag_counts_using_sti
|
336
|
+
SpecialPost.create!(:text => "Test", :tag_list => "Nature")
|
337
|
+
|
338
|
+
assert_tag_counts SpecialPost.tag_counts, :nature => 1
|
339
|
+
end
|
340
|
+
|
341
|
+
def test_case_insensitivity
|
342
|
+
assert_difference "Tag.count", 1 do
|
343
|
+
Post.create!(:text => "Test", :tag_list => "one")
|
344
|
+
Post.create!(:text => "Test", :tag_list => "One")
|
345
|
+
end
|
346
|
+
|
347
|
+
assert_equal Post.find_tagged_with("Nature").collect(&:id),
|
348
|
+
Post.find_tagged_with("nature").collect(&:id)
|
349
|
+
end
|
350
|
+
|
351
|
+
def test_tag_not_destroyed_when_unused
|
352
|
+
posts(:jonathan_sky).tag_list.add("Random")
|
353
|
+
posts(:jonathan_sky).save!
|
354
|
+
|
355
|
+
assert_no_difference 'Tag.count' do
|
356
|
+
posts(:jonathan_sky).tag_list.remove("Random")
|
357
|
+
posts(:jonathan_sky).save!
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def test_tag_destroyed_when_unused
|
362
|
+
Tag.destroy_unused = true
|
363
|
+
|
364
|
+
posts(:jonathan_sky).tag_list.add("Random")
|
365
|
+
posts(:jonathan_sky).save!
|
366
|
+
|
367
|
+
assert_difference 'Tag.count', -1 do
|
368
|
+
posts(:jonathan_sky).tag_list.remove("Random")
|
369
|
+
posts(:jonathan_sky).save!
|
370
|
+
end
|
371
|
+
ensure
|
372
|
+
Tag.destroy_unused = false
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
#class ActsAsTaggableOnSteroidsFormTest < ActiveSupport::TestCase
|
377
|
+
# include ActionView::Helpers::FormHelper
|
378
|
+
#
|
379
|
+
# def test_tag_list_contents
|
380
|
+
# fields_for :post, posts(:jonathan_sky) do |f|
|
381
|
+
# assert_match posts(:jonathan_sky).tag_list.to_s, f.text_field(:tag_list)
|
382
|
+
# end
|
383
|
+
# end
|
384
|
+
#end
|