tagtical 1.0.6
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 +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
|