mongoid_taggable_with_context 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'mongoid', '~> 2.0.0.beta.20'
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development do
8
+ gem 'database_cleaner'
9
+ gem 'bson', '~> 1.2.1'
10
+ gem 'bson_ext', '~> 1.2.1'
11
+ gem 'rspec', '~> 2.3.0'
12
+ gem 'yard', '~> 0.6.0'
13
+ gem 'bundler', '~> 1.0.0'
14
+ gem 'jeweler', '~> 1.5.2'
15
+ gem 'rcov', '>= 0'
16
+ gem 'reek', '~> 1.2.8'
17
+ gem 'roodi', '~> 2.1.0'
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,67 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.4)
5
+ activesupport (= 3.0.4)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.4)
8
+ activesupport (3.0.4)
9
+ bson (1.2.1)
10
+ bson_ext (1.2.1)
11
+ builder (2.1.2)
12
+ database_cleaner (0.6.0)
13
+ diff-lcs (1.1.2)
14
+ git (1.2.5)
15
+ i18n (0.5.0)
16
+ jeweler (1.5.2)
17
+ bundler (~> 1.0.0)
18
+ git (>= 1.2.5)
19
+ rake
20
+ mongo (1.2.1)
21
+ bson (>= 1.2.1)
22
+ mongoid (2.0.0.rc.7)
23
+ activemodel (~> 3.0)
24
+ mongo (~> 1.2)
25
+ tzinfo (~> 0.3.22)
26
+ will_paginate (~> 3.0.pre)
27
+ rake (0.8.7)
28
+ rcov (0.9.9)
29
+ reek (1.2.8)
30
+ ruby2ruby (~> 1.2)
31
+ ruby_parser (~> 2.0)
32
+ sexp_processor (~> 3.0)
33
+ roodi (2.1.0)
34
+ ruby_parser
35
+ rspec (2.3.0)
36
+ rspec-core (~> 2.3.0)
37
+ rspec-expectations (~> 2.3.0)
38
+ rspec-mocks (~> 2.3.0)
39
+ rspec-core (2.3.1)
40
+ rspec-expectations (2.3.0)
41
+ diff-lcs (~> 1.1.2)
42
+ rspec-mocks (2.3.0)
43
+ ruby2ruby (1.2.5)
44
+ ruby_parser (~> 2.0)
45
+ sexp_processor (~> 3.0)
46
+ ruby_parser (2.0.5)
47
+ sexp_processor (~> 3.0)
48
+ sexp_processor (3.0.5)
49
+ tzinfo (0.3.24)
50
+ will_paginate (3.0.pre2)
51
+ yard (0.6.4)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ bson (~> 1.2.1)
58
+ bson_ext (~> 1.2.1)
59
+ bundler (~> 1.0.0)
60
+ database_cleaner
61
+ jeweler (~> 1.5.2)
62
+ mongoid (~> 2.0.0.beta.20)
63
+ rcov
64
+ reek (~> 1.2.8)
65
+ roodi (~> 2.1.0)
66
+ rspec (~> 2.3.0)
67
+ yard (~> 0.6.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Aaron Qian
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,133 @@
1
+ == Mongoid Taggable With Context
2
+
3
+ A tagging plugin for Mongoid that allows for custom tagging along dynamic contexts. This plugin was originally based on Mongoid Taggable by Wilker Lúcio and Ches Martin. It has evolved substantially since that point, but all credit goes to them for the initial tagging functionality.
4
+
5
+ For instance, in a social network, a user might have tags that are called skills, interests, sports, and more. There is no real way to differentiate between tags and so an implementation of this type is not possible with Mongoid Taggable.
6
+
7
+ Another example, aggregation such as counting tag occurrences was achieved by map-reduce with Mongoid Taggable. It was ok for small amount of tags, but when the amount of tags and documents grow, the original Mongoid Taggable won't be able to scale to real-time statistics demand.
8
+
9
+ Enter Mongoid Taggable With Context. Rather than tying functionality to a specific keyword (namely "tags"), Mongoid Taggable With Context allows you to specify an arbitrary number of tag "contexts" that can be used locally or in combination in the same way Mongoid Taggable was used.
10
+
11
+ Mongoid Taggable With Context implements tag aggregation without map-reduce, instead it quickly adjusts the aggregation collection whenever tags are inserted or removed with $inc operator. So performance won't be impacted as the number of tags and documents grow.
12
+
13
+ == Installation
14
+
15
+ You can simply install from rubygems:
16
+
17
+ gem install mongoid_taggable_with_context
18
+
19
+ or in Gemfile:
20
+
21
+ gem 'mongoid_taggable_with_context'
22
+
23
+ == Basic Usage
24
+
25
+ To make a document taggable you need to include Mongoid::TaggableOnContext into your document and call the *taggable* macro with optional arguments:
26
+
27
+ class Post
28
+ include Mongoid::Document
29
+ include Mongoid::TaggableWithContext
30
+
31
+ field :title
32
+ field :content
33
+
34
+ # default context is 'tags'.
35
+ # This creates #tags, #tags=, #tags_array, #tags_array= instance methods
36
+ # separator is " " by default
37
+ # #tags method returns space separated string
38
+ # #tags= methods accepts space separated string
39
+ # #tags_array method returns an array of tags
40
+ # #tags_array= method accepts an array of tags
41
+ # #tags and #tags_array are automatically synched.
42
+ taggable
43
+
44
+ # tagging for 'interests' context.
45
+ # This creates #interests, #interests=, #interests_array, #interests_array= instance methods
46
+ taggable :interests
47
+
48
+ # tagging for 'skills' context.
49
+ # This creates #skills, #skills=, #skills_array, #skills_array= instance methods
50
+ # changing tag separator to "," (Default is " ")
51
+ taggable :skills, :separator => ','
52
+ end
53
+
54
+ Then in your form, for example:
55
+
56
+ <% form_for @post do |f| %>
57
+ <p>
58
+ <%= f.label :title %><br />
59
+ <%= f.text_field :title %>
60
+ </p>
61
+ <p>
62
+ <%= f.label :content %><br />
63
+ <%= f.text_area :content %>
64
+ </p>
65
+ <p>
66
+ <%= f.label :tags %><br />
67
+ <%= text_field_tag 'post[tags]' %>
68
+ </p>
69
+ <p>
70
+ <%= f.label :interests %><br />
71
+ <%= text_field_tag 'post[interests]' %>
72
+ </p>
73
+ <p>
74
+ <%= f.label :skills %><br />
75
+ <%= text_field_tag 'post[skills]' %>
76
+ </p>
77
+ <p>
78
+ <button type="submit">Send</button>
79
+ </p>
80
+ <% end %>
81
+
82
+
83
+ == Tag Aggregation with Counts
84
+
85
+ This lib can automatically create aggregate collections of contexts and their counts for you. Each aggregate collection is scoped to the document collection type and context.
86
+
87
+ Post.create!(:tags => "food,ant,bee")
88
+ Post.create!(:tags => "juice,food,bee,zip")
89
+ Post.create!(:tags => "honey,strip,food")
90
+
91
+ Post.tags # will retrieve ["ant", "bee", "food", "honey", "juice", "strip", "zip"]
92
+ Post.tags_with_weight # will retrieve:
93
+ # [
94
+ # ['ant', 1],
95
+ # ['bee', 2],
96
+ # ['food', 3],
97
+ # ['honey', 1],
98
+ # ['juice', 1],
99
+ # ['strip', 1],
100
+ # ['zip', 1]
101
+ # ]
102
+
103
+ == Changing default separator
104
+
105
+ To change the default separator you may pass a *separator* argument to the macro:
106
+
107
+ class Post
108
+ include Mongoid::Document
109
+ include Mongoid::Taggable
110
+
111
+ field :title
112
+ field :content
113
+
114
+ taggable :separator => ',' # tags will be delineated by comma instead of space
115
+ end
116
+
117
+ == Development and Testing
118
+
119
+ Mongoid Taggable With Context uses Bundler for easy installation of development dependent gems.
120
+ To get all dependent gems for development, while inside mongoid_taggable_with_context directory:
121
+
122
+ bundle install
123
+
124
+
125
+ Mongoid Taggable With Context uses RSpec for its test coverage.
126
+ Inside mongoid_taggable_with_context directory, you can run the specs with:
127
+
128
+ rake spec
129
+
130
+ To see the test coverage, you need to be on ruby 1.8.7 (since latest rcov doesn't work 1.9)
131
+ and run the following command:
132
+
133
+ rake rcov
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "mongoid_taggable_with_context"
15
+ gem.homepage = "http://github.com/aq1018/mongoid_taggable_with_context"
16
+ gem.license = "MIT"
17
+ gem.summary = %Q{Mongoid taggable behaviour}
18
+ gem.description = %Q{It provides some helpers to create taggable documents with context.}
19
+ gem.email = "aq1018@gmail.com"
20
+ gem.authors = ["Aaron Qian"]
21
+ gem.add_runtime_dependency 'mongoid', '~> 2.0.0.beta.20'
22
+ end
23
+ Jeweler::RubygemsDotOrgTasks.new
24
+
25
+ require 'rspec/core'
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new(:spec) do |spec|
28
+ spec.pattern = FileList['spec/**/*_spec.rb']
29
+ spec.rspec_opts = "--color --format progress"
30
+ end
31
+
32
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ spec.rcov_opts = "--exclude ~\/.rvm,spec"
36
+ end
37
+
38
+ require 'reek/rake/task'
39
+ Reek::Rake::Task.new do |t|
40
+ t.fail_on_error = true
41
+ t.verbose = false
42
+ t.source_files = 'lib/**/*.rb'
43
+ end
44
+
45
+ require 'roodi'
46
+ require 'roodi_task'
47
+ RoodiTask.new do |t|
48
+ t.verbose = false
49
+ end
50
+
51
+ task :default => :spec
52
+
53
+ require 'yard'
54
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'mongoid/taggable_with_context'
@@ -0,0 +1,249 @@
1
+ # Copyright (c) 2010 Wilker Lúcio <wilkerlucio@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongoid::TaggableWithContext
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ class_inheritable_reader :taggable_with_context_options
20
+ end
21
+
22
+ module ClassMethods
23
+ # Macro to declare a document class as taggable, specify field name
24
+ # for tags, and set options for tagging behavior.
25
+ #
26
+ # @example Define a taggable document.
27
+ #
28
+ # class Article
29
+ # include Mongoid::Document
30
+ # include Mongoid::Taggable
31
+ # taggable :keywords, :separator => ' ', :aggregation => true, :default_type => "seo"
32
+ # end
33
+ #
34
+ # @param [ Symbol ] field The name of the field for tags.
35
+ # @param [ Hash ] options Options for taggable behavior.
36
+ #
37
+ # @option options [ String ] :separator The tag separator to
38
+ # convert from; defaults to ','
39
+ # @option options [ true, false ] :aggregation Whether or not to
40
+ # aggregate counts of tags within the document collection using
41
+ # map/reduce; defaults to false
42
+ # @option options [ String ] :default_type The default type of the tag.
43
+ # Each tag can optionally have a tag type. The default type is nil
44
+ def taggable(*args)
45
+ # init variables
46
+ options = args.extract_options!
47
+ tags_field = (args.blank? ? :tags : args.shift).to_sym
48
+ options.reverse_merge!(
49
+ :separator => ' ',
50
+ :array_field => "#{tags_field}_array".to_sym
51
+ )
52
+ tags_array_field = options[:array_field]
53
+ first_invoke = taggable_with_context_options.nil?
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
+ if first_invoke
66
+ delegate "convert_string_to_array", :to => 'self.class'
67
+ delegate "convert_array_to_string", :to => 'self.class'
68
+ delegate "get_tag_separator_for", :to => 'self.class'
69
+ delegate "tag_contexts", :to => 'self.class'
70
+ delegate "aggregation_collection_for", :to => 'self.class'
71
+ delegate "tag_options_for", :to => 'self.class'
72
+
73
+ set_callback :create, :after, :increment_tags_agregation
74
+ set_callback :save, :after, :update_tags_aggregation
75
+ set_callback :destroy, :after, :decrement_tags_aggregation
76
+ end
77
+
78
+ extend SingletonMethods
79
+ include InstanceMethods
80
+
81
+ # singleton methods
82
+ class_eval <<-END
83
+ class << self
84
+ def #{tags_field}_aggregation_collection
85
+ @#{tags_field}_aggregation_collection ||= aggregation_collection_for(:"#{tags_field}")
86
+ end
87
+
88
+ def #{tags_field}
89
+ tags_for(:"#{tags_field}")
90
+ end
91
+
92
+ def #{tags_field}_with_weight
93
+ tags_with_weight_for(:"#{tags_field}")
94
+ end
95
+
96
+ def #{tags_field}_separator
97
+ get_tag_separator_for(:"#{tags_field}")
98
+ end
99
+
100
+ def #{tags_field}_separator=(value)
101
+ set_tag_separator_for(:"#{tags_field}", value)
102
+ end
103
+
104
+ def #{tags_field}_tagged_with(tags)
105
+ tagged_with(:"#{tags_field}", tags)
106
+ end
107
+ end
108
+ END
109
+
110
+ # instance methods
111
+ class_eval <<-END
112
+ def #{tags_field}=(s)
113
+ super
114
+ write_attribute(:#{tags_array_field}, convert_string_to_array(s, get_tag_separator_for(:"#{tags_field}")))
115
+ end
116
+
117
+ def #{tags_array_field}=(a)
118
+ super
119
+ write_attribute(:#{tags_field}, convert_array_to_string(a, get_tag_separator_for(:"#{tags_field}")))
120
+ end
121
+ END
122
+ end
123
+ end
124
+
125
+ module SingletonMethods
126
+ def tag_contexts
127
+ taggable_with_context_options.keys
128
+ end
129
+
130
+ def tag_options_for(context)
131
+ taggable_with_context_options[context]
132
+ end
133
+
134
+ # Collection name for storing results of tag count aggregation
135
+ def aggregation_collection_for(context)
136
+ "#{collection_name}_#{context}_aggregation"
137
+ end
138
+
139
+ def tags_for(context, conditions={})
140
+ conditions = {:sort => '_id'}.merge(conditions)
141
+ db.collection(aggregation_collection_for(context)).find({:value => {"$gt" => 0 }}, conditions).to_a.map{ |t| t["_id"] }
142
+ end
143
+
144
+ # retrieve the list of tag with weight(count), this is useful for
145
+ # creating tag clouds
146
+ def tags_with_weight_for(context, conditions={})
147
+ conditions = {:sort => '_id'}.merge(conditions)
148
+ db.collection(aggregation_collection_for(context)).find({:value => {"$gt" => 0 }}, conditions).to_a.map{ |t| [t["_id"], t["value"]] }
149
+ end
150
+
151
+ def get_tag_separator_for(context)
152
+ taggable_with_context_options[context][:separator]
153
+ end
154
+
155
+ def set_tag_separator_for(context, value)
156
+ taggable_with_context_options[context][:separator] = value.nil? ? " " : value.to_s
157
+ end
158
+
159
+ # Find documents tagged with all tags passed as a parameter, given
160
+ # as an Array or a String using the configured separator.
161
+ #
162
+ # @example Find matching all tags in an Array.
163
+ # Article.tagged_with(['ruby', 'mongodb'])
164
+ # @example Find matching all tags in a String.
165
+ # Article.tagged_with('ruby, mongodb')
166
+ #
167
+ # @param [ String ] :field The field name of the tag.
168
+ # @param [ Array<String, Symbol>, String ] :tags Tags to match.
169
+ # @return [ Criteria ] A new criteria.
170
+ def tagged_with(context, tags)
171
+ tags = convert_string_to_array(tags, get_tag_separator_for(context)) if tags.is_a? String
172
+ array_field = tag_options_for(context)[:array_field]
173
+ all_in(array_field => tags)
174
+ end
175
+
176
+ # Helper method to convert a String to an Array based on the
177
+ # configured tag separator.
178
+ def convert_string_to_array(str = "", seperator = " ")
179
+ str.split(seperator).map(&:strip).uniq.compact
180
+ end
181
+
182
+ def convert_array_to_string(ary = [], seperator = " ")
183
+ ary.uniq.compact.join(seperator)
184
+ end
185
+ end
186
+
187
+ module InstanceMethods
188
+ def need_update_tags_aggregation?
189
+ !changed_contexts.empty?
190
+ end
191
+
192
+ def changed_contexts
193
+ tag_contexts & previous_changes.keys.map(&:to_sym)
194
+ end
195
+
196
+ def increment_tags_agregation
197
+ # if document is created by using MyDocument.new
198
+ # and attributes are individually assigned
199
+ # #previous_changes won't be empty and aggregation
200
+ # is updated in after_save, so we simply skip it.
201
+ return unless previous_changes.empty?
202
+
203
+ # if the document is created by using MyDocument.create(:tags => "tag1 tag2")
204
+ # #previous_changes hash is empty and we have to update aggregation here
205
+ tag_contexts.each do |context|
206
+ coll = self.class.db.collection(self.class.aggregation_collection_for(context))
207
+ field_name = self.class.tag_options_for(context)[:array_field]
208
+ tags = self.send field_name || []
209
+ tags.each do |t|
210
+ coll.update({:_id => t}, {'$inc' => {:value => 1}}, :upsert => true)
211
+ end
212
+ end
213
+ end
214
+
215
+ def decrement_tags_aggregation
216
+ tag_contexts.each do |context|
217
+ coll = self.class.db.collection(self.class.aggregation_collection_for(context))
218
+ field_name = self.class.tag_options_for(context)[:array_field]
219
+ tags = self.send field_name || []
220
+ tags.each do |t|
221
+ coll.update({:_id => t}, {'$inc' => {:value => -1}}, :upsert => true)
222
+ end
223
+ end
224
+ end
225
+
226
+ def update_tags_aggregation
227
+ return unless need_update_tags_aggregation?
228
+
229
+ changed_contexts.each do |context|
230
+ coll = self.class.db.collection(self.class.aggregation_collection_for(context))
231
+ field_name = self.class.tag_options_for(context)[:array_field]
232
+ old_tags, new_tags = previous_changes["#{field_name}"]
233
+ old_tags ||= []
234
+ new_tags ||= []
235
+ unchanged_tags = old_tags & new_tags
236
+ tags_removed = old_tags - unchanged_tags
237
+ tags_added = new_tags - unchanged_tags
238
+
239
+ tags_removed.each do |t|
240
+ coll.update({:_id => t}, {'$inc' => {:value => -1}}, :upsert => true)
241
+ end
242
+
243
+ tags_added.each do |t|
244
+ coll.update({:_id => t}, {'$inc' => {:value => 1}}, :upsert => true)
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,88 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mongoid_taggable_with_context}
8
+ s.version = "0.5.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aaron Qian"]
12
+ s.date = %q{2011-02-12}
13
+ s.description = %q{It provides some helpers to create taggable documents with context.}
14
+ s.email = %q{aq1018@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "init.rb",
28
+ "lib/mongoid_taggable_with_context.rb",
29
+ "mongoid_taggable_with_context.gemspec",
30
+ "spec/mongoid_taggable_with_context_spec.rb",
31
+ "spec/spec_helper.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/aq1018/mongoid_taggable_with_context}
34
+ s.licenses = ["MIT"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.5.2}
37
+ s.summary = %q{Mongoid taggable behaviour}
38
+ s.test_files = [
39
+ "spec/mongoid_taggable_with_context_spec.rb",
40
+ "spec/spec_helper.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<mongoid>, ["~> 2.0.0.beta.20"])
48
+ s.add_development_dependency(%q<database_cleaner>, [">= 0"])
49
+ s.add_development_dependency(%q<bson>, ["~> 1.2.1"])
50
+ s.add_development_dependency(%q<bson_ext>, ["~> 1.2.1"])
51
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
52
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
53
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
54
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
55
+ s.add_development_dependency(%q<rcov>, [">= 0"])
56
+ s.add_development_dependency(%q<reek>, ["~> 1.2.8"])
57
+ s.add_development_dependency(%q<roodi>, ["~> 2.1.0"])
58
+ s.add_runtime_dependency(%q<mongoid>, ["~> 2.0.0.beta.20"])
59
+ else
60
+ s.add_dependency(%q<mongoid>, ["~> 2.0.0.beta.20"])
61
+ s.add_dependency(%q<database_cleaner>, [">= 0"])
62
+ s.add_dependency(%q<bson>, ["~> 1.2.1"])
63
+ s.add_dependency(%q<bson_ext>, ["~> 1.2.1"])
64
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
65
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
66
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
68
+ s.add_dependency(%q<rcov>, [">= 0"])
69
+ s.add_dependency(%q<reek>, ["~> 1.2.8"])
70
+ s.add_dependency(%q<roodi>, ["~> 2.1.0"])
71
+ s.add_dependency(%q<mongoid>, ["~> 2.0.0.beta.20"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<mongoid>, ["~> 2.0.0.beta.20"])
75
+ s.add_dependency(%q<database_cleaner>, [">= 0"])
76
+ s.add_dependency(%q<bson>, ["~> 1.2.1"])
77
+ s.add_dependency(%q<bson_ext>, ["~> 1.2.1"])
78
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
79
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
80
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
81
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
82
+ s.add_dependency(%q<rcov>, [">= 0"])
83
+ s.add_dependency(%q<reek>, ["~> 1.2.8"])
84
+ s.add_dependency(%q<roodi>, ["~> 2.1.0"])
85
+ s.add_dependency(%q<mongoid>, ["~> 2.0.0.beta.20"])
86
+ end
87
+ end
88
+
@@ -0,0 +1,272 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class MyModel
4
+ include Mongoid::Document
5
+ include Mongoid::TaggableWithContext
6
+ taggable
7
+ taggable :artists
8
+ end
9
+
10
+ describe Mongoid::TaggableWithContext do
11
+ context "saving tags from plain text" do
12
+ before :each do
13
+ @m = MyModel.new
14
+ end
15
+
16
+ it "should set tags array from string" do
17
+ @m.tags = "some new tag"
18
+ @m.tags_array.should == %w[some new tag]
19
+ end
20
+
21
+ it "should set artists array from string" do
22
+ @m.artists = "some new tag"
23
+ @m.artists_array.should == %w[some new tag]
24
+ end
25
+
26
+ it "should retrieve tags string from array" do
27
+ @m.tags_array = %w[some new tags]
28
+ @m.tags.should == "some new tags"
29
+ end
30
+
31
+ it "should retrieve artists string from array" do
32
+ @m.artists_array = %w[some new tags]
33
+ @m.artists.should == "some new tags"
34
+ end
35
+
36
+ it "should strip tags before put in array" do
37
+ @m.tags = "now with some spaces in places "
38
+ @m.tags_array.should == %w[now with some spaces in places]
39
+ end
40
+
41
+ it "should remove repeated tags from string" do
42
+ @m.tags = "some new tags some new tags"
43
+ @m.tags_array.should == %w[some new tags]
44
+ end
45
+
46
+ it "should remove repeated tags from array" do
47
+ @m.tags_array = %w[some new tags some new tags]
48
+ @m.tags.should == "some new tags"
49
+ end
50
+
51
+ it "should remove nil tags from array" do
52
+ @m.tags_array = ["some", nil, "new", nil, "tags"]
53
+ @m.tags.should == "some new tags"
54
+ end
55
+ end
56
+
57
+ context "changing separator" do
58
+ before :all do
59
+ MyModel.tags_separator = ";"
60
+ end
61
+
62
+ after :all do
63
+ MyModel.tags_separator = " "
64
+ end
65
+
66
+ before :each do
67
+ @m = MyModel.new
68
+ end
69
+
70
+ it "should split with custom separator" do
71
+ @m.tags = "some;other;separator"
72
+ @m.tags_array.should == %w[some other separator]
73
+ end
74
+
75
+ it "should join with custom separator" do
76
+ @m.tags_array = %w[some other sep]
77
+ @m.tags.should == "some;other;sep"
78
+ end
79
+ end
80
+
81
+ context "indexing tags" do
82
+ it "should generate the tags aggregation collection name correctly" do
83
+ MyModel.tags_aggregation_collection.should == "my_models_tags_aggregation"
84
+ end
85
+
86
+ it "should generate the artists aggregation collection name correctly" do
87
+ MyModel.artists_aggregation_collection.should == "my_models_artists_aggregation"
88
+ end
89
+
90
+ context "retriving index" do
91
+ context "on create directly" do
92
+ before :each do
93
+ MyModel.create!(:tags => "food ant bee", :artists => "jeff greg mandy aaron andy")
94
+ MyModel.create!(:tags => "juice food bee zip", :artists => "grant andrew andy")
95
+ MyModel.create!(:tags => "honey strip food", :artists => "mandy aaron andy")
96
+ end
97
+
98
+ it "should retrieve the list of all saved tags distinct and ordered" do
99
+ MyModel.tags.should == %w[ant bee food honey juice strip zip]
100
+ MyModel.artists.should == %w[aaron andrew andy grant greg jeff mandy]
101
+ end
102
+
103
+ it "should retrieve a list of tags with weight" do
104
+ MyModel.tags_with_weight.should == [
105
+ ['ant', 1],
106
+ ['bee', 2],
107
+ ['food', 3],
108
+ ['honey', 1],
109
+ ['juice', 1],
110
+ ['strip', 1],
111
+ ['zip', 1]
112
+ ]
113
+
114
+ MyModel.artists_with_weight.should == [
115
+ ['aaron', 2],
116
+ ['andrew', 1],
117
+ ['andy', 3],
118
+ ['grant', 1],
119
+ ['greg', 1],
120
+ ['jeff', 1],
121
+ ['mandy', 2]
122
+ ]
123
+ end
124
+ end
125
+
126
+ context "on new then change attributes directly" do
127
+ before :each do
128
+ m = MyModel.new
129
+ m.tags = "food ant bee"
130
+ m.artists = "jeff greg mandy aaron andy"
131
+ m.save!
132
+
133
+ m = MyModel.new
134
+ m.tags = "juice food bee zip"
135
+ m.artists = "grant andrew andy"
136
+ m.save!
137
+
138
+ m = MyModel.new
139
+ m.tags = "honey strip food"
140
+ m.artists = "mandy aaron andy"
141
+ m.save!
142
+ end
143
+
144
+ it "should retrieve the list of all saved tags distinct and ordered" do
145
+ MyModel.tags.should == %w[ant bee food honey juice strip zip]
146
+ MyModel.artists.should == %w[aaron andrew andy grant greg jeff mandy]
147
+ end
148
+
149
+ it "should retrieve a list of tags with weight" do
150
+ MyModel.tags_with_weight.should == [
151
+ ['ant', 1],
152
+ ['bee', 2],
153
+ ['food', 3],
154
+ ['honey', 1],
155
+ ['juice', 1],
156
+ ['strip', 1],
157
+ ['zip', 1]
158
+ ]
159
+
160
+ MyModel.artists_with_weight.should == [
161
+ ['aaron', 2],
162
+ ['andrew', 1],
163
+ ['andy', 3],
164
+ ['grant', 1],
165
+ ['greg', 1],
166
+ ['jeff', 1],
167
+ ['mandy', 2]
168
+ ]
169
+ end
170
+ end
171
+
172
+ context "on create then update" do
173
+ before :each do
174
+ m1 = MyModel.create!(:tags => "food ant bee", :artists => "jeff greg mandy aaron andy")
175
+ m2 = MyModel.create!(:tags => "juice food bee zip", :artists => "grant andrew andy")
176
+ m3 = MyModel.create!(:tags => "honey strip food", :artists => "mandy aaron andy")
177
+
178
+ m1.tags_array = m1.tags_array + %w[honey strip shoe]
179
+ m1.save!
180
+
181
+ m3.artists_array = m3.artists_array + %w[grant greg gory]
182
+ m3.save!
183
+ end
184
+
185
+ it "should retrieve the list of all saved tags distinct and ordered" do
186
+ MyModel.tags.should == %w[ant bee food honey juice shoe strip zip]
187
+ MyModel.artists.should == %w[aaron andrew andy gory grant greg jeff mandy]
188
+ end
189
+
190
+ it "should retrieve a list of tags with weight" do
191
+ MyModel.tags_with_weight.should == [
192
+ ['ant', 1],
193
+ ['bee', 2],
194
+ ['food', 3],
195
+ ['honey', 2],
196
+ ['juice', 1],
197
+ ['shoe', 1],
198
+ ['strip', 2],
199
+ ['zip', 1]
200
+ ]
201
+
202
+ MyModel.artists_with_weight.should == [
203
+ ['aaron', 2],
204
+ ['andrew', 1],
205
+ ['andy', 3],
206
+ ['gory', 1],
207
+ ['grant', 2],
208
+ ['greg', 2],
209
+ ['jeff', 1],
210
+ ['mandy', 2]
211
+ ]
212
+ end
213
+ end
214
+
215
+ context "on create, update, then destroy" do
216
+ before :each do
217
+ m1 = MyModel.create!(:tags => "food ant bee", :artists => "jeff greg mandy aaron andy")
218
+ m2 = MyModel.create!(:tags => "juice food bee zip", :artists => "grant andrew andy")
219
+ m3 = MyModel.create!(:tags => "honey strip food", :artists => "mandy aaron andy")
220
+
221
+ m1.tags_array = m1.tags_array + %w[honey strip shoe] - %w[food]
222
+ m1.save!
223
+
224
+ m3.artists_array = m3.artists_array + %w[grant greg gory] - %w[andy]
225
+ m3.save!
226
+
227
+ m2.destroy
228
+ end
229
+
230
+ it "should retrieve the list of all saved tags distinct and ordered" do
231
+ MyModel.tags.should == %w[ant bee food honey shoe strip]
232
+ MyModel.artists.should == %w[aaron andy gory grant greg jeff mandy]
233
+ end
234
+
235
+ it "should retrieve a list of tags with weight" do
236
+ MyModel.tags_with_weight.should == [
237
+ ['ant', 1],
238
+ ['bee', 1],
239
+ ['food', 1],
240
+ ['honey', 2],
241
+ ['shoe', 1],
242
+ ['strip', 2]
243
+ ]
244
+
245
+ MyModel.artists_with_weight.should == [
246
+ ['aaron', 2],
247
+ ['andy', 1],
248
+ ['gory', 1],
249
+ ['grant', 1],
250
+ ['greg', 2],
251
+ ['jeff', 1],
252
+ ['mandy', 2]
253
+ ]
254
+ end
255
+ end
256
+
257
+ end
258
+
259
+ context "tagged_with" do
260
+ before :each do
261
+ @m1 = MyModel.create!(:tags => "food ant bee", :artists => "jeff greg mandy aaron andy")
262
+ @m2 = MyModel.create!(:tags => "juice food bee zip", :artists => "grant andrew andy")
263
+ @m3 = MyModel.create!(:tags => "honey strip food", :artists => "mandy aaron andy")
264
+ end
265
+
266
+ it "should retrieve a list of documents" do
267
+ (MyModel.tags_tagged_with("food").to_a - [@m1, @m2, @m3]).should be_empty
268
+ (MyModel.artists_tagged_with("aaron").to_a - [@m1, @m3]).should be_empty
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'mongoid'
6
+ require 'mongoid_taggable_with_context.rb'
7
+ require 'database_cleaner'
8
+
9
+ RSpec.configure do |config|
10
+ config.before(:suite) do
11
+ DatabaseCleaner.strategy = :truncation
12
+ end
13
+
14
+ config.after(:each) do
15
+ DatabaseCleaner.clean
16
+ end
17
+ end
18
+
19
+ Mongoid.configure do |config|
20
+ config.master = Mongo::Connection.new.db("mongoid_taggable_with_context_test")
21
+ end
metadata ADDED
@@ -0,0 +1,271 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid_taggable_with_context
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
+ platform: ruby
12
+ authors:
13
+ - Aaron Qian
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-12 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 62196427
29
+ segments:
30
+ - 2
31
+ - 0
32
+ - 0
33
+ - beta
34
+ - 20
35
+ version: 2.0.0.beta.20
36
+ name: mongoid
37
+ version_requirements: *id001
38
+ prerelease: false
39
+ - !ruby/object:Gem::Dependency
40
+ type: :development
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 3
47
+ segments:
48
+ - 0
49
+ version: "0"
50
+ name: database_cleaner
51
+ version_requirements: *id002
52
+ prerelease: false
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 29
61
+ segments:
62
+ - 1
63
+ - 2
64
+ - 1
65
+ version: 1.2.1
66
+ name: bson
67
+ version_requirements: *id003
68
+ prerelease: false
69
+ - !ruby/object:Gem::Dependency
70
+ type: :development
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 29
77
+ segments:
78
+ - 1
79
+ - 2
80
+ - 1
81
+ version: 1.2.1
82
+ name: bson_ext
83
+ version_requirements: *id004
84
+ prerelease: false
85
+ - !ruby/object:Gem::Dependency
86
+ type: :development
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 2
95
+ - 3
96
+ - 0
97
+ version: 2.3.0
98
+ name: rspec
99
+ version_requirements: *id005
100
+ prerelease: false
101
+ - !ruby/object:Gem::Dependency
102
+ type: :development
103
+ requirement: &id006 !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ hash: 7
109
+ segments:
110
+ - 0
111
+ - 6
112
+ - 0
113
+ version: 0.6.0
114
+ name: yard
115
+ version_requirements: *id006
116
+ prerelease: false
117
+ - !ruby/object:Gem::Dependency
118
+ type: :development
119
+ requirement: &id007 !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ hash: 23
125
+ segments:
126
+ - 1
127
+ - 0
128
+ - 0
129
+ version: 1.0.0
130
+ name: bundler
131
+ version_requirements: *id007
132
+ prerelease: false
133
+ - !ruby/object:Gem::Dependency
134
+ type: :development
135
+ requirement: &id008 !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ~>
139
+ - !ruby/object:Gem::Version
140
+ hash: 7
141
+ segments:
142
+ - 1
143
+ - 5
144
+ - 2
145
+ version: 1.5.2
146
+ name: jeweler
147
+ version_requirements: *id008
148
+ prerelease: false
149
+ - !ruby/object:Gem::Dependency
150
+ type: :development
151
+ requirement: &id009 !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ hash: 3
157
+ segments:
158
+ - 0
159
+ version: "0"
160
+ name: rcov
161
+ version_requirements: *id009
162
+ prerelease: false
163
+ - !ruby/object:Gem::Dependency
164
+ type: :development
165
+ requirement: &id010 !ruby/object:Gem::Requirement
166
+ none: false
167
+ requirements:
168
+ - - ~>
169
+ - !ruby/object:Gem::Version
170
+ hash: 15
171
+ segments:
172
+ - 1
173
+ - 2
174
+ - 8
175
+ version: 1.2.8
176
+ name: reek
177
+ version_requirements: *id010
178
+ prerelease: false
179
+ - !ruby/object:Gem::Dependency
180
+ type: :development
181
+ requirement: &id011 !ruby/object:Gem::Requirement
182
+ none: false
183
+ requirements:
184
+ - - ~>
185
+ - !ruby/object:Gem::Version
186
+ hash: 11
187
+ segments:
188
+ - 2
189
+ - 1
190
+ - 0
191
+ version: 2.1.0
192
+ name: roodi
193
+ version_requirements: *id011
194
+ prerelease: false
195
+ - !ruby/object:Gem::Dependency
196
+ type: :runtime
197
+ requirement: &id012 !ruby/object:Gem::Requirement
198
+ none: false
199
+ requirements:
200
+ - - ~>
201
+ - !ruby/object:Gem::Version
202
+ hash: 62196427
203
+ segments:
204
+ - 2
205
+ - 0
206
+ - 0
207
+ - beta
208
+ - 20
209
+ version: 2.0.0.beta.20
210
+ name: mongoid
211
+ version_requirements: *id012
212
+ prerelease: false
213
+ description: It provides some helpers to create taggable documents with context.
214
+ email: aq1018@gmail.com
215
+ executables: []
216
+
217
+ extensions: []
218
+
219
+ extra_rdoc_files:
220
+ - LICENSE.txt
221
+ - README.rdoc
222
+ files:
223
+ - .document
224
+ - Gemfile
225
+ - Gemfile.lock
226
+ - LICENSE.txt
227
+ - README.rdoc
228
+ - Rakefile
229
+ - VERSION
230
+ - init.rb
231
+ - lib/mongoid_taggable_with_context.rb
232
+ - mongoid_taggable_with_context.gemspec
233
+ - spec/mongoid_taggable_with_context_spec.rb
234
+ - spec/spec_helper.rb
235
+ has_rdoc: true
236
+ homepage: http://github.com/aq1018/mongoid_taggable_with_context
237
+ licenses:
238
+ - MIT
239
+ post_install_message:
240
+ rdoc_options: []
241
+
242
+ require_paths:
243
+ - lib
244
+ required_ruby_version: !ruby/object:Gem::Requirement
245
+ none: false
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ hash: 3
250
+ segments:
251
+ - 0
252
+ version: "0"
253
+ required_rubygems_version: !ruby/object:Gem::Requirement
254
+ none: false
255
+ requirements:
256
+ - - ">="
257
+ - !ruby/object:Gem::Version
258
+ hash: 3
259
+ segments:
260
+ - 0
261
+ version: "0"
262
+ requirements: []
263
+
264
+ rubyforge_project:
265
+ rubygems_version: 1.5.2
266
+ signing_key:
267
+ specification_version: 3
268
+ summary: Mongoid taggable behaviour
269
+ test_files:
270
+ - spec/mongoid_taggable_with_context_spec.rb
271
+ - spec/spec_helper.rb