acts_as_20ggable 1.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.
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