acts-as-taggable-on 2.0.0 → 3.2.2
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 +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +23 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +112 -0
- data/CONTRIBUTING.md +44 -0
- data/Gemfile +9 -5
- data/Guardfile +5 -0
- data/{MIT-LICENSE → LICENSE.md} +1 -1
- data/README.md +385 -0
- data/Rakefile +13 -53
- data/UPGRADING.md +13 -0
- data/acts-as-taggable-on.gemspec +38 -0
- data/{lib/generators/acts_as_taggable_on/migration/templates/active_record/migration.rb → db/migrate/1_acts_as_taggable_on_migration.rb} +5 -3
- data/db/migrate/2_add_missing_unique_indices.rb +19 -0
- data/db/migrate/3_add_taggings_counter_cache_to_tags.rb +14 -0
- data/gemfiles/activerecord_3.2.gemfile +15 -0
- data/gemfiles/activerecord_4.0.gemfile +15 -0
- data/gemfiles/activerecord_4.1.gemfile +15 -0
- data/gemfiles/activerecord_edge.gemfile +16 -0
- data/lib/acts-as-taggable-on.rb +65 -22
- data/lib/acts_as_taggable_on/acts_as_taggable_on/cache.rb +53 -24
- data/lib/acts_as_taggable_on/acts_as_taggable_on/collection.rb +120 -42
- data/lib/acts_as_taggable_on/acts_as_taggable_on/compatibility.rb +35 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/core.rb +309 -124
- data/lib/acts_as_taggable_on/acts_as_taggable_on/dirty.rb +36 -0
- data/lib/acts_as_taggable_on/acts_as_taggable_on/ownership.rb +96 -72
- data/lib/acts_as_taggable_on/acts_as_taggable_on/related.rb +43 -36
- data/lib/acts_as_taggable_on/engine.rb +5 -0
- data/lib/acts_as_taggable_on/tag.rb +133 -63
- data/lib/acts_as_taggable_on/tag_list.rb +162 -81
- data/lib/acts_as_taggable_on/taggable.rb +101 -0
- data/lib/acts_as_taggable_on/{acts_as_tagger.rb → tagger.rb} +34 -13
- data/lib/acts_as_taggable_on/tagging.rb +28 -18
- data/lib/acts_as_taggable_on/tags_helper.rb +12 -14
- data/lib/acts_as_taggable_on/utils.rb +62 -0
- data/lib/acts_as_taggable_on/version.rb +4 -0
- data/lib/acts_as_taggable_on.rb +6 -0
- data/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb +189 -195
- data/spec/acts_as_taggable_on/acts_as_tagger_spec.rb +78 -81
- data/spec/acts_as_taggable_on/caching_spec.rb +76 -0
- data/spec/acts_as_taggable_on/related_spec.rb +89 -0
- data/spec/acts_as_taggable_on/single_table_inheritance_spec.rb +210 -0
- data/spec/acts_as_taggable_on/tag_list_spec.rb +149 -61
- data/spec/acts_as_taggable_on/tag_spec.rb +245 -65
- data/spec/acts_as_taggable_on/taggable_spec.rb +776 -187
- data/spec/acts_as_taggable_on/tagger_spec.rb +119 -70
- data/spec/acts_as_taggable_on/tagging_spec.rb +17 -21
- data/spec/acts_as_taggable_on/tags_helper_spec.rb +38 -22
- data/spec/acts_as_taggable_on/utils_spec.rb +15 -0
- data/spec/internal/app/models/altered_inheriting_taggable_model.rb +3 -0
- data/spec/internal/app/models/cached_model.rb +3 -0
- data/spec/internal/app/models/cached_model_with_array.rb +5 -0
- data/spec/internal/app/models/company.rb +15 -0
- data/spec/internal/app/models/inheriting_taggable_model.rb +2 -0
- data/spec/internal/app/models/market.rb +2 -0
- data/spec/internal/app/models/models.rb +90 -0
- data/spec/internal/app/models/non_standard_id_taggable_model.rb +8 -0
- data/spec/internal/app/models/ordered_taggable_model.rb +4 -0
- data/spec/internal/app/models/other_cached_model.rb +3 -0
- data/spec/internal/app/models/other_taggable_model.rb +4 -0
- data/spec/internal/app/models/student.rb +2 -0
- data/spec/internal/app/models/taggable_model.rb +13 -0
- data/spec/internal/app/models/untaggable_model.rb +3 -0
- data/spec/internal/app/models/user.rb +3 -0
- data/spec/internal/config/database.yml.sample +19 -0
- data/spec/internal/db/schema.rb +97 -0
- data/spec/schema.rb +57 -18
- data/spec/spec_helper.rb +12 -47
- data/spec/support/array.rb +9 -0
- data/spec/support/database.rb +42 -0
- data/spec/support/database_cleaner.rb +21 -0
- metadata +286 -63
- data/CHANGELOG +0 -25
- data/README.rdoc +0 -221
- data/VERSION +0 -1
- data/generators/acts_as_taggable_on_migration/acts_as_taggable_on_migration_generator.rb +0 -7
- data/generators/acts_as_taggable_on_migration/templates/migration.rb +0 -29
- data/lib/acts_as_taggable_on/acts_as_taggable_on.rb +0 -53
- data/lib/acts_as_taggable_on/compatibility/Gemfile +0 -6
- data/lib/acts_as_taggable_on/compatibility/active_record_backports.rb +0 -17
- data/lib/generators/acts_as_taggable_on/migration/migration_generator.rb +0 -31
- data/rails/init.rb +0 -1
- data/spec/bm.rb +0 -52
- data/spec/models.rb +0 -30
- /data/{spec/spec.opts → .rspec} +0 -0
data/Rakefile
CHANGED
|
@@ -1,59 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require 'spec/rake/spectask'
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'bundler/setup'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Spec::Rake::SpecTask.new do |t|
|
|
8
|
-
t.spec_files = FileList["spec/**/*_spec.rb"]
|
|
9
|
-
end
|
|
4
|
+
desc 'Default: run specs'
|
|
5
|
+
task default: :spec
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
t.rcov_opts = ['--exclude', 'spec']
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
rescue LoadError
|
|
18
|
-
# Rspec 2.0
|
|
19
|
-
require 'rspec/core/rake_task'
|
|
20
|
-
|
|
21
|
-
desc 'Default: run specs'
|
|
22
|
-
task :default => :spec
|
|
23
|
-
Rspec::Core::RakeTask.new do |t|
|
|
24
|
-
t.pattern = "spec/**/*_spec.rb"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
Rspec::Core::RakeTask.new('rcov') do |t|
|
|
28
|
-
t.pattern = "spec/**/*_spec.rb"
|
|
29
|
-
t.rcov = true
|
|
30
|
-
t.rcov_opts = ['--exclude', 'spec']
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
rescue LoadError
|
|
34
|
-
puts "Rspec not available. Install it with: gem install rspec"
|
|
7
|
+
desc 'Copy sample spec database.yml over if not exists'
|
|
8
|
+
task :copy_db_config do
|
|
9
|
+
cp 'spec/internal/config/database.yml.sample', 'spec/internal/config/database.yml'
|
|
35
10
|
end
|
|
36
11
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
12
|
+
task spec: [:copy_db_config]
|
|
13
|
+
|
|
14
|
+
require 'rspec/core/rake_task'
|
|
15
|
+
RSpec::Core::RakeTask.new do |t|
|
|
16
|
+
t.pattern = 'spec/**/*_spec.rb'
|
|
43
17
|
end
|
|
44
18
|
|
|
45
|
-
|
|
46
|
-
require 'jeweler'
|
|
47
|
-
Jeweler::Tasks.new do |gemspec|
|
|
48
|
-
gemspec.name = "acts-as-taggable-on"
|
|
49
|
-
gemspec.summary = "ActsAsTaggableOn is a tagging plugin for Rails that provides multiple tagging contexts on a single model."
|
|
50
|
-
gemspec.description = "With ActsAsTaggableOn, you could tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality."
|
|
51
|
-
gemspec.email = "michael@intridea.com"
|
|
52
|
-
gemspec.homepage = "http://github.com/mbleigh/acts-as-taggable-on"
|
|
53
|
-
gemspec.authors = ["Michael Bleigh"]
|
|
54
|
-
gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
|
|
55
|
-
end
|
|
56
|
-
Jeweler::GemcutterTasks.new
|
|
57
|
-
rescue LoadError
|
|
58
|
-
puts "Jeweler not available. Install it with: gem install jeweler"
|
|
59
|
-
end
|
|
19
|
+
Bundler::GemHelper.install_tasks
|
data/UPGRADING.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
When upgrading
|
|
2
|
+
|
|
3
|
+
Re-run the migrations generator
|
|
4
|
+
|
|
5
|
+
rake acts_as_taggable_on_engine:install:migrations
|
|
6
|
+
|
|
7
|
+
It will create any new migrations and skip existing ones
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
##Breaking changes:
|
|
11
|
+
|
|
12
|
+
- ActsAsTaggableOn::Tag is not extend with ActsAsTaggableOn::Utils anymore.
|
|
13
|
+
Please use ActsAsTaggableOn::Utils instead
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'acts_as_taggable_on/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = 'acts-as-taggable-on'
|
|
8
|
+
gem.version = ActsAsTaggableOn::VERSION
|
|
9
|
+
gem.authors = ['Michael Bleigh', 'Joost Baaij']
|
|
10
|
+
gem.email = %w(michael@intridea.com joost@spacebabies.nl)
|
|
11
|
+
gem.description = %q{With ActsAsTaggableOn, you can tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality.}
|
|
12
|
+
gem.summary = 'Advanced tagging for Rails.'
|
|
13
|
+
gem.homepage = 'https://github.com/mbleigh/acts-as-taggable-on'
|
|
14
|
+
gem.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
gem.files = `git ls-files`.split($/)
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
|
18
|
+
gem.require_paths = ['lib']
|
|
19
|
+
gem.required_ruby_version = '>= 1.9.3'
|
|
20
|
+
|
|
21
|
+
if File.exist?('UPGRADING.md')
|
|
22
|
+
gem.post_install_message = File.read('UPGRADING.md')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
gem.add_runtime_dependency 'activerecord', ['>= 3', '< 5']
|
|
26
|
+
gem.add_runtime_dependency 'actionpack', ['>= 3', '< 5']
|
|
27
|
+
|
|
28
|
+
gem.add_development_dependency 'sqlite3'
|
|
29
|
+
gem.add_development_dependency 'mysql2'
|
|
30
|
+
gem.add_development_dependency 'pg'
|
|
31
|
+
|
|
32
|
+
gem.add_development_dependency 'rspec-rails' , '~> 3.0.0.beta2'
|
|
33
|
+
gem.add_development_dependency 'rspec-its', '~> 1.0'
|
|
34
|
+
gem.add_development_dependency 'rspec', '3.0.0.beta2'
|
|
35
|
+
gem.add_development_dependency 'ammeter', '~> 1.0'
|
|
36
|
+
gem.add_development_dependency 'barrier', '~> 1.0'
|
|
37
|
+
gem.add_development_dependency 'database_cleaner', '~> 1.2'
|
|
38
|
+
end
|
|
@@ -9,10 +9,12 @@ class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
|
|
9
9
|
|
|
10
10
|
# You should make sure that the column created is
|
|
11
11
|
# long enough to store the required class names.
|
|
12
|
-
t.references :taggable, :
|
|
13
|
-
t.references :tagger, :
|
|
12
|
+
t.references :taggable, polymorphic: true
|
|
13
|
+
t.references :tagger, polymorphic: true
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# Limit is created to prevent MySQL error on index
|
|
16
|
+
# length for MyISAM table type: http://bit.ly/vgW2Ql
|
|
17
|
+
t.string :context, limit: 128
|
|
16
18
|
|
|
17
19
|
t.datetime :created_at
|
|
18
20
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class AddMissingUniqueIndices < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
add_index :tags, :name, unique: true
|
|
4
|
+
|
|
5
|
+
remove_index :taggings, :tag_id
|
|
6
|
+
remove_index :taggings, [:taggable_id, :taggable_type, :context]
|
|
7
|
+
add_index :taggings,
|
|
8
|
+
[:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type],
|
|
9
|
+
unique: true, name: 'taggings_idx'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.down
|
|
13
|
+
remove_index :tags, :name
|
|
14
|
+
|
|
15
|
+
remove_index :taggings, name: 'taggings_idx'
|
|
16
|
+
add_index :taggings, :tag_id
|
|
17
|
+
add_index :taggings, [:taggable_id, :taggable_type, :context]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class AddTaggingsCounterCacheToTags < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
add_column :tags, :taggings_count, :integer, default: 0
|
|
4
|
+
|
|
5
|
+
ActsAsTaggableOn::Tag.reset_column_information
|
|
6
|
+
ActsAsTaggableOn::Tag.find_each do |tag|
|
|
7
|
+
ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.down
|
|
12
|
+
remove_column :tags, :taggings_count
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "activerecord", "~> 3.2"
|
|
6
|
+
gem "actionpack", "~> 3.2"
|
|
7
|
+
|
|
8
|
+
group :local_development do
|
|
9
|
+
gem "guard"
|
|
10
|
+
gem "guard-rspec"
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "rake"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
gemspec :path => "../"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "activerecord", "~> 4.0"
|
|
6
|
+
gem "actionpack", "~> 4.0"
|
|
7
|
+
|
|
8
|
+
group :local_development do
|
|
9
|
+
gem "guard"
|
|
10
|
+
gem "guard-rspec"
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "rake"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
gemspec :path => "../"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "activerecord", "~> 4.1"
|
|
6
|
+
gem "actionpack", "~> 4.1"
|
|
7
|
+
|
|
8
|
+
group :local_development do
|
|
9
|
+
gem "guard"
|
|
10
|
+
gem "guard-rspec"
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "rake"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
gemspec :path => "../"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This file was generated by Appraisal
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
gem "activerecord", :github => "rails/rails"
|
|
6
|
+
gem "actionpack", :github => "rails/rails"
|
|
7
|
+
gem "arel", :github => "rails/arel"
|
|
8
|
+
|
|
9
|
+
group :local_development do
|
|
10
|
+
gem "guard"
|
|
11
|
+
gem "guard-rspec"
|
|
12
|
+
gem "appraisal"
|
|
13
|
+
gem "rake"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
gemspec :path => "../"
|
data/lib/acts-as-taggable-on.rb
CHANGED
|
@@ -1,30 +1,73 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require 'active_record/version'
|
|
3
|
+
require 'active_support/core_ext/module'
|
|
4
|
+
require 'action_view'
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
require 'digest/sha1'
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
module ActsAsTaggableOn
|
|
9
|
+
class DuplicateTagError < StandardError
|
|
10
|
+
end
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
require "acts_as_taggable_on/acts_as_taggable_on/ownership"
|
|
13
|
-
require "acts_as_taggable_on/acts_as_taggable_on/related"
|
|
12
|
+
def self.setup
|
|
13
|
+
@configuration ||= Configuration.new
|
|
14
|
+
yield @configuration if block_given?
|
|
15
|
+
end
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
require "acts_as_taggable_on/tagging"
|
|
17
|
+
def self.method_missing(method_name, *args, &block)
|
|
18
|
+
@configuration.respond_to?(method_name) ?
|
|
19
|
+
@configuration.send(method_name, *args, &block) : super
|
|
20
|
+
end
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
def self.respond_to?(method_name, include_private=false)
|
|
23
|
+
@configuration.respond_to? method_name
|
|
24
|
+
end
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
def self.glue
|
|
27
|
+
setting = @configuration.delimiter
|
|
28
|
+
delimiter = setting.kind_of?(Array) ? setting[0] : setting
|
|
29
|
+
delimiter.ends_with?(' ') ? delimiter : "#{delimiter} "
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Configuration
|
|
33
|
+
attr_accessor :delimiter, :force_lowercase, :force_parameterize,
|
|
34
|
+
:strict_case_match, :remove_unused_tags
|
|
35
|
+
|
|
36
|
+
def initialize
|
|
37
|
+
@delimiter = ','
|
|
38
|
+
@force_lowercase = false
|
|
39
|
+
@force_parameterize = false
|
|
40
|
+
@strict_case_match = false
|
|
41
|
+
@remove_unused_tags = false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
setup
|
|
26
46
|
end
|
|
27
47
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
require 'acts_as_taggable_on/utils'
|
|
49
|
+
|
|
50
|
+
require 'acts_as_taggable_on/taggable'
|
|
51
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/compatibility'
|
|
52
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/core'
|
|
53
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/collection'
|
|
54
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/cache'
|
|
55
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/ownership'
|
|
56
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/related'
|
|
57
|
+
require 'acts_as_taggable_on/acts_as_taggable_on/dirty'
|
|
58
|
+
|
|
59
|
+
require 'acts_as_taggable_on/tagger'
|
|
60
|
+
require 'acts_as_taggable_on/tag'
|
|
61
|
+
require 'acts_as_taggable_on/tag_list'
|
|
62
|
+
require 'acts_as_taggable_on/tags_helper'
|
|
63
|
+
require 'acts_as_taggable_on/tagging'
|
|
64
|
+
require 'acts_as_taggable_on/engine'
|
|
65
|
+
|
|
66
|
+
ActiveSupport.on_load(:active_record) do
|
|
67
|
+
extend ActsAsTaggableOn::Compatibility
|
|
68
|
+
extend ActsAsTaggableOn::Taggable
|
|
69
|
+
include ActsAsTaggableOn::Tagger
|
|
70
|
+
end
|
|
71
|
+
ActiveSupport.on_load(:action_view) do
|
|
72
|
+
include ActsAsTaggableOn::TagsHelper
|
|
73
|
+
end
|
|
@@ -1,53 +1,82 @@
|
|
|
1
1
|
module ActsAsTaggableOn::Taggable
|
|
2
2
|
module Cache
|
|
3
3
|
def self.included(base)
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
# When included, conditionally adds tag caching methods when the model
|
|
5
|
+
# has any "cached_#{tag_type}_list" column
|
|
6
|
+
base.instance_eval do
|
|
7
|
+
# @private
|
|
8
|
+
def _has_tags_cache_columns?(db_columns)
|
|
9
|
+
db_column_names = db_columns.map(&:name)
|
|
10
|
+
tag_types.any? do |context|
|
|
11
|
+
db_column_names.include?("cached_#{context.to_s.singularize}_list")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @private
|
|
16
|
+
def _add_tags_caching_methods
|
|
17
|
+
send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods
|
|
18
|
+
extend ActsAsTaggableOn::Taggable::Cache::ClassMethods
|
|
19
|
+
|
|
20
|
+
before_save :save_cached_tag_list
|
|
21
|
+
|
|
22
|
+
initialize_tags_cache
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# ActiveRecord::Base.columns makes a database connection and caches the
|
|
26
|
+
# calculated columns hash for the record as @columns. Since we don't
|
|
27
|
+
# want to add caching methods until we confirm the presence of a
|
|
28
|
+
# caching column, and we don't want to force opening a database
|
|
29
|
+
# connection when the class is loaded, here we intercept and cache
|
|
30
|
+
# the call to :columns as @acts_as_taggable_on_cache_columns
|
|
31
|
+
# to mimic the underlying behavior. While processing this first
|
|
32
|
+
# call to columns, we do the caching column check and dynamically add
|
|
33
|
+
# the class and instance methods
|
|
34
|
+
# FIXME: this method cannot compile in rubinius
|
|
35
|
+
def columns
|
|
36
|
+
@acts_as_taggable_on_cache_columns ||= begin
|
|
37
|
+
db_columns = super
|
|
38
|
+
_add_tags_caching_methods if _has_tags_cache_columns?(db_columns)
|
|
39
|
+
db_columns
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
12
43
|
end
|
|
13
|
-
|
|
14
|
-
base.intialize_acts_as_taggable_on_cache
|
|
15
44
|
end
|
|
16
|
-
|
|
45
|
+
|
|
17
46
|
module ClassMethods
|
|
18
|
-
def
|
|
47
|
+
def initialize_tags_cache
|
|
19
48
|
tag_types.map(&:to_s).each do |tag_type|
|
|
20
|
-
class_eval
|
|
49
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
21
50
|
def self.caching_#{tag_type.singularize}_list?
|
|
22
51
|
caching_tag_list_on?("#{tag_type}")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
end
|
|
52
|
+
end
|
|
53
|
+
RUBY
|
|
54
|
+
end
|
|
26
55
|
end
|
|
27
|
-
|
|
56
|
+
|
|
28
57
|
def acts_as_taggable_on(*args)
|
|
29
58
|
super(*args)
|
|
30
|
-
|
|
59
|
+
initialize_tags_cache
|
|
31
60
|
end
|
|
32
|
-
|
|
61
|
+
|
|
33
62
|
def caching_tag_list_on?(context)
|
|
34
63
|
column_names.include?("cached_#{context.to_s.singularize}_list")
|
|
35
64
|
end
|
|
36
65
|
end
|
|
37
|
-
|
|
38
|
-
module InstanceMethods
|
|
66
|
+
|
|
67
|
+
module InstanceMethods
|
|
39
68
|
def save_cached_tag_list
|
|
40
69
|
tag_types.map(&:to_s).each do |tag_type|
|
|
41
70
|
if self.class.send("caching_#{tag_type.singularize}_list?")
|
|
42
71
|
if tag_list_cache_set_on(tag_type)
|
|
43
|
-
list = tag_list_cache_on(tag_type
|
|
72
|
+
list = tag_list_cache_on(tag_type).to_a.flatten.compact.join(', ')
|
|
44
73
|
self["cached_#{tag_type.singularize}_list"] = list
|
|
45
74
|
end
|
|
46
75
|
end
|
|
47
76
|
end
|
|
48
|
-
|
|
77
|
+
|
|
49
78
|
true
|
|
50
79
|
end
|
|
51
80
|
end
|
|
52
81
|
end
|
|
53
|
-
end
|
|
82
|
+
end
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
module ActsAsTaggableOn::Taggable
|
|
2
2
|
module Collection
|
|
3
3
|
def self.included(base)
|
|
4
|
-
base.send :include, ActsAsTaggableOn::Taggable::Collection::InstanceMethods
|
|
5
4
|
base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods
|
|
6
5
|
base.initialize_acts_as_taggable_on_collection
|
|
7
6
|
end
|
|
8
|
-
|
|
7
|
+
|
|
9
8
|
module ClassMethods
|
|
10
9
|
def initialize_acts_as_taggable_on_collection
|
|
11
10
|
tag_types.map(&:to_s).each do |tag_type|
|
|
12
|
-
class_eval
|
|
11
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
13
12
|
def self.#{tag_type.singularize}_counts(options={})
|
|
14
13
|
tag_counts_on('#{tag_type}', options)
|
|
15
14
|
end
|
|
@@ -19,25 +18,62 @@ module ActsAsTaggableOn::Taggable
|
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def top_#{tag_type}(limit = 10)
|
|
22
|
-
tag_counts_on('#{tag_type}', :
|
|
21
|
+
tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def self.top_#{tag_type}(limit = 10)
|
|
26
|
-
tag_counts_on('#{tag_type}', :
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
end
|
|
25
|
+
tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i)
|
|
26
|
+
end
|
|
27
|
+
RUBY
|
|
28
|
+
end
|
|
30
29
|
end
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
def acts_as_taggable_on(*args)
|
|
33
32
|
super(*args)
|
|
34
33
|
initialize_acts_as_taggable_on_collection
|
|
35
34
|
end
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
def tag_counts_on(context, options = {})
|
|
38
|
-
all_tag_counts(options.merge({:
|
|
37
|
+
all_tag_counts(options.merge({on: context.to_s}))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def tags_on(context, options = {})
|
|
41
|
+
all_tags(options.merge({on: context.to_s}))
|
|
39
42
|
end
|
|
40
|
-
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Calculate the tag names.
|
|
46
|
+
# To be used when you don't need tag counts and want to avoid the taggable joins.
|
|
47
|
+
#
|
|
48
|
+
# @param [Hash] options Options:
|
|
49
|
+
# * :start_at - Restrict the tags to those created after a certain time
|
|
50
|
+
# * :end_at - Restrict the tags to those created before a certain time
|
|
51
|
+
# * :conditions - A piece of SQL conditions to add to the query. Note we don't join the taggable objects for performance reasons.
|
|
52
|
+
# * :limit - The maximum number of tags to return
|
|
53
|
+
# * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc'
|
|
54
|
+
# * :on - Scope the find to only include a certain context
|
|
55
|
+
def all_tags(options = {})
|
|
56
|
+
options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on
|
|
57
|
+
|
|
58
|
+
## Generate conditions:
|
|
59
|
+
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
60
|
+
|
|
61
|
+
## Generate scope:
|
|
62
|
+
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id")
|
|
63
|
+
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit])
|
|
64
|
+
|
|
65
|
+
# Joins and conditions
|
|
66
|
+
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
67
|
+
tag_scope = tag_scope.where(options[:conditions])
|
|
68
|
+
|
|
69
|
+
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
70
|
+
|
|
71
|
+
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
|
72
|
+
tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key).group(group_columns)
|
|
73
|
+
|
|
74
|
+
tag_scope_joins(tag_scope, tagging_scope)
|
|
75
|
+
end
|
|
76
|
+
|
|
41
77
|
##
|
|
42
78
|
# Calculate the tag counts for all tags.
|
|
43
79
|
#
|
|
@@ -53,46 +89,88 @@ module ActsAsTaggableOn::Taggable
|
|
|
53
89
|
def all_tag_counts(options = {})
|
|
54
90
|
options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id
|
|
55
91
|
|
|
56
|
-
|
|
57
|
-
end_at = sanitize_sql(["#{Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
58
|
-
|
|
59
|
-
taggable_type = sanitize_sql(["#{Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
60
|
-
taggable_id = sanitize_sql(["#{Tagging.table_name}.taggable_id = ?", options.delete(:id)]) if options[:id]
|
|
92
|
+
## Generate conditions:
|
|
61
93
|
options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions]
|
|
62
94
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
]
|
|
95
|
+
## Generate joins:
|
|
96
|
+
taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id"
|
|
97
|
+
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
|
|
98
|
+
|
|
99
|
+
## Generate scope:
|
|
100
|
+
tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count")
|
|
101
|
+
tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit])
|
|
102
|
+
|
|
103
|
+
# Joins and conditions
|
|
104
|
+
tagging_scope = tagging_scope.joins(taggable_join)
|
|
105
|
+
tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) }
|
|
106
|
+
tag_scope = tag_scope.where(options[:conditions])
|
|
107
|
+
|
|
108
|
+
# GROUP BY and HAVING clauses:
|
|
109
|
+
having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0"]
|
|
110
|
+
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least]
|
|
111
|
+
having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most]
|
|
112
|
+
having = having.compact.join(' AND ')
|
|
113
|
+
|
|
114
|
+
group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id"
|
|
115
|
+
|
|
116
|
+
unless options[:id]
|
|
117
|
+
# Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore:
|
|
118
|
+
tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
tagging_scope = tagging_scope.group(group_columns).having(having)
|
|
122
|
+
|
|
123
|
+
tag_scope_joins(tag_scope, tagging_scope)
|
|
124
|
+
end
|
|
70
125
|
|
|
71
|
-
|
|
126
|
+
def safe_to_sql(relation)
|
|
127
|
+
connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement { relation.to_sql } : relation.to_sql
|
|
128
|
+
end
|
|
72
129
|
|
|
73
|
-
|
|
74
|
-
joins << sanitize_sql(["AND #{Tagging.table_name}.context = ?",options.delete(:on).to_s]) unless options[:on].nil?
|
|
75
|
-
joins << " INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{Tagging.table_name}.taggable_id"
|
|
130
|
+
private
|
|
76
131
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
132
|
+
def generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key)
|
|
133
|
+
table_name_pkey = "#{table_name}.#{primary_key}"
|
|
134
|
+
if ActsAsTaggableOn::Utils.using_mysql?
|
|
135
|
+
# See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details
|
|
136
|
+
scoped_ids = select(table_name_pkey).map(&:id)
|
|
137
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids)
|
|
138
|
+
else
|
|
139
|
+
tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})")
|
|
80
140
|
end
|
|
81
141
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
142
|
+
tagging_scope
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def tagging_conditions(options)
|
|
146
|
+
tagging_conditions = []
|
|
147
|
+
tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
|
|
148
|
+
tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
|
|
149
|
+
|
|
150
|
+
taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name])
|
|
151
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on]
|
|
152
|
+
taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id]
|
|
153
|
+
|
|
154
|
+
tagging_conditions.push taggable_conditions
|
|
87
155
|
|
|
88
|
-
|
|
156
|
+
tagging_conditions
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def tag_scope_joins(tag_scope, tagging_scope)
|
|
160
|
+
tag_scope = tag_scope.joins("JOIN (#{safe_to_sql(tagging_scope)}) AS #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id")
|
|
161
|
+
tag_scope.extending(CalculationMethods)
|
|
89
162
|
end
|
|
90
163
|
end
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
|
|
165
|
+
def tag_counts_on(context, options={})
|
|
166
|
+
self.class.tag_counts_on(context, options.merge(id: id))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
module CalculationMethods
|
|
170
|
+
def count(column_name=:all)
|
|
171
|
+
# https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2
|
|
172
|
+
super
|
|
95
173
|
end
|
|
96
174
|
end
|
|
97
175
|
end
|
|
98
|
-
end
|
|
176
|
+
end
|