acts_as_20ggable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +1 -0
  3. data/CHANGELOG +6 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +61 -0
  6. data/README +76 -0
  7. data/Rakefile +24 -0
  8. data/acts_as_20ggable.gemspec +26 -0
  9. data/generators/acts_as_20ggable_migration/acts_as_20ggable_migration_generator.rb +11 -0
  10. data/generators/acts_as_20ggable_migration/templates/migration.rb +45 -0
  11. data/lib/acts_as_20ggable.rb +7 -0
  12. data/lib/acts_as_taggable.rb +201 -0
  13. data/lib/tag.rb +146 -0
  14. data/lib/tag_counts_extension.rb +3 -0
  15. data/lib/tag_hierarchy_builder.rb +201 -0
  16. data/lib/tag_list.rb +108 -0
  17. data/lib/tagging.rb +10 -0
  18. data/lib/tags_helper.rb +13 -0
  19. data/test/fixtures/magazine.rb +3 -0
  20. data/test/fixtures/magazines.yml +7 -0
  21. data/test/fixtures/photo.rb +8 -0
  22. data/test/fixtures/photos.yml +24 -0
  23. data/test/fixtures/post.rb +7 -0
  24. data/test/fixtures/posts.yml +34 -0
  25. data/test/fixtures/schema.rb +73 -0
  26. data/test/fixtures/special_post.rb +2 -0
  27. data/test/fixtures/subscription.rb +4 -0
  28. data/test/fixtures/subscriptions.yml +3 -0
  29. data/test/fixtures/taggings.yml +162 -0
  30. data/test/fixtures/tags.yml +75 -0
  31. data/test/fixtures/tags_hierarchy.yml +31 -0
  32. data/test/fixtures/tags_synonyms.yml +16 -0
  33. data/test/fixtures/tags_transitive_hierarchy.yml +96 -0
  34. data/test/fixtures/user.rb +9 -0
  35. data/test/fixtures/users.yml +7 -0
  36. data/test/fixtures/video.rb +3 -0
  37. data/test/fixtures/videos.yml +9 -0
  38. data/test/lib/acts_as_taggable_test.rb +359 -0
  39. data/test/lib/tag_hierarchy_builder_test.rb +109 -0
  40. data/test/lib/tag_list_test.rb +120 -0
  41. data/test/lib/tag_test.rb +45 -0
  42. data/test/lib/tagging_test.rb +14 -0
  43. data/test/lib/tags_helper_test.rb +28 -0
  44. data/test/lib/tags_hierarchy_test.rb +12 -0
  45. data/test/support/activerecord_test_connector.rb +129 -0
  46. data/test/support/custom_asserts.rb +35 -0
  47. data/test/support/database.yml +10 -0
  48. data/test/test_helper.rb +20 -0
  49. metadata +183 -0
@@ -0,0 +1,109 @@
1
+ require 'test_helper'
2
+ require 'tag_hierarchy_builder'
3
+
4
+ class TagHierarchyBuilderTest < ActiveSupport::TestCase
5
+ fixtures :tags, :tags_hierarchy, :tags_synonyms, :tags_transitive_hierarchy
6
+
7
+ def hierarchy_blank?(options = {})
8
+ except = [ options[:except] ].flatten.compact
9
+ Tag.find(:all).all? { |tag| !except.include?(tag.name) || (tag.parents.empty? && tag.children.empty? && tag.synonyms.empty?) }.inspect
10
+ end
11
+
12
+ def test_dump_hierarchy
13
+ hierarchy = TagHierarchyBuilder.dump_hierarchy
14
+ hierarchy_fixture = [
15
+ ['Nature', 'Animals', 'Domestic Animals', 'Cat', 'Kitten'],
16
+ ['Nature', 'Animals', 'Horse'],
17
+ ['People', 'Children'],
18
+ ['People', 'Me'],
19
+ ['People', 'Nude'],
20
+ ]
21
+
22
+ assert_equal hierarchy, hierarchy_fixture
23
+ end
24
+
25
+ def test_dump_synonyms
26
+ synonyms = TagHierarchyBuilder.dump_synonyms
27
+ synonyms_fixture = [
28
+ ['Cat', 'Kitty', 'Pussy'],
29
+ ['Horse', 'Racehorse'],
30
+ ['Question', 'Problem']
31
+ ]
32
+
33
+ assert_equivalent synonyms, synonyms_fixture
34
+ end
35
+
36
+ def test_dump_orphans
37
+ orphans = TagHierarchyBuilder.dump_orphans
38
+ orphans_fixture = ['Bad', 'Crazy animal', 'Very good']
39
+
40
+ assert_equivalent orphans, orphans_fixture
41
+ end
42
+
43
+ def test_rebuild_hierarchy_with_empty_specification
44
+ TagHierarchyBuilder.rebuild_hierarchy([''])
45
+ assert hierarchy_blank?
46
+ end
47
+
48
+ def test_rebuild_hierarchy_with_only_comments_in_specification
49
+ TagHierarchyBuilder.rebuild_hierarchy(['#Comment one', ' # Comment two'])
50
+ assert hierarchy_blank?
51
+ end
52
+
53
+ def test_rebuild_hierarchy_with_synonyms
54
+ TagHierarchyBuilder.rebuild_hierarchy([' Cat = Racehorse = Problem '])
55
+
56
+ assert_equal Tag.find_with_like_by_name("Cat").synonyms.map(&:name).sort,
57
+ ['Racehorse', 'Problem'].sort
58
+
59
+ assert hierarchy_blank?(:except => 'Cat')
60
+ end
61
+
62
+ def test_rebuild_hierarchy_with_synonyms_and_parents
63
+ TagHierarchyBuilder.rebuild_hierarchy(['Cat = Racehorse = Problem', ' Apple / Cocoa / Kitty ', 'Apple / Kitty'])
64
+
65
+ assert_equal Tag.find_with_like_by_name("Cat").synonyms.map(&:name).sort,
66
+ ['Racehorse', 'Problem'].sort
67
+
68
+ assert_equal Tag.find_with_like_by_name("Cocoa").parents.map(&:name).sort,
69
+ ['Apple'].sort
70
+ assert_equal Tag.find_with_like_by_name("Kitty").parents.map(&:name).sort,
71
+ ['Apple', 'Cocoa'].sort
72
+ assert hierarchy_blank?(:except => ['Cat', 'Kitty', 'Apple', 'Cocoa'])
73
+ end
74
+
75
+
76
+ def test_rebuild_hierarchy_with_invalid_lines
77
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy(['/ HEY / YO '])}
78
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy(['HEY / YO/'])}
79
+
80
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy([' / HEY / YO '])}
81
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy(['HEY / YO/ '])}
82
+
83
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy(['= HEY = YO '])}
84
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy(['HEY = YO='])}
85
+
86
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy([' = HEY = YO '])}
87
+ assert_raise(TagHierarchyBuilder::WrongSpecificationSyntax) { TagHierarchyBuilder.rebuild_hierarchy(['HEY = YO= '])}
88
+ end
89
+
90
+ def test_rebuild_hierarchy_with_cycles
91
+ assert_raise(Tag::HierarchyCycle) { TagHierarchyBuilder.rebuild_hierarchy(['Apple/Cocoa/Banana/Apple', 'Apple/Delta'])}
92
+ assert_raise(Tag::HierarchyCycle) { TagHierarchyBuilder.rebuild_hierarchy(['Apple/Apple'])}
93
+
94
+ assert_raise(Tag::HierarchyCycle) { TagHierarchyBuilder.rebuild_hierarchy(['Apple/Cocoa/Banana/Apple'])}
95
+ assert_raise(Tag::HierarchyCycle) { TagHierarchyBuilder.rebuild_hierarchy(['Apple/Cocoa/Banana/Apple/Cocoa'])}
96
+ end
97
+
98
+ def test_transitive_hierarchy_rebuilding_does_not_breaks_fixture
99
+ tags = Tag.find(:all)
100
+ old_hierarchy = tags.inject({}) { |memo, tag| memo[tag] = tag.transitive_children.find(:all).sort_by(&:name); memo }
101
+
102
+ TagHierarchyBuilder.rebuild_transitive_closure
103
+
104
+ tags = Tag.find(:all)
105
+ new_hierarchy = tags.inject({}) { |memo, tag| memo[tag] = tag.transitive_children.find(:all).sort_by(&:name); memo }
106
+
107
+ assert_equal old_hierarchy, new_hierarchy
108
+ end
109
+ end
@@ -0,0 +1,120 @@
1
+ require 'test_helper'
2
+ require 'tag_list'
3
+
4
+ class TagListTest < ActiveSupport::TestCase
5
+ def test_from_leaves_string_unchanged
6
+ tags = '"One ", Two'
7
+ original = tags.dup
8
+ TagList.from(tags)
9
+ assert_equal tags, original
10
+ end
11
+
12
+ def test_from_single_name
13
+ assert_equal %w(Fun), TagList.from("Fun")
14
+ assert_equal %w(Fun), TagList.from('"Fun"')
15
+ end
16
+
17
+ def test_from_blank
18
+ assert_equal [], TagList.from(nil)
19
+ assert_equal [], TagList.from("")
20
+ end
21
+
22
+ def test_from_single_quoted_tag
23
+ assert_equal ['with, comma'], TagList.from('"with, comma"')
24
+ end
25
+
26
+ def test_spaces_do_not_delineate
27
+ assert_equal ['A B', 'C'], TagList.from('A B, C')
28
+ end
29
+
30
+ def test_from_multiple_tags
31
+ assert_equivalent %w(Alpha Beta Delta Gamma), TagList.from("Alpha, Beta, Delta, Gamma")
32
+ end
33
+
34
+ def test_from_multiple_tags_with_quotes
35
+ assert_equivalent %w(Alpha Beta Delta Gamma), TagList.from('Alpha, "Beta", Gamma , "Delta"')
36
+ end
37
+
38
+ def test_from_with_single_quotes
39
+ assert_equivalent ['A B', 'C'], TagList.from("'A B', C")
40
+ end
41
+
42
+ def test_from_multiple_tags_with_quote_and_commas
43
+ assert_equivalent ['Alpha, Beta', 'Delta', 'Gamma, something'], TagList.from('"Alpha, Beta", Delta, "Gamma, something"')
44
+ end
45
+
46
+ def test_from_with_inner_quotes
47
+ assert_equivalent ["House", "Drum 'n' Bass", "Trance"], TagList.from("House, Drum 'n' Bass, Trance")
48
+ assert_equivalent ["House", "Drum'n'Bass", "Trance"], TagList.from("House, Drum'n'Bass, Trance")
49
+ end
50
+
51
+ def test_from_removes_white_space
52
+ assert_equivalent %w(Alpha Beta), TagList.from('" Alpha ", "Beta "')
53
+ assert_equivalent %w(Alpha Beta), TagList.from(' Alpha, Beta ')
54
+ end
55
+
56
+ def test_from_and_new_treat_both_accept_arrays
57
+ tags = ["One", "Two"]
58
+
59
+ assert_equal TagList.from(tags), TagList.new(tags)
60
+ end
61
+
62
+ def test_alternative_delimiter
63
+ TagList.delimiter = " "
64
+
65
+ assert_equal %w(One Two), TagList.from("One Two")
66
+ assert_equal ['One two', 'three', 'four'], TagList.from('"One two" three four')
67
+ ensure
68
+ TagList.delimiter = ","
69
+ end
70
+
71
+ def test_duplicate_tags_removed
72
+ assert_equal %w(One), TagList.from("One, One")
73
+ end
74
+
75
+ def test_to_s_with_commas
76
+ assert_equal "Question, Crazy Animal", TagList.new("Question", "Crazy Animal").to_s
77
+ end
78
+
79
+ def test_to_s_with_alternative_delimiter
80
+ TagList.delimiter = " "
81
+
82
+ assert_equal '"Crazy Animal" Question', TagList.new("Crazy Animal", "Question").to_s
83
+ ensure
84
+ TagList.delimiter = ","
85
+ end
86
+
87
+ def test_add
88
+ tag_list = TagList.new("One")
89
+ assert_equal %w(One), tag_list
90
+
91
+ assert_equal %w(One Two), tag_list.add("Two")
92
+ assert_equal %w(One Two Three), tag_list.add(["Three"])
93
+ end
94
+
95
+ def test_remove
96
+ tag_list = TagList.new("One", "Two")
97
+ assert_equal %w(Two), tag_list.remove("One")
98
+ assert_equal %w(), tag_list.remove(["Two"])
99
+ end
100
+
101
+ def test_new_with_parsing
102
+ assert_equal %w(One Two), TagList.new("One, Two", :parse => true)
103
+ end
104
+
105
+ def test_add_with_parsing
106
+ assert_equal %w(One Two), TagList.new.add("One, Two", :parse => true)
107
+ end
108
+
109
+ def test_remove_with_parsing
110
+ tag_list = TagList.from("Three, Four, Five")
111
+ assert_equal %w(Four), tag_list.remove("Three, Five", :parse => true)
112
+ end
113
+
114
+ def test_toggle
115
+ tag_list = TagList.new("One", "Two")
116
+ assert_equal %w(One Three), tag_list.toggle("Two", "Three")
117
+ assert_equal %w(), tag_list.toggle("One", "Three")
118
+ assert_equal %w(Four), tag_list.toggle("Four")
119
+ end
120
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ require 'tag'
3
+
4
+ class TagTest < ActiveSupport::TestCase
5
+ fixtures :tags, :taggings, :users, :photos, :posts
6
+
7
+ def test_name_required
8
+ t = Tag.create
9
+ assert_match /blank/, t.errors[:name].to_s
10
+ end
11
+
12
+ def test_name_unique
13
+ t = Tag.create!(:name => "My tag")
14
+ duplicate = t.dup
15
+
16
+ assert !duplicate.save
17
+ assert_match /taken/, duplicate.errors[:name].to_s
18
+ end
19
+
20
+ def test_taggings
21
+ assert_equivalent [taggings(:jonathan_sky_good), taggings(:sam_flowers_good), taggings(:sam_flower_good), taggings(:ruby_good)], tags(:good).taggings
22
+ assert_equivalent [taggings(:sam_ground_bad), taggings(:jonathan_bad_cat_bad)], tags(:bad).taggings
23
+ end
24
+
25
+ def test_to_s
26
+ assert_equal tags(:good).name, tags(:good).to_s
27
+ end
28
+
29
+ def test_equality
30
+ assert_equal tags(:good), tags(:good)
31
+ assert_equal Tag.find(1), Tag.find(1)
32
+ assert_equal Tag.new(:name => 'A'), Tag.new(:name => 'A')
33
+ assert_not_equal Tag.new(:name => 'A'), Tag.new(:name => 'B')
34
+ end
35
+
36
+ def test_taggings_removed_when_tag_destroyed
37
+ assert_difference "Tagging.count", -Tagging.count(:conditions => { :tag_id => tags(:good).id }) do
38
+ assert tags(:good).destroy
39
+ end
40
+ end
41
+
42
+ def test_all_counts
43
+ assert_tag_counts Tag.counts, :good => 4, :bad => 2, :nature => 10, :question => 2, :animal => 3, :nature_animals_domestic_cat => 1, :nature_animals_domestic_cat_kitten => 1
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ require 'tagging'
3
+
4
+ class TaggingTest < ActiveSupport::TestCase
5
+ fixtures :tags, :taggings, :posts
6
+
7
+ def test_tag
8
+ assert_equal tags(:good), taggings(:jonathan_sky_good).tag
9
+ end
10
+
11
+ def test_taggable
12
+ assert_equal posts(:jonathan_sky), taggings(:jonathan_sky_good).taggable
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class TagsHelperTest < ActiveSupport::TestCase
4
+ fixtures :tags, :taggings, :posts
5
+
6
+ include ::TagsHelper
7
+
8
+ def test_tag_cloud
9
+ cloud_elements = []
10
+
11
+ tag_cloud Post.tag_counts, %w(css1 css2 css3 css4) do |tag, css_class|
12
+ cloud_elements << [tag, css_class]
13
+ end
14
+
15
+ assert_equal [
16
+ [tags(:good), "css2"],
17
+ [tags(:bad), "css1"],
18
+ [tags(:nature), "css4"],
19
+ [tags(:question), "css1"]
20
+ ], cloud_elements
21
+ end
22
+
23
+ def test_tag_cloud_when_no_tags
24
+ tag_cloud SpecialPost.tag_counts, %w(css1) do
25
+ assert false, "tag_cloud should not yield"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ require 'test_helper'
2
+
3
+ class TagsHierarchyTest < ActiveSupport::TestCase
4
+ fixtures :tags, :taggings, :videos, :users, :tags_transitive_hierarchy
5
+
6
+ def test_find_tagged_with_subtags
7
+ assert_equivalent [videos(:jonathan_good_cat), videos(:sam_kitten)],
8
+ Video.find_tagged_with('"Domestic Animals"')
9
+
10
+ end
11
+ end
12
+
@@ -0,0 +1,129 @@
1
+ require 'active_record'
2
+ require 'active_record/fixtures'
3
+ require 'active_support/multibyte' # needed for Ruby 1.9.1
4
+ require 'stringio'
5
+
6
+ $query_count = 0
7
+ $query_sql = []
8
+
9
+ ignore_sql = /
10
+ ^(
11
+ PRAGMA | SHOW\ max_identifier_length |
12
+ SELECT\ (currval|CAST|@@IDENTITY|@@ROWCOUNT) |
13
+ SHOW\ ((FULL\ )?FIELDS|TABLES)
14
+ )\b |
15
+ \bFROM\ (sqlite_master|pg_tables|pg_attribute)\b
16
+ /x
17
+
18
+ ActiveSupport::Notifications.subscribe(/^sql\./) do |*args|
19
+ payload = args.last
20
+ unless payload[:name] =~ /^Fixture/ or payload[:sql] =~ ignore_sql
21
+ $query_count += 1
22
+ $query_sql << payload[:sql]
23
+ end
24
+ end
25
+
26
+ module ActiverecordTestConnector
27
+ extend self
28
+
29
+ attr_accessor :able_to_connect
30
+ attr_accessor :connected
31
+
32
+ FIXTURES_PATH = File.expand_path('../../fixtures', __FILE__)
33
+
34
+ Fixtures = defined?(ActiveRecord::FixtureSet) ? ActiveRecord::FixtureSet :
35
+ defined?(ActiveRecord::Fixtures) ? ActiveRecord::Fixtures :
36
+ ::Fixtures
37
+
38
+ # Set our defaults
39
+ self.connected = false
40
+ self.able_to_connect = true
41
+
42
+ def setup
43
+ unless self.connected || !self.able_to_connect
44
+ setup_connection
45
+ load_schema
46
+ add_load_path FIXTURES_PATH
47
+ self.connected = true
48
+ end
49
+ rescue Exception => e # errors from ActiveRecord setup
50
+ $stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n"
51
+ self.able_to_connect = false
52
+ end
53
+
54
+ private
55
+
56
+ def add_load_path(path)
57
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
58
+ dep.autoload_paths.unshift path
59
+ end
60
+
61
+ def setup_connection
62
+ db = ENV['DB'].blank?? 'sqlite3' : ENV['DB']
63
+
64
+ configurations = YAML.load_file(File.expand_path('../../support/database.yml', __FILE__))
65
+ raise "no configuration for '#{db}'" unless configurations.key? db
66
+ configuration = configurations[db]
67
+
68
+ # ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb'
69
+
70
+ ActiveRecord::Base.configurations = { db => configuration }
71
+ ActiveRecord::Base.establish_connection(db.to_sym)
72
+ ActiveRecord::Base.default_timezone = :utc
73
+ end
74
+
75
+ def load_schema
76
+ begin
77
+ $stdout = StringIO.new
78
+ ActiveRecord::Migration.verbose = false
79
+ load File.join(FIXTURES_PATH, 'schema.rb')
80
+ ensure
81
+ $stdout = STDOUT
82
+ end
83
+ end
84
+
85
+ module FixtureSetup
86
+ def fixtures(*tables)
87
+ cattr_accessor :loaded_fixtures
88
+ cattr_accessor :fixture_cache
89
+ cattr_accessor :fixture_table_names
90
+
91
+ table_names = tables.map { |t| t.to_s }
92
+ self.fixture_table_names = table_names
93
+
94
+ define_method(:setup) do
95
+ fixtures = Fixtures.create_fixtures ActiverecordTestConnector::FIXTURES_PATH, self.class.fixture_table_names
96
+ self.class.loaded_fixtures = {}
97
+ self.class.fixture_cache = {}
98
+
99
+ unless fixtures.nil?
100
+ if fixtures.instance_of?(Fixtures)
101
+ self.class.loaded_fixtures[fixtures.table_name] = fixtures
102
+ else
103
+ fixtures.each { |f| self.class.loaded_fixtures[f.table_name] = f }
104
+ end
105
+ end
106
+ end
107
+
108
+ define_method(:teardown) do
109
+ Fixtures.reset_cache
110
+ end
111
+
112
+ table_names.each do |table_name|
113
+ define_method(table_name) do |*fixtures|
114
+ self.class.fixture_cache[table_name] ||= {}
115
+
116
+ instances = fixtures.map do |fixture|
117
+ if self.class.loaded_fixtures[table_name][fixture.to_s]
118
+ self.class.fixture_cache[table_name][fixture] ||= self.class.loaded_fixtures[table_name][fixture.to_s].find
119
+ else
120
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
121
+ end
122
+ end
123
+
124
+ instances.size == 1 ? instances.first : instances
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end