cldwalker-has_machine_tags 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +6 -3
- data/VERSION.yml +2 -2
- data/lib/has_machine_tags.rb +8 -4
- data/lib/has_machine_tags/{singleton_methods.rb → finder.rb} +1 -1
- data/lib/has_machine_tags/tag.rb +1 -1
- data/lib/has_machine_tags/tag_console.rb +32 -0
- data/lib/has_machine_tags/tag_list.rb +24 -2
- data/lib/has_machine_tags/tag_methods.rb +17 -29
- data/lib/has_machine_tags/tagging.rb +1 -1
- data/test/finder_test.rb +84 -0
- data/test/has_machine_tags_test.rb +1 -1
- data/test/{tag_test.rb → tag_methods_test.rb} +1 -1
- data/test/test_helper.rb +1 -0
- metadata +9 -6
data/README.rdoc
CHANGED
@@ -127,11 +127,14 @@ these characters are off limit unless used in the machine tag context:
|
|
127
127
|
|
128
128
|
== Todo
|
129
129
|
|
130
|
-
*
|
131
|
-
*
|
132
|
-
*
|
130
|
+
* Add a match_all option to tagged_with().
|
131
|
+
* More helper methods ie for showing relations between namespaces, predicates + values
|
132
|
+
* Possible add support for other ORM's ie DataMapper.
|
133
133
|
* Play friendly with other tagging plugins as needed.
|
134
134
|
|
135
|
+
== Bugs
|
136
|
+
Please report them here: http://cldwalker.lighthouseapp.com/projects/27818-has_machine_tags/tickets.
|
137
|
+
|
135
138
|
== Credits
|
136
139
|
|
137
140
|
Thanks goes to Flickr for popularizing this tagging model.
|
data/VERSION.yml
CHANGED
data/lib/has_machine_tags.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
current_dir = File.dirname(__FILE__)
|
2
2
|
$:.unshift(current_dir) unless $:.include?(current_dir) || $:.include?(File.expand_path(current_dir))
|
3
|
-
require 'has_machine_tags/
|
3
|
+
require 'has_machine_tags/finder'
|
4
4
|
require 'has_machine_tags/tag_list'
|
5
5
|
require 'has_machine_tags/console'
|
6
6
|
|
@@ -23,7 +23,7 @@ module HasMachineTags
|
|
23
23
|
after_save :save_tags
|
24
24
|
|
25
25
|
include HasMachineTags::InstanceMethods
|
26
|
-
extend HasMachineTags::
|
26
|
+
extend HasMachineTags::Finder
|
27
27
|
if options[:console]
|
28
28
|
include HasMachineTags::Console::InstanceMethods
|
29
29
|
extend HasMachineTags::Console::ClassMethods
|
@@ -55,9 +55,13 @@ module HasMachineTags
|
|
55
55
|
|
56
56
|
# Fetches latest tag list for an object
|
57
57
|
def tag_list
|
58
|
-
@tag_list ||= self.tags.map(&:name)
|
58
|
+
@tag_list ||= TagList.new(self.tags.map(&:name))
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
|
+
def quick_mode_tag_list
|
62
|
+
tag_list.to_quick_mode_string
|
63
|
+
end
|
64
|
+
|
61
65
|
protected
|
62
66
|
# :stopdoc:
|
63
67
|
def save_tags
|
data/lib/has_machine_tags/tag.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
module HasMachineTags
|
2
|
+
module TagConsole #:nodoc:
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval %[
|
5
|
+
named_scope :namespace_counts, :select=>'*, namespace as counter, count(namespace) as count', :group=>"namespace HAVING count(namespace)>=1"
|
6
|
+
named_scope :predicate_counts, :select=>'*, predicate as counter, count(predicate) as count', :group=>"predicate HAVING count(predicate)>=1"
|
7
|
+
named_scope :value_counts, :select=>'*, value as counter, count(value) as count', :group=>"value HAVING count(value)>=1"
|
8
|
+
named_scope :distinct_namespaces, :select=>"distinct namespace"
|
9
|
+
named_scope :distinct_predicates, :select=>"distinct predicate"
|
10
|
+
named_scope :distinct_values, :select=>"distinct value"
|
11
|
+
]
|
12
|
+
base.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
#:stopdoc:
|
17
|
+
def namespaces; distinct_namespaces.map(&:namespace).compact; end
|
18
|
+
def predicates; distinct_predicates.map(&:predicate).compact; end
|
19
|
+
def values; distinct_values.map(&:value).compact; end
|
20
|
+
#:startdoc:
|
21
|
+
|
22
|
+
# To be used with the *counts methods.
|
23
|
+
# For example:
|
24
|
+
# stat(:namespace_counts)
|
25
|
+
# This prints out pairs of a namespaces and their counts in the tags table.
|
26
|
+
def stat(type)
|
27
|
+
shortcuts = {:n=>:namespace_counts, :p=>:predicate_counts, :v=>:value_counts }
|
28
|
+
send(shortcuts[type] || type).map {|e| [e.counter, e.count] }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -24,7 +24,7 @@ module HasMachineTags
|
|
24
24
|
array = parse_quick_mode(array) if @options[:quick_mode]
|
25
25
|
concat array
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def parse_quick_mode(mtag_list) #:nodoc:
|
29
29
|
mtag_list = mtag_list.map {|e|
|
30
30
|
if e.include?(Tag::PREDICATE_DELIMITER)
|
@@ -38,7 +38,29 @@ module HasMachineTags
|
|
38
38
|
end
|
39
39
|
}.flatten
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
|
+
def namespace_hashes #:nodoc:
|
43
|
+
self.inject({}) {|h, e|
|
44
|
+
namespace, *predicate_value = Tag.split_machine_tag(e)
|
45
|
+
(h[namespace] ||= []) << predicate_value unless namespace.nil?
|
46
|
+
h
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def non_machine_tags
|
51
|
+
self.reject {|e| Tag.machine_tag?(e)}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Converts tag_list to a stringified version of quick_mode.
|
55
|
+
def to_quick_mode_string
|
56
|
+
machine_tags = namespace_hashes.map {|namespace, predicate_values|
|
57
|
+
"#{namespace}:" + predicate_values.map {|pred, value|
|
58
|
+
pred == self.default_predicate ? value : "#{pred}#{Tag::VALUE_DELIMITER}#{value}"
|
59
|
+
}.join(QUICK_MODE_DELIMITER)
|
60
|
+
}
|
61
|
+
(machine_tags + non_machine_tags).join("#{delimiter} ")
|
62
|
+
end
|
63
|
+
|
42
64
|
def to_s #:nodoc:
|
43
65
|
join("#{delimiter} ")
|
44
66
|
end
|
@@ -8,7 +8,7 @@ module HasMachineTags
|
|
8
8
|
# and underscore. A value can contain any characters that normal tags use.
|
9
9
|
#
|
10
10
|
# == Wildcard Machine Tags
|
11
|
-
# Wildcard machine tag syntax is used with Tag.machine_tags() and {tagged_with() or find_tagged_with()}[link:classes/HasMachineTags/
|
11
|
+
# Wildcard machine tag syntax is used with Tag.machine_tags() and {tagged_with() or find_tagged_with()}[link:classes/HasMachineTags/Finder.html] of tagged objects.
|
12
12
|
# This syntax allows one to fetch items that fall under a group of tags, as specified by namespace, predicate, value or
|
13
13
|
# a combination of these ways. While this plugin supports {Flickr's wildcard format}[http://code.flickr.com/blog/2008/07/18/wildcard-machine-tag-urls/],
|
14
14
|
# it also supports its own slightly shorter format.
|
@@ -82,34 +82,12 @@ module HasMachineTags
|
|
82
82
|
|
83
83
|
#disallow machine tags special characters and tag list delimiter OR allow machine tag format
|
84
84
|
validates_format_of :name, :with=>name_format
|
85
|
-
|
86
|
-
named_scope :namespace_counts, :select=>'*, namespace as counter, count(namespace) as count', :group=>"namespace HAVING count(namespace)>=1"
|
87
|
-
named_scope :predicate_counts, :select=>'*, predicate as counter, count(predicate) as count', :group=>"predicate HAVING count(predicate)>=1"
|
88
|
-
named_scope :value_counts, :select=>'*, value as counter, count(value) as count', :group=>"value HAVING count(value)>=1"
|
89
|
-
named_scope :distinct_namespaces, :select=>"distinct namespace"
|
90
|
-
named_scope :distinct_predicates, :select=>"distinct predicate"
|
91
|
-
named_scope :distinct_values, :select=>"distinct value"
|
92
85
|
]
|
93
86
|
base.extend(ClassMethods)
|
94
87
|
base.send :include, InstanceMethods
|
95
88
|
end
|
96
89
|
|
97
90
|
module ClassMethods
|
98
|
-
#:stopdoc:
|
99
|
-
def namespaces; distinct_namespaces.map(&:namespace).compact; end
|
100
|
-
def predicates; distinct_predicates.map(&:predicate).compact; end
|
101
|
-
def values; distinct_values.map(&:value).compact; end
|
102
|
-
#:startdoc:
|
103
|
-
|
104
|
-
# To be used with the *counts methods.
|
105
|
-
# For example:
|
106
|
-
# stat(:namespace_counts)
|
107
|
-
# This prints out pairs of a namespaces and their counts in the tags table.
|
108
|
-
def stat(type)
|
109
|
-
shortcuts = {:n=>:namespace_counts, :p=>:predicate_counts, :v=>:value_counts }
|
110
|
-
send(shortcuts[type] || type).map {|e| [e.counter, e.count] }
|
111
|
-
end
|
112
|
-
|
113
91
|
# Takes a wildcard machine tag and returns matching tags.
|
114
92
|
def machine_tags(name)
|
115
93
|
conditions = if (match = match_wildcard_machine_tag(name))
|
@@ -126,7 +104,21 @@ module HasMachineTags
|
|
126
104
|
def build_machine_tag(namespace, predicate, value)
|
127
105
|
"#{namespace}:#{predicate}=#{value}"
|
128
106
|
end
|
129
|
-
|
107
|
+
|
108
|
+
# Returns an array of machine tag parts: [namespace, predicate, value]
|
109
|
+
def split_machine_tag(machine_tag)
|
110
|
+
extract_from_name(machine_tag) || []
|
111
|
+
end
|
112
|
+
|
113
|
+
# Boolean indicating if given tag is a machine tag.
|
114
|
+
def machine_tag?(machine_tag)
|
115
|
+
!extract_from_name(machine_tag).nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
def extract_from_name(tag_name) #:nodoc:
|
119
|
+
(tag_name =~ /^(#{NAMESPACE_REGEX})\:(#{PREDICATE_REGEX})\=(#{VALUE_REGEX})$/) ? [$1, $2, $3] : nil
|
120
|
+
end
|
121
|
+
|
130
122
|
# Valid wildcards with their equivalent shortcuts
|
131
123
|
# namespace:*=* -> namespace:
|
132
124
|
# *:predicate=* -> predicate=
|
@@ -149,14 +141,10 @@ module HasMachineTags
|
|
149
141
|
end
|
150
142
|
|
151
143
|
module InstanceMethods
|
152
|
-
def extract_from_name(tag_name) #:nodoc:
|
153
|
-
(tag_name =~ /^(#{NAMESPACE_REGEX})\:(#{PREDICATE_REGEX})\=(#{VALUE_REGEX})$/) ? [$1, $2, $3] : nil
|
154
|
-
end
|
155
|
-
|
156
144
|
private
|
157
145
|
|
158
146
|
def update_name_related_columns
|
159
|
-
if self.changed.include?('name') && (arr = extract_from_name(self.name))
|
147
|
+
if self.changed.include?('name') && (arr = self.class.extract_from_name(self.name))
|
160
148
|
self[:namespace], self[:predicate], self[:value] = arr
|
161
149
|
end
|
162
150
|
end
|
data/test/finder_test.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class HasMachineTags::FinderTest < Test::Unit::TestCase
|
4
|
+
before(:each) {
|
5
|
+
[Tag, Tagging, TaggableModel].each {|e| e.delete_all}
|
6
|
+
}
|
7
|
+
|
8
|
+
def create_extra_taggable
|
9
|
+
TaggableModel.create(:tag_list=>"blah:blih=bluh")
|
10
|
+
end
|
11
|
+
|
12
|
+
context "TaggableModel" do
|
13
|
+
context "finds by" do
|
14
|
+
before(:each) {
|
15
|
+
@taggable = TaggableModel.create(:tag_list=>"url:lang=ruby")
|
16
|
+
create_extra_taggable
|
17
|
+
}
|
18
|
+
|
19
|
+
test "namespace wildcard machine tag" do
|
20
|
+
TaggableModel.tagged_with("url:").should == [@taggable]
|
21
|
+
end
|
22
|
+
|
23
|
+
test "predicate wildcard machine tag" do
|
24
|
+
TaggableModel.tagged_with("lang=").should == [@taggable]
|
25
|
+
end
|
26
|
+
|
27
|
+
test "value wildcard machine tag" do
|
28
|
+
TaggableModel.tagged_with("=ruby").should == [@taggable]
|
29
|
+
end
|
30
|
+
|
31
|
+
test "namespace-value wildcard machine tag" do
|
32
|
+
TaggableModel.tagged_with("url.ruby").should == [@taggable]
|
33
|
+
end
|
34
|
+
|
35
|
+
test "predicate-value wildcard machine tag" do
|
36
|
+
TaggableModel.tagged_with("lang=ruby").should == [@taggable]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "finds with" do
|
41
|
+
test "multiple machine tags as an array" do
|
42
|
+
@taggable = TaggableModel.create(:tag_list=>"article:todo=later")
|
43
|
+
@taggable2 = TaggableModel.create(:tag_list=>"article:tags=funny")
|
44
|
+
create_extra_taggable
|
45
|
+
results = TaggableModel.tagged_with(["article:todo=later", "article:tags=funny"])
|
46
|
+
results.size.should == 2
|
47
|
+
results.include?(@taggable).should be(true)
|
48
|
+
results.include?(@taggable2).should be(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
test "multiple machine tags as a delimited string" do
|
52
|
+
@taggable = TaggableModel.create(:tag_list=>"article:todo=later")
|
53
|
+
@taggable2 = TaggableModel.create(:tag_list=>"article:tags=funny")
|
54
|
+
create_extra_taggable
|
55
|
+
results = TaggableModel.tagged_with("article:todo=later, article:tags=funny")
|
56
|
+
results.size.should == 2
|
57
|
+
results.include?(@taggable).should be(true)
|
58
|
+
results.include?(@taggable2).should be(true)
|
59
|
+
end
|
60
|
+
|
61
|
+
test "condition option" do
|
62
|
+
@taggable = TaggableModel.create(:title=>"so limiting", :tag_list=>"url:tags=funny" )
|
63
|
+
create_extra_taggable
|
64
|
+
TaggableModel.tagged_with("url:tags=funny", :conditions=>"title = 'so limiting'").should == [@taggable]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when queried with normal tag" do
|
69
|
+
before(:each) { @taggable = TaggableModel.new }
|
70
|
+
test "doesn't find if machine tagged" do
|
71
|
+
@taggable.tag_list = 'url:tags=square'
|
72
|
+
@taggable.save
|
73
|
+
Tag.count.should == 1
|
74
|
+
TaggableModel.tagged_with("square").should == []
|
75
|
+
end
|
76
|
+
|
77
|
+
test "finds if tagged normally" do
|
78
|
+
@taggable.tag_list = 'square, some:machine=tag'
|
79
|
+
@taggable.save
|
80
|
+
TaggableModel.tagged_with("square").should == [@taggable]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
2
|
|
3
|
-
class HasMachineTags::
|
3
|
+
class HasMachineTags::TagMethodsTest < Test::Unit::TestCase
|
4
4
|
test "create with normal tag name only touches name" do
|
5
5
|
obj = Tag.create(:name=>'blah1')
|
6
6
|
[:name, :namespace, :predicate, :value].map {|e| obj.send(e)}.should == ['blah1', nil, nil, nil]
|
data/test/test_helper.rb
CHANGED
@@ -8,6 +8,7 @@ require File.join(File.dirname(__FILE__), '..', 'init')
|
|
8
8
|
|
9
9
|
#Setup logger
|
10
10
|
require 'logger'
|
11
|
+
# ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "test.log"))
|
11
12
|
ActiveRecord::Base.logger = Logger.new(STDERR)
|
12
13
|
ActiveRecord::Base.logger.level = Logger::WARN
|
13
14
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cldwalker-has_machine_tags
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriel Horner
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-22 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -35,21 +35,24 @@ files:
|
|
35
35
|
- generators/has_machine_tags_migration/templates/migration.rb
|
36
36
|
- lib/has_machine_tags
|
37
37
|
- lib/has_machine_tags/console.rb
|
38
|
-
- lib/has_machine_tags/
|
38
|
+
- lib/has_machine_tags/finder.rb
|
39
39
|
- lib/has_machine_tags/tag.rb
|
40
|
+
- lib/has_machine_tags/tag_console.rb
|
40
41
|
- lib/has_machine_tags/tag_list.rb
|
41
42
|
- lib/has_machine_tags/tag_methods.rb
|
42
43
|
- lib/has_machine_tags/tagging.rb
|
43
44
|
- lib/has_machine_tags.rb
|
45
|
+
- test/finder_test.rb
|
44
46
|
- test/has_machine_tags_test.rb
|
45
47
|
- test/schema.rb
|
46
|
-
- test/
|
48
|
+
- test/tag_methods_test.rb
|
47
49
|
- test/test_helper.rb
|
48
50
|
has_rdoc: true
|
49
51
|
homepage: http://github.com/cldwalker/has_machine_tags
|
50
52
|
post_install_message:
|
51
|
-
rdoc_options:
|
52
|
-
|
53
|
+
rdoc_options:
|
54
|
+
- --inline-source
|
55
|
+
- --charset=UTF-8
|
53
56
|
require_paths:
|
54
57
|
- lib
|
55
58
|
required_ruby_version: !ruby/object:Gem::Requirement
|