cldwalker-has_machine_tags 0.1.4 → 0.1.5
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/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
|