taggregator 0.0.2.dev → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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