taggregator 0.0.2.dev → 0.0.3

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2.dev
1
+ 0.0.3
@@ -0,0 +1,4 @@
1
+ module Taggregator
2
+ module InstanceMethods
3
+ end
4
+ end
data/lib/taggregator.rb CHANGED
@@ -1,231 +1,230 @@
1
1
  module MongoMapper
2
2
  module Plugins
3
3
  autoload :Taggregator, 'taggregator'
4
- end
5
- end
6
-
7
- module Taggregator
8
- extend ActiveSupport::Concern
9
-
10
- included do
11
- class_inheritable_reader :taggable_with_context_options
12
- write_inheritable_attribute(:taggable_with_context_options, {})
13
- delegate "convert_string_to_array", :to => 'self.class'
14
- delegate "convert_array_to_string", :to => 'self.class'
15
- delegate "get_tag_separator_for", :to => 'self.class'
16
- delegate "tag_contexts", :to => 'self.class'
17
- delegate "tag_options_for", :to => 'self.class'
18
- set_callback :create, :after, :increment_tags_agregation
19
- set_callback :save, :after, :update_tags_aggregation
20
- set_callback :destroy, :after, :decrement_tags_aggregation
21
- end
22
-
23
- module ClassMethods
24
- # Macro to declare a document class as taggable, specify field name
25
- # for tags, and set options for tagging behavior.
26
- #
27
- # @example Define a taggable document.
28
- #
29
- # class Article
30
- # include Mongoid::Document
31
- # include Mongoid::Taggable
32
- # taggable :keywords, :separator => ' ', :aggregation => true, :default_type => "seo"
33
- # end
34
- #
35
- # @param [ Symbol ] field The name of the field for tags.
36
- # @param [ Hash ] options Options for taggable behavior.
37
- #
38
- # @option options [ String ] :separator The tag separator to
39
- # convert from; defaults to ','
40
- # @option options [ true, false ] :aggregation Whether or not to
41
- # aggregate counts of tags within the document collection using
42
- # map/reduce; defaults to false
43
- # @option options [ String ] :default_type The default type of the tag.
44
- # Each tag can optionally have a tag type. The default type is nil
45
- def taggable(*args)
46
- # init variables
47
- options = args.extract_options!
48
- tags_field = (args.blank? ? :tags : args.shift).to_sym
49
- options.reverse_merge!(
50
- :separator => ' ',
51
- :array_field => "#{tags_field}_array".to_sym
52
- )
53
- tags_array_field = options[:array_field]
54
-
55
- # register / update settings
56
- class_options = taggable_with_context_options || {}
57
- class_options[tags_field] = options
58
- write_inheritable_attribute(:taggable_with_context_options, class_options)
59
-
60
- # setup fields & indexes
61
- field tags_field, :default => ""
62
- field tags_array_field, :type => Array, :default => []
63
- index tags_array_field
64
-
65
- # singleton methods
66
- class_eval <<-END
67
- class << self
68
- def #{tags_field}
69
- tags_for(:"#{tags_field}")
70
- end
71
-
72
- def #{tags_field}_with_weight
73
- tags_with_weight_for(:"#{tags_field}")
74
- end
75
-
76
- def #{tags_field}_separator
77
- get_tag_separator_for(:"#{tags_field}")
78
- end
79
-
80
- def #{tags_field}_separator=(value)
81
- set_tag_separator_for(:"#{tags_field}", value)
82
- end
83
-
84
- def #{tags_field}_tagged_with(tags)
85
- tagged_with(:"#{tags_field}", tags)
86
- end
4
+
5
+ module Taggregator
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_inheritable_reader :taggable_with_context_options
10
+ write_inheritable_attribute(:taggable_with_context_options, {})
11
+ delegate "convert_string_to_array", :to => 'self.class'
12
+ delegate "convert_array_to_string", :to => 'self.class'
13
+ delegate "get_tag_separator_for", :to => 'self.class'
14
+ delegate "tag_contexts", :to => 'self.class'
15
+ delegate "tag_options_for", :to => 'self.class'
16
+ set_callback :create, :after, :increment_tags_agregation
17
+ set_callback :save, :after, :update_tags_aggregation
18
+ set_callback :destroy, :after, :decrement_tags_aggregation
19
+ end
20
+
21
+ module ClassMethods
22
+ # Macro to declare a document class as taggable, specify field name
23
+ # for tags, and set options for tagging behavior.
24
+ #
25
+ # @example Define a taggable document.
26
+ #
27
+ # class Article
28
+ # include Mongoid::Document
29
+ # include Mongoid::Taggable
30
+ # taggable :keywords, :separator => ' ', :aggregation => true, :default_type => "seo"
31
+ # end
32
+ #
33
+ # @param [ Symbol ] field The name of the field for tags.
34
+ # @param [ Hash ] options Options for taggable behavior.
35
+ #
36
+ # @option options [ String ] :separator The tag separator to
37
+ # convert from; defaults to ','
38
+ # @option options [ true, false ] :aggregation Whether or not to
39
+ # aggregate counts of tags within the document collection using
40
+ # map/reduce; defaults to false
41
+ # @option options [ String ] :default_type The default type of the tag.
42
+ # Each tag can optionally have a tag type. The default type is nil
43
+ def taggable(*args)
44
+ # init variables
45
+ options = args.extract_options!
46
+ tags_field = (args.blank? ? :tags : args.shift).to_sym
47
+ options.reverse_merge!(
48
+ :separator => ' ',
49
+ :array_field => "#{tags_field}_array".to_sym
50
+ )
51
+ tags_array_field = options[:array_field]
52
+
53
+ # register / update settings
54
+ class_options = taggable_with_context_options || {}
55
+ class_options[tags_field] = options
56
+ write_inheritable_attribute(:taggable_with_context_options, class_options)
57
+
58
+ # setup fields & indexes
59
+ key tags_field.to_sym, String, :default => ""
60
+ key tags_array_field.to_sym, Array, :default => []
61
+ self.ensure_index tags_array_field.to_sym
62
+
63
+ # singleton methods
64
+ class_eval <<-END
65
+ class << self
66
+ def #{tags_field}
67
+ tags_for(:"#{tags_field}")
68
+ end
69
+
70
+ def #{tags_field}_with_weight
71
+ tags_with_weight_for(:"#{tags_field}")
72
+ end
73
+
74
+ def #{tags_field}_separator
75
+ get_tag_separator_for(:"#{tags_field}")
76
+ end
77
+
78
+ def #{tags_field}_separator=(value)
79
+ set_tag_separator_for(:"#{tags_field}", value)
80
+ end
81
+
82
+ def #{tags_field}_tagged_with(tags)
83
+ tagged_with(:"#{tags_field}", tags)
84
+ end
85
+ end
86
+ END
87
+
88
+ # instance methods
89
+ class_eval <<-END
90
+ def #{tags_field}=(s)
91
+ super
92
+ write_attribute(:#{tags_array_field}, self.convert_string_to_array(s, get_tag_separator_for(:"#{tags_field}")))
93
+ end
94
+
95
+ def #{tags_array_field}=(a)
96
+ super
97
+ write_attribute(:#{tags_field}, self.convert_array_to_string(a, get_tag_separator_for(:"#{tags_field}")))
98
+ end
99
+
100
+ def increment_tags_agregation
101
+ # if document is created by using MyDocument.new
102
+ # and attributes are individually assigned
103
+ # #previous_changes won't be empty and aggregation
104
+ # is updated in after_save, so we simply skip it.
105
+ return unless changes.empty?
106
+
107
+ # if the document is created by using MyDocument.create(:tags => "tag1 tag2")
108
+ # #previous_changes hash is empty and we have to update aggregation here
109
+ tag_contexts.each do |context|
110
+ coll = MongoMapper.database.collection(self.class.aggregation_collection_for(context))
111
+ field_name = self.class.tag_options_for(context)[:array_field]
112
+ tags = self.send field_name || []
113
+ tags.each do |t|
114
+ coll.update({:_id => t}, {'$inc' => {:value => 1}}, :upsert => true)
115
+ end
116
+ end
117
+ end
118
+
119
+ def decrement_tags_aggregation
120
+ tag_contexts.each do |context|
121
+ coll = MongoMapper.database.collection(self.class.aggregation_collection_for(context))
122
+ field_name = self.class.tag_options_for(context)[:array_field]
123
+ tags = self.send field_name || []
124
+ tags.each do |t|
125
+ coll.update({:_id => t}, {'$inc' => {:value => -1}}, :upsert => true)
126
+ end
127
+ end
128
+ end
129
+
130
+ def update_tags_aggregation
131
+ return unless self.need_update_tags_aggregation?
132
+
133
+ changed_contexts.each do |context|
134
+ coll = MongoMapper.database.collection(self.class.aggregation_collection_for(context))
135
+ field_name = self.class.tag_options_for(context)[:array_field]
136
+ old_tags, new_tags = changes[field_name]
137
+ old_tags ||= []
138
+ new_tags ||= []
139
+ unchanged_tags = old_tags & new_tags
140
+ tags_removed = old_tags - unchanged_tags
141
+ tags_added = new_tags - unchanged_tags
142
+
143
+ tags_removed.each do |t|
144
+ coll.update({:_id => t}, {'$inc' => {:value => -1}}, :upsert => true)
145
+ end
146
+
147
+ tags_added.each do |t|
148
+ coll.update({:_id => t}, {'$inc' => {:value => 1}}, :upsert => true)
149
+ end
150
+ end
151
+ end
152
+
153
+ def need_update_tags_aggregation?
154
+ !changed_contexts.empty?
155
+ end
156
+
157
+ def changed_contexts
158
+ tag_contexts & changes.keys.map(&:to_sym)
159
+ end
160
+ END
87
161
  end
88
- END
89
-
90
- # instance methods
91
- class_eval <<-END
92
- def #{tags_field}=(s)
93
- super
94
- write_attribute(:#{tags_array_field}, convert_string_to_array(s, get_tag_separator_for(:"#{tags_field}")))
162
+
163
+ def tag_contexts
164
+ taggable_with_context_options.keys
95
165
  end
96
-
97
- def #{tags_array_field}=(a)
98
- super
99
- write_attribute(:#{tags_field}, convert_array_to_string(a, get_tag_separator_for(:"#{tags_field}")))
166
+
167
+ def tag_options_for(context)
168
+ taggable_with_context_options[context]
100
169
  end
101
- END
102
- end
103
-
104
- def tag_contexts
105
- taggable_with_context_options.keys
106
- end
107
-
108
- def tag_options_for(context)
109
- taggable_with_context_options[context]
110
- end
111
170
 
112
- def tags_for(context, conditions={})
113
- raise AggregationStrategyMissing
114
- end
115
-
116
- # Collection name for storing results of tag count aggregation
117
- def aggregation_collection_for(context)
118
- "#{collection_name}_#{context}_aggregation"
119
- end
120
-
121
- def tags_for(context, conditions={})
122
- conditions = {:sort => '_id'}.merge(conditions)
123
- db.collection(aggregation_collection_for(context)).find({:value => {"$gt" => 0 }}, conditions).to_a.map{ |t| t["_id"] }
124
- end
171
+ def tags_for(context, conditions={})
172
+ raise AggregationStrategyMissing
173
+ end
125
174
 
126
- # retrieve the list of tag with weight(count), this is useful for
127
- # creating tag clouds
128
- def tags_with_weight_for(context, conditions={})
129
- conditions = {:sort => '_id'}.merge(conditions)
130
- db.collection(aggregation_collection_for(context)).find({:value => {"$gt" => 0 }}, conditions).to_a.map{ |t| [t["_id"], t["value"]] }
131
- end
132
- end
133
-
134
- private
135
- def need_update_tags_aggregation?
136
- !changed_contexts.empty?
137
- end
175
+ # Collection name for storing results of tag count aggregation
176
+ def aggregation_collection_for(context)
177
+ "#{collection_name}_#{context}_aggregation"
178
+ end
138
179
 
139
- def changed_contexts
140
- tag_contexts & previous_changes.keys.map(&:to_sym)
141
- end
142
-
143
- def increment_tags_agregation
144
- # if document is created by using MyDocument.new
145
- # and attributes are individually assigned
146
- # #previous_changes won't be empty and aggregation
147
- # is updated in after_save, so we simply skip it.
148
- return unless previous_changes.empty?
149
-
150
- # if the document is created by using MyDocument.create(:tags => "tag1 tag2")
151
- # #previous_changes hash is empty and we have to update aggregation here
152
- tag_contexts.each do |context|
153
- coll = self.class.db.collection(self.class.aggregation_collection_for(context))
154
- field_name = self.class.tag_options_for(context)[:array_field]
155
- tags = self.send field_name || []
156
- tags.each do |t|
157
- coll.update({:_id => t}, {'$inc' => {:value => 1}}, :upsert => true)
180
+ def tags_for(context, conditions={})
181
+ conditions = {:sort => '_id'}.merge(conditions)
182
+ MongoMapper.database.collection(aggregation_collection_for(context)).find({:value => {"$gt" => 0 }}, conditions).to_a.map{ |t| t["_id"] }
158
183
  end
159
- end
160
- end
161
184
 
162
- def decrement_tags_aggregation
163
- tag_contexts.each do |context|
164
- coll = self.class.db.collection(self.class.aggregation_collection_for(context))
165
- field_name = self.class.tag_options_for(context)[:array_field]
166
- tags = self.send field_name || []
167
- tags.each do |t|
168
- coll.update({:_id => t}, {'$inc' => {:value => -1}}, :upsert => true)
185
+ # retrieve the list of tag with weight(count), this is useful for
186
+ # creating tag clouds
187
+ def tags_with_weight_for(context, conditions={})
188
+ conditions = {:sort => '_id'}.merge(conditions)
189
+ MongoMapper.database.collection(aggregation_collection_for(context)).find({:value => {"$gt" => 0 }}, conditions).to_a.map{ |t| [t["_id"], t["value"]] }
169
190
  end
170
- end
171
- end
172
191
 
173
- def update_tags_aggregation
174
- return unless need_update_tags_aggregation?
175
-
176
- changed_contexts.each do |context|
177
- coll = self.class.db.collection(self.class.aggregation_collection_for(context))
178
- field_name = self.class.tag_options_for(context)[:array_field]
179
- old_tags, new_tags = previous_changes["#{field_name}"]
180
- old_tags ||= []
181
- new_tags ||= []
182
- unchanged_tags = old_tags & new_tags
183
- tags_removed = old_tags - unchanged_tags
184
- tags_added = new_tags - unchanged_tags
185
-
186
- tags_removed.each do |t|
187
- coll.update({:_id => t}, {'$inc' => {:value => -1}}, :upsert => true)
192
+ def get_tag_separator_for(context)
193
+ taggable_with_context_options[context][:separator] || ' '
188
194
  end
189
195
 
190
- tags_added.each do |t|
191
- coll.update({:_id => t}, {'$inc' => {:value => 1}}, :upsert => true)
196
+ def set_tag_separator_for(context, value)
197
+ taggable_with_context_options[context][:separator] = value.nil? ? " " : value.to_s
192
198
  end
193
- end
194
-
195
- def get_tag_separator_for(context)
196
- taggable_with_context_options[context][:separator]
197
- end
198
199
 
199
- def set_tag_separator_for(context, value)
200
- taggable_with_context_options[context][:separator] = value.nil? ? " " : value.to_s
201
- end
202
-
203
- # Find documents tagged with all tags passed as a parameter, given
204
- # as an Array or a String using the configured separator.
205
- #
206
- # @example Find matching all tags in an Array.
207
- # Article.tagged_with(['ruby', 'mongodb'])
208
- # @example Find matching all tags in a String.
209
- # Article.tagged_with('ruby, mongodb')
210
- #
211
- # @param [ String ] :field The field name of the tag.
212
- # @param [ Array<String, Symbol>, String ] :tags Tags to match.
213
- # @return [ Criteria ] A new criteria.
214
- def tagged_with(context, tags)
215
- tags = convert_string_to_array(tags, get_tag_separator_for(context)) if tags.is_a? String
216
- array_field = tag_options_for(context)[:array_field]
217
- all_in(array_field => tags)
218
- end
219
-
220
- # Helper method to convert a String to an Array based on the
221
- # configured tag separator.
222
- def convert_string_to_array(str = "", seperator = " ")
223
- str.split(seperator).map(&:strip).uniq.compact
224
- end
225
-
226
- def convert_array_to_string(ary = [], seperator = " ")
227
- ary.uniq.compact.join(seperator)
200
+ # Find documents tagged with all tags passed as a parameter, given
201
+ # as an Array or a String using the configured separator.
202
+ #
203
+ # @example Find matching all tags in an Array.
204
+ # Article.tagged_with(['ruby', 'mongodb'])
205
+ # @example Find matching all tags in a String.
206
+ # Article.tagged_with('ruby, mongodb')
207
+ #
208
+ # @param [ String ] :field The field name of the tag.
209
+ # @param [ Array<String, Symbol>, String ] :tags Tags to match.
210
+ # @return [ Criteria ] A new criteria.
211
+ def tagged_with(context, tags)
212
+ tags = convert_string_to_array(tags, get_tag_separator_for(context)) if tags.is_a? String
213
+ array_field = tag_options_for(context)[:array_field]
214
+ all_in(array_field => tags)
215
+ end
216
+
217
+ # Helper method to convert a String to an Array based on the
218
+ # configured tag separator.
219
+ def convert_string_to_array(str = "", seperator = " ")
220
+ str.split(seperator).map(&:strip).uniq.compact
221
+ end
222
+
223
+ def convert_array_to_string(ary = [], seperator = " ")
224
+ ary.uniq.compact.join(seperator)
225
+ end
226
+
227
+ end
228
228
  end
229
229
  end
230
-
231
230
  end
data/taggregator.gemspec CHANGED
@@ -5,9 +5,9 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{taggregator}
8
- s.version = "0.0.2.dev"
8
+ s.version = "0.0.3"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Mark Coates"]
12
12
  s.date = %q{2011-07-21}
13
13
  s.description = %q{Taggable in context and aggregation of tags for MongoMapper. Adds weight and distribution convenience methods to models in which it is included. Uses MongoDB's increment/decrement ($inc/$dec) to keep real-time counts of individual tags in context with optional type. Based on 'Mongoid Taggable With Context' (https://github.com/aq1018/mongoid_taggable_with_context).}
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  "Rakefile",
26
26
  "VERSION",
27
27
  "lib/taggregator.rb",
28
+ "lib/taggregator/instance_methods.rb",
28
29
  "taggregator.gemspec",
29
30
  "test/helper.rb",
30
31
  "test/test_taggregator.rb"
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taggregator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.dev
5
- prerelease: 6
4
+ version: 0.0.3
5
+ prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mark Coates
@@ -14,7 +14,7 @@ default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: shoulda
17
- requirement: &2153013020 !ruby/object:Gem::Requirement
17
+ requirement: &2152520280 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
- version_requirements: *2153013020
25
+ version_requirements: *2152520280
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bundler
28
- requirement: &2153012040 !ruby/object:Gem::Requirement
28
+ requirement: &2152519520 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.0.0
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *2153012040
36
+ version_requirements: *2152519520
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: jeweler
39
- requirement: &2153011220 !ruby/object:Gem::Requirement
39
+ requirement: &2152518360 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 1.6.2
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *2153011220
47
+ version_requirements: *2152518360
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: rcov
50
- requirement: &2153010400 !ruby/object:Gem::Requirement
50
+ requirement: &2152517480 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *2153010400
58
+ version_requirements: *2152517480
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: mongo_mapper
61
- requirement: &2153009760 !ruby/object:Gem::Requirement
61
+ requirement: &2152516380 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ~>
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: 0.9.0
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *2153009760
69
+ version_requirements: *2152516380
70
70
  description: Taggable in context and aggregation of tags for MongoMapper. Adds weight
71
71
  and distribution convenience methods to models in which it is included. Uses MongoDB's
72
72
  increment/decrement ($inc/$dec) to keep real-time counts of individual tags in context
@@ -86,6 +86,7 @@ files:
86
86
  - Rakefile
87
87
  - VERSION
88
88
  - lib/taggregator.rb
89
+ - lib/taggregator/instance_methods.rb
89
90
  - taggregator.gemspec
90
91
  - test/helper.rb
91
92
  - test/test_taggregator.rb
@@ -105,13 +106,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
106
  version: '0'
106
107
  segments:
107
108
  - 0
108
- hash: -779744515643755822
109
+ hash: -1132430800889875187
109
110
  required_rubygems_version: !ruby/object:Gem::Requirement
110
111
  none: false
111
112
  requirements:
112
- - - ! '>'
113
+ - - ! '>='
113
114
  - !ruby/object:Gem::Version
114
- version: 1.3.1
115
+ version: '0'
115
116
  requirements: []
116
117
  rubyforge_project:
117
118
  rubygems_version: 1.6.2