mongoid_taggable_with_context 1.1.3 → 1.1.4
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 +13 -5
- data/.document +5 -5
- data/.travis.yml +7 -7
- data/Gemfile +12 -12
- data/LICENSE.txt +20 -20
- data/README.md +223 -223
- data/Rakefile +32 -32
- data/lib/mongoid/taggable_with_context.rb +320 -320
- data/lib/mongoid/taggable_with_context/aggregation_strategy/map_reduce.rb +71 -71
- data/lib/mongoid/taggable_with_context/aggregation_strategy/real_time.rb +116 -116
- data/lib/mongoid/taggable_with_context/aggregation_strategy/real_time_group_by.rb +51 -51
- data/lib/mongoid/taggable_with_context/deprecations.rb +25 -25
- data/lib/mongoid/taggable_with_context/version.rb +6 -6
- data/lib/mongoid_taggable_with_context.rb +7 -7
- data/mongoid_taggable_with_context.gemspec +71 -70
- data/spec/mongoid_taggable_with_context_spec.rb +465 -465
- data/spec/spec_helper.rb +16 -16
- metadata +25 -22
@@ -1,71 +1,71 @@
|
|
1
|
-
module Mongoid::TaggableWithContext::AggregationStrategy
|
2
|
-
module MapReduce
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
included do
|
5
|
-
set_callback :save, :after, :map_reduce_all_contexts!, if: :tags_changed?
|
6
|
-
set_callback :destroy, :after, :map_reduce_all_contexts!
|
7
|
-
delegate :aggregation_collection_for, to: "self.class"
|
8
|
-
end
|
9
|
-
|
10
|
-
module ClassMethods
|
11
|
-
# Collection name for storing results of tag count aggregation
|
12
|
-
|
13
|
-
def aggregation_database_collection_for(context)
|
14
|
-
(@aggregation_database_collection ||= {})[context] ||= Moped::Collection.new(self.collection.database, aggregation_collection_for(context))
|
15
|
-
end
|
16
|
-
|
17
|
-
def aggregation_collection_for(context)
|
18
|
-
"#{collection_name}_#{context}_aggregation"
|
19
|
-
end
|
20
|
-
|
21
|
-
def tags_for(context, conditions={})
|
22
|
-
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(_id: 1).to_a.map{ |t| t["_id"] }
|
23
|
-
end
|
24
|
-
|
25
|
-
# retrieve the list of tag with weight(count), this is useful for
|
26
|
-
# creating tag clouds
|
27
|
-
def tags_with_weight_for(context, conditions={})
|
28
|
-
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(_id: 1).to_a.map{ |t| [t["_id"], t["value"].to_i] }
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
protected
|
34
|
-
|
35
|
-
def changed_tag_arrays
|
36
|
-
self.class.tag_database_fields & changes.keys.map(&:to_sym)
|
37
|
-
end
|
38
|
-
|
39
|
-
def tags_changed?
|
40
|
-
!changed_tag_arrays.empty?
|
41
|
-
end
|
42
|
-
|
43
|
-
def map_reduce_all_contexts!
|
44
|
-
self.class.tag_contexts.each do |context|
|
45
|
-
map_reduce_context!(context)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def map_reduce_context!(context)
|
50
|
-
db_field = self.class.tag_options_for(context)[:db_field]
|
51
|
-
|
52
|
-
map = <<-END
|
53
|
-
function() {
|
54
|
-
if (!this.#{db_field})return;
|
55
|
-
for (index in this.#{db_field})
|
56
|
-
emit(this.#{db_field}[index], 1);
|
57
|
-
}
|
58
|
-
END
|
59
|
-
|
60
|
-
reduce = <<-END
|
61
|
-
function(key, values) {
|
62
|
-
var count = 0;
|
63
|
-
for (index in values) count += values[index];
|
64
|
-
return count;
|
65
|
-
}
|
66
|
-
END
|
67
|
-
|
68
|
-
self.class.map_reduce(map, reduce).out(replace: aggregation_collection_for(context)).time
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
1
|
+
module Mongoid::TaggableWithContext::AggregationStrategy
|
2
|
+
module MapReduce
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
set_callback :save, :after, :map_reduce_all_contexts!, if: :tags_changed?
|
6
|
+
set_callback :destroy, :after, :map_reduce_all_contexts!
|
7
|
+
delegate :aggregation_collection_for, to: "self.class"
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Collection name for storing results of tag count aggregation
|
12
|
+
|
13
|
+
def aggregation_database_collection_for(context)
|
14
|
+
(@aggregation_database_collection ||= {})[context] ||= Moped::Collection.new(self.collection.database, aggregation_collection_for(context))
|
15
|
+
end
|
16
|
+
|
17
|
+
def aggregation_collection_for(context)
|
18
|
+
"#{collection_name}_#{context}_aggregation"
|
19
|
+
end
|
20
|
+
|
21
|
+
def tags_for(context, conditions={})
|
22
|
+
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(_id: 1).to_a.map{ |t| t["_id"] }
|
23
|
+
end
|
24
|
+
|
25
|
+
# retrieve the list of tag with weight(count), this is useful for
|
26
|
+
# creating tag clouds
|
27
|
+
def tags_with_weight_for(context, conditions={})
|
28
|
+
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(_id: 1).to_a.map{ |t| [t["_id"], t["value"].to_i] }
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def changed_tag_arrays
|
36
|
+
self.class.tag_database_fields & changes.keys.map(&:to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
def tags_changed?
|
40
|
+
!changed_tag_arrays.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def map_reduce_all_contexts!
|
44
|
+
self.class.tag_contexts.each do |context|
|
45
|
+
map_reduce_context!(context)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def map_reduce_context!(context)
|
50
|
+
db_field = self.class.tag_options_for(context)[:db_field]
|
51
|
+
|
52
|
+
map = <<-END
|
53
|
+
function() {
|
54
|
+
if (!this.#{db_field})return;
|
55
|
+
for (index in this.#{db_field})
|
56
|
+
emit(this.#{db_field}[index], 1);
|
57
|
+
}
|
58
|
+
END
|
59
|
+
|
60
|
+
reduce = <<-END
|
61
|
+
function(key, values) {
|
62
|
+
var count = 0;
|
63
|
+
for (index in values) count += values[index];
|
64
|
+
return count;
|
65
|
+
}
|
66
|
+
END
|
67
|
+
|
68
|
+
self.class.map_reduce(map, reduce).out(replace: aggregation_collection_for(context)).time
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,116 +1,116 @@
|
|
1
|
-
module Mongoid::TaggableWithContext::AggregationStrategy
|
2
|
-
module RealTime
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
set_callback :save, :after, :update_tags_aggregations_on_save
|
7
|
-
set_callback :destroy, :after, :update_tags_aggregations_on_destroy
|
8
|
-
end
|
9
|
-
|
10
|
-
module ClassMethods
|
11
|
-
def tag_name_attribute
|
12
|
-
"_id"
|
13
|
-
end
|
14
|
-
|
15
|
-
# Collection name for storing results of tag count aggregation
|
16
|
-
|
17
|
-
def aggregation_database_collection_for(context)
|
18
|
-
(@aggregation_database_collection ||= {})[context] ||= Moped::Collection.new(self.collection.database, aggregation_collection_for(context))
|
19
|
-
end
|
20
|
-
|
21
|
-
def aggregation_collection_for(context)
|
22
|
-
"#{collection_name}_#{context}_aggregation"
|
23
|
-
end
|
24
|
-
|
25
|
-
def tags_for(context, conditions={})
|
26
|
-
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(tag_name_attribute.to_sym => 1).to_a.map{ |t| t[tag_name_attribute] }
|
27
|
-
end
|
28
|
-
|
29
|
-
# retrieve the list of tag with weight(count), this is useful for
|
30
|
-
# creating tag clouds
|
31
|
-
def tags_with_weight_for(context, conditions={})
|
32
|
-
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(tag_name_attribute.to_sym => 1).to_a.map{ |t| [t[tag_name_attribute], t["value"].to_i] }
|
33
|
-
end
|
34
|
-
|
35
|
-
def recalculate_all_context_tag_weights!
|
36
|
-
tag_contexts.each do |context|
|
37
|
-
recalculate_tag_weights!(context)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def recalculate_tag_weights!(context)
|
42
|
-
db_field = self.class.tag_options_for(context)[:db_field]
|
43
|
-
|
44
|
-
map = <<-END
|
45
|
-
function() {
|
46
|
-
if (!this.#{db_field})return;
|
47
|
-
for (index in this.#{db_field})
|
48
|
-
emit(this.#{db_field}[index], 1);
|
49
|
-
}
|
50
|
-
END
|
51
|
-
|
52
|
-
reduce = <<-END
|
53
|
-
function(key, values) {
|
54
|
-
var count = 0;
|
55
|
-
for (index in values) count += values[index];
|
56
|
-
return count;
|
57
|
-
}
|
58
|
-
END
|
59
|
-
|
60
|
-
self.class.map_reduce(map, reduce).out(replace: aggregation_collection_for(context)).time
|
61
|
-
end
|
62
|
-
|
63
|
-
# adapted from https://github.com/jesuisbonbon/mongoid_taggable/commit/42feddd24dedd66b2b6776f9694d1b5b8bf6903d
|
64
|
-
def tags_autocomplete(context, criteria, options={})
|
65
|
-
result = aggregation_database_collection_for(context).find({tag_name_attribute.to_sym => /^#{criteria}/})
|
66
|
-
result = result.sort(value: -1) if options[:sort_by_count] == true
|
67
|
-
result = result.limit(options[:max]) if options[:max] > 0
|
68
|
-
result.to_a.map{ |r| [r[tag_name_attribute], r["value"]] }
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
protected
|
73
|
-
|
74
|
-
def get_conditions(context, tag)
|
75
|
-
{self.class.tag_name_attribute.to_sym => tag}
|
76
|
-
end
|
77
|
-
|
78
|
-
def update_tags_aggregation(context, old_tags=[], new_tags=[])
|
79
|
-
coll = self.class.aggregation_database_collection_for(context)
|
80
|
-
|
81
|
-
old_tags ||= []
|
82
|
-
new_tags ||= []
|
83
|
-
unchanged_tags = old_tags & new_tags
|
84
|
-
tags_removed = old_tags - unchanged_tags
|
85
|
-
tags_added = new_tags - unchanged_tags
|
86
|
-
|
87
|
-
|
88
|
-
tags_removed.each do |tag|
|
89
|
-
coll.find(get_conditions(context, tag)).upsert({'$inc' => {value: -1}})
|
90
|
-
end
|
91
|
-
tags_added.each do |tag|
|
92
|
-
coll.find(get_conditions(context, tag)).upsert({'$inc' => {value: 1}})
|
93
|
-
end
|
94
|
-
#coll.find({_id: {"$in" => tags_removed}}).update({'$inc' => {:value => -1}}, [:upsert])
|
95
|
-
#coll.find({_id: {"$in" => tags_added}}).update({'$inc' => {:value => 1}}, [:upsert])
|
96
|
-
end
|
97
|
-
|
98
|
-
def update_tags_aggregations_on_save
|
99
|
-
indifferent_changes = HashWithIndifferentAccess.new changes
|
100
|
-
self.class.tag_database_fields.each do |field|
|
101
|
-
next if indifferent_changes[field].nil?
|
102
|
-
|
103
|
-
old_tags, new_tags = indifferent_changes[field]
|
104
|
-
update_tags_aggregation(field, old_tags, new_tags)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def update_tags_aggregations_on_destroy
|
109
|
-
self.class.tag_database_fields.each do |field|
|
110
|
-
old_tags = send field
|
111
|
-
new_tags = []
|
112
|
-
update_tags_aggregation(field, old_tags, new_tags)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
1
|
+
module Mongoid::TaggableWithContext::AggregationStrategy
|
2
|
+
module RealTime
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
set_callback :save, :after, :update_tags_aggregations_on_save
|
7
|
+
set_callback :destroy, :after, :update_tags_aggregations_on_destroy
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def tag_name_attribute
|
12
|
+
"_id"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Collection name for storing results of tag count aggregation
|
16
|
+
|
17
|
+
def aggregation_database_collection_for(context)
|
18
|
+
(@aggregation_database_collection ||= {})[context] ||= Moped::Collection.new(self.collection.database, aggregation_collection_for(context))
|
19
|
+
end
|
20
|
+
|
21
|
+
def aggregation_collection_for(context)
|
22
|
+
"#{collection_name}_#{context}_aggregation"
|
23
|
+
end
|
24
|
+
|
25
|
+
def tags_for(context, conditions={})
|
26
|
+
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(tag_name_attribute.to_sym => 1).to_a.map{ |t| t[tag_name_attribute] }
|
27
|
+
end
|
28
|
+
|
29
|
+
# retrieve the list of tag with weight(count), this is useful for
|
30
|
+
# creating tag clouds
|
31
|
+
def tags_with_weight_for(context, conditions={})
|
32
|
+
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }}).sort(tag_name_attribute.to_sym => 1).to_a.map{ |t| [t[tag_name_attribute], t["value"].to_i] }
|
33
|
+
end
|
34
|
+
|
35
|
+
def recalculate_all_context_tag_weights!
|
36
|
+
tag_contexts.each do |context|
|
37
|
+
recalculate_tag_weights!(context)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def recalculate_tag_weights!(context)
|
42
|
+
db_field = self.class.tag_options_for(context)[:db_field]
|
43
|
+
|
44
|
+
map = <<-END
|
45
|
+
function() {
|
46
|
+
if (!this.#{db_field})return;
|
47
|
+
for (index in this.#{db_field})
|
48
|
+
emit(this.#{db_field}[index], 1);
|
49
|
+
}
|
50
|
+
END
|
51
|
+
|
52
|
+
reduce = <<-END
|
53
|
+
function(key, values) {
|
54
|
+
var count = 0;
|
55
|
+
for (index in values) count += values[index];
|
56
|
+
return count;
|
57
|
+
}
|
58
|
+
END
|
59
|
+
|
60
|
+
self.class.map_reduce(map, reduce).out(replace: aggregation_collection_for(context)).time
|
61
|
+
end
|
62
|
+
|
63
|
+
# adapted from https://github.com/jesuisbonbon/mongoid_taggable/commit/42feddd24dedd66b2b6776f9694d1b5b8bf6903d
|
64
|
+
def tags_autocomplete(context, criteria, options={})
|
65
|
+
result = aggregation_database_collection_for(context).find({tag_name_attribute.to_sym => /^#{criteria}/})
|
66
|
+
result = result.sort(value: -1) if options[:sort_by_count] == true
|
67
|
+
result = result.limit(options[:max]) if options[:max] > 0
|
68
|
+
result.to_a.map{ |r| [r[tag_name_attribute], r["value"]] }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
|
74
|
+
def get_conditions(context, tag)
|
75
|
+
{self.class.tag_name_attribute.to_sym => tag}
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_tags_aggregation(context, old_tags=[], new_tags=[])
|
79
|
+
coll = self.class.aggregation_database_collection_for(context)
|
80
|
+
|
81
|
+
old_tags ||= []
|
82
|
+
new_tags ||= []
|
83
|
+
unchanged_tags = old_tags & new_tags
|
84
|
+
tags_removed = old_tags - unchanged_tags
|
85
|
+
tags_added = new_tags - unchanged_tags
|
86
|
+
|
87
|
+
|
88
|
+
tags_removed.each do |tag|
|
89
|
+
coll.find(get_conditions(context, tag)).upsert({'$inc' => {value: -1}})
|
90
|
+
end
|
91
|
+
tags_added.each do |tag|
|
92
|
+
coll.find(get_conditions(context, tag)).upsert({'$inc' => {value: 1}})
|
93
|
+
end
|
94
|
+
#coll.find({_id: {"$in" => tags_removed}}).update({'$inc' => {:value => -1}}, [:upsert])
|
95
|
+
#coll.find({_id: {"$in" => tags_added}}).update({'$inc' => {:value => 1}}, [:upsert])
|
96
|
+
end
|
97
|
+
|
98
|
+
def update_tags_aggregations_on_save
|
99
|
+
indifferent_changes = HashWithIndifferentAccess.new changes
|
100
|
+
self.class.tag_database_fields.each do |field|
|
101
|
+
next if indifferent_changes[field].nil?
|
102
|
+
|
103
|
+
old_tags, new_tags = indifferent_changes[field]
|
104
|
+
update_tags_aggregation(field, old_tags, new_tags)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def update_tags_aggregations_on_destroy
|
109
|
+
self.class.tag_database_fields.each do |field|
|
110
|
+
old_tags = send field
|
111
|
+
new_tags = []
|
112
|
+
update_tags_aggregation(field, old_tags, new_tags)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -1,52 +1,52 @@
|
|
1
|
-
module Mongoid::TaggableWithContext::AggregationStrategy
|
2
|
-
module RealTimeGroupBy
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
include Mongoid::TaggableWithContext::AggregationStrategy::RealTime
|
5
|
-
|
6
|
-
module ClassMethods
|
7
|
-
def tag_name_attribute
|
8
|
-
"_name"
|
9
|
-
end
|
10
|
-
|
11
|
-
def tags_for(context, group_by, conditions={})
|
12
|
-
results = if group_by
|
13
|
-
query(context, group_by).to_a.map{ |t| t[tag_name_attribute] }
|
14
|
-
else
|
15
|
-
super(context, conditions)
|
16
|
-
end
|
17
|
-
results.uniq
|
18
|
-
end
|
19
|
-
|
20
|
-
def tags_with_weight_for(context, group_by, conditions={})
|
21
|
-
results = if group_by
|
22
|
-
query(context, group_by).to_a.map{ |t| [t[tag_name_attribute], t["value"].to_i] }
|
23
|
-
else
|
24
|
-
super(context, conditions)
|
25
|
-
end
|
26
|
-
|
27
|
-
tag_hash = {}
|
28
|
-
results.each do |tag, weight|
|
29
|
-
tag_hash[tag] ||= 0
|
30
|
-
tag_hash[tag] += weight
|
31
|
-
end
|
32
|
-
tag_hash.to_a
|
33
|
-
end
|
34
|
-
|
35
|
-
protected
|
36
|
-
def query(context, group_by)
|
37
|
-
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }, group_by: group_by}).sort(tag_name_attribute.to_sym => 1)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
protected
|
42
|
-
|
43
|
-
def get_conditions(context, tag)
|
44
|
-
conditions = {self.class.tag_name_attribute.to_sym => tag}
|
45
|
-
group_by = self.class.get_tag_group_by_field_for(context)
|
46
|
-
if group_by
|
47
|
-
conditions.merge!({group_by: self.send(group_by)})
|
48
|
-
end
|
49
|
-
conditions
|
50
|
-
end
|
51
|
-
end
|
1
|
+
module Mongoid::TaggableWithContext::AggregationStrategy
|
2
|
+
module RealTimeGroupBy
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Mongoid::TaggableWithContext::AggregationStrategy::RealTime
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def tag_name_attribute
|
8
|
+
"_name"
|
9
|
+
end
|
10
|
+
|
11
|
+
def tags_for(context, group_by, conditions={})
|
12
|
+
results = if group_by
|
13
|
+
query(context, group_by).to_a.map{ |t| t[tag_name_attribute] }
|
14
|
+
else
|
15
|
+
super(context, conditions)
|
16
|
+
end
|
17
|
+
results.uniq
|
18
|
+
end
|
19
|
+
|
20
|
+
def tags_with_weight_for(context, group_by, conditions={})
|
21
|
+
results = if group_by
|
22
|
+
query(context, group_by).to_a.map{ |t| [t[tag_name_attribute], t["value"].to_i] }
|
23
|
+
else
|
24
|
+
super(context, conditions)
|
25
|
+
end
|
26
|
+
|
27
|
+
tag_hash = {}
|
28
|
+
results.each do |tag, weight|
|
29
|
+
tag_hash[tag] ||= 0
|
30
|
+
tag_hash[tag] += weight
|
31
|
+
end
|
32
|
+
tag_hash.to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
def query(context, group_by)
|
37
|
+
aggregation_database_collection_for(context).find({value: {"$gt" => 0 }, group_by: group_by}).sort(tag_name_attribute.to_sym => 1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def get_conditions(context, tag)
|
44
|
+
conditions = {self.class.tag_name_attribute.to_sym => tag}
|
45
|
+
group_by = self.class.get_tag_group_by_field_for(context)
|
46
|
+
if group_by
|
47
|
+
conditions.merge!({group_by: self.send(group_by)})
|
48
|
+
end
|
49
|
+
conditions
|
50
|
+
end
|
51
|
+
end
|
52
52
|
end
|