yetanothernguyen-acts-as-taggable-on 0.0.4 → 0.0.5
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.
- data/VERSION +1 -1
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +3 -3
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +28 -35
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +10 -31
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +3 -7
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +2 -6
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +1 -5
- data/lib/acts_as_taggable_on/tag.rb +3 -13
- data/spec/acts_as_taggable_on/taggable_spec.rb +0 -17
- data/spec/spec_helper.rb +2 -2
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.5
|
@@ -11,11 +11,11 @@ module ActsAsTaggableOn::Taggable
|
|
11
11
|
before_save :save_cached_tag_list
|
12
12
|
end
|
13
13
|
|
14
|
-
base.
|
14
|
+
base.intialize_acts_as_taggable_on_cache
|
15
15
|
end
|
16
16
|
|
17
17
|
module ClassMethods
|
18
|
-
def
|
18
|
+
def intialize_acts_as_taggable_on_cache
|
19
19
|
tag_types.map(&:to_s).each do |tag_type|
|
20
20
|
class_eval %(
|
21
21
|
def self.caching_#{tag_type.singularize}_list?
|
@@ -27,7 +27,7 @@ module ActsAsTaggableOn::Taggable
|
|
27
27
|
|
28
28
|
def acts_as_taggable_on(*args)
|
29
29
|
super(*args)
|
30
|
-
|
30
|
+
intialize_acts_as_taggable_on_cache
|
31
31
|
end
|
32
32
|
|
33
33
|
def caching_tag_list_on?(context)
|
@@ -61,72 +61,65 @@ module ActsAsTaggableOn::Taggable
|
|
61
61
|
|
62
62
|
## Generate conditions:
|
63
63
|
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
64
|
-
|
64
|
+
|
65
65
|
start_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
66
66
|
end_at_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
67
|
-
|
67
|
+
|
68
68
|
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
69
|
-
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options.delete(:id)])
|
70
|
-
|
71
|
-
|
72
|
-
tagging_conditions = [
|
69
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
|
70
|
+
|
71
|
+
conditions = [
|
73
72
|
taggable_conditions,
|
73
|
+
options[:conditions],
|
74
74
|
scope[:conditions],
|
75
75
|
start_at_conditions,
|
76
76
|
end_at_conditions
|
77
77
|
].compact.reverse
|
78
78
|
|
79
|
-
tag_conditions = [
|
80
|
-
options[:conditions]
|
81
|
-
].compact.reverse
|
82
|
-
|
83
79
|
## Generate joins:
|
80
|
+
tagging_join = "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tag.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
81
|
+
tagging_join << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
82
|
+
|
84
83
|
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
85
84
|
taggable_join << " AND #{table_name}.#{inheritance_column} = '#{name}'" unless descends_from_active_record? # Current model is STI descendant, so add type checking to the join condition
|
86
85
|
|
87
|
-
|
86
|
+
joins = [
|
87
|
+
tagging_join,
|
88
88
|
taggable_join,
|
89
89
|
scope[:joins]
|
90
90
|
].compact
|
91
91
|
|
92
|
-
|
93
|
-
].compact
|
92
|
+
joins = joins.reverse if ActiveRecord::VERSION::MAJOR < 3
|
94
93
|
|
95
|
-
[tagging_joins, tag_joins].each(&:reverse!) if ActiveRecord::VERSION::MAJOR < 3
|
96
94
|
|
97
95
|
## Generate scope:
|
98
|
-
|
99
|
-
|
100
|
-
|
96
|
+
scope = ActsAsTaggableOn::Tag.scoped(:select => "#{ActsAsTaggableOn::Tag.table_name}.*, COUNT(*) AS count").order(options[:order]).limit(options[:limit])
|
97
|
+
|
101
98
|
# Joins and conditions
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
tag_joins.each { |join| tag_scope = tag_scope.joins(join) }
|
106
|
-
tag_conditions.each { |condition| tag_scope = tag_scope.where(condition) }
|
107
|
-
|
99
|
+
joins.each { |join| scope = scope.joins(join) }
|
100
|
+
conditions.each { |condition| scope = scope.where(condition) }
|
101
|
+
|
108
102
|
# GROUP BY and HAVING clauses:
|
109
|
-
at_least = sanitize_sql(['
|
110
|
-
at_most = sanitize_sql(['
|
111
|
-
having = [
|
112
|
-
|
113
|
-
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
103
|
+
at_least = sanitize_sql(['COUNT(*) >= ?', options.delete(:at_least)]) if options[:at_least]
|
104
|
+
at_most = sanitize_sql(['COUNT(*) <= ?', options.delete(:at_most)]) if options[:at_most]
|
105
|
+
having = [at_least, at_most].compact.join(' AND ')
|
114
106
|
|
115
107
|
if ActiveRecord::VERSION::MAJOR >= 3
|
116
108
|
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
117
109
|
scoped_select = "#{table_name}.#{primary_key}"
|
118
|
-
|
119
|
-
|
120
|
-
|
110
|
+
scope = scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{select(scoped_select).to_sql})")
|
111
|
+
|
112
|
+
# We have having() in RoR 3.0 so use it:
|
113
|
+
having = having.blank? ? "COUNT(*) > 0" : "COUNT(*) > 0 AND #{having}"
|
114
|
+
scope = scope.group(grouped_column_names_for(ActsAsTaggableOn::Tag)).having(having)
|
121
115
|
else
|
122
116
|
# Having is not available in 2.3.x:
|
123
|
-
group_by = "#{
|
117
|
+
group_by = "#{grouped_column_names_for(ActsAsTaggableOn::Tag)} HAVING COUNT(*) > 0"
|
124
118
|
group_by << " AND #{having}" unless having.blank?
|
125
|
-
|
119
|
+
scope = scope.group(group_by)
|
126
120
|
end
|
127
121
|
|
128
|
-
|
129
|
-
tag_scope
|
122
|
+
scope
|
130
123
|
end
|
131
124
|
end
|
132
125
|
|
@@ -21,7 +21,7 @@ module ActsAsTaggableOn::Taggable
|
|
21
21
|
|
22
22
|
class_eval do
|
23
23
|
has_many context_taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
|
24
|
-
|
24
|
+
:conditions => ["#{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_type]
|
25
25
|
has_many context_tags, :through => context_taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag"
|
26
26
|
end
|
27
27
|
|
@@ -80,18 +80,8 @@ module ActsAsTaggableOn::Taggable
|
|
80
80
|
conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
|
81
81
|
|
82
82
|
elsif options.delete(:any)
|
83
|
-
|
84
|
-
|
85
|
-
tagging_join = " JOIN #{ActsAsTaggableOn::Tagging.table_name}" +
|
86
|
-
" ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{table_name}.#{primary_key}" +
|
87
|
-
" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)}" +
|
88
|
-
" JOIN #{ActsAsTaggableOn::Tag.table_name}" +
|
89
|
-
" ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id"
|
90
|
-
|
91
|
-
tagging_join << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.context = ?", context.to_s]) if context
|
92
|
-
select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one?
|
93
|
-
|
94
|
-
joins << tagging_join
|
83
|
+
tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", t]) }.join(" OR ")
|
84
|
+
conditions << "#{table_name}.#{primary_key} IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})"
|
95
85
|
|
96
86
|
else
|
97
87
|
tags = ActsAsTaggableOn::Tag.named_any(tag_list)
|
@@ -101,7 +91,7 @@ module ActsAsTaggableOn::Taggable
|
|
101
91
|
safe_tag = tag.name.gsub(/[^a-zA-Z0-9]/, '')
|
102
92
|
prefix = "#{safe_tag}_#{rand(1024)}"
|
103
93
|
|
104
|
-
taggings_alias = "#{
|
94
|
+
taggings_alias = "#{table_name}_taggings_#{prefix}"
|
105
95
|
|
106
96
|
tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
107
97
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
@@ -113,20 +103,18 @@ module ActsAsTaggableOn::Taggable
|
|
113
103
|
end
|
114
104
|
end
|
115
105
|
|
116
|
-
taggings_alias, tags_alias = "#{
|
106
|
+
taggings_alias, tags_alias = "#{table_name}_taggings_group", "#{table_name}_tags_group"
|
117
107
|
|
118
108
|
if options.delete(:match_all)
|
119
109
|
joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" +
|
120
110
|
" ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key}" +
|
121
111
|
" AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)}"
|
122
112
|
|
123
|
-
|
124
|
-
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}"
|
125
|
-
group = "#{group_columns} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
|
113
|
+
group = "#{grouped_column_names_for(self)} HAVING COUNT(#{taggings_alias}.taggable_id) = #{tags.size}"
|
126
114
|
end
|
127
115
|
|
128
|
-
|
129
|
-
|
116
|
+
|
117
|
+
scoped(:joins => joins.join(" "),
|
130
118
|
:group => group,
|
131
119
|
:conditions => conditions.join(" AND "),
|
132
120
|
:order => options[:order],
|
@@ -188,17 +176,8 @@ module ActsAsTaggableOn::Taggable
|
|
188
176
|
tag_table_name = ActsAsTaggableOn::Tag.table_name
|
189
177
|
tagging_table_name = ActsAsTaggableOn::Tagging.table_name
|
190
178
|
|
191
|
-
opts
|
192
|
-
|
193
|
-
|
194
|
-
if ActsAsTaggableOn::Tag.using_postgresql?
|
195
|
-
group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag)
|
196
|
-
scope = scope.order("max(#{tagging_table_name}.created_at)").group(group_columns)
|
197
|
-
else
|
198
|
-
scope = scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}")
|
199
|
-
end
|
200
|
-
|
201
|
-
scope.all
|
179
|
+
opts = ["#{tagging_table_name}.context = ?", context.to_s]
|
180
|
+
base_tags.where(opts).order("max(#{tagging_table_name}.created_at)").group("#{tag_table_name}.id, #{tag_table_name}.name").all
|
202
181
|
end
|
203
182
|
|
204
183
|
##
|
@@ -30,13 +30,9 @@ module ActsAsTaggableOn::Taggable
|
|
30
30
|
|
31
31
|
module InstanceMethods
|
32
32
|
def owner_tags_on(owner, context)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
|
37
|
-
#{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
|
38
|
-
#{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
|
39
|
-
end
|
33
|
+
base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND
|
34
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND
|
35
|
+
#{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.to_s]).all
|
40
36
|
end
|
41
37
|
|
42
38
|
def cached_owned_tag_list_on(context)
|
@@ -42,12 +42,10 @@ module ActsAsTaggableOn::Taggable
|
|
42
42
|
|
43
43
|
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
44
44
|
|
45
|
-
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
|
46
|
-
|
47
45
|
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
|
48
46
|
:from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
|
49
47
|
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context],
|
50
|
-
:group =>
|
48
|
+
:group => grouped_column_names_for(klass),
|
51
49
|
:order => "count DESC" }.update(options))
|
52
50
|
end
|
53
51
|
|
@@ -56,12 +54,10 @@ module ActsAsTaggableOn::Taggable
|
|
56
54
|
|
57
55
|
exclude_self = "#{klass.table_name}.id != #{id} AND" if self.class == klass
|
58
56
|
|
59
|
-
group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(klass) : "#{klass.table_name}.#{klass.primary_key}"
|
60
|
-
|
61
57
|
klass.scoped({ :select => "#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.id) AS count",
|
62
58
|
:from => "#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}",
|
63
59
|
:conditions => ["#{exclude_self} #{klass.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.to_s}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?)", tags_to_find],
|
64
|
-
:group =>
|
60
|
+
:group => grouped_column_names_for(klass),
|
65
61
|
:order => "count DESC" }.update(options))
|
66
62
|
end
|
67
63
|
end
|
@@ -9,11 +9,7 @@ module ActsAsTaggableOn
|
|
9
9
|
named_scope :order, lambda { |order| { :order => order } }
|
10
10
|
named_scope :select, lambda { |select| { :select => select } }
|
11
11
|
named_scope :limit, lambda { |limit| { :limit => limit } }
|
12
|
-
named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
|
13
|
-
|
14
|
-
def self.to_sql
|
15
|
-
construct_finder_sql({})
|
16
|
-
end
|
12
|
+
named_scope :readonly, lambda { |readonly| { :readonly => readonly } }
|
17
13
|
end
|
18
14
|
end
|
19
15
|
end
|
@@ -14,10 +14,6 @@ module ActsAsTaggableOn
|
|
14
14
|
validates_uniqueness_of :name
|
15
15
|
|
16
16
|
### SCOPES:
|
17
|
-
|
18
|
-
def self.using_postgresql?
|
19
|
-
connection.adapter_name == 'PostgreSQL'
|
20
|
-
end
|
21
17
|
|
22
18
|
def self.named(name)
|
23
19
|
where(["name #{like_operator} ?", name])
|
@@ -47,10 +43,7 @@ module ActsAsTaggableOn
|
|
47
43
|
return [] if list.empty?
|
48
44
|
|
49
45
|
existing_tags = Tag.named_any(list).all
|
50
|
-
new_tag_names = list.reject
|
51
|
-
name = comparable_name(name)
|
52
|
-
existing_tags.any? { |tag| comparable_name(tag.name) == name }
|
53
|
-
end
|
46
|
+
new_tag_names = list.reject { |name| existing_tags.any? { |tag| tag.name.mb_chars.downcase == name.mb_chars.downcase } }
|
54
47
|
created_tags = new_tag_names.map { |name| Tag.create(:name => name) }
|
55
48
|
|
56
49
|
existing_tags + created_tags
|
@@ -73,12 +66,9 @@ module ActsAsTaggableOn
|
|
73
66
|
class << self
|
74
67
|
private
|
75
68
|
def like_operator
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def comparable_name(str)
|
80
|
-
RUBY_VERSION >= "1.9" ? str.downcase : str.mb_chars.downcase
|
69
|
+
connection.adapter_name == 'PostgreSQL' ? 'ILIKE' : 'LIKE'
|
81
70
|
end
|
82
71
|
end
|
72
|
+
|
83
73
|
end
|
84
74
|
end
|
@@ -4,7 +4,6 @@ describe "Taggable" do
|
|
4
4
|
before(:each) do
|
5
5
|
clean_database!
|
6
6
|
@taggable = TaggableModel.new(:name => "Bob Jones")
|
7
|
-
@taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
|
8
7
|
end
|
9
8
|
|
10
9
|
it "should have tag types" do
|
@@ -69,22 +68,6 @@ describe "Taggable" do
|
|
69
68
|
@taggable.should have(2).skills
|
70
69
|
end
|
71
70
|
|
72
|
-
it "should be able to select taggables by subset of tags using ActiveRelation methods" do
|
73
|
-
@taggables[0].tag_list = "bob"
|
74
|
-
@taggables[1].tag_list = "charlie"
|
75
|
-
@taggables[0].skill_list = "ruby"
|
76
|
-
@taggables[1].skill_list = "css"
|
77
|
-
@taggables.each{|taggable| taggable.save}
|
78
|
-
|
79
|
-
@found_taggables_by_tag = TaggableModel.joins(:tags).where(:tags => {:name => ["bob"]})
|
80
|
-
@found_taggables_by_skill = TaggableModel.joins(:skills).where(:tags => {:name => ["ruby"]})
|
81
|
-
|
82
|
-
@found_taggables_by_tag.should include @taggables[0]
|
83
|
-
@found_taggables_by_tag.should_not include @taggables[1]
|
84
|
-
@found_taggables_by_skill.should include @taggables[0]
|
85
|
-
@found_taggables_by_skill.should_not include @taggables[1]
|
86
|
-
end
|
87
|
-
|
88
71
|
it "should be able to find by tag" do
|
89
72
|
@taggable.skill_list = "ruby, rails, css"
|
90
73
|
@taggable.save
|
data/spec/spec_helper.rb
CHANGED
@@ -13,7 +13,7 @@ begin
|
|
13
13
|
Bundler.setup
|
14
14
|
rescue Bundler::GemNotFound
|
15
15
|
raise RuntimeError, "Bundler couldn't find some gems." +
|
16
|
-
"Did you run
|
16
|
+
"Did you run `bundle install`?"
|
17
17
|
end
|
18
18
|
|
19
19
|
Bundler.require
|
@@ -57,4 +57,4 @@ def clean_database!
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
clean_database!
|
60
|
+
clean_database!
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yetanothernguyen-acts-as-taggable-on
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Michael Bleigh
|