tagtical 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +25 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +25 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +306 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/generators/tagtical_migration/tagtical_migration_generator.rb +7 -0
- data/generators/tagtical_migration/templates/migration.rb +34 -0
- data/lib/generators/tagtical/migration/migration_generator.rb +32 -0
- data/lib/generators/tagtical/migration/templates/active_record/migration.rb +35 -0
- data/lib/tagtical/acts_as_tagger.rb +69 -0
- data/lib/tagtical/compatibility/Gemfile +8 -0
- data/lib/tagtical/compatibility/active_record_backports.rb +21 -0
- data/lib/tagtical/tag.rb +314 -0
- data/lib/tagtical/tag_list.rb +133 -0
- data/lib/tagtical/taggable/cache.rb +53 -0
- data/lib/tagtical/taggable/collection.rb +141 -0
- data/lib/tagtical/taggable/core.rb +317 -0
- data/lib/tagtical/taggable/ownership.rb +110 -0
- data/lib/tagtical/taggable/related.rb +60 -0
- data/lib/tagtical/taggable.rb +51 -0
- data/lib/tagtical/tagging.rb +42 -0
- data/lib/tagtical/tags_helper.rb +17 -0
- data/lib/tagtical.rb +47 -0
- data/rails/init.rb +1 -0
- data/spec/bm.rb +53 -0
- data/spec/database.yml +17 -0
- data/spec/database.yml.sample +17 -0
- data/spec/models.rb +60 -0
- data/spec/schema.rb +46 -0
- data/spec/spec_helper.rb +159 -0
- data/spec/tagtical/acts_as_tagger_spec.rb +94 -0
- data/spec/tagtical/tag_list_spec.rb +102 -0
- data/spec/tagtical/tag_spec.rb +301 -0
- data/spec/tagtical/taggable_spec.rb +460 -0
- data/spec/tagtical/tagger_spec.rb +76 -0
- data/spec/tagtical/tagging_spec.rb +52 -0
- data/spec/tagtical/tags_helper_spec.rb +28 -0
- data/spec/tagtical/tagtical_spec.rb +340 -0
- metadata +132 -0
data/spec/bm.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'action_view'
|
3
|
+
require File.expand_path('../../lib/tagtical', __FILE__)
|
4
|
+
|
5
|
+
if defined?(ActiveRecord::Acts::TaggableOn)
|
6
|
+
ActiveRecord::Base.send :include, ActiveRecord::Acts::TaggableOn
|
7
|
+
ActiveRecord::Base.send :include, ActiveRecord::Acts::Tagger
|
8
|
+
ActionView::Base.send :include, TagsHelper if defined?(ActionView::Base)
|
9
|
+
end
|
10
|
+
|
11
|
+
TEST_DATABASE_FILE = File.join(File.dirname(__FILE__), '..', 'test.sqlite3')
|
12
|
+
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
13
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => TEST_DATABASE_FILE
|
14
|
+
|
15
|
+
ActiveRecord::Base.silence do
|
16
|
+
ActiveRecord::Migration.verbose = false
|
17
|
+
ActiveRecord::Schema.define :version => 0 do
|
18
|
+
create_table "taggings", :force => true do |t|
|
19
|
+
t.float "relevance"
|
20
|
+
t.integer "tag_id", :limit => 11
|
21
|
+
t.integer "taggable_id", :limit => 11
|
22
|
+
t.string "taggable_type"
|
23
|
+
t.string "context"
|
24
|
+
t.datetime "created_at"
|
25
|
+
t.integer "tagger_id", :limit => 11
|
26
|
+
t.string "tagger_type" if Tagtical.config.polymorphic_tagger?
|
27
|
+
end
|
28
|
+
|
29
|
+
add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
|
30
|
+
add_index "taggings", ["taggable_id", "taggable_type"], :name => "index_taggings_on_taggable_id_and_taggable_type"
|
31
|
+
|
32
|
+
create_table :tags, :force => true do |t|
|
33
|
+
t.string :value
|
34
|
+
t.string :type
|
35
|
+
end
|
36
|
+
add_index :tags, [:type, :value], :unique => true
|
37
|
+
add_index :tags, :value
|
38
|
+
|
39
|
+
create_table :taggable_models, :force => true do |t|
|
40
|
+
t.column :name, :string
|
41
|
+
t.column :type, :string
|
42
|
+
t.column :cached_tag_list, :string
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class TaggableModel < ActiveRecord::Base
|
47
|
+
acts_as_taggable(:languages, :skills, :needs, :offerings)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
puts Benchmark.measure {
|
52
|
+
1000.times { TaggableModel.create :tag_list => "awesome, epic, neat" }
|
53
|
+
}
|
data/spec/database.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: tagtical.sqlite3
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
hostname: localhost
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: tagtical
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
adapter: postgresql
|
14
|
+
hostname: localhost
|
15
|
+
username: postgres
|
16
|
+
password:
|
17
|
+
database: tagtical
|
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: tagtical.sqlite3
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
hostname: localhost
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: tagtical
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
adapter: postgresql
|
14
|
+
hostname: localhost
|
15
|
+
username: postgres
|
16
|
+
password:
|
17
|
+
database: tagtical
|
data/spec/models.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
### START Tag Subclasses ###
|
2
|
+
|
3
|
+
module Tagtical
|
4
|
+
class Tag
|
5
|
+
class Language < Tagtical::Tag
|
6
|
+
end
|
7
|
+
class PartTag < Tagtical::Tag
|
8
|
+
|
9
|
+
def dump_value(value)
|
10
|
+
value && value.downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
module Tag
|
17
|
+
class Skill < Tagtical::Tag
|
18
|
+
|
19
|
+
def load_value(value)
|
20
|
+
value.gsub("ball", "baller") if value
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
class Craft < Skill # Multiple levels of inheritance
|
25
|
+
end
|
26
|
+
end
|
27
|
+
class NeedTag < Tagtical::Tag # Tag subclass ending in "Tag"
|
28
|
+
end
|
29
|
+
class Offering < Tagtical::Tag # Top level
|
30
|
+
end
|
31
|
+
|
32
|
+
### END Tag Subclasses ###
|
33
|
+
|
34
|
+
class TaggableModel < ActiveRecord::Base
|
35
|
+
acts_as_taggable(:languages, :skills, :crafts, :needs, :offerings)
|
36
|
+
has_many :untaggable_models
|
37
|
+
end
|
38
|
+
|
39
|
+
class CachedModel < ActiveRecord::Base
|
40
|
+
acts_as_taggable
|
41
|
+
end
|
42
|
+
|
43
|
+
class OtherTaggableModel < ActiveRecord::Base
|
44
|
+
acts_as_taggable(:languages, :needs, :offerings)
|
45
|
+
end
|
46
|
+
|
47
|
+
class InheritingTaggableModel < TaggableModel
|
48
|
+
end
|
49
|
+
|
50
|
+
class AlteredInheritingTaggableModel < TaggableModel
|
51
|
+
acts_as_taggable(:parts)
|
52
|
+
end
|
53
|
+
|
54
|
+
class TaggableUser < ActiveRecord::Base
|
55
|
+
acts_as_tagger
|
56
|
+
end
|
57
|
+
|
58
|
+
class UntaggableModel < ActiveRecord::Base
|
59
|
+
belongs_to :taggable_model
|
60
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
ActiveRecord::Schema.define :version => 0 do
|
2
|
+
create_table "taggings", :force => true do |t|
|
3
|
+
t.integer "tag_id", :limit => 11
|
4
|
+
t.integer "taggable_id", :limit => 11
|
5
|
+
t.string "taggable_type"
|
6
|
+
t.datetime "created_at"
|
7
|
+
t.column :tagger_id, :integer
|
8
|
+
t.column :tagger_type, :string
|
9
|
+
t.column :relevance, :float
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :taggings, :tag_id
|
13
|
+
add_index :taggings, [:taggable_id, :taggable_type]
|
14
|
+
|
15
|
+
create_table :tags, :force => true do |t|
|
16
|
+
t.column :value, :string
|
17
|
+
t.column :type, :string
|
18
|
+
end
|
19
|
+
add_index :tags, [:type, :value], :unique => true
|
20
|
+
add_index :tags, :value
|
21
|
+
|
22
|
+
create_table :taggable_models, :force => true do |t|
|
23
|
+
t.column :name, :string
|
24
|
+
t.column :type, :string
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :untaggable_models, :force => true do |t|
|
28
|
+
t.column :taggable_model_id, :integer
|
29
|
+
t.column :name, :string
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :cached_models, :force => true do |t|
|
33
|
+
t.column :name, :string
|
34
|
+
t.column :type, :string
|
35
|
+
t.column :cached_tag_list, :string
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table :taggable_users, :force => true do |t|
|
39
|
+
t.column :name, :string
|
40
|
+
end
|
41
|
+
|
42
|
+
create_table :other_taggable_models, :force => true do |t|
|
43
|
+
t.column :name, :string
|
44
|
+
t.column :type, :string
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
$LOAD_PATH << "." unless $LOAD_PATH.include?(".")
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "rubygems"
|
5
|
+
require "bundler"
|
6
|
+
|
7
|
+
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
|
8
|
+
raise RuntimeError, "Your bundler version is too old." +
|
9
|
+
"Run `gem install bundler` to upgrade."
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set up load paths for all bundled gems
|
13
|
+
Bundler.setup
|
14
|
+
rescue Bundler::GemNotFound
|
15
|
+
raise RuntimeError, "Bundler couldn't find some gems." +
|
16
|
+
"Did you run \`bundler install\`?"
|
17
|
+
end
|
18
|
+
|
19
|
+
Bundler.require(:test, :default)
|
20
|
+
require File.expand_path('../../lib/tagtical', __FILE__)
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.mock_with :mocha
|
24
|
+
end
|
25
|
+
|
26
|
+
RSpec::Matchers.define :have_tag_values do |expected|
|
27
|
+
match do |actual|
|
28
|
+
actual.map(&:value).sort == expected.sort
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
unless [].respond_to?(:freq)
|
33
|
+
class Array
|
34
|
+
def freq
|
35
|
+
k=Hash.new(0)
|
36
|
+
each {|e| k[e]+=1}
|
37
|
+
k
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ENV['DB'] ||= 'sqlite3'
|
43
|
+
|
44
|
+
database_yml = File.expand_path('../database.yml', __FILE__)
|
45
|
+
if File.exists?(database_yml)
|
46
|
+
active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
|
47
|
+
|
48
|
+
ActiveRecord::Base.establish_connection(active_record_configuration)
|
49
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
|
50
|
+
|
51
|
+
ActiveRecord::Base.silence do
|
52
|
+
ActiveRecord::Migration.verbose = false
|
53
|
+
|
54
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
55
|
+
load(File.dirname(__FILE__) + '/models.rb')
|
56
|
+
end
|
57
|
+
|
58
|
+
else
|
59
|
+
raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
|
60
|
+
end
|
61
|
+
|
62
|
+
def clean_database!
|
63
|
+
models = [Tagtical::Tag, Tagtical::Tagging, TaggableModel, OtherTaggableModel, InheritingTaggableModel,
|
64
|
+
AlteredInheritingTaggableModel, TaggableUser, UntaggableModel]
|
65
|
+
models.each do |model|
|
66
|
+
ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
clean_database!
|
71
|
+
|
72
|
+
module QueryAnalyzer
|
73
|
+
extend self
|
74
|
+
attr_accessor :enabled, :options
|
75
|
+
self.enabled = false
|
76
|
+
self.options = {}
|
77
|
+
|
78
|
+
IGNORE_SQL_REGEXP = /^explain/i # ignore SQL explain
|
79
|
+
|
80
|
+
def start(options=nil)
|
81
|
+
options = {:match => options} if options.is_a?(Regexp)
|
82
|
+
if block_given?
|
83
|
+
begin
|
84
|
+
(old_options, @options = @options, options) if options
|
85
|
+
old_enabled, @enabled = @enabled, true
|
86
|
+
yield
|
87
|
+
ensure
|
88
|
+
@enabled = old_enabled
|
89
|
+
@options = old_options if options
|
90
|
+
end
|
91
|
+
else
|
92
|
+
@options = options if options
|
93
|
+
@enabled = true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Always disable it in production environment
|
98
|
+
def enabled?(sql=nil)
|
99
|
+
!Rails.env.production? && (@enabled && (!sql || match_sql?(sql)))
|
100
|
+
end
|
101
|
+
|
102
|
+
def match_sql?(sql)
|
103
|
+
sql !~ IGNORE_SQL_REGEXP && (!options[:match] || options[:match]===sql)
|
104
|
+
end
|
105
|
+
|
106
|
+
def stop
|
107
|
+
options.clear
|
108
|
+
@enabled = false
|
109
|
+
end
|
110
|
+
|
111
|
+
# class Results < Array
|
112
|
+
#
|
113
|
+
# def qa_columnized
|
114
|
+
# sized = {}
|
115
|
+
# self.each do |row|
|
116
|
+
# row.values.each_with_index do |value, i|
|
117
|
+
# sized[i] = [sized[i].to_i, row.keys[i].length, value.to_s.length].max
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# table = []
|
122
|
+
# table << qa_columnized_row(self.first.keys, sized)
|
123
|
+
# table << '-' * table.first.length
|
124
|
+
# self.each { |row| table << qa_columnized_row(row.values, sized) }
|
125
|
+
# table.join("\n ") # Spaces added to work with format_log_entry
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# private
|
129
|
+
#
|
130
|
+
# def qa_columnized_row(fields, sized)
|
131
|
+
# row = []
|
132
|
+
# fields.each_with_index do |f, i|
|
133
|
+
# row << sprintf("%0-#{sized[i]}s", f.to_s)
|
134
|
+
# end
|
135
|
+
# row.join(' | ')
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# end # Results
|
139
|
+
end
|
140
|
+
|
141
|
+
module ActiveRecord
|
142
|
+
module ConnectionAdapters
|
143
|
+
class SQLiteAdapter < AbstractAdapter
|
144
|
+
private
|
145
|
+
|
146
|
+
def execute_with_analyzer(sql, name = nil)
|
147
|
+
if QueryAnalyzer.enabled?(sql)
|
148
|
+
display = "\nQUERY ANALYZER: \n#{sql}"
|
149
|
+
puts [display, nil, caller.map { |str| str.insert(0, " -> ")}]
|
150
|
+
@logger.debug(display) if @logger && @logger.debug?
|
151
|
+
end
|
152
|
+
execute_without_analyzer(sql, name)
|
153
|
+
end
|
154
|
+
# Always disable this in production
|
155
|
+
alias_method_chain :execute, :analyzer unless Rails.env.production?
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "acts_as_tagger" do
|
4
|
+
before(:each) do
|
5
|
+
clean_database!
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Tagger Method Generation" do
|
9
|
+
before(:each) do
|
10
|
+
@tagger = TaggableUser.new()
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should add #is_tagger? query method to the class-side" do
|
14
|
+
TaggableUser.should respond_to(:is_tagger?)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return true from the class-side #is_tagger?" do
|
18
|
+
TaggableUser.is_tagger?.should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return false from the base #is_tagger?" do
|
22
|
+
ActiveRecord::Base.is_tagger?.should be_false
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should add #is_tagger? query method to the singleton" do
|
26
|
+
@tagger.should respond_to(:is_tagger?)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add #tag method on the instance-side" do
|
30
|
+
@tagger.should respond_to(:tag)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should generate an association for #owned_taggings and #owned_tags" do
|
34
|
+
@tagger.should respond_to(:owned_taggings, :owned_tags)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#tag" do
|
39
|
+
context 'when called with a non-existent tag context' do
|
40
|
+
before(:each) do
|
41
|
+
@tagger = TaggableUser.new()
|
42
|
+
@taggable = TaggableModel.new(:name=>"Richard Prior")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should by default not throw an exception " do
|
46
|
+
@taggable.tag_list_on(:foo).should be_empty
|
47
|
+
lambda {
|
48
|
+
@tagger.tag(@taggable, :with=>'this, and, that', :on=>:foo)
|
49
|
+
}.should_not raise_error
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should show all the tag list when both public and owned tags exist" do
|
53
|
+
@taggable.tag_list = 'ruby, python'
|
54
|
+
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
|
55
|
+
@taggable.all_tags_on(:tags).map(&:value).sort.should == %w(ruby python java lisp).sort
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not add owned tags to the common list" do
|
59
|
+
@taggable.tag_list = 'ruby, python'
|
60
|
+
@tagger.tag(@taggable, :with => 'java, lisp', :on => :tags)
|
61
|
+
@taggable.tag_list.should == %w(ruby python)
|
62
|
+
@tagger.tag(@taggable, :with => '', :on => :tags)
|
63
|
+
@taggable.tag_list.should == %w(ruby python)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "when called by multiple tagger's" do
|
69
|
+
before(:each) do
|
70
|
+
@user_x = TaggableUser.create(:name => "User X")
|
71
|
+
@user_y = TaggableUser.create(:name => "User Y")
|
72
|
+
@taggable = TaggableModel.create(:name => 'tagtical', :tag_list => 'plugin')
|
73
|
+
|
74
|
+
@user_x.tag(@taggable, :with => 'ruby, rails', :on => :tags)
|
75
|
+
@user_y.tag(@taggable, :with => 'ruby, plugin', :on => :tags)
|
76
|
+
|
77
|
+
@user_y.tag(@taggable, :with => '', :on => :tags)
|
78
|
+
@user_y.tag(@taggable, :with => '', :on => :tags)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should delete owned tags" do
|
82
|
+
@user_y.owned_tags.should == []
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should not delete other taggers tags" do
|
86
|
+
@user_x.owned_tags.should have(2).items
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should not delete original tags" do
|
90
|
+
@taggable.all_tags_list_on(:tags).should include('plugin')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Tagtical::TagList do
|
4
|
+
before { @tag_list = Tagtical::TagList.new("awesome", "radical") }
|
5
|
+
subject { @tag_list }
|
6
|
+
|
7
|
+
it { should be_an Array }
|
8
|
+
|
9
|
+
specify do
|
10
|
+
@tag_list.each do |value|
|
11
|
+
value.should be_an_instance_of Tagtical::TagList::TagValue
|
12
|
+
value.relevance.should be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".from" do
|
17
|
+
|
18
|
+
it "should accept a hash with relevance values" do
|
19
|
+
@tag_list = Tagtical::TagList.from("tag 1" => 4.5, "tag 2" => 4.454)
|
20
|
+
@tag_list.map(&:relevance).should == [4.5, 4.454]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should convert all values to Tagtical::TagList::TagValue" do
|
26
|
+
@tag_list << "foo"
|
27
|
+
@tag_list.concat(["bar"])
|
28
|
+
@tag_list.each do |value|
|
29
|
+
value.should be_an_instance_of Tagtical::TagList::TagValue
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be able to be add a new tag word" do
|
34
|
+
@tag_list.add("cool")
|
35
|
+
@tag_list.include?("cool").should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should be able to add delimited lists of words" do
|
39
|
+
@tag_list.add("cool, wicked", :parse => true)
|
40
|
+
@tag_list.should include("cool", "wicked")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to add delimited list of words with quoted delimiters" do
|
44
|
+
@tag_list.add("'cool, wicked', \"really cool, really wicked\"", :parse => true)
|
45
|
+
@tag_list.should include("cool, wicked", "really cool, really wicked")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be able to handle other uses of quotation marks correctly" do
|
49
|
+
@tag_list.add("john's cool car, mary's wicked toy", :parse => true)
|
50
|
+
@tag_list.should include("john's cool car", "mary's wicked toy")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should be able to add an array of words" do
|
54
|
+
@tag_list.add(["cool", "wicked"], :parse => true)
|
55
|
+
@tag_list.should include("cool", "wicked")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should be able to add a hash" do
|
59
|
+
@tag_list.add("crazy" => 0.45, "narly" => 5.4)
|
60
|
+
@tag_list.should include("crazy", "narly")
|
61
|
+
@tag_list.detect { |t| t=="crazy" }.relevance.should == 0.45
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should be able to remove words" do
|
65
|
+
@tag_list.remove("awesome")
|
66
|
+
@tag_list.include?("awesome").should be_false
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should be able to remove delimited lists of words" do
|
70
|
+
@tag_list.remove("awesome, radical", :parse => true)
|
71
|
+
@tag_list.should be_empty
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be able to remove an array of words" do
|
75
|
+
@tag_list.remove(["awesome", "radical"], :parse => true)
|
76
|
+
@tag_list.should be_empty
|
77
|
+
end
|
78
|
+
|
79
|
+
its(:to_s) { should == "awesome, radical" }
|
80
|
+
|
81
|
+
it "should quote escape tags with commas in them" do
|
82
|
+
@tag_list.add("cool","rad,bodacious")
|
83
|
+
@tag_list.to_s.should == "awesome, radical, cool, \"rad,bodacious\""
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should be able to call to_s on a frozen tag list" do
|
87
|
+
@tag_list.freeze
|
88
|
+
lambda { @tag_list.add("cool","rad,bodacious") }.should raise_error
|
89
|
+
lambda { @tag_list.to_s }.should_not raise_error
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "TagValue" do
|
93
|
+
before do
|
94
|
+
@tag_value = Tagtical::TagList::TagValue.new("sweetness", @relevance = 3.2)
|
95
|
+
end
|
96
|
+
subject { @tag_value }
|
97
|
+
|
98
|
+
its(:relevance) { should == @relevance }
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|