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 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