acts-as-taggable-on-mongoid 6.0.1.1

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