acts_as_20ggable 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 +15 -0
- data/.gitignore +1 -0
- data/CHANGELOG +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +61 -0
- data/README +76 -0
- data/Rakefile +24 -0
- data/acts_as_20ggable.gemspec +26 -0
- data/generators/acts_as_20ggable_migration/acts_as_20ggable_migration_generator.rb +11 -0
- data/generators/acts_as_20ggable_migration/templates/migration.rb +45 -0
- data/lib/acts_as_20ggable.rb +7 -0
- data/lib/acts_as_taggable.rb +201 -0
- data/lib/tag.rb +146 -0
- data/lib/tag_counts_extension.rb +3 -0
- data/lib/tag_hierarchy_builder.rb +201 -0
- data/lib/tag_list.rb +108 -0
- data/lib/tagging.rb +10 -0
- data/lib/tags_helper.rb +13 -0
- data/test/fixtures/magazine.rb +3 -0
- data/test/fixtures/magazines.yml +7 -0
- data/test/fixtures/photo.rb +8 -0
- data/test/fixtures/photos.yml +24 -0
- data/test/fixtures/post.rb +7 -0
- data/test/fixtures/posts.yml +34 -0
- data/test/fixtures/schema.rb +73 -0
- data/test/fixtures/special_post.rb +2 -0
- data/test/fixtures/subscription.rb +4 -0
- data/test/fixtures/subscriptions.yml +3 -0
- data/test/fixtures/taggings.yml +162 -0
- data/test/fixtures/tags.yml +75 -0
- data/test/fixtures/tags_hierarchy.yml +31 -0
- data/test/fixtures/tags_synonyms.yml +16 -0
- data/test/fixtures/tags_transitive_hierarchy.yml +96 -0
- data/test/fixtures/user.rb +9 -0
- data/test/fixtures/users.yml +7 -0
- data/test/fixtures/video.rb +3 -0
- data/test/fixtures/videos.yml +9 -0
- data/test/lib/acts_as_taggable_test.rb +359 -0
- data/test/lib/tag_hierarchy_builder_test.rb +109 -0
- data/test/lib/tag_list_test.rb +120 -0
- data/test/lib/tag_test.rb +45 -0
- data/test/lib/tagging_test.rb +14 -0
- data/test/lib/tags_helper_test.rb +28 -0
- data/test/lib/tags_hierarchy_test.rb +12 -0
- data/test/support/activerecord_test_connector.rb +129 -0
- data/test/support/custom_asserts.rb +35 -0
- data/test/support/database.yml +10 -0
- data/test/test_helper.rb +20 -0
- metadata +183 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZGFiNzE5NjQ1ZWI5YmJmODA3MWQ4OWIyMGVlZTNiNmI5YTY2MjQ1NA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MmE1ZjYxYjczZGE0MGNlYzI5YzExZDU4MTE1MTg4MzQzMjRjOTNhZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Y2MyOGU3ZjE0ZjcyZDY5ODlmYTZlNWNkMDNkYWZmODcyNmJhMWUzNGY4NDQ3
|
10
|
+
MDIwYTYxYzkxZTM1ZDliYmE0YzQ2NGUzODEyNzViMDBmYTMxZjgwYTZmYzEy
|
11
|
+
ODMzODNhMzc1YzNmMGJlNDgxMTUyNjU3YThiY2RiMzgzMmJkODg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OWU5Y2Y5NzA2OWM1NGJiMTU4ODQxYmZhZDlkNzAxMTRkMjAxZGZlNmVkNmVm
|
14
|
+
OWM0ZDc0NTlhMmJkYTJhNGI0MTBhNDQzNzc2MTgwMzA5YWM3ZTgxYjkzYjg5
|
15
|
+
ZjgzZGM3MDE0ODQwZDI1MzYzNTk4ODAyM2I0ZjQ2M2QyYzE1NTI=
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
test/debug.log
|
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
acts_as_20ggable (1.0.0)
|
5
|
+
activerecord (~> 3.2.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionpack (3.2.19)
|
11
|
+
activemodel (= 3.2.19)
|
12
|
+
activesupport (= 3.2.19)
|
13
|
+
builder (~> 3.0.0)
|
14
|
+
erubis (~> 2.7.0)
|
15
|
+
journey (~> 1.0.4)
|
16
|
+
rack (~> 1.4.5)
|
17
|
+
rack-cache (~> 1.2)
|
18
|
+
rack-test (~> 0.6.1)
|
19
|
+
sprockets (~> 2.2.1)
|
20
|
+
activemodel (3.2.19)
|
21
|
+
activesupport (= 3.2.19)
|
22
|
+
builder (~> 3.0.0)
|
23
|
+
activerecord (3.2.19)
|
24
|
+
activemodel (= 3.2.19)
|
25
|
+
activesupport (= 3.2.19)
|
26
|
+
arel (~> 3.0.2)
|
27
|
+
tzinfo (~> 0.3.29)
|
28
|
+
activesupport (3.2.19)
|
29
|
+
i18n (~> 0.6, >= 0.6.4)
|
30
|
+
multi_json (~> 1.0)
|
31
|
+
arel (3.0.3)
|
32
|
+
builder (3.0.4)
|
33
|
+
erubis (2.7.0)
|
34
|
+
hike (1.2.3)
|
35
|
+
i18n (0.6.11)
|
36
|
+
journey (1.0.4)
|
37
|
+
multi_json (1.10.1)
|
38
|
+
rack (1.4.5)
|
39
|
+
rack-cache (1.2)
|
40
|
+
rack (>= 0.4)
|
41
|
+
rack-test (0.6.2)
|
42
|
+
rack (>= 1.0)
|
43
|
+
rake (10.3.2)
|
44
|
+
sprockets (2.2.2)
|
45
|
+
hike (~> 1.2)
|
46
|
+
multi_json (~> 1.0)
|
47
|
+
rack (~> 1.0)
|
48
|
+
tilt (~> 1.1, != 1.3.0)
|
49
|
+
sqlite3 (1.3.9)
|
50
|
+
tilt (1.4.1)
|
51
|
+
tzinfo (0.3.41)
|
52
|
+
|
53
|
+
PLATFORMS
|
54
|
+
ruby
|
55
|
+
|
56
|
+
DEPENDENCIES
|
57
|
+
actionpack (~> 3.2.0)
|
58
|
+
activesupport (~> 3.2.0)
|
59
|
+
acts_as_20ggable!
|
60
|
+
rake
|
61
|
+
sqlite3
|
data/README
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
= acts_as_20ggable
|
2
|
+
|
3
|
+
This gem implements categories ('Tags v2.0') engine inspired by Dmitriy Smirnov's
|
4
|
+
post http://spectator.ru/technology/web-building/tags2null .
|
5
|
+
|
6
|
+
It is heavily based on acts_as_taggable_on_steroids by Jonathan Viney (thanks!):
|
7
|
+
http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids/
|
8
|
+
|
9
|
+
Also, it is under development and not production-ready yet. Look at FIXMEs, TODOs and OPTIMIZEs in code. Interface is subject to change, too.
|
10
|
+
|
11
|
+
== Instructions & usage
|
12
|
+
|
13
|
+
Almost everything concerned with original acts_as_taggable_on_steriods applies equally
|
14
|
+
to this plugin, so read original README first:
|
15
|
+
|
16
|
+
http://svn.viney.net.nz/things/rails/plugins/acts_as_taggable_on_steroids/README
|
17
|
+
|
18
|
+
=== Attention
|
19
|
+
|
20
|
+
TagHierarchyBuilder.rebuild_hierarchy relies on DB transactions. So, on
|
21
|
+
non-transactional datastores everything can break suddenly.
|
22
|
+
|
23
|
+
=== Playground configuration
|
24
|
+
|
25
|
+
Generate and apply migration:
|
26
|
+
|
27
|
+
ruby script/generate acts_as_20ggable_migration
|
28
|
+
rake db:migrate
|
29
|
+
|
30
|
+
Let's suppose we have photos and we want those photos to have tags:
|
31
|
+
|
32
|
+
class Photo < ActiveRecord::Base
|
33
|
+
acts_as_taggable
|
34
|
+
end
|
35
|
+
|
36
|
+
Also let's suppose we already have some photos with some tags in DB.
|
37
|
+
|
38
|
+
=== Tags hierarchy editing
|
39
|
+
|
40
|
+
To dump tags hierarchy for editing, use
|
41
|
+
|
42
|
+
hierarchy = TagHierarchyBuilder.dump_tags # => ['# Categories',
|
43
|
+
'# Synonyms',
|
44
|
+
'# Unlinked tags',
|
45
|
+
'Nature',
|
46
|
+
'Horse',
|
47
|
+
'Cat',
|
48
|
+
'Kitty',
|
49
|
+
'Animals']
|
50
|
+
|
51
|
+
Let user edit it as plain text, then to update hierarchy use
|
52
|
+
|
53
|
+
# hierarchy => ['# Categories',
|
54
|
+
'Nature / Animals',
|
55
|
+
'Animals / Horse',
|
56
|
+
'Nature / Animals / Cat',
|
57
|
+
'# Synonyms',
|
58
|
+
'Cat = Kitty']
|
59
|
+
|
60
|
+
TagHierarchyBuilder.rebuild_hierarchy(hierarchy)
|
61
|
+
|
62
|
+
Comments ("# …") in hierarchy specification are purely optional and inserted only
|
63
|
+
for user convenience.
|
64
|
+
|
65
|
+
TagHierarchyBuilder can throw TagHierarchyBuilder::WrongSpecificationSyntax or Tag::HierarchyCycle. Errors descriptions still not implemented, sorry.
|
66
|
+
|
67
|
+
=== Finding tagged objects
|
68
|
+
|
69
|
+
find_tagged_with by default returns models with all subtags:
|
70
|
+
|
71
|
+
Photo.find_tagged_with('Animals') # => Everything tagged with Animals, Horse, Cat, Kitty
|
72
|
+
Photo.find_tagged_with('Animals', :exclude_subtags => true) # => Only tagged with Animals
|
73
|
+
|
74
|
+
== Other
|
75
|
+
|
76
|
+
Problems, comments, and suggestions all welcome. avanie@gmail.com
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the acts_as_taggable_on_steroids plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.libs << 'test/support'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Generate documentation for the acts_as_taggable_on_steroids plugin.'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'Acts As Taggable On Steroids'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "acts_as_20ggable"
|
5
|
+
s.version = "1.0.0"
|
6
|
+
|
7
|
+
s.authors = ["Dmitriy Timokhin", "Andrey Subbota"]
|
8
|
+
s.email = ["subbota@gmail.com"]
|
9
|
+
s.homepage = "https://github.com/numbata/acts_as_20ggable"
|
10
|
+
s.summary = "Implements categories ('Tags v2.0') engine inspired by Dmitriy Smirnov's"
|
11
|
+
s.description = "This gem is rails3.2 compatible version of Dmitriy Timokhin's plugin acts_as_20ggable: https://github.com/pager/acts_as_20ggable"
|
12
|
+
s.license = "MIT"
|
13
|
+
s.extra_rdoc_files = [
|
14
|
+
"CHANGELOG",
|
15
|
+
"README"
|
16
|
+
]
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "activerecord", '~> 3.2', ">= 3.2.0"
|
22
|
+
s.add_development_dependency "rake", "~> 0"
|
23
|
+
s.add_development_dependency "actionpack", '~> 3.2', ">= 3.2.0"
|
24
|
+
s.add_development_dependency "activesupport", "~> 3.2", ">= 3.2.0"
|
25
|
+
s.add_development_dependency "sqlite3", "~> 0"
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class ActsAs20ggableMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :tags do |t|
|
4
|
+
t.column :name, :string
|
5
|
+
end
|
6
|
+
|
7
|
+
create_table :taggings do |t|
|
8
|
+
t.column :tag_id, :integer
|
9
|
+
t.column :taggable_id, :integer
|
10
|
+
|
11
|
+
# You should make sure that the column created is
|
12
|
+
# long enough to store the required class names.
|
13
|
+
t.column :taggable_type, :string
|
14
|
+
|
15
|
+
t.column :created_at, :datetime
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :tags_hierarchy, :id => false do |t|
|
19
|
+
t.column :tag_id, :integer
|
20
|
+
t.column :child_id, :integer
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :tags_transitive_hierarchy, :id => false do |t|
|
24
|
+
t.column :tag_id, :integer
|
25
|
+
t.column :child_id, :integer
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :tags_synonyms, :id => false do |t|
|
29
|
+
t.column :tag_id, :integer
|
30
|
+
t.column :synonym_id, :integer
|
31
|
+
end
|
32
|
+
|
33
|
+
add_index :taggings, :tag_id
|
34
|
+
add_index :taggings, [:taggable_id, :taggable_type]
|
35
|
+
add_index :tags, :name
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.down
|
39
|
+
drop_table :taggings
|
40
|
+
drop_table :tags
|
41
|
+
drop_table :tags_hierarchy
|
42
|
+
drop_table :tags_transitive_hierarchy
|
43
|
+
drop_table :tags_synonyms
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
module ActiveRecord
|
3
|
+
module Acts
|
4
|
+
module Taggable
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def acts_as_taggable
|
10
|
+
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
|
11
|
+
has_many :tags, :through => :taggings
|
12
|
+
|
13
|
+
before_save :save_cached_tag_list
|
14
|
+
after_save :save_tags
|
15
|
+
|
16
|
+
extend ActiveRecord::Acts::Taggable::SingletonMethods
|
17
|
+
|
18
|
+
alias_method_chain :reload, :tag_list
|
19
|
+
end
|
20
|
+
|
21
|
+
def cached_tag_list_column_name
|
22
|
+
:cached_tag_list
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_cached_tag_list_column_name(value = nil, &block)
|
26
|
+
define_attr_method :cached_tag_list_column_name, value, &block
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module SingletonMethods
|
31
|
+
# Returns an array of related tags.
|
32
|
+
# Related tags are all the other tags that are found on the models tagged with the provided tags.
|
33
|
+
#
|
34
|
+
# Pass either a tag, string, or an array of strings or tags.
|
35
|
+
#
|
36
|
+
# Options:
|
37
|
+
# :order - SQL Order how to order the tags. Defaults to "count DESC, tags.name".
|
38
|
+
def find_related_tags(tags, options = {})
|
39
|
+
tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)
|
40
|
+
|
41
|
+
related_models = find_tagged_with(tags)
|
42
|
+
|
43
|
+
return [] unless related_models.exists?
|
44
|
+
|
45
|
+
related_ids = related_models.select("distinct #{table_name}.id")
|
46
|
+
|
47
|
+
Tag.select("#{Tag.table_name}.*, COUNT(#{Tag.table_name}.id) AS count").
|
48
|
+
joins("JOIN #{Tagging.table_name} ON #{Tagging.table_name}.taggable_type = '#{base_class.name}'
|
49
|
+
AND #{Tagging.table_name}.taggable_id IN (#{related_ids.to_sql})
|
50
|
+
AND #{Tagging.table_name}.tag_id = #{Tag.table_name}.id").
|
51
|
+
order(options[:order] || "count DESC, #{Tag.table_name}.name").
|
52
|
+
group("#{Tag.table_name}.id, #{Tag.table_name}.name HAVING LOWER(#{Tag.table_name}.name) NOT IN (#{tags.map { |n| quote_value(n.downcase) }.join(",")})")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Pass either a tag, string, or an array of strings or tags.
|
56
|
+
#
|
57
|
+
# Options:
|
58
|
+
# :exclude - Find models that are not tagged with the given tags
|
59
|
+
# :match_all - Find models that match all of the given tags, not just one
|
60
|
+
# :conditions - A piece of SQL conditions to add to the query
|
61
|
+
# :exclude_subtags - Find models that are tagged with only given tags, not their subtags
|
62
|
+
def find_tagged_with(tags, options = {})
|
63
|
+
tags = tags.is_a?(Array) ? TagList.new(tags.map(&:to_s)) : TagList.from(tags)
|
64
|
+
|
65
|
+
options = options.dup
|
66
|
+
exclude_subtags = options.delete(:exclude_subtags)
|
67
|
+
|
68
|
+
return where("1=0") if tags.empty?
|
69
|
+
|
70
|
+
taggings_alias = "#{table_name}_taggings"
|
71
|
+
tags_alias = "#{table_name}_tags"
|
72
|
+
|
73
|
+
results = base_class.joins("INNER JOIN #{Tagging.table_name} #{taggings_alias} ON #{taggings_alias}.taggable_id = #{table_name}.#{primary_key} AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name)} " +
|
74
|
+
"INNER JOIN #{Tag.table_name} #{tags_alias} ON #{tags_alias}.id = #{taggings_alias}.tag_id")
|
75
|
+
|
76
|
+
if options.delete(:exclude)
|
77
|
+
tc = tags_condition(tags, Tag.table_name, !exclude_subtags)
|
78
|
+
results = results.where("#{table_name}.id NOT IN
|
79
|
+
(SELECT #{Tagging.table_name}.taggable_id FROM #{Tagging.table_name}
|
80
|
+
INNER JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id
|
81
|
+
WHERE #{tc} AND #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)})")
|
82
|
+
else
|
83
|
+
if options.delete(:match_all)
|
84
|
+
tc = tags_condition(tags, Tag.table_name, !exclude_subtags)
|
85
|
+
results = results.where("
|
86
|
+
(SELECT COUNT(*) FROM #{Tagging.table_name}
|
87
|
+
INNER JOIN #{Tag.table_name} ON #{Tagging.table_name}.tag_id = #{Tag.table_name}.id
|
88
|
+
WHERE #{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)} AND
|
89
|
+
taggable_id = #{table_name}.id AND
|
90
|
+
#{tc}) = #{tags.size}")
|
91
|
+
else
|
92
|
+
results = results.where(tags_condition(tags, tags_alias, !exclude_subtags))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
results
|
97
|
+
end
|
98
|
+
|
99
|
+
# Calculate the tag counts for all tags.
|
100
|
+
#
|
101
|
+
# See Tag.counts for available options.
|
102
|
+
def tag_counts(options = {})
|
103
|
+
Tag.find(:all, find_options_for_tag_counts(options))
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_options_for_tag_counts(options = {})
|
107
|
+
options = options.dup
|
108
|
+
scope = scoped
|
109
|
+
|
110
|
+
conditions = []
|
111
|
+
conditions << send(:sanitize_conditions, options.delete(:conditions)) if options[:conditions]
|
112
|
+
conditions << scope.where_values.reduce(:and).to_sql if scope.where_values.any?
|
113
|
+
conditions << "#{Tagging.table_name}.taggable_type = #{quote_value(base_class.name)}"
|
114
|
+
conditions << type_condition.to_sql unless descends_from_active_record?
|
115
|
+
conditions.compact!
|
116
|
+
conditions = conditions.join(" AND ")
|
117
|
+
|
118
|
+
joins = ["INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"]
|
119
|
+
joins << options.delete(:joins) if options[:joins]
|
120
|
+
joins << scope.joins_values.map(&:to_sql).join if scope.joins_values.any?
|
121
|
+
joins = joins.join(" ")
|
122
|
+
|
123
|
+
options = { :conditions => conditions, :joins => joins }.update(options)
|
124
|
+
|
125
|
+
Tag.options_for_counts(options)
|
126
|
+
end
|
127
|
+
|
128
|
+
def caching_tag_list?
|
129
|
+
column_names.include?(cached_tag_list_column_name.to_s)
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
def tags_condition(tags, table_name = Tag.table_name, include_subtags = true)
|
134
|
+
# FIXME N+1
|
135
|
+
tags += tags.map do |tag_name|
|
136
|
+
tag = Tag.find_with_like_by_name(tag_name)
|
137
|
+
tag ? tag.transitive_children.find(:all).map(&:name) : []
|
138
|
+
end.flatten if include_subtags
|
139
|
+
condition = tags.map { |t| sanitize_sql(["#{table_name}.name LIKE ?", t]) }.join(" OR ")
|
140
|
+
condition.blank? ? '(1=0)' : "(" + condition + ")"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
included do
|
145
|
+
def tag_list
|
146
|
+
return @tag_list if @tag_list
|
147
|
+
|
148
|
+
if self.class.caching_tag_list? and !(cached_value = send(self.class.cached_tag_list_column_name)).nil?
|
149
|
+
@tag_list = TagList.from(cached_value)
|
150
|
+
else
|
151
|
+
@tag_list = TagList.new(*tags.map(&:name))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def tag_list=(value)
|
156
|
+
@tag_list = TagList.from(value)
|
157
|
+
end
|
158
|
+
|
159
|
+
def save_cached_tag_list
|
160
|
+
if self.class.caching_tag_list?
|
161
|
+
self[self.class.cached_tag_list_column_name] = tag_list.to_s
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def save_tags
|
166
|
+
return unless @tag_list
|
167
|
+
|
168
|
+
new_tag_names = @tag_list - tags.map(&:name)
|
169
|
+
old_tags = tags.reject { |tag| @tag_list.include?(tag.name) }
|
170
|
+
|
171
|
+
self.class.transaction do
|
172
|
+
if old_tags.any?
|
173
|
+
taggings.find(:all, :conditions => ["tag_id IN (?)", old_tags.map(&:id)]).each(&:destroy)
|
174
|
+
taggings.reset
|
175
|
+
end
|
176
|
+
|
177
|
+
new_tag_names.each do |new_tag_name|
|
178
|
+
tags << Tag.find_or_create_with_like_by_name(new_tag_name)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
true
|
183
|
+
end
|
184
|
+
|
185
|
+
# Calculate the tag counts for the tags used by this model.
|
186
|
+
#
|
187
|
+
# The possible options are the same as the tag_counts class method, excluding :conditions.
|
188
|
+
def tag_counts(options = {})
|
189
|
+
self.class.tag_counts({ :conditions => self.class.send(:tags_condition, tag_list, Tag.table_name, false) }.reverse_merge!(options))
|
190
|
+
end
|
191
|
+
|
192
|
+
def reload_with_tag_list(*args) #:nodoc:
|
193
|
+
@tag_list = nil
|
194
|
+
reload_without_tag_list(*args)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
ActiveRecord::Base.send(:include, ActiveRecord::Acts::Taggable)
|