make_taggable 0.7.4 → 1.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +79 -0
- data/CONTRIBUTING.md +65 -22
- data/LICENSE.md +18 -17
- data/README.md +73 -444
- data/UPGRADING.md +28 -2
- data/docs/caching.md +93 -0
- data/docs/configuration.md +108 -0
- data/docs/contexts.md +147 -0
- data/docs/database.md +106 -0
- data/docs/getting-started.md +114 -0
- data/docs/migrating-from-aato.md +67 -0
- data/docs/ownership.md +111 -0
- data/docs/parsers.md +109 -0
- data/docs/querying.md +163 -0
- data/docs/tag-clouds.md +74 -0
- data/lib/make_taggable/default_parser.rb +45 -32
- data/lib/make_taggable/engine.rb +6 -0
- data/lib/make_taggable/generic_parser.rb +30 -4
- data/lib/make_taggable/tag.rb +121 -19
- data/lib/make_taggable/tag_list.rb +77 -24
- data/lib/make_taggable/taggable/cache.rb +61 -11
- data/lib/make_taggable/taggable/collection.rb +102 -30
- data/lib/make_taggable/taggable/core.rb +154 -34
- data/lib/make_taggable/taggable/ownership.rb +82 -4
- data/lib/make_taggable/taggable/related.rb +66 -3
- data/lib/make_taggable/taggable/tag_list_type.rb +8 -0
- data/lib/make_taggable/taggable/tagged_with_query/all_tags_query.rb +10 -0
- data/lib/make_taggable/taggable/tagged_with_query/any_tags_query.rb +10 -0
- data/lib/make_taggable/taggable/tagged_with_query/exclude_tags_query.rb +10 -0
- data/lib/make_taggable/taggable/tagged_with_query/query_base.rb +15 -0
- data/lib/make_taggable/taggable/tagged_with_query.rb +17 -0
- data/lib/make_taggable/taggable.rb +34 -33
- data/lib/make_taggable/tagger.rb +73 -12
- data/lib/make_taggable/tagging.rb +30 -3
- data/lib/make_taggable/tags_helper.rb +22 -1
- data/lib/make_taggable/utils.rb +40 -5
- data/lib/make_taggable/version.rb +8 -1
- data/lib/make_taggable.rb +153 -8
- data/make_taggable.gemspec +13 -20
- metadata +50 -165
- data/.dummyrc +0 -17
- data/.github/workflows/ci.yml +0 -140
- data/.github/workflows/standard-ci.yml +0 -27
- data/.gitignore +0 -17
- data/.rspec +0 -3
- data/Appraisals +0 -15
- data/Gemfile +0 -16
- data/Rakefile +0 -13
- data/gemfiles/rails_5.gemfile +0 -9
- data/gemfiles/rails_6.gemfile +0 -9
- data/gemfiles/rails_6_1.gemfile +0 -9
- data/gemfiles/rails_master.gemfile +0 -9
- data/lib/tasks/setup_test_db.rake +0 -8
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MakeTaggable::Taggable
|
|
4
|
+
##
|
|
5
|
+
# Counting tags, for tag clouds and "most used" lists.
|
|
6
|
+
#
|
|
7
|
+
# Each context gets `<context>_counts` and `top_<context>` on both the class and its instances.
|
|
8
|
+
#
|
|
2
9
|
module Collection
|
|
10
|
+
##
|
|
11
|
+
# @param base [Class] the model being made taggable
|
|
12
|
+
# @return [void]
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
#
|
|
3
16
|
def self.included(base)
|
|
4
17
|
base.extend MakeTaggable::Taggable::Collection::ClassMethods
|
|
5
18
|
base.initialize_make_taggable_collection
|
|
6
19
|
end
|
|
7
20
|
|
|
21
|
+
##
|
|
22
|
+
# Added to every taggable model.
|
|
23
|
+
#
|
|
8
24
|
module ClassMethods
|
|
25
|
+
##
|
|
26
|
+
# Defines the counting methods for each context.
|
|
27
|
+
#
|
|
28
|
+
# @return [void]
|
|
29
|
+
#
|
|
9
30
|
def initialize_make_taggable_collection
|
|
10
31
|
tag_types.map(&:to_s).each do |tag_type|
|
|
11
32
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
@@ -28,30 +49,56 @@ module MakeTaggable::Taggable
|
|
|
28
49
|
end
|
|
29
50
|
end
|
|
30
51
|
|
|
52
|
+
##
|
|
53
|
+
# Adds contexts and refreshes the counting methods.
|
|
54
|
+
#
|
|
55
|
+
# @param args [Array<Symbol, String>] the contexts to add
|
|
56
|
+
# @return [void]
|
|
57
|
+
#
|
|
31
58
|
def make_taggable(*args)
|
|
32
|
-
super
|
|
59
|
+
super
|
|
33
60
|
initialize_make_taggable_collection
|
|
34
61
|
end
|
|
35
62
|
|
|
63
|
+
##
|
|
64
|
+
# Tags used in one context, each carrying how often it was used.
|
|
65
|
+
#
|
|
66
|
+
# @param context [Symbol, String] the tagging context
|
|
67
|
+
# @param options [Hash] options accepted by {#all_tag_counts}
|
|
68
|
+
# @return [ActiveRecord::Relation]
|
|
69
|
+
#
|
|
70
|
+
# @example
|
|
71
|
+
# Book.tag_counts_on(:genres)
|
|
72
|
+
#
|
|
36
73
|
def tag_counts_on(context, options = {})
|
|
37
74
|
all_tag_counts(options.merge({on: context.to_s}))
|
|
38
75
|
end
|
|
39
76
|
|
|
77
|
+
##
|
|
78
|
+
# Tags used in one context, without counting them.
|
|
79
|
+
#
|
|
80
|
+
# @param context [Symbol, String] the tagging context
|
|
81
|
+
# @param options [Hash] options accepted by {#all_tags}
|
|
82
|
+
# @return [ActiveRecord::Relation]
|
|
83
|
+
#
|
|
40
84
|
def tags_on(context, options = {})
|
|
41
85
|
all_tags(options.merge({on: context.to_s}))
|
|
42
86
|
end
|
|
43
87
|
|
|
44
88
|
##
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
89
|
+
# Every tag applied to this model, without counting them.
|
|
90
|
+
#
|
|
91
|
+
# Cheaper than {#all_tag_counts}, which has to join the taggables.
|
|
92
|
+
#
|
|
93
|
+
# @param options [Hash] the query options
|
|
94
|
+
# @option options [Time, Date] :start_at only tags applied after this time
|
|
95
|
+
# @option options [Time, Date] :end_at only tags applied before this time
|
|
96
|
+
# @option options [String, Array] :conditions SQL conditions added to the tag query
|
|
97
|
+
# @option options [Integer] :limit the most tags to return
|
|
98
|
+
# @option options [String] :order SQL to order by, such as `"tags.name asc"`
|
|
99
|
+
# @option options [Symbol, String] :on only tags applied in this context
|
|
100
|
+
# @return [ActiveRecord::Relation]
|
|
101
|
+
#
|
|
55
102
|
def all_tags(options = {})
|
|
56
103
|
options = options.dup
|
|
57
104
|
options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
|
|
@@ -76,17 +123,22 @@ module MakeTaggable::Taggable
|
|
|
76
123
|
end
|
|
77
124
|
|
|
78
125
|
##
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# @param [Hash] options
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
126
|
+
# Every tag applied to this model, each carrying how often it was used as a `count` attribute.
|
|
127
|
+
#
|
|
128
|
+
# @param options [Hash] the query options
|
|
129
|
+
# @option options [Time, Date] :start_at only tags applied after this time
|
|
130
|
+
# @option options [Time, Date] :end_at only tags applied before this time
|
|
131
|
+
# @option options [String, Array] :conditions SQL conditions added to the tag query
|
|
132
|
+
# @option options [Integer] :limit the most tags to return
|
|
133
|
+
# @option options [String] :order SQL to order by, such as `"count desc"`
|
|
134
|
+
# @option options [Integer] :at_least skip tags used fewer times than this
|
|
135
|
+
# @option options [Integer] :at_most skip tags used more times than this
|
|
136
|
+
# @option options [Symbol, String] :on only tags applied in this context
|
|
137
|
+
# @return [ActiveRecord::Relation]
|
|
138
|
+
#
|
|
139
|
+
# @example The ten most used genres
|
|
140
|
+
# Book.all_tag_counts(on: :genres, order: "count desc", limit: 10)
|
|
141
|
+
#
|
|
90
142
|
def all_tag_counts(options = {})
|
|
91
143
|
options = options.dup
|
|
92
144
|
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
|
@@ -101,7 +153,7 @@ module MakeTaggable::Taggable
|
|
|
101
153
|
# Current model is STI descendant, so add type checking to the join condition
|
|
102
154
|
unless descends_from_active_record?
|
|
103
155
|
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{MakeTaggable::Tagging.table_name}.taggable_id"
|
|
104
|
-
taggable_join
|
|
156
|
+
taggable_join += sanitize_sql([" AND #{table_name}.#{inheritance_column} = ?", name])
|
|
105
157
|
tagging_scope = tagging_scope.joins(taggable_join)
|
|
106
158
|
end
|
|
107
159
|
|
|
@@ -127,8 +179,16 @@ module MakeTaggable::Taggable
|
|
|
127
179
|
tag_scope_joins(tag_scope, tagging_scope)
|
|
128
180
|
end
|
|
129
181
|
|
|
182
|
+
##
|
|
183
|
+
# A relation's SQL with its bind parameters inlined, so it can be embedded in another query.
|
|
184
|
+
#
|
|
185
|
+
# @param relation [ActiveRecord::Relation] the relation to render
|
|
186
|
+
# @return [String]
|
|
187
|
+
#
|
|
188
|
+
# @api private
|
|
189
|
+
#
|
|
130
190
|
def safe_to_sql(relation)
|
|
131
|
-
connection.
|
|
191
|
+
connection.unprepared_statement { relation.to_sql }
|
|
132
192
|
end
|
|
133
193
|
|
|
134
194
|
private
|
|
@@ -166,17 +226,29 @@ module MakeTaggable::Taggable
|
|
|
166
226
|
end
|
|
167
227
|
end
|
|
168
228
|
|
|
229
|
+
##
|
|
230
|
+
# Tags used on this record in one context, each carrying how often it was used.
|
|
231
|
+
#
|
|
232
|
+
# @param context [Symbol, String] the tagging context
|
|
233
|
+
# @param options [Hash] options accepted by {ClassMethods#all_tag_counts}
|
|
234
|
+
# @return [ActiveRecord::Relation]
|
|
235
|
+
#
|
|
169
236
|
def tag_counts_on(context, options = {})
|
|
170
237
|
self.class.tag_counts_on(context, options.merge(id: id))
|
|
171
238
|
end
|
|
172
239
|
|
|
240
|
+
# These relations carry a custom SELECT -- the tag columns plus an aliased
|
|
241
|
+
# count -- which Active Record would otherwise fold into the COUNT(). Count
|
|
242
|
+
# rows instead, whatever the relation selects.
|
|
173
243
|
module CalculationMethods
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
244
|
+
##
|
|
245
|
+
# Counts rows rather than the relation's selected columns.
|
|
246
|
+
#
|
|
247
|
+
# @param column_name [Symbol, String] the column to count
|
|
248
|
+
# @return [Integer]
|
|
249
|
+
#
|
|
250
|
+
def count(column_name = :all)
|
|
251
|
+
super
|
|
180
252
|
end
|
|
181
253
|
end
|
|
182
254
|
end
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative "tagged_with_query"
|
|
2
4
|
require_relative "tag_list_type"
|
|
3
5
|
|
|
4
6
|
module MakeTaggable::Taggable
|
|
7
|
+
##
|
|
8
|
+
# The heart of tagging: the associations, the generated `<context>_list` readers and writers, and
|
|
9
|
+
# the `tagged_with` query.
|
|
10
|
+
#
|
|
5
11
|
module Core
|
|
12
|
+
##
|
|
13
|
+
# @param base [Class] the model being made taggable
|
|
14
|
+
# @return [void]
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
#
|
|
6
18
|
def self.included(base)
|
|
7
19
|
base.extend MakeTaggable::Taggable::Core::ClassMethods
|
|
8
20
|
|
|
@@ -14,12 +26,21 @@ module MakeTaggable::Taggable
|
|
|
14
26
|
base.initialize_make_taggable_core
|
|
15
27
|
end
|
|
16
28
|
|
|
29
|
+
##
|
|
30
|
+
# Added to every taggable model.
|
|
31
|
+
#
|
|
17
32
|
module ClassMethods
|
|
33
|
+
##
|
|
34
|
+
# Builds the associations and the `<context>_list` methods for each context.
|
|
35
|
+
#
|
|
36
|
+
# @return [void]
|
|
37
|
+
#
|
|
18
38
|
def initialize_make_taggable_core
|
|
19
39
|
include taggable_mixin
|
|
40
|
+
|
|
20
41
|
tag_types.map(&:to_s).each do |tags_type|
|
|
21
42
|
tag_type = tags_type.to_s.singularize
|
|
22
|
-
context_taggings = "#{tag_type}_taggings"
|
|
43
|
+
context_taggings = :"#{tag_type}_taggings"
|
|
23
44
|
context_tags = tags_type.to_sym
|
|
24
45
|
taggings_order = (preserve_tag_order? ? "#{MakeTaggable::Tagging.table_name}.id" : [])
|
|
25
46
|
|
|
@@ -38,7 +59,7 @@ module MakeTaggable::Taggable
|
|
|
38
59
|
through: context_taggings,
|
|
39
60
|
source: :tag
|
|
40
61
|
|
|
41
|
-
attribute "#{tags_type.singularize}_list"
|
|
62
|
+
attribute :"#{tags_type.singularize}_list", MakeTaggable::Taggable::TagListType.new
|
|
42
63
|
end
|
|
43
64
|
|
|
44
65
|
taggable_mixin.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
@@ -50,12 +71,8 @@ module MakeTaggable::Taggable
|
|
|
50
71
|
parsed_new_list = MakeTaggable.default_parser.new(new_tags).parse
|
|
51
72
|
|
|
52
73
|
if self.class.preserve_tag_order? || (parsed_new_list.sort != #{tag_type}_list.sort)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
else
|
|
56
|
-
unless #{tag_type}_list_changed?
|
|
57
|
-
@attributes["#{tag_type}_list"] = ActiveModel::Attribute.from_user("#{tag_type}_list", #{tag_type}_list, MakeTaggable::Taggable::TagListType.new)
|
|
58
|
-
end
|
|
74
|
+
unless #{tag_type}_list_changed?
|
|
75
|
+
@attributes["#{tag_type}_list"] = ActiveModel::Attribute.from_user("#{tag_type}_list", #{tag_type}_list, MakeTaggable::Taggable::TagListType.new)
|
|
59
76
|
end
|
|
60
77
|
write_attribute("#{tag_type}_list", parsed_new_list)
|
|
61
78
|
end
|
|
@@ -75,8 +92,17 @@ module MakeTaggable::Taggable
|
|
|
75
92
|
end
|
|
76
93
|
end
|
|
77
94
|
|
|
95
|
+
##
|
|
96
|
+
# Adds contexts and rebuilds the generated methods.
|
|
97
|
+
#
|
|
98
|
+
# @param preserve_tag_order [TrueClass, FalseClass] whether to keep tags in the order added
|
|
99
|
+
# @param tag_types [Array<Symbol, String>] the contexts to add
|
|
100
|
+
# @return [void]
|
|
101
|
+
#
|
|
102
|
+
# @api private
|
|
103
|
+
#
|
|
78
104
|
def taggable_on(preserve_tag_order, *tag_types)
|
|
79
|
-
super
|
|
105
|
+
super
|
|
80
106
|
initialize_make_taggable_core
|
|
81
107
|
end
|
|
82
108
|
|
|
@@ -86,26 +112,34 @@ module MakeTaggable::Taggable
|
|
|
86
112
|
end
|
|
87
113
|
|
|
88
114
|
##
|
|
89
|
-
#
|
|
115
|
+
# Records tagged with the given tags.
|
|
116
|
+
#
|
|
117
|
+
# By default a record must carry every tag given. `:any` relaxes that to at least one, and
|
|
118
|
+
# `:exclude` inverts it to none.
|
|
90
119
|
#
|
|
91
|
-
# @param tags
|
|
92
|
-
# @param [Hash]
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
120
|
+
# @param tags [String, Array<String>] the tags to match
|
|
121
|
+
# @param options [Hash] the query options
|
|
122
|
+
# @option options [TrueClass, FalseClass] :any match records carrying any of the tags
|
|
123
|
+
# @option options [TrueClass, FalseClass] :exclude match records carrying none of the tags
|
|
124
|
+
# @option options [TrueClass, FalseClass] :match_all match records carrying only these tags
|
|
125
|
+
# @option options [TrueClass, FalseClass] :wild match tags containing the given text
|
|
126
|
+
# @option options [TrueClass, FalseClass] :order_by_matching_tag_count with `:any`, order by
|
|
127
|
+
# how many tags matched, most first
|
|
128
|
+
# @option options [ActiveRecord::Base] :owned_by only tags applied by this tagger
|
|
129
|
+
# @option options [Symbol, String] :on only tags applied in this context
|
|
130
|
+
# @option options [Time, Date] :start_at only tags applied after this time
|
|
131
|
+
# @option options [Time, Date] :end_at only tags applied before this time
|
|
132
|
+
# @return [ActiveRecord::Relation] empty when no tags are given
|
|
133
|
+
#
|
|
134
|
+
# @example Every tag
|
|
135
|
+
# User.tagged_with(["awesome", "cool"])
|
|
136
|
+
#
|
|
137
|
+
# @example Any tag, most matches first
|
|
138
|
+
# User.tagged_with(["awesome", "cool"], any: true, order_by_matching_tag_count: true)
|
|
139
|
+
#
|
|
140
|
+
# @example Scoped to a context and an owner
|
|
141
|
+
# Photo.tagged_with("paris", on: :locations, owned_by: @user)
|
|
100
142
|
#
|
|
101
|
-
# Example:
|
|
102
|
-
# User.tagged_with(["awesome", "cool"]) # Users that are tagged with awesome and cool
|
|
103
|
-
# User.tagged_with(["awesome", "cool"], :exclude => true) # Users that are not tagged with awesome or cool
|
|
104
|
-
# User.tagged_with(["awesome", "cool"], :any => true) # Users that are tagged with awesome or cool
|
|
105
|
-
# User.tagged_with(["awesome", "cool"], :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending
|
|
106
|
-
# User.tagged_with(["awesome", "cool"], :match_all => true) # Users that are tagged with just awesome and cool
|
|
107
|
-
# User.tagged_with(["awesome", "cool"], :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo'
|
|
108
|
-
# User.tagged_with(["awesome", "cool"], :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today
|
|
109
143
|
def tagged_with(tags, options = {})
|
|
110
144
|
tag_list = MakeTaggable.default_parser.new(tags).parse
|
|
111
145
|
options = options.dup
|
|
@@ -119,6 +153,12 @@ module MakeTaggable::Taggable
|
|
|
119
153
|
true
|
|
120
154
|
end
|
|
121
155
|
|
|
156
|
+
##
|
|
157
|
+
# The module the generated `<context>_list` methods are defined on, so a model can override
|
|
158
|
+
# one and call `super`.
|
|
159
|
+
#
|
|
160
|
+
# @return [Module]
|
|
161
|
+
#
|
|
122
162
|
def taggable_mixin
|
|
123
163
|
@taggable_mixin ||= Module.new
|
|
124
164
|
end
|
|
@@ -129,6 +169,11 @@ module MakeTaggable::Taggable
|
|
|
129
169
|
self.class.grouped_column_names_for(object)
|
|
130
170
|
end
|
|
131
171
|
|
|
172
|
+
##
|
|
173
|
+
# Contexts this record has been tagged in beyond those the model declares.
|
|
174
|
+
#
|
|
175
|
+
# @return [Array<String>]
|
|
176
|
+
#
|
|
132
177
|
def custom_contexts
|
|
133
178
|
@custom_contexts ||= taggings.map(&:context).uniq
|
|
134
179
|
end
|
|
@@ -137,19 +182,43 @@ module MakeTaggable::Taggable
|
|
|
137
182
|
self.class.is_taggable?
|
|
138
183
|
end
|
|
139
184
|
|
|
185
|
+
##
|
|
186
|
+
# Records a context the model does not declare, so it takes part in saving and reloading.
|
|
187
|
+
#
|
|
188
|
+
# @param value [Symbol, String] the context
|
|
189
|
+
# @return [Array<String>, NilClass]
|
|
190
|
+
#
|
|
140
191
|
def add_custom_context(value)
|
|
141
192
|
custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) || self.class.tag_types.map(&:to_s).include?(value.to_s)
|
|
142
193
|
end
|
|
143
194
|
|
|
195
|
+
##
|
|
196
|
+
# The rendered tag list held in this context's caching column, if the model has one.
|
|
197
|
+
#
|
|
198
|
+
# @param context [Symbol, String] the tagging context
|
|
199
|
+
# @return [String, NilClass]
|
|
200
|
+
#
|
|
144
201
|
def cached_tag_list_on(context)
|
|
145
202
|
self["cached_#{context.to_s.singularize}_list"]
|
|
146
203
|
end
|
|
147
204
|
|
|
205
|
+
##
|
|
206
|
+
# Whether a context's tag list has been loaded or assigned on this record.
|
|
207
|
+
#
|
|
208
|
+
# @param context [Symbol, String] the tagging context
|
|
209
|
+
# @return [TrueClass, FalseClass, MakeTaggable::TagList]
|
|
210
|
+
#
|
|
148
211
|
def tag_list_cache_set_on(context)
|
|
149
212
|
variable_name = "@#{context.to_s.singularize}_list"
|
|
150
213
|
instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
|
|
151
214
|
end
|
|
152
215
|
|
|
216
|
+
##
|
|
217
|
+
# A context's tag list, loading it from the caching column or the database as needed.
|
|
218
|
+
#
|
|
219
|
+
# @param context [Symbol, String] the tagging context
|
|
220
|
+
# @return [MakeTaggable::TagList]
|
|
221
|
+
#
|
|
153
222
|
def tag_list_cache_on(context)
|
|
154
223
|
variable_name = "@#{context.to_s.singularize}_list"
|
|
155
224
|
if instance_variable_get(variable_name)
|
|
@@ -161,11 +230,28 @@ module MakeTaggable::Taggable
|
|
|
161
230
|
end
|
|
162
231
|
end
|
|
163
232
|
|
|
233
|
+
##
|
|
234
|
+
# A context's tag list, covering contexts the model does not declare.
|
|
235
|
+
#
|
|
236
|
+
# Only unowned tags appear here. Use {#all_tags_list_on} to include tags applied by a tagger.
|
|
237
|
+
#
|
|
238
|
+
# @param context [Symbol, String] the tagging context
|
|
239
|
+
# @return [MakeTaggable::TagList]
|
|
240
|
+
#
|
|
241
|
+
# @example
|
|
242
|
+
# @user.tag_list_on(:customs) # => ["one", "two"]
|
|
243
|
+
#
|
|
164
244
|
def tag_list_on(context)
|
|
165
245
|
add_custom_context(context)
|
|
166
246
|
tag_list_cache_on(context)
|
|
167
247
|
end
|
|
168
248
|
|
|
249
|
+
##
|
|
250
|
+
# A context's tag list including tags applied by a tagger.
|
|
251
|
+
#
|
|
252
|
+
# @param context [Symbol, String] the tagging context
|
|
253
|
+
# @return [MakeTaggable::TagList] frozen
|
|
254
|
+
#
|
|
169
255
|
def all_tags_list_on(context)
|
|
170
256
|
variable_name = "@all_#{context.to_s.singularize}_list"
|
|
171
257
|
return instance_variable_get(variable_name) if instance_variable_defined?(variable_name) && instance_variable_get(variable_name)
|
|
@@ -199,6 +285,18 @@ module MakeTaggable::Taggable
|
|
|
199
285
|
scope
|
|
200
286
|
end
|
|
201
287
|
|
|
288
|
+
##
|
|
289
|
+
# Replaces a context's tag list, covering contexts the model does not declare. Saved with the
|
|
290
|
+
# record.
|
|
291
|
+
#
|
|
292
|
+
# @param context [Symbol, String] the tagging context
|
|
293
|
+
# @param new_list [String, Array<String>] the tags to apply
|
|
294
|
+
# @return [MakeTaggable::TagList]
|
|
295
|
+
#
|
|
296
|
+
# @example
|
|
297
|
+
# @user.set_tag_list_on(:customs, "same, as, tag, list")
|
|
298
|
+
# @user.save
|
|
299
|
+
#
|
|
202
300
|
def set_tag_list_on(context, new_list)
|
|
203
301
|
add_custom_context(context)
|
|
204
302
|
|
|
@@ -209,17 +307,28 @@ module MakeTaggable::Taggable
|
|
|
209
307
|
instance_variable_set(variable_name, parsed_new_list)
|
|
210
308
|
end
|
|
211
309
|
|
|
310
|
+
##
|
|
311
|
+
# Every context this record tags in, declared and dynamic alike.
|
|
312
|
+
#
|
|
313
|
+
# @return [Array<String>]
|
|
314
|
+
#
|
|
212
315
|
def tagging_contexts
|
|
213
316
|
self.class.tag_types.map(&:to_s) + custom_contexts
|
|
214
317
|
end
|
|
215
318
|
|
|
319
|
+
##
|
|
320
|
+
# Reloads the record, discarding the tag lists held in memory.
|
|
321
|
+
#
|
|
322
|
+
# @param args [Array<Object>] arguments forwarded to Active Record
|
|
323
|
+
# @return [ActiveRecord::Base] self
|
|
324
|
+
#
|
|
216
325
|
def reload(*args)
|
|
217
326
|
self.class.tag_types.each do |context|
|
|
218
327
|
instance_variable_set("@#{context.to_s.singularize}_list", nil)
|
|
219
328
|
instance_variable_set("@all_#{context.to_s.singularize}_list", nil)
|
|
220
329
|
end
|
|
221
330
|
|
|
222
|
-
super
|
|
331
|
+
super
|
|
223
332
|
end
|
|
224
333
|
|
|
225
334
|
##
|
|
@@ -228,6 +337,11 @@ module MakeTaggable::Taggable
|
|
|
228
337
|
MakeTaggable::Tag.find_or_create_all_with_like_by_name(tag_list)
|
|
229
338
|
end
|
|
230
339
|
|
|
340
|
+
##
|
|
341
|
+
# Writes every assigned tag list to the database. Runs after save.
|
|
342
|
+
#
|
|
343
|
+
# @return [TrueClass]
|
|
344
|
+
#
|
|
231
345
|
def save_tags
|
|
232
346
|
tagging_contexts.each do |context|
|
|
233
347
|
next unless tag_list_cache_set_on(context)
|
|
@@ -298,14 +412,17 @@ module MakeTaggable::Taggable
|
|
|
298
412
|
end
|
|
299
413
|
|
|
300
414
|
##
|
|
301
|
-
#
|
|
302
|
-
# context is provided so that you may conditionally use a Tag subclass
|
|
303
|
-
# only for some contexts.
|
|
415
|
+
# Finds or creates the tag records for a list, given the context they are being applied in.
|
|
304
416
|
#
|
|
305
|
-
#
|
|
417
|
+
# Override it to keep a separate vocabulary for one context by returning tags from a
|
|
418
|
+
# {MakeTaggable::Tag} subclass.
|
|
419
|
+
#
|
|
420
|
+
# @example A separate Tag subclass for one context
|
|
306
421
|
# class Company < ActiveRecord::Base
|
|
307
422
|
# make_taggable :markets, :locations
|
|
308
423
|
#
|
|
424
|
+
# private
|
|
425
|
+
#
|
|
309
426
|
# def find_or_create_tags_from_list_with_context(tag_list, context)
|
|
310
427
|
# if context.to_sym == :markets
|
|
311
428
|
# MarketTag.find_or_create_all_with_like_by_name(tag_list)
|
|
@@ -313,9 +430,12 @@ module MakeTaggable::Taggable
|
|
|
313
430
|
# super
|
|
314
431
|
# end
|
|
315
432
|
# end
|
|
433
|
+
# end
|
|
434
|
+
#
|
|
435
|
+
# @param tag_list [Array<String>] the tags to find or create
|
|
436
|
+
# @param _context [Symbol] the context the tags are being applied in
|
|
437
|
+
# @return [Array<MakeTaggable::Tag>]
|
|
316
438
|
#
|
|
317
|
-
# @param [Array<String>] tag_list Tags to find or create
|
|
318
|
-
# @param [Symbol] context The tag context for the tag_list
|
|
319
439
|
def find_or_create_tags_from_list_with_context(tag_list, _context)
|
|
320
440
|
load_tags(tag_list)
|
|
321
441
|
end
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module MakeTaggable::Taggable
|
|
4
|
+
##
|
|
5
|
+
# Tags applied by a tagger, kept separate from the record's own tags.
|
|
6
|
+
#
|
|
7
|
+
# Owned tags do not appear in `tag_list`, which only ever returns unowned tags. Use
|
|
8
|
+
# `all_tags_list` to see both together.
|
|
9
|
+
#
|
|
10
|
+
# @see MakeTaggable::Tagger
|
|
11
|
+
#
|
|
2
12
|
module Ownership
|
|
13
|
+
##
|
|
14
|
+
# @param base [Class] the model being made taggable
|
|
15
|
+
# @return [void]
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
#
|
|
3
19
|
def self.included(base)
|
|
4
20
|
base.extend MakeTaggable::Taggable::Ownership::ClassMethods
|
|
5
21
|
|
|
@@ -10,12 +26,26 @@ module MakeTaggable::Taggable
|
|
|
10
26
|
base.initialize_make_taggable_ownership
|
|
11
27
|
end
|
|
12
28
|
|
|
29
|
+
##
|
|
30
|
+
# Added to every taggable model.
|
|
31
|
+
#
|
|
13
32
|
module ClassMethods
|
|
33
|
+
##
|
|
34
|
+
# Adds contexts and refreshes the ownership readers.
|
|
35
|
+
#
|
|
36
|
+
# @param args [Array<Symbol, String>] the contexts to add
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
14
39
|
def make_taggable(*args)
|
|
15
40
|
initialize_make_taggable_ownership
|
|
16
|
-
super
|
|
41
|
+
super
|
|
17
42
|
end
|
|
18
43
|
|
|
44
|
+
##
|
|
45
|
+
# Defines a `<context>_from(owner)` reader for each context.
|
|
46
|
+
#
|
|
47
|
+
# @return [void]
|
|
48
|
+
#
|
|
19
49
|
def initialize_make_taggable_ownership
|
|
20
50
|
tag_types.map(&:to_s).each do |tag_type|
|
|
21
51
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
@@ -27,6 +57,12 @@ module MakeTaggable::Taggable
|
|
|
27
57
|
end
|
|
28
58
|
end
|
|
29
59
|
|
|
60
|
+
##
|
|
61
|
+
# This record's tags belonging to one owner, across every context.
|
|
62
|
+
#
|
|
63
|
+
# @param owner [ActiveRecord::Base, NilClass] the tagger, or `nil` for every tag on the record
|
|
64
|
+
# @return [ActiveRecord::Relation]
|
|
65
|
+
#
|
|
30
66
|
def owner_tags(owner)
|
|
31
67
|
scope = if owner.nil?
|
|
32
68
|
base_tags
|
|
@@ -48,6 +84,16 @@ module MakeTaggable::Taggable
|
|
|
48
84
|
end
|
|
49
85
|
end
|
|
50
86
|
|
|
87
|
+
##
|
|
88
|
+
# This record's tags belonging to one owner, in one context.
|
|
89
|
+
#
|
|
90
|
+
# @param owner [ActiveRecord::Base, NilClass] the tagger, or `nil` for every tag on the record
|
|
91
|
+
# @param context [Symbol, String] the tagging context
|
|
92
|
+
# @return [ActiveRecord::Relation]
|
|
93
|
+
#
|
|
94
|
+
# @example
|
|
95
|
+
# @photo.owner_tags_on(@user, :locations)
|
|
96
|
+
#
|
|
51
97
|
def owner_tags_on(owner, context)
|
|
52
98
|
owner_tags(owner).where(
|
|
53
99
|
MakeTaggable::Tagging.table_name.to_s => {
|
|
@@ -56,11 +102,24 @@ module MakeTaggable::Taggable
|
|
|
56
102
|
)
|
|
57
103
|
end
|
|
58
104
|
|
|
105
|
+
##
|
|
106
|
+
# The per-owner tag lists held in memory for a context, keyed by owner.
|
|
107
|
+
#
|
|
108
|
+
# @param context [Symbol, String] the tagging context
|
|
109
|
+
# @return [Hash{ActiveRecord::Base => MakeTaggable::TagList}]
|
|
110
|
+
#
|
|
59
111
|
def cached_owned_tag_list_on(context)
|
|
60
112
|
variable_name = "@owned_#{context}_list"
|
|
61
113
|
(instance_variable_defined?(variable_name) && instance_variable_get(variable_name)) || instance_variable_set(variable_name, {})
|
|
62
114
|
end
|
|
63
115
|
|
|
116
|
+
##
|
|
117
|
+
# One owner's tag list for a context.
|
|
118
|
+
#
|
|
119
|
+
# @param owner [ActiveRecord::Base] the tagger
|
|
120
|
+
# @param context [Symbol, String] the tagging context
|
|
121
|
+
# @return [MakeTaggable::TagList]
|
|
122
|
+
#
|
|
64
123
|
def owner_tag_list_on(owner, context)
|
|
65
124
|
add_custom_context(context)
|
|
66
125
|
|
|
@@ -69,6 +128,14 @@ module MakeTaggable::Taggable
|
|
|
69
128
|
cache[owner] ||= MakeTaggable::TagList.new(*owner_tags_on(owner, context).map(&:name))
|
|
70
129
|
end
|
|
71
130
|
|
|
131
|
+
##
|
|
132
|
+
# Replaces one owner's tag list for a context. Saved with the record.
|
|
133
|
+
#
|
|
134
|
+
# @param owner [ActiveRecord::Base] the tagger
|
|
135
|
+
# @param context [Symbol, String] the tagging context
|
|
136
|
+
# @param new_list [String, Array<String>] the tags to apply
|
|
137
|
+
# @return [MakeTaggable::TagList]
|
|
138
|
+
#
|
|
72
139
|
def set_owner_tag_list_on(owner, context, new_list)
|
|
73
140
|
add_custom_context(context)
|
|
74
141
|
|
|
@@ -77,14 +144,25 @@ module MakeTaggable::Taggable
|
|
|
77
144
|
cache[owner] = MakeTaggable.default_parser.new(new_list).parse
|
|
78
145
|
end
|
|
79
146
|
|
|
147
|
+
##
|
|
148
|
+
# Reloads the record, discarding the owned tag lists held in memory.
|
|
149
|
+
#
|
|
150
|
+
# @param args [Array<Object>] arguments forwarded to Active Record
|
|
151
|
+
# @return [ActiveRecord::Base] self
|
|
152
|
+
#
|
|
80
153
|
def reload(*args)
|
|
81
154
|
self.class.tag_types.each do |context|
|
|
82
155
|
instance_variable_set("@owned_#{context}_list", nil)
|
|
83
156
|
end
|
|
84
157
|
|
|
85
|
-
super
|
|
158
|
+
super
|
|
86
159
|
end
|
|
87
160
|
|
|
161
|
+
##
|
|
162
|
+
# Writes every owner's tag lists to the database. Runs after save.
|
|
163
|
+
#
|
|
164
|
+
# @return [TrueClass]
|
|
165
|
+
#
|
|
88
166
|
def save_owned_tags
|
|
89
167
|
tagging_contexts.each do |context|
|
|
90
168
|
cached_owned_tag_list_on(context).each do |owner, tag_list|
|
|
@@ -120,8 +198,8 @@ module MakeTaggable::Taggable
|
|
|
120
198
|
# have the correct context, and are removed from the list.
|
|
121
199
|
if old_tags.present?
|
|
122
200
|
MakeTaggable::Tagging.where(taggable_id: id, taggable_type: self.class.base_class.to_s,
|
|
123
|
-
|
|
124
|
-
|
|
201
|
+
tagger_type: owner.class.base_class.to_s, tagger_id: owner.id,
|
|
202
|
+
tag_id: old_tags, context: context).destroy_all
|
|
125
203
|
end
|
|
126
204
|
|
|
127
205
|
# Create new taggings:
|