acts-as-taggable-on-mongoid 6.0.1.1

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +63 -0
  3. data/.gitignore +54 -0
  4. data/.reek.yml +8 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +59 -0
  7. data/.ruby-version +1 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +10 -0
  10. data/Gemfile.lock +203 -0
  11. data/LICENSE.txt +21 -0
  12. data/PULL_REQUEST_TEMPLATE.md +11 -0
  13. data/README.md +741 -0
  14. data/Rakefile +8 -0
  15. data/acts-as-taggable-on-mongoid.gemspec +54 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/codecov.yml +3 -0
  19. data/config/pronto-circleci.yml +7 -0
  20. data/lib/acts-as-taggable-on-mongoid.rb +80 -0
  21. data/lib/acts_as_taggable_on_mongoid/configuration.rb +94 -0
  22. data/lib/acts_as_taggable_on_mongoid/default_parser.rb +120 -0
  23. data/lib/acts_as_taggable_on_mongoid/errors/duplicate_tag_error.rb +9 -0
  24. data/lib/acts_as_taggable_on_mongoid/generic_parser.rb +44 -0
  25. data/lib/acts_as_taggable_on_mongoid/models/tag.rb +103 -0
  26. data/lib/acts_as_taggable_on_mongoid/models/tagging.rb +80 -0
  27. data/lib/acts_as_taggable_on_mongoid/tag_list.rb +169 -0
  28. data/lib/acts_as_taggable_on_mongoid/taggable.rb +131 -0
  29. data/lib/acts_as_taggable_on_mongoid/taggable/changeable.rb +71 -0
  30. data/lib/acts_as_taggable_on_mongoid/taggable/core.rb +219 -0
  31. data/lib/acts_as_taggable_on_mongoid/taggable/list_tags.rb +45 -0
  32. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition.rb +189 -0
  33. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/attributes.rb +77 -0
  34. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/changeable.rb +140 -0
  35. data/lib/acts_as_taggable_on_mongoid/taggable/tag_type_definition/names.rb +39 -0
  36. data/lib/acts_as_taggable_on_mongoid/taggable/utils/tag_list_diff.rb +121 -0
  37. data/lib/acts_as_taggable_on_mongoid/version.rb +5 -0
  38. metadata +352 -0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 CardTapp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ ## RALLY STORY/ISSUE DESCRIPTION
2
+
3
+ rally_story_link or issue link and description of what is being addressed in the PR.
4
+
5
+ ## DEVELOPER NOTES
6
+
7
+ - [ ] example_dev_note
8
+
9
+ ## QA TESTING NOTES
10
+
11
+ - [ ] example_testing_effort
@@ -0,0 +1,741 @@
1
+ # ActsAsTaggableOnMongoid
2
+
3
+ [ActsAsTaggableOn](https://github.com/mbleigh/acts-as-taggable-on) is the clear leader in tagging
4
+ solutions in Rails. Unfortunately it does not appear to work well with Mongoid. For Mongo the
5
+ clear leader for tagging solutions is to include an indexed array of strings as tags. There are
6
+ several solutions that use this mechanism. Unfortunately, sometimes you actually do need a
7
+ many-to-many table solution even in Mongo which happens to be the situation I somehow have found
8
+ myself in.
9
+
10
+ Therefore, we are building a new solution to implement an `ActsLikeTaggableOn` like solution using
11
+ Mongo. The general goal is to mimic the features and interface of ActsLikeTaggableOn as much as
12
+ feasible/possible.
13
+
14
+ This is not a direct port of `ActsLikeTaggableOn` at this time for several reason, the main one being
15
+ time. Mongoid and ActiveRecord are enough different that the complications that would arise from forking
16
+ and trying to modify it to work with Mongoid do not seem insignificant.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'acts-as-taggable-on-mongoid'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install acts-as-taggable-on-mongoid
33
+
34
+ ## Architecture and design concepts
35
+
36
+ ### Termonology.
37
+
38
+ * **tag_list** - a generic term for the list field in a `Taggable` object that is
39
+ defined using the `acts_as_taggable_on` method. Each tag_list is definied
40
+ independentally and can have separate settings.
41
+ * **tag_type, context** - For whatever reason, these two terms are both used and
42
+ are synonymous with each other. `context` is used in the Tag and Taggable
43
+ tables to differentiate that a record is associated with a particular tag_list
44
+ * **Taggable object** - A database model object which can have tag_lists associated with
45
+ it. All taggable objects can have multiple tag_lists in it.
46
+ * **Tagging table** - A Table for storing the which Tags are associated with a Taggable
47
+ object. The Tag details are denormalized into the Tagging table to allow efficient
48
+ querying of Taggable objects without having to go through the Taggings table.
49
+ * **Tag table** - A Table for storing the tags. Tags are used primarily to maintain
50
+ a usage counter as the tag details are denormalized into the Tagging tagle
51
+
52
+ **NOTE**: Unlike the `ActsAsTaggableOn` gem, I group Tags by context. Tags with the same
53
+ name for different contexts keep separate counts and are considered different Tags.
54
+ It is simpler to combine Tags and their counts by name then it is to split them out.
55
+
56
+ The database structure is:
57
+ ```
58
+ +----------+ +---------+ +-----+
59
+ | Taggable | -> | Tagging | <- | Tag |
60
+ +----------+ +---------+ +-----+
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ Setup
66
+
67
+ ```ruby
68
+ class User
69
+ include ::Mongoid::Document
70
+
71
+ acts_as_taggable # Alias for acts_as_taggable_on :tags
72
+ acts_as_taggable_on :skills, :interests
73
+ end
74
+
75
+ class UsersController < ApplicationController
76
+ def user_params
77
+ params.require(:user).permit(:name, :tag_list) ## Rails 4 strong params usage
78
+ end
79
+ end
80
+
81
+ @user = User.new(:name => "Bobby")
82
+ ```
83
+
84
+ Add and remove a single tag
85
+
86
+ ```ruby
87
+ @user.tag_list.add("awesome") # add a single tag. alias for <<
88
+ @user.tag_list.remove("awesome") # remove a single tag
89
+ @user.save # save to persist tag_list
90
+ ```
91
+
92
+ Add and remove multiple tags in an array
93
+
94
+ ```ruby
95
+ @user.tag_list.add("awesome", "slick")
96
+ @user.tag_list.remove("awesome", "slick")
97
+ @user.save
98
+ ```
99
+
100
+ You can also add and remove tags in format of String. This would
101
+ be convenient in some cases such as handling tag input param in a String.
102
+
103
+ Pay attention you need to add `parse: true` as option in this case.
104
+
105
+ You may also want to take a look at delimiter in the string. The default
106
+ is comma `,` so you don't need to do anything here. However, if you want to
107
+ use a different delimiter you will have to change the delmiter on the
108
+ `DefaultParser` or create a new Parser which splits the string using the
109
+ algorithm or method you want/need. See the `GenericParser` class for details
110
+ on creating a new custom parser of your own design.
111
+
112
+ ```ruby
113
+ @user.tag_list.add("awesome, slick", parse: true)
114
+ @user.tag_list.remove("awesome, slick", parse: true)
115
+ ```
116
+
117
+ You can also add and remove tags by direct assignment. By default, direct
118
+ assignment will parse values passed into it. Note this will
119
+ remove existing tags so use it with attention.
120
+
121
+ ```ruby
122
+ @user.tag_list = "awesome, slick, hefty"
123
+ @user.save
124
+ @user.reload
125
+ @user.tags
126
+ => [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "awesome", taggings_count: 1>,
127
+ #<ActsAsTaggableOnMongoid::Models::Tag id: 2, name: "slick", taggings_count: 1>,
128
+ #<ActsAsTaggableOnMongoid::Models::Tag id: 3, name: "hefty", taggings_count: 1>]
129
+ ```
130
+
131
+ With the defined context in model, you have multiple new methods at disposal
132
+ to manage and view the tags in the context. For example, with `:skill` context
133
+ these methods are added to the model: `skill_list`(and `skill_list.add`, `skill_list.remove`
134
+ `skill_list=`), `skills`(plural), `skill_counts`.
135
+
136
+ ```ruby
137
+ @user.skill_list = "joking, clowning, boxing"
138
+ @user.save
139
+ @user.reload
140
+ @user.skills
141
+ => [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "joking", taggings_count: 1>,
142
+ #<ActsAsTaggableOnMongoid::Models::Tag id: 2, name: "clowning", taggings_count: 1>,
143
+ #<ActsAsTaggableOnMongoid::Models::Tag id: 3, name: "boxing", taggings_count: 1>]
144
+
145
+ @user.skill_list.add("coding")
146
+
147
+ @user.skill_list
148
+ # => ["joking", "clowning", "boxing", "coding"]
149
+
150
+ @another_user = User.new(:name => "Alice")
151
+ @another_user.skill_list.add("clowning")
152
+ @another_user.save
153
+
154
+ User.skill_counts
155
+ => [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "joking", taggings_count: 1>,
156
+ #<ActsAsTaggableOnMongoid::Models::Tag id: 2, name: "clowning", taggings_count: 2>,
157
+ #<ActsAsTaggableOnMongoid::Models::Tag id: 3, name: "boxing", taggings_count: 1>]
158
+ ```
159
+
160
+ To preserve the order in which tags are created use `acts_as_ordered_taggable`:
161
+
162
+ ```ruby
163
+ class User < ActiveRecord::Base
164
+ # Alias for acts_as_ordered_taggable_on :tags
165
+ acts_as_ordered_taggable
166
+ acts_as_ordered_taggable_on :skills, :interests
167
+ end
168
+
169
+ @user = User.new(:name => "Bobby")
170
+ @user.tag_list = "east, south"
171
+ @user.save
172
+
173
+ @user.tag_list = "north, east, south, west"
174
+ @user.save
175
+
176
+ @user.reload
177
+ @user.tag_list # => ["north", "east", "south", "west"]
178
+ ```
179
+
180
+ #### `acts_as_taggable_on` documentation details
181
+
182
+ ```Ruby
183
+ class MyTaggable
184
+ include ::Mongoid::Document
185
+
186
+ acts_as_taggable_on :my_tags,
187
+ :your_tags,
188
+ :other_tags,
189
+ :etc_tags,
190
+ parser: MyCustomParser,
191
+ preserve_tag_order: true,
192
+ cached_in_model: true,
193
+ force_lowercase: true,
194
+ force_parameterize: true,
195
+ remove_unused_tags: true,
196
+ tags_table: CustomTag,
197
+ taggings_table: CusomTagging,
198
+ default: "defalut, values"
199
+ end
200
+ ```
201
+
202
+ `acts_as_taggable_on` will define the following methods and relationships based on the values passed in
203
+ (only methods for the `my_tags` tag_list are shown, but methods for each of the other tag_lists will be
204
+ created also.)
205
+
206
+ * `custom_taggings` - a relationship on a `MyTaggable` object which will return all `CustomTagging`s across
207
+ all contexts/tag_types for a particular tagging. Usage:
208
+ ```Ruby
209
+ my_taggable = MyTaggable.find(taggable_id)
210
+ my_taggable.custom_taggings.to_a
211
+
212
+ => [#<CustomTagging context: "my_tags", taggable_id: my_taggable.id>,
213
+ #<CustomTagging context: "your_tags", taggable_id: my_taggable.id>, ...]
214
+ ```
215
+ * `base_custom_tags` - Returns a `Criteria` for all `CustomTag`s which are associated with any `MyTaggable`.
216
+ Usage:
217
+ ```Ruby
218
+ my_taggable = MyTaggable.find(taggable_id)
219
+ my_taggable.base_custom_tags.to_a
220
+
221
+ => [#<CustomTag context: "my_tags", taggable_type: "MyTaggable">,
222
+ #<CustomTag context: "your_tags", taggable_type: "MyTaggable">, ...]
223
+ ```
224
+ * `my_tag_custom_taggings` - Returns all `CustomTagging`s for a particular `MyTaggable` object for the
225
+ named context in the appropriate sorted order for that context (based on if `preserve_tag_order` is true.)
226
+ Usage:
227
+ ```Ruby
228
+ my_taggable = MyTaggable.find(taggable_id)
229
+ my_taggable.my_tag_custom_taggings.to_a
230
+
231
+ => [#<CustomTagging context: "my_tags", taggable_id: my_taggable.id>,
232
+ #<CustomTagging context: "my_tags", taggable_id: my_taggable.id>, ...]
233
+ ```
234
+ * `my_tags` - Returns all `CustomTag`s for a particular `MyTaggable` object for the named context sorted in the order
235
+ that the tags were added to that object if `preserve_tag_order` is true.
236
+ NOTE: This is done through a mapping from the `custom_taggings` relationship and is therefore not very efficient.
237
+ Usage:
238
+ ```Ruby
239
+ my_taggable = MyTaggable.find(taggable_id)
240
+ my_taggable.my_tags.to_a
241
+
242
+ => [#<CustomTag context: "my_tags", taggable_type: "MyTaggable">,
243
+ #<CustomTag context: "my_tags", taggable_type: "MyTaggable">, ...]
244
+ ```
245
+ * `my_tag_list` - Returns an array of all tag values that are associated with a context for a `MyTaggable` object.
246
+ The array will be a simple array of strings that are sorted in the appropriate order based on the
247
+ value of `preserve_tag_order`.
248
+ Usage:
249
+ ```Ruby
250
+ my_taggable = MyTaggable.find(taggable_id)
251
+ my_taggable.my_tag_list
252
+
253
+ => ["tag value 1", "tag value 2", "tag value 3", ...]
254
+ # This list will be sorted in the order that the values were added to the list if `preserve_tag_order` is true.
255
+ ```
256
+ * `my_tag_list=` - Sets the list of tags for that context for a particular `MyTaggable` object. Setting the value
257
+ in this way will automatically parse the string. You can force the string to not be parsed or to use a particular
258
+ parser by passing an array with the appropriate options (`parse` and `parser`). If `preserve_tag_order` is true
259
+ then values will be removed and re-added as necessary to ensure that the tag order is preserved for the set.
260
+ Changes to the `my_tag_list` will not be persisted until after the object is saved just like any other field.
261
+ Usage:
262
+ ```Ruby
263
+ # Set list and save - Sets the list of tags for the list
264
+ my_taggable = MyTaggable.find(taggable_id)
265
+
266
+ # preserve_tag_order = false
267
+ my_taggable.my_tag_list = "tag value 3, tag value 2, tag value 1"
268
+ my_taggable.save!
269
+ my_taggable.reload.my_tag_list
270
+
271
+ => ["tag value 3", "tag value 2", "tag value 1"]
272
+ ```
273
+ ```Ruby
274
+ # Use update_attributes - Sets the list of tags for the list as if assigned (i.e. parsing is assumed)
275
+ # preserve_tag_order = false
276
+ my_taggable.update_attributes! my_tag_list: "tag value 2, tag value 1, tag value 3"
277
+ my_taggable.reload.my_tag_list
278
+
279
+ => ["tag value 3", "tag value 2", "tag value 1"]
280
+ ```
281
+ ```Ruby
282
+ # Use update_attributes - Sets the list of tags for the list - keep the passed in order.
283
+ # preserve_tag_order = true
284
+ my_taggable.update_attributes! my_tag_list: "tag value 2, tag value 1, tag value 3"
285
+ my_taggable.reload.my_tag_list
286
+
287
+ => ["tag value 2", "tag value 1", "tag value 3"]
288
+ ```
289
+ ```Ruby
290
+ # Set list and save - disabling parsing. The string passed in i used as-is
291
+ # preserve_tag_order = false
292
+ my_taggable.my_tag_list = ["tag value 3, tag value 2, tag value 1", parse: false]
293
+ my_taggable.save!
294
+ my_taggable.reload.my_tag_list
295
+
296
+ => ["tag value 3, tag value 2, tag value 1"]
297
+ ```
298
+ ```Ruby
299
+ # Set the list and save using a custom parser:
300
+ # preserve_tag_order = false
301
+ my_taggable.my_tag_list = ["tag value 2;tag value 1;tag value 3", parser: SemiColonParser]
302
+ my_taggable.save!
303
+ my_taggable.reload.my_tag_list
304
+
305
+ => ["tag value 2", "tag value 1", "tag value 3"]
306
+ ```
307
+ ```Ruby
308
+ # Use add, remove, concat and << to change the list and save.
309
+ # preserve_tag_order = false
310
+ # parse is ONLY assumed true for assignment and default values.
311
+ my_taggable.my_tag_list.add "1, 2", "3, 4", "5", parse: true
312
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "2", "3", "4", "5"]
313
+ my_taggable.my_tag_list.add "6, 7", "8, 9", "10"
314
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "2", "3", "4", "5", "6, 7", "8, 9", "10"]
315
+ my_taggable.my_tag_list.remove "2, 3", "4, 9", parse: true
316
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "6, 7", "8, 9", "10"]
317
+ my_taggable.my_tag_list.remove "6, 7", "10", "not in list"
318
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9"]
319
+ my_taggable.my_tag_list << ["11, 12", "13, 14", parse: true]
320
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14"]
321
+ my_taggable.my_tag_list << ["15, 16", "17, 18"]
322
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14", "15, 16", "17, 18"]
323
+ my_taggable.my_tag_list << "19"
324
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14", "15, 16", "17, 18", "19"]
325
+
326
+ # NOTE: Unlike the other methods, passing options for concat is not supported.
327
+ my_taggable.my_tag_list.concat ["20", "21"]
328
+ # my_tag_list => ["tag value 2", "tag value 1", "tag value 3", "1", "5", "8, 9", "11", "12", "13", "14", "15, 16", "17, 18", "19", "20", "21"]
329
+
330
+ # assignment will apply preferences:
331
+ # force_lowercase = true
332
+ my_taggable.my_tag_list = "Tag Value 1, Tag Value 2, Tag Value 3"
333
+ # my_tag_list => ["tag value 3", "tag value 2", "tag value 1"]
334
+ my_taggable.my_tag_list.remove "TAG VALUE 1"
335
+ # my_tag_list => ["tag value 3", "tag value 2"]
336
+
337
+ # force_parameterize = true
338
+ my_taggable.my_tag_list = "tag value 1, tag value 2, tag value 3"
339
+ # my_tag_list => ["tag-value-1", "tag-value-2", "tag-value-3"]
340
+ my_taggable.my_tag_list.remove "tag value 1"
341
+ # my_tag_list => ["tag-value-2", "tag-value-3"]
342
+ ```
343
+ ```Ruby
344
+ # Values are not stored until save is called.
345
+ # preserve_tag_order = false
346
+ my_taggable.my_tag_list = "tag value 3, tag value 2, tag value 1"
347
+ my_taggable.reload.my_tag_list
348
+
349
+ => []
350
+ ```
351
+ * `all_my_tags_list` - Returns all `CustomTag`s for the given context for any `MyTaggable` object.
352
+ NOTE: Unlike `ActsAsTaggableOn`, the Tags returned represent all Tags that have ever been used for that context
353
+ for the `MyTaggable` context. There is no guarantee that the `CustomTag`s returned are currently being used
354
+ by any `MyTaggable` object.
355
+ Usage:
356
+ ```Ruby
357
+ my_taggable = MyTaggable.find(taggable_id)
358
+ my_taggable.all_my_tags_list.to_a
359
+
360
+ => [#<CustomTag context: "my_tags", taggable_type: "MyTaggable">,
361
+ #<CustomTag context: "my_tags", taggable_type: "MyTaggable">, ...]
362
+ ```
363
+ * `my_tag_list?` - Returns true if the list has been set to a value.
364
+ Usage:
365
+ ```Ruby
366
+ my_taggable = MyTaggable.find(taggable_id)
367
+ my_taggable.my_tag_list?
368
+ => false
369
+
370
+ my_taggable.my_tag_list = "tag value 1, tag_value 2"
371
+ my_taggable.my_tag_list?
372
+ => true
373
+ ```
374
+ * `my_tag_list_change` - Returns the old and new values for a changed list.
375
+ Usage:
376
+ ```Ruby
377
+ my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2")
378
+ my_taggable.my_tag_list = "tag value 2, tag value 3"
379
+ my_taggable.my_tag_list_change
380
+ => [["tag value 1", "tag value 2"], ["tag value 2", "tag value 2"]]
381
+ ```
382
+ * `my_tag_list_changed?` - Returns the old and new values for a changed list.
383
+ Usage:
384
+ ```Ruby
385
+ my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2")
386
+ my_taggable.my_tag_list_changed?
387
+ => false
388
+
389
+ my_taggable.my_tag_list = "tag value 2, tag value 3"
390
+ my_taggable.my_tag_list_changed?
391
+ => true
392
+ ```
393
+ * `my_tag_list_will_change` - Tells the model that the `my_tag_list` field will be changing.
394
+ This method is primarily intended for internal use.
395
+ Usage:
396
+ ```Ruby
397
+ my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2")
398
+ my_taggable.my_tag_list_will_change
399
+ ```
400
+ * `my_tag_list_changed_from_default?` - Returns true if the value does not match the default for the field
401
+ Usage:
402
+ ```Ruby
403
+ my_taggable = MyTaggable.create!
404
+ my_taggable.my_tag_list_changed_from_default?
405
+ => false
406
+
407
+ my_taggable.my_tag_list = "tag value 2, tag value 3"
408
+ my_taggable.my_tag_list_changed_from_default?
409
+ => true
410
+ ```
411
+ * `my_tag_list_was` - Returns the old values for a changed list.
412
+ Usage:
413
+ ```Ruby
414
+ my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2")
415
+ my_taggable.my_tag_list = "tag value 2, tag value 3"
416
+ my_taggable.my_tag_list_was
417
+ => ["tag value 1", "tag value 2"]
418
+ ```
419
+ * `reset_my_tag_list!` - Resets a changed value back to what it was before
420
+ Usage:
421
+ ```Ruby
422
+ my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2")
423
+ my_taggable.my_tag_list = "tag value 2, tag value 3"
424
+ my_taggable.reset_my_tag_list!
425
+ => ["tag value 1", "tag value 2"]
426
+ ```
427
+ * `reset_my_tag_list_to_default!` - Resets a changed value back to the default values.
428
+ Usage:
429
+ ```Ruby
430
+ my_taggable = MyTaggable.create!(my_tag_list: "tag value 1, tag_value 2")
431
+ my_taggable.my_tag_list = "tag value 2, tag value 3"
432
+ my_taggable.reset_my_tag_list_to_default!
433
+ => ["default", "values"]
434
+ ```
435
+
436
+ ### Finding most or least used tags
437
+
438
+ You can find the most or least used tags by using:
439
+
440
+ ```ruby
441
+ ActsAsTaggableOnMongoid::Models::Tag.most_used
442
+ ActsAsTaggableOnMongoid::Models::Tag.least_used
443
+ ```
444
+
445
+ You can also filter the results by passing the method a limit, however the default limit is 20.
446
+
447
+ ```ruby
448
+ ActsAsTaggableOnMongoid::Models::Tag.most_used(10)
449
+ ActsAsTaggableOnMongoid::Models::Tag.least_used(10)
450
+ ```
451
+
452
+ ### ~~Finding Tagged Objects~~ Not implimented yet
453
+
454
+ Acts As Taggable On uses scopes to create an association for tags.
455
+ This way you can mix and match to filter down your results.
456
+
457
+ ```ruby
458
+ class User < ActiveRecord::Base
459
+ acts_as_taggable_on :tags, :skills
460
+ scope :by_join_date, order("created_at DESC")
461
+ end
462
+
463
+ User.tagged_with("awesome").by_join_date
464
+ User.tagged_with("awesome").by_join_date.paginate(:page => params[:page], :per_page => 20)
465
+
466
+ # Find users that matches all given tags:
467
+ # NOTE: This only matches users that have the exact set of specified tags. If a user has additional tags, they are not returned.
468
+ User.tagged_with(["awesome", "cool"], :match_all => true)
469
+
470
+ # Find users with any of the specified tags:
471
+ User.tagged_with(["awesome", "cool"], :any => true)
472
+
473
+ # Find users that have not been tagged with awesome or cool:
474
+ User.tagged_with(["awesome", "cool"], :exclude => true)
475
+
476
+ # Find users with any of the tags based on context:
477
+ User.tagged_with(['awesome', 'cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true)
478
+ ```
479
+
480
+ You can also use `:wild => true` option along with `:any` or `:exclude` option. It will be looking for `%awesome%` and `%cool%` in SQL.
481
+
482
+ __Tip:__ `User.tagged_with([])` or `User.tagged_with('')` will return `[]`, an empty set of records.
483
+
484
+
485
+ ### ~~Relationships~~ Not implimented yet
486
+
487
+ You can find objects of the same type based on similar tags on certain contexts.
488
+ Also, objects will be returned in descending order based on the total number of
489
+ matched tags.
490
+
491
+ ```ruby
492
+ @bobby = User.find_by_name("Bobby")
493
+ @bobby.skill_list # => ["jogging", "diving"]
494
+
495
+ @frankie = User.find_by_name("Frankie")
496
+ @frankie.skill_list # => ["hacking"]
497
+
498
+ @tom = User.find_by_name("Tom")
499
+ @tom.skill_list # => ["hacking", "jogging", "diving"]
500
+
501
+ @tom.find_related_skills # => [<User name="Bobby">, <User name="Frankie">]
502
+ @bobby.find_related_skills # => [<User name="Tom">]
503
+ @frankie.find_related_skills # => [<User name="Tom">]
504
+ ```
505
+
506
+ ### ~~Dynamic Tag Contexts~~ Not implimented yet
507
+
508
+ In addition to the generated tag contexts in the definition, it is also possible
509
+ to allow for dynamic tag contexts (this could be user generated tag contexts!)
510
+
511
+ ```ruby
512
+ @user = User.new(:name => "Bobby")
513
+ @user.set_tag_list_on(:customs, "same, as, tag, list")
514
+ @user.tag_list_on(:customs) # => ["same", "as", "tag", "list"]
515
+ @user.save
516
+ @user.tags_on(:customs) # => [<Tag name='same'>,...]
517
+ @user.tag_counts_on(:customs)
518
+ User.tagged_with("same", :on => :customs) # => [@user]
519
+ ```
520
+
521
+ ### Tag Parsers
522
+
523
+ If you want to change how tags are parsed, you can define your own implementation:
524
+
525
+ ```ruby
526
+ class MyParser < ActsAsTaggableOnMongoid::GenericParser
527
+ def parse
528
+ tags.each_with_object do |tag, tag_list|
529
+ tag_list.add tag.split('|')
530
+ end
531
+ end
532
+
533
+ def to_s
534
+ tags.join("|")
535
+ end
536
+ end
537
+ ```
538
+
539
+ Now you can use this parser, passing it as parameter:
540
+
541
+ ```ruby
542
+ @user = User.new(:name => "Bobby")
543
+ @user.tag_list = "east, south"
544
+ @user.tag_list.add("north|west", parser: MyParser)
545
+ @user.tag_list # => ["north", "east", "south", "west"]
546
+
547
+ # Or also:
548
+ @user.tag_list.parser = MyParser
549
+ @user.tag_list.add("north|west")
550
+ @user.tag_list # => ["north", "east", "south", "west"]
551
+ ```
552
+
553
+ Or change it globally:
554
+
555
+ ```ruby
556
+ ActsAsTaggableOnMongoid.default_parser = MyParser
557
+ @user = User.new(:name => "Bobby")
558
+ @user.tag_list = "east|south"
559
+ @user.tag_list # => ["east", "south"]
560
+ ```
561
+
562
+ ### ~~Tag Ownership~~ Not implimented yet
563
+
564
+ Tags can have owners:
565
+
566
+ ```ruby
567
+ class User < ActiveRecord::Base
568
+ acts_as_tagger
569
+ end
570
+
571
+ class Photo < ActiveRecord::Base
572
+ acts_as_taggable_on :locations
573
+ end
574
+
575
+ @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations)
576
+ @some_user.owned_taggings
577
+ @some_user.owned_tags
578
+ Photo.tagged_with("paris", :on => :locations, :owned_by => @some_user)
579
+ @some_photo.locations_from(@some_user) # => ["paris", "normandy"]
580
+ @some_photo.owner_tags_on(@some_user, :locations) # => [#<ActsAsTaggableOnMongoid::Models::Tag id: 1, name: "paris">...]
581
+ @some_photo.owner_tags_on(nil, :locations) # => Ownerships equivalent to saying @some_photo.locations
582
+ @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations, :skip_save => true) #won't save @some_photo object
583
+ ```
584
+
585
+ #### Working with Owned Tags
586
+ Note that `tag_list` only returns tags whose taggings do not have an owner. Continuing from the above example:
587
+ ```ruby
588
+ @some_photo.tag_list # => []
589
+ ```
590
+ To retrieve all tags of an object (regardless of ownership) or if only one owner can tag the object, use `all_tags_list`.
591
+
592
+ ##### Adding owned tags
593
+ Note that **owned tags** are added all at once, in the form of ***comma seperated tags*** in string.
594
+ Also, when you try to add **owned tags** again, it simply overwrites the previous set of **owned tags**.
595
+ So to append tags in previously existing **owned tags** list, go as follows:
596
+ ```ruby
597
+ def add_owned_tag
598
+ @some_item = Item.find(params[:id])
599
+ owned_tag_list = @some_item.all_tags_list - @some_item.tag_list
600
+ owned_tag_list += [(params[:tag])]
601
+ @tag_owner.tag(@some_item, :with => stringify(owned_tag_list), :on => :tags)
602
+ @some_item.save
603
+ end
604
+
605
+ def stringify(tag_list)
606
+ tag_list.inject('') { |memo, tag| memo += (tag + ',') }[0..-1]
607
+ end
608
+ ```
609
+ ##### Removing owned tags
610
+ Similarly as above, removing will be as follows:
611
+ ```ruby
612
+ def remove_owned_tag
613
+ @some_item = Item.find(params[:id])
614
+ owned_tag_list = @some_item.all_tags_list - @some_item.tag_list
615
+ owned_tag_list -= [(params[:tag])]
616
+ @tag_owner.tag(@some_item, :with => stringify(owned_tag_list), :on => :tags)
617
+ @some_item.save
618
+ end
619
+ ```
620
+
621
+ ### Dirty objects
622
+
623
+ ```ruby
624
+ @bobby = User.find_by_name("Bobby")
625
+ @bobby.skill_list # => ["jogging", "diving"]
626
+
627
+ @bobby.skill_list_changed? #=> false
628
+ @bobby.changes #=> {}
629
+
630
+ @bobby.skill_list = "swimming"
631
+ @bobby.changes.should == {"skill_list"=>["jogging, diving", ["swimming"]]}
632
+ @bobby.skill_list_changed? #=> true
633
+
634
+ @bobby.skill_list_change.should == ["jogging, diving", ["swimming"]]
635
+ ```
636
+
637
+ ### ~~Tag cloud calculations~~ Not implimented yet
638
+
639
+ To construct tag clouds, the frequency of each tag needs to be calculated.
640
+ Because we specified `acts_as_taggable_on` on the `User` class, we can
641
+ get a calculation of all the tag counts by using `User.tag_counts_on(:customs)`. But what if we wanted a tag count for
642
+ a single user's posts? To achieve this we call tag_counts on the association:
643
+
644
+ ```ruby
645
+ User.find(:first).posts.tag_counts_on(:tags)
646
+ ```
647
+
648
+ A helper is included to assist with generating tag clouds.
649
+
650
+ Here is an example that generates a tag cloud.
651
+
652
+ Helper:
653
+
654
+ ```ruby
655
+ module PostsHelper
656
+ include ActsAsTaggableOnMongoid::TagsHelper
657
+ end
658
+ ```
659
+
660
+ Controller:
661
+
662
+ ```ruby
663
+ class PostController < ApplicationController
664
+ def tag_cloud
665
+ @tags = Post.tag_counts_on(:tags)
666
+ end
667
+ end
668
+ ```
669
+
670
+ View:
671
+
672
+ ```erb
673
+ <% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %>
674
+ <%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %>
675
+ <% end %>
676
+ ```
677
+
678
+ CSS:
679
+
680
+ ```css
681
+ .css1 { font-size: 1.0em; }
682
+ .css2 { font-size: 1.2em; }
683
+ .css3 { font-size: 1.4em; }
684
+ .css4 { font-size: 1.6em; }
685
+ ```
686
+
687
+ ## Configuration
688
+
689
+ Configurations set on the `ActsAsTaggableOnMongoid` (also `ActsAsTaggableOnMongoid.configuration` or
690
+ `ActsAsTaggableOnMongoid.configure { |config| }`) set global defaults for these settings. Individual
691
+ tags can override the values to whatever they want. Custom contexts will be created using the configuration
692
+ defaults.
693
+
694
+ If you would like to remove unused tag objects after removing taggings, add:
695
+
696
+ ```ruby
697
+ ActsAsTaggableOnMongoid.remove_unused_tags = true
698
+ ```
699
+
700
+ If you want force tags to be saved downcased:
701
+
702
+ ```ruby
703
+ ActsAsTaggableOnMongoid.force_lowercase = true
704
+ ```
705
+
706
+ If you want tags to be saved parametrized (you can redefine to_param as well):
707
+
708
+ ```ruby
709
+ ActsAsTaggableOnMongoid.force_parameterize = true
710
+ ```
711
+
712
+ If you would like tags to be case-sensitive and not use LIKE queries for creation:
713
+
714
+ ```ruby
715
+ ActsAsTaggableOnMongoid.tags_table = AatoTags
716
+ ActsAsTaggableOnMongoid.taggings_table = AatoTaggings
717
+ ```
718
+
719
+ If you want to change the default delimiter (it defaults to ','). You can also pass in an array of delimiters such as ([',', '|']):
720
+
721
+ ```ruby
722
+ ActsAsTaggableOnMongoid::DefaultParser.delimiter = ','
723
+ ```
724
+
725
+ ## Development
726
+
727
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
728
+
729
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
730
+
731
+ ## Contributing
732
+
733
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/acts-as-taggable-on-mongoid. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
734
+
735
+ ## License
736
+
737
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
738
+
739
+ ## Code of Conduct
740
+
741
+ Everyone interacting in the ActsAsTaggableOnMongoid project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/acts-as-taggable-on-mongoid/blob/master/CODE_OF_CONDUCT.md).