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 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
- * More tests!
131
- * Console methods for showing relations between namespaces, predicates + values
132
- * Add support for DataMapper to be more ORM-agnostic
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
@@ -1,4 +1,4 @@
1
1
  ---
2
- :major: 0
3
2
  :minor: 1
4
- :patch: 4
3
+ :patch: 5
4
+ :major: 0
@@ -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/singleton_methods'
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::SingletonMethods
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
@@ -1,5 +1,5 @@
1
1
  module HasMachineTags
2
- module SingletonMethods
2
+ module Finder
3
3
  # Takes a string of delimited tags or an array of tags.
4
4
  # Note that each tag is interpreted as a possible wildcard machine tag.
5
5
  #
@@ -1,3 +1,3 @@
1
- class ::Tag < ActiveRecord::Base
1
+ class ::Tag < ActiveRecord::Base #:nodoc:
2
2
  include HasMachineTags::TagMethods
3
3
  end
@@ -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/SingletonMethods.html] of tagged objects.
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
@@ -1,4 +1,4 @@
1
- class Tagging < ActiveRecord::Base
1
+ class Tagging < ActiveRecord::Base #:nodoc:
2
2
  belongs_to :tag
3
3
  belongs_to :taggable, :polymorphic => true
4
4
  end
@@ -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
@@ -40,7 +40,7 @@ class HasMachineTagsTest < Test::Unit::TestCase
40
40
  end
41
41
  end
42
42
 
43
- context "HasMachineTags" do
43
+ context "InstanceMethods" do
44
44
  before(:each) { @taggable = TaggableModel.new }
45
45
 
46
46
  test "creates all tags" do
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), 'test_helper')
2
2
 
3
- class HasMachineTags::TagTest < Test::Unit::TestCase
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
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-02-23 00:00:00 -08:00
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/singleton_methods.rb
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/tag_test.rb
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