yetanothernguyen-acts-as-taggable-on 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|