acts-as-taggable-on-3.5.0/0000755000004100000410000000000012503660213015256 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/Rakefile0000644000004100000410000000073012503660023016722 0ustar www-datawww-datarequire 'rubygems' require 'bundler/setup' import "./lib/tasks/tags_collate_utf8.rake" desc 'Default: run specs' task default: :spec desc 'Copy sample spec database.yml over if not exists' task :copy_db_config do cp 'spec/internal/config/database.yml.sample', 'spec/internal/config/database.yml' end task spec: [:copy_db_config] require 'rspec/core/rake_task' RSpec::Core::RakeTask.new do |t| t.pattern = 'spec/**/*_spec.rb' end Bundler::GemHelper.install_tasks acts-as-taggable-on-3.5.0/UPGRADING.md0000644000004100000410000000032312503660023017115 0ustar www-datawww-dataWhen upgrading Re-run the migrations generator rake acts_as_taggable_on_engine:install:migrations This will create any new migrations and skip existing ones Version 3.5.0 has a migration for mysql adapteracts-as-taggable-on-3.5.0/Gemfile0000644000004100000410000000025412503660023016551 0ustar www-datawww-datasource 'https://rubygems.org' gemspec group :local_development do gem 'guard' gem 'guard-rspec' gem 'appraisal' gem 'rake' gem 'byebug' , platform: :mri_21 end acts-as-taggable-on-3.5.0/acts-as-taggable-on.gemspec0000644000004100000410000000263712503660023022343 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'acts_as_taggable_on/version' Gem::Specification.new do |gem| gem.name = 'acts-as-taggable-on' gem.version = ActsAsTaggableOn::VERSION gem.authors = ['Michael Bleigh', 'Joost Baaij'] gem.email = %w(michael@intridea.com joost@spacebabies.nl) 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.} gem.summary = 'Advanced tagging for Rails.' gem.homepage = 'https://github.com/mbleigh/acts-as-taggable-on' gem.license = 'MIT' gem.files = `git ls-files`.split($/) gem.test_files = gem.files.grep(%r{^spec/}) gem.require_paths = ['lib'] gem.required_ruby_version = '>= 1.9.3' if File.exist?('UPGRADING.md') gem.post_install_message = File.read('UPGRADING.md') end gem.add_runtime_dependency 'activerecord', ['>= 3.2', '< 5'] gem.add_development_dependency 'sqlite3' gem.add_development_dependency 'mysql2', '~> 0.3.7' gem.add_development_dependency 'pg' gem.add_development_dependency 'rspec-rails' gem.add_development_dependency 'rspec-its' gem.add_development_dependency 'rspec' gem.add_development_dependency 'barrier' gem.add_development_dependency 'database_cleaner' end acts-as-taggable-on-3.5.0/db/0000755000004100000410000000000012503660023015642 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/db/migrate/0000755000004100000410000000000012503660023017272 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/db/migrate/3_add_taggings_counter_cache_to_tags.rb0000644000004100000410000000057112503660023027061 0ustar www-datawww-dataclass AddTaggingsCounterCacheToTags < ActiveRecord::Migration def self.up add_column :tags, :taggings_count, :integer, default: 0 ActsAsTaggableOn::Tag.reset_column_information ActsAsTaggableOn::Tag.find_each do |tag| ActsAsTaggableOn::Tag.reset_counters(tag.id, :taggings) end end def self.down remove_column :tags, :taggings_count end end acts-as-taggable-on-3.5.0/db/migrate/4_add_missing_taggable_index.rb0000644000004100000410000000035612503660023025344 0ustar www-datawww-dataclass AddMissingTaggableIndex < ActiveRecord::Migration def self.up add_index :taggings, [:taggable_id, :taggable_type, :context] end def self.down remove_index :taggings, [:taggable_id, :taggable_type, :context] end end acts-as-taggable-on-3.5.0/db/migrate/1_acts_as_taggable_on_migration.rb0000644000004100000410000000137612503660023026056 0ustar www-datawww-dataclass ActsAsTaggableOnMigration < ActiveRecord::Migration def self.up create_table :tags do |t| t.string :name end create_table :taggings do |t| t.references :tag # You should make sure that the column created is # long enough to store the required class names. t.references :taggable, polymorphic: true t.references :tagger, polymorphic: true # Limit is created to prevent MySQL error on index # length for MyISAM table type: http://bit.ly/vgW2Ql t.string :context, limit: 128 t.datetime :created_at end add_index :taggings, :tag_id add_index :taggings, [:taggable_id, :taggable_type, :context] end def self.down drop_table :taggings drop_table :tags end end acts-as-taggable-on-3.5.0/db/migrate/5_change_collation_for_tag_names.rb0000644000004100000410000000050112503660023026214 0ustar www-datawww-data# This migration is added to circumvent issue #623 and have special characters # work properly class ChangeCollationForTagNames < ActiveRecord::Migration def up if ActsAsTaggableOn::Utils.using_mysql? execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") end end end acts-as-taggable-on-3.5.0/db/migrate/2_add_missing_unique_indices.rb0000644000004100000410000000112212503660023025401 0ustar www-datawww-dataclass AddMissingUniqueIndices < ActiveRecord::Migration def self.up add_index :tags, :name, unique: true remove_index :taggings, :tag_id remove_index :taggings, [:taggable_id, :taggable_type, :context] add_index :taggings, [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], unique: true, name: 'taggings_idx' end def self.down remove_index :tags, :name remove_index :taggings, name: 'taggings_idx' add_index :taggings, :tag_id add_index :taggings, [:taggable_id, :taggable_type, :context] end end acts-as-taggable-on-3.5.0/.rspec0000644000004100000410000000002512503660023016367 0ustar www-datawww-data--colour --backtrace acts-as-taggable-on-3.5.0/data.tar.gz0000444000004100000410000001700012503660213017312 0ustar www-datawww-dataqT}{6{)Py[IL[Nq{;v?LZ"U&Azq]kD1<{]?ݿ}zm}}kco]mnVެooס|Q_X|Ql>; 'VGϋFamp]{RC+G7ny_ngc7Z'/cvێ\kں}^'|ӹ.u}Ak_of}c⳺ z0,uqX/BUϞwvm < ,*VM˿:}Y?X ߯ ׿:/_(>B1g7uSԤaCFuL"PNjk~=yV2Q[66C|t#ahs+:vvuW#R'/\X¾ݹjBD5dz@7Wv,n}yvǕ_!qE\v;4_n}]vT߽8@f 2cvťbgۇs^AsmZA]w"ER ݡ`7FTzBWbTnʭNG L+n$a*lђؒзfh<`?%GR6bnAΓM&Jx0áwpAqa"\e>+I*ő)!+ "֦.D}^_x";"UV'y+dǢU;Zn)jMڀY 5 'F6d𞈳Bڍs#:Ag047?zƳApxI>na^omQc8q 347fg#z&8};@pZ{`9'Fy8ђP$m@au@yÁa+;@;:@xܸO[煇A]z5sU8ؚl=;qy2%a26BhHH H;Ђht!-pdO&]uD0gߏȠHH5}Da _4qm1Z~]ַVI//!un5peGij]jJ_y둽 ߈`1NA lg ,vwr,+mEl@ai,{V:J ZEq/Z ݂#9!lKzaO;a6f60Fx޷\i r\[[+ •.ݞ7p;`˷i g.ݎ[v|Al*ǡA1I74Rm *$KN #wۇZǗ .4F|+th4q%f%Wfk"a9CGH67HN fp\+7@>9wCDQC;Qt IsNjб=hXJ1H[-҈Z#n2BcX0@x$$C#ĩbENjA7 Ni DŽM?v 8GˌkN}]xyCkF8?8->vdlӒ38$;6g{/q N-̛w[ ̛SyA/ g%A ?,&(0H@,SHa~nooLc#EQ&)Gފ]y-`.,? m paIJ k\a/0uج4 -r_2 H./X\# qa AR K<[x5cӭ|loB7 m S:9LImDكَ 7-X |JYxnEve9 }ם)@0`@|̅Ŏ?"YR@kR9=`$e˲}eeӲP*:A̮P}K(\}`{Y njc^{8K3)G~u3e :urDIXr 'Рw9w`M KBKbz]wDqFa?XZS+$X]&by߁Eax@bػJ&ZQ oqJnPо\6iR/,"<ڒXm ՗C*X wF_DӇ4^O>leS*Mp2EU"uVM!ﱐkn ,ڰem}ypRN}uYWbA#)C_A;1Z]Gg" )yevtabgxʣu4w ED펷J.ͽ7H~usA4'v]R4p¯Pu`zje]؁zQ>=zyD5p*rc aIT8F@[K jO7 p:{hlck6m@zmI$+}4֗led m=]v֟056p}MطN}NBʽA;cy N6j.Am]^1B}A] 6Fޥdqiuqi BVnjPT7I5@Kz. -PW#Ԟ޺ުMu""8{`#[9k zM]:41 O^~)~&q&|rptH`,>mGCm=td F xdz-d6ZFt{E-' w^6T``ldp͝L_xpP35u8L#8eA>` UU}7b+i%Uw]6Fh+v4[-$ LP ah*s`=/=3c%nzvDo*˵ɓ ED*U:8 ݫEҜXš(v5%̳󖌹$rRT9!9B*N[%-va_P=[}ϿϡZcm}{$j5v2]%]bM0񿣻sCv3SԖc;F nPIU%>2 q& :~]{_3[#UDa/tk㝔C;tJ_]R?U!C=rnITŠgh~<~Lc_66}H7-^ ?_k}¿Juk+}E*'jJ'B ?-q @{'1_M=?jQ聈%ʝyq[@hu{{HdElTxXV fm"2 "1~~*K[ # YXNžR|WB1 _?=Uҧa*zyץa FWԳ>=X Ʊh~<0$( pBc9ER[#)G z|e 0(R~9W ߸4^r?ҕh}|]xXdA[6l"?`QaL8aZ/鯻D=>{U/GN8=8|Y-qt/p;/ѩx}==ءl`*?FUūCl4+{{,޾;~{tݿ,:^ZHrx$ ?/_SWcoo?r*~9zr/>}zA=xS/wOc$tO]a.mgFy|zp_'8!yNqD@}nZVw' ,/w_C['X,l=&ho~q|]%6ƞgdyZλT?g bqPFng'_Xv< 8FsW#H , CCa:' 5@= FABSp^,u++ 3v=).x>Y_B8xI솁Dx?c4'edͽ}5@>l=⮸;T49nXZ:ʒL{W`#pL#E@PI'ʩHA) ڡ2H C<E9'v+kUM\*Jv~g3Hr2\48_B xO rNEnFtNe el6wjRX@(EE"*Wvu-+Cdd6Vrȃ ɝg=ܪ(FF0@% Ew^ā/ ,\wddBM4 n~ ET 3'-䣇\)2(ht"r\ђ lә@* p@}lDFwP?&jBfOHVԌ=. 6F̵}[\T/Ex4P9OeL+-4tV(F@*FѳUQ`iQ灞:&1;/~WFEI7\ DUs< &T p< 1ض_*Ռk#Yl/G "rV2eA#.FJ HVMK4EJMa0(@1nDMM4t<&yq~U) 1H\p$-M,XE$lٽBLiZb%)VuԼE"5mSlYxŽ.YKy7ܒGIbpތ^'! eҗjacts-as-taggable-on-3.5.0/spec/0000755000004100000410000000000012503660023016207 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/spec_helper.rb0000644000004100000410000000075612503660023021035 0ustar www-datawww-databegin require 'byebug' rescue LoadError end $LOAD_PATH << '.' unless $LOAD_PATH.include?('.') $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) require 'logger' require File.expand_path('../../lib/acts-as-taggable-on', __FILE__) I18n.enforce_available_locales = true require 'rails' require 'rspec/its' require 'barrier' require 'database_cleaner' Dir['./spec/support/**/*.rb'].sort.each { |f| require f } RSpec.configure do |config| config.raise_errors_for_deprecations! end acts-as-taggable-on-3.5.0/spec/internal/0000755000004100000410000000000012503660023020023 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/internal/db/0000755000004100000410000000000012503660023020410 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/internal/db/schema.rb0000644000004100000410000000525412503660023022203 0ustar www-datawww-dataActiveRecord::Schema.define version: 0 do create_table :tags, force: true do |t| t.string :name t.integer :taggings_count, default: 0 t.string :type end add_index 'tags', ['name'], name: 'index_tags_on_name', unique: true create_table :taggings, force: true do |t| t.references :tag # You should make sure that the column created is # long enough to store the required class names. t.references :taggable, polymorphic: true t.references :tagger, polymorphic: true # Limit is created to prevent MySQL error on index # length for MyISAM table type: http://bit.ly/vgW2Ql t.string :context, limit: 128 t.datetime :created_at end add_index 'taggings', ['tag_id', 'taggable_id', 'taggable_type', 'context', 'tagger_id', 'tagger_type'], unique: true, name: 'taggings_idx' # above copied from # generators/acts_as_taggable_on/migration/migration_generator create_table :taggable_models, force: true do |t| t.column :name, :string t.column :type, :string end create_table :non_standard_id_taggable_models, primary_key: 'an_id', force: true do |t| t.column :name, :string t.column :type, :string end create_table :untaggable_models, force: true do |t| t.column :taggable_model_id, :integer t.column :name, :string end create_table :cached_models, force: true do |t| t.column :name, :string t.column :type, :string t.column :cached_tag_list, :string end create_table :other_cached_models, force: true do |t| t.column :name, :string t.column :type, :string t.column :cached_language_list, :string t.column :cached_status_list, :string t.column :cached_glass_list, :string end create_table :companies, force: true do |t| t.column :name, :string end create_table :users, force: true do |t| t.column :name, :string end create_table :other_taggable_models, force: true do |t| t.column :name, :string t.column :type, :string end create_table :ordered_taggable_models, force: true do |t| t.column :name, :string t.column :type, :string end # Special cases for postgresql if using_postgresql? create_table :other_cached_with_array_models, force: true do |t| t.column :name, :string t.column :type, :string t.column :cached_language_list, :string, array: true t.column :cached_status_list, :string, array: true t.column :cached_glass_list, :string, array: true end if postgresql_support_json? create_table :taggable_model_with_jsons, :force => true do |t| t.column :name, :string t.column :type, :string t.column :opts, :json end end end end acts-as-taggable-on-3.5.0/spec/internal/config/0000755000004100000410000000000012503660023021270 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/internal/config/database.yml.sample0000644000004100000410000000047112503660023025041 0ustar www-datawww-datasqlite3: adapter: sqlite3 database: ':memory:' mysql: adapter: mysql2 host: localhost username: root password: database: acts_as_taggable_on charset: utf8 postgresql: adapter: postgresql hostname: localhost username: postgres password: database: acts_as_taggable_on encoding: utf8 acts-as-taggable-on-3.5.0/spec/internal/app/0000755000004100000410000000000012503660023020603 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/internal/app/models/0000755000004100000410000000000012503660023022066 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/internal/app/models/taggable_model.rb0000644000004100000410000000047112503660023025343 0ustar www-datawww-dataclass TaggableModel < ActiveRecord::Base acts_as_taggable acts_as_taggable_on :languages acts_as_taggable_on :skills acts_as_taggable_on :needs, :offerings has_many :untaggable_models attr_reader :tag_list_submethod_called def tag_list=(v) @tag_list_submethod_called = true super end end acts-as-taggable-on-3.5.0/spec/internal/app/models/inheriting_taggable_model.rb0000644000004100000410000000006212503660023027557 0ustar www-datawww-dataclass InheritingTaggableModel < TaggableModel end acts-as-taggable-on-3.5.0/spec/internal/app/models/student.rb0000644000004100000410000000003112503660023024073 0ustar www-datawww-dataclass Student < User end acts-as-taggable-on-3.5.0/spec/internal/app/models/untaggable_model.rb0000644000004100000410000000011412503660023025700 0ustar www-datawww-dataclass UntaggableModel < ActiveRecord::Base belongs_to :taggable_model end acts-as-taggable-on-3.5.0/spec/internal/app/models/altered_inheriting_taggable_model.rb0000644000004100000410000000012612503660023031260 0ustar www-datawww-dataclass AlteredInheritingTaggableModel < TaggableModel acts_as_taggable_on :parts end acts-as-taggable-on-3.5.0/spec/internal/app/models/other_cached_model.rb0000644000004100000410000000014612503660023026204 0ustar www-datawww-dataclass OtherCachedModel < ActiveRecord::Base acts_as_taggable_on :languages, :statuses, :glasses end acts-as-taggable-on-3.5.0/spec/internal/app/models/ordered_taggable_model.rb0000644000004100000410000000016612503660023027050 0ustar www-datawww-dataclass OrderedTaggableModel < ActiveRecord::Base acts_as_ordered_taggable acts_as_ordered_taggable_on :colours end acts-as-taggable-on-3.5.0/spec/internal/app/models/user.rb0000644000004100000410000000006412503660023023371 0ustar www-datawww-dataclass User < ActiveRecord::Base acts_as_tagger endacts-as-taggable-on-3.5.0/spec/internal/app/models/models.rb0000644000004100000410000000360512503660023023702 0ustar www-datawww-dataclass TaggableModel < ActiveRecord::Base acts_as_taggable acts_as_taggable_on :languages acts_as_taggable_on :skills acts_as_taggable_on :needs, :offerings has_many :untaggable_models attr_reader :tag_list_submethod_called def tag_list=(v) @tag_list_submethod_called = true super end end class CachedModel < ActiveRecord::Base acts_as_taggable end class OtherCachedModel < ActiveRecord::Base acts_as_taggable_on :languages, :statuses, :glasses end class OtherTaggableModel < ActiveRecord::Base acts_as_taggable_on :tags, :languages acts_as_taggable_on :needs, :offerings end class InheritingTaggableModel < TaggableModel end class AlteredInheritingTaggableModel < TaggableModel acts_as_taggable_on :parts end class Market < ActsAsTaggableOn::Tag end class Company < ActiveRecord::Base acts_as_taggable_on :locations, :markets has_many :markets, :through => :market_taggings, :source => :tag private def find_or_create_tags_from_list_with_context(tag_list, context) if context.to_sym == :markets Market.find_or_create_all_with_like_by_name(tag_list) else super end end end class User < ActiveRecord::Base acts_as_tagger end class Student < User end class UntaggableModel < ActiveRecord::Base belongs_to :taggable_model end class NonStandardIdTaggableModel < ActiveRecord::Base self.primary_key = :an_id acts_as_taggable acts_as_taggable_on :languages acts_as_taggable_on :skills acts_as_taggable_on :needs, :offerings has_many :untaggable_models end class OrderedTaggableModel < ActiveRecord::Base acts_as_ordered_taggable acts_as_ordered_taggable_on :colours end if using_postgresql? class CachedModelWithArray < ActiveRecord::Base acts_as_taggable end if postgresql_support_json? class TaggableModelWithJson < ActiveRecord::Base acts_as_taggable acts_as_taggable_on :skills end end end acts-as-taggable-on-3.5.0/spec/internal/app/models/cached_model.rb0000644000004100000410000000007612503660023025005 0ustar www-datawww-dataclass CachedModel < ActiveRecord::Base acts_as_taggable end acts-as-taggable-on-3.5.0/spec/internal/app/models/other_taggable_model.rb0000644000004100000410000000020312503660023026535 0ustar www-datawww-dataclass OtherTaggableModel < ActiveRecord::Base acts_as_taggable_on :tags, :languages acts_as_taggable_on :needs, :offerings end acts-as-taggable-on-3.5.0/spec/internal/app/models/non_standard_id_taggable_model.rb0000644000004100000410000000036012503660023030546 0ustar www-datawww-dataclass NonStandardIdTaggableModel < ActiveRecord::Base self.primary_key = 'an_id' acts_as_taggable acts_as_taggable_on :languages acts_as_taggable_on :skills acts_as_taggable_on :needs, :offerings has_many :untaggable_models end acts-as-taggable-on-3.5.0/spec/internal/app/models/cached_model_with_array.rb0000644000004100000410000000014612503660023027234 0ustar www-datawww-dataif using_postgresql? class CachedModelWithArray < ActiveRecord::Base acts_as_taggable end end acts-as-taggable-on-3.5.0/spec/internal/app/models/market.rb0000644000004100000410000000005012503660023023671 0ustar www-datawww-dataclass Market < ActsAsTaggableOn::Tag endacts-as-taggable-on-3.5.0/spec/internal/app/models/company.rb0000644000004100000410000000054612503660023024066 0ustar www-datawww-dataclass Company < ActiveRecord::Base acts_as_taggable_on :locations, :markets has_many :markets, :through => :market_taggings, :source => :tag private def find_or_create_tags_from_list_with_context(tag_list, context) if context.to_sym == :markets Market.find_or_create_all_with_like_by_name(tag_list) else super end end endacts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/0000755000004100000410000000000012503660023022146 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/tag_list_parser_spec.rb0000644000004100000410000000312112503660023026664 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::TagListParser do it '#parse should return empty array if empty array is passed' do expect(ActsAsTaggableOn::TagListParser.parse([])).to be_empty end describe 'Multiple Delimiter' do before do @old_delimiter = ActsAsTaggableOn.delimiter end after do ActsAsTaggableOn.delimiter = @old_delimiter end it 'should separate tags by delimiters' do ActsAsTaggableOn.delimiter = [',', ' ', '\|'] tag_list = ActsAsTaggableOn::TagListParser.parse('cool, data|I have') expect(tag_list.to_s).to eq('cool, data, I, have') end it 'should escape quote' do ActsAsTaggableOn.delimiter = [',', ' ', '\|'] tag_list = ActsAsTaggableOn::TagListParser.parse "'I have'|cool, data" expect(tag_list.to_s).to eq('"I have", cool, data') tag_list = ActsAsTaggableOn::TagListParser.parse '"I, have"|cool, data' expect(tag_list.to_s).to eq('"I, have", cool, data') end it 'should work for utf8 delimiter and long delimiter' do ActsAsTaggableOn.delimiter = [',', '的', '可能是'] tag_list = ActsAsTaggableOn::TagListParser.parse('我的东西可能是不见了,还好有备份') expect(tag_list.to_s).to eq('我, 东西, 不见了, 还好有备份') end it 'should work for multiple quoted tags' do ActsAsTaggableOn.delimiter = [','] tag_list = ActsAsTaggableOn::TagListParser.parse('"Ruby Monsters","eat Katzenzungen"') expect(tag_list.to_s).to eq('Ruby Monsters, eat Katzenzungen') end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/generic_parser_spec.rb0000644000004100000410000000070412503660023026476 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::GenericParser do it '#parse should return empty array if empty tag string is passed' do tag_list = ActsAsTaggableOn::GenericParser.new('') expect(tag_list.parse).to be_empty end it '#parse should separate tags by comma' do tag_list = ActsAsTaggableOn::GenericParser.new('cool,data,,I,have') expect(tag_list.parse).to eq(%w(cool data I have)) end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/caching_spec.rb0000644000004100000410000000543012503660023025103 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe 'Acts As Taggable On' do describe 'Caching' do before(:each) do @taggable = CachedModel.new(name: 'Bob Jones') @another_taggable = OtherCachedModel.new(name: 'John Smith') end it 'should add saving of tag lists and cached tag lists to the instance' do expect(@taggable).to respond_to(:save_cached_tag_list) expect(@another_taggable).to respond_to(:save_cached_tag_list) expect(@taggable).to respond_to(:save_tags) end it 'should add cached tag lists to the instance if cached column is not present' do expect(TaggableModel.new(name: 'Art Kram')).to_not respond_to(:save_cached_tag_list) end it 'should generate a cached column checker for each tag type' do expect(CachedModel).to respond_to(:caching_tag_list?) expect(OtherCachedModel).to respond_to(:caching_language_list?) end it 'should not have cached tags' do expect(@taggable.cached_tag_list).to be_blank expect(@another_taggable.cached_language_list).to be_blank end it 'should cache tags' do @taggable.update_attributes(tag_list: 'awesome, epic') expect(@taggable.cached_tag_list).to eq('awesome, epic') @another_taggable.update_attributes(language_list: 'ruby, .net') expect(@another_taggable.cached_language_list).to eq('ruby, .net') end it 'should keep the cache' do @taggable.update_attributes(tag_list: 'awesome, epic') @taggable = CachedModel.find(@taggable.id) @taggable.save! expect(@taggable.cached_tag_list).to eq('awesome, epic') end it 'should update the cache' do @taggable.update_attributes(tag_list: 'awesome, epic') @taggable.update_attributes(tag_list: 'awesome') expect(@taggable.cached_tag_list).to eq('awesome') end it 'should remove the cache' do @taggable.update_attributes(tag_list: 'awesome, epic') @taggable.update_attributes(tag_list: '') expect(@taggable.cached_tag_list).to be_blank end it 'should have a tag list' do @taggable.update_attributes(tag_list: 'awesome, epic') @taggable = CachedModel.find(@taggable.id) expect(@taggable.tag_list.sort).to eq(%w(awesome epic).sort) end it 'should keep the tag list' do @taggable.update_attributes(tag_list: 'awesome, epic') @taggable = CachedModel.find(@taggable.id) @taggable.save! expect(@taggable.tag_list.sort).to eq(%w(awesome epic).sort) end it 'should clear the cache on reset_column_information' do CachedModel.column_names CachedModel.reset_column_information expect(CachedModel.instance_variable_get(:@acts_as_taggable_on_cache_columns)).to eql(nil) end end describe 'CachingWithArray' do pending '#TODO' end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/tagger_spec.rb0000644000004100000410000001233012503660023024755 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe 'Tagger' do before(:each) do @user = User.create @taggable = TaggableModel.create(name: 'Bob Jones') end it 'should have taggings' do @user.tag(@taggable, with: 'ruby,scheme', on: :tags) expect(@user.owned_taggings.size).to eq(2) end it 'should have tags' do @user.tag(@taggable, with: 'ruby,scheme', on: :tags) expect(@user.owned_tags.size).to eq(2) end it 'should scope objects returned by tagged_with by owners' do @taggable2 = TaggableModel.create(name: 'Jim Jones') @taggable3 = TaggableModel.create(name: 'Jane Doe') @user2 = User.new @user.tag(@taggable, with: 'ruby, scheme', on: :tags) @user2.tag(@taggable2, with: 'ruby, scheme', on: :tags) @user2.tag(@taggable3, with: 'ruby, scheme', on: :tags) expect(TaggableModel.tagged_with(%w(ruby scheme), owned_by: @user).count).to eq(1) expect(TaggableModel.tagged_with(%w(ruby scheme), owned_by: @user2).count).to eq(2) end it 'only returns objects tagged by owned_by when any is true' do @user2 = User.new @taggable2 = TaggableModel.create(name: 'Jim Jones') @taggable3 = TaggableModel.create(name: 'Jane Doe') @user.tag(@taggable, with: 'ruby', on: :tags) @user.tag(@taggable2, with: 'java', on: :tags) @user2.tag(@taggable3, with: 'ruby', on: :tags) tags = TaggableModel.tagged_with(%w(ruby java), owned_by: @user, any: true) expect(tags).to include(@taggable, @taggable2) expect(tags.size).to eq(2) end it 'only returns objects tagged by owned_by when exclude is true' do @user2 = User.new @taggable2 = TaggableModel.create(name: 'Jim Jones') @taggable3 = TaggableModel.create(name: 'Jane Doe') @user.tag(@taggable, with: 'ruby', on: :tags) @user.tag(@taggable2, with: 'java', on: :tags) @user2.tag(@taggable3, with: 'java', on: :tags) tags = TaggableModel.tagged_with(%w(ruby), owned_by: @user, exclude: true) expect(tags).to eq([@taggable2]) end it 'should not overlap tags from different taggers' do @user2 = User.new expect(-> { @user.tag(@taggable, with: 'ruby, scheme', on: :tags) @user2.tag(@taggable, with: 'java, python, lisp, ruby', on: :tags) }).to change(ActsAsTaggableOn::Tagging, :count).by(6) [@user, @user2, @taggable].each(&:reload) expect(@user.owned_tags.map(&:name).sort).to eq(%w(ruby scheme).sort) expect(@user2.owned_tags.map(&:name).sort).to eq(%w(java python lisp ruby).sort) expect(@taggable.tags_from(@user).sort).to eq(%w(ruby scheme).sort) expect(@taggable.tags_from(@user2).sort).to eq(%w(java lisp python ruby).sort) expect(@taggable.all_tags_list.sort).to eq(%w(ruby scheme java python lisp).sort) expect(@taggable.all_tags_on(:tags).size).to eq(5) end it 'should not lose tags from different taggers' do @user2 = User.create @user2.tag(@taggable, with: 'java, python, lisp, ruby', on: :tags) @user.tag(@taggable, with: 'ruby, scheme', on: :tags) expect(-> { @user2.tag(@taggable, with: 'java, python, lisp', on: :tags) }).to change(ActsAsTaggableOn::Tagging, :count).by(-1) [@user, @user2, @taggable].each(&:reload) expect(@taggable.tags_from(@user).sort).to eq(%w(ruby scheme).sort) expect(@taggable.tags_from(@user2).sort).to eq(%w(java python lisp).sort) expect(@taggable.all_tags_list.sort).to eq(%w(ruby scheme java python lisp).sort) expect(@taggable.all_tags_on(:tags).length).to eq(5) end it 'should not lose tags' do @user2 = User.create @user.tag(@taggable, with: 'awesome', on: :tags) @user2.tag(@taggable, with: 'awesome, epic', on: :tags) expect(-> { @user2.tag(@taggable, with: 'epic', on: :tags) }).to change(ActsAsTaggableOn::Tagging, :count).by(-1) @taggable.reload expect(@taggable.all_tags_list).to include('awesome') expect(@taggable.all_tags_list).to include('epic') end it 'should not lose tags' do @taggable.update_attributes(tag_list: 'ruby') @user.tag(@taggable, with: 'ruby, scheme', on: :tags) [@taggable, @user].each(&:reload) expect(@taggable.tag_list).to eq(%w(ruby)) expect(@taggable.all_tags_list.sort).to eq(%w(ruby scheme).sort) expect(-> { @taggable.update_attributes(tag_list: '') }).to change(ActsAsTaggableOn::Tagging, :count).by(-1) expect(@taggable.tag_list).to be_empty expect(@taggable.all_tags_list.sort).to eq(%w(ruby scheme).sort) end it 'is tagger' do expect(@user.is_tagger?).to be_truthy end it 'should skip save if skip_save is passed as option' do expect(-> { @user.tag(@taggable, with: 'epic', on: :tags, skip_save: true) }).to_not change(ActsAsTaggableOn::Tagging, :count) end it 'should change tags order in ordered taggable' do @ordered_taggable = OrderedTaggableModel.create name: 'hey!' @user.tag @ordered_taggable, with: 'tag, tag1', on: :tags expect(@ordered_taggable.reload.tags_from(@user)).to eq(['tag', 'tag1']) @user.tag @ordered_taggable, with: 'tag2, tag1', on: :tags expect(@ordered_taggable.reload.tags_from(@user)).to eq(['tag2', 'tag1']) @user.tag @ordered_taggable, with: 'tag1, tag2', on: :tags expect(@ordered_taggable.reload.tags_from(@user)).to eq(['tag1', 'tag2']) end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/tags_helper_spec.rb0000644000004100000410000000243012503660023026001 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::TagsHelper do before(:each) do @bob = TaggableModel.create(name: 'Bob Jones', language_list: 'ruby, php') @tom = TaggableModel.create(name: 'Tom Marley', language_list: 'ruby, java') @eve = TaggableModel.create(name: 'Eve Nodd', language_list: 'ruby, c++') @helper = class Helper include ActsAsTaggableOn::TagsHelper end.new end it 'should yield the proper css classes' do tags = {} @helper.tag_cloud(TaggableModel.tag_counts_on(:languages), %w(sucky awesome)) do |tag, css_class| tags[tag.name] = css_class end expect(tags['ruby']).to eq('awesome') expect(tags['java']).to eq('sucky') expect(tags['c++']).to eq('sucky') expect(tags['php']).to eq('sucky') end it 'should handle tags with zero counts (build for empty)' do ActsAsTaggableOn::Tag.create(name: 'php') ActsAsTaggableOn::Tag.create(name: 'java') ActsAsTaggableOn::Tag.create(name: 'c++') tags = {} @helper.tag_cloud(ActsAsTaggableOn::Tag.all, %w(sucky awesome)) do |tag, css_class| tags[tag.name] = css_class end expect(tags['java']).to eq('sucky') expect(tags['c++']).to eq('sucky') expect(tags['php']).to eq('sucky') end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/tagging_spec.rb0000644000004100000410000000357012503660023025132 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::Tagging do before(:each) do @tagging = ActsAsTaggableOn::Tagging.new end it 'should not be valid with a invalid tag' do @tagging.taggable = TaggableModel.create(name: 'Bob Jones') @tagging.tag = ActsAsTaggableOn::Tag.new(name: '') @tagging.context = 'tags' expect(@tagging).to_not be_valid expect(@tagging.errors[:tag_id]).to eq(['can\'t be blank']) end it 'should not create duplicate taggings' do @taggable = TaggableModel.create(name: 'Bob Jones') @tag = ActsAsTaggableOn::Tag.create(name: 'awesome') expect(-> { 2.times { ActsAsTaggableOn::Tagging.create(taggable: @taggable, tag: @tag, context: 'tags') } }).to change(ActsAsTaggableOn::Tagging, :count).by(1) end it 'should not delete tags of other records' do 6.times { TaggableModel.create(name: 'Bob Jones', tag_list: 'very, serious, bug') } expect(ActsAsTaggableOn::Tag.count).to eq(3) taggable = TaggableModel.first taggable.tag_list = 'bug' taggable.save expect(taggable.tag_list).to eq(['bug']) another_taggable = TaggableModel.where('id != ?', taggable.id).sample expect(another_taggable.tag_list.sort).to eq(%w(very serious bug).sort) end it 'should destroy unused tags after tagging destroyed' do previous_setting = ActsAsTaggableOn.remove_unused_tags ActsAsTaggableOn.remove_unused_tags = true ActsAsTaggableOn::Tag.destroy_all @taggable = TaggableModel.create(name: 'Bob Jones') @taggable.update_attribute :tag_list, 'aaa,bbb,ccc' @taggable.update_attribute :tag_list, '' expect(ActsAsTaggableOn::Tag.count).to eql(0) ActsAsTaggableOn.remove_unused_tags = previous_setting end pending 'context scopes' do describe '.by_context' describe '.by_contexts' describe '.owned_by' describe '.not_owned' end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/taggable/0000755000004100000410000000000012503660023023714 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/taggable/dirty_spec.rb0000644000004100000410000000716012503660023026412 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::Taggable::Dirty do context 'with un-contexted tags' do before(:each) do @taggable = TaggableModel.create(tag_list: 'awesome, epic') end context 'when tag_list changed' do before(:each) do expect(@taggable.changes).to be_empty @taggable.tag_list = 'one' end it 'should show changes of dirty object' do expect(@taggable.changes).to eq({'tag_list' => ['awesome, epic', ['one']]}) end it 'flags tag_list as changed' do expect(@taggable.tag_list_changed?).to be_truthy end it 'preserves original value' do expect(@taggable.tag_list_was).to eq('awesome, epic') end it 'shows what the change was' do expect(@taggable.tag_list_change).to eq(['awesome, epic', ['one']]) end context 'without order' do it 'should not mark attribute if order change ' do taggable = TaggableModel.create(name: 'Dirty Harry', tag_list: %w(d c b a)) taggable.tag_list = %w(a b c d) expect(taggable.tag_list_changed?).to be_falsey end end context 'with order' do it 'should mark attribute if order change' do taggable = OrderedTaggableModel.create(name: 'Clean Harry', tag_list: 'd,c,b,a') taggable.save taggable.tag_list = %w(a b c d) expect(taggable.tag_list_changed?).to be_truthy end end end context 'when tag_list is the same' do before(:each) do @taggable.tag_list = 'awesome, epic' end it 'is not flagged as changed' do expect(@taggable.tag_list_changed?).to be_falsy end it 'does not show any changes to the taggable item' do expect(@taggable.changes).to be_empty end context "and using a delimiter different from a ','" do before do @old_delimiter = ActsAsTaggableOn.delimiter ActsAsTaggableOn.delimiter = ';' end after do ActsAsTaggableOn.delimiter = @old_delimiter end it 'does not show any changes to the taggable item when using array assignments' do @taggable.tag_list = %w(awesome epic) expect(@taggable.changes).to be_empty end end end end context 'with context tags' do before(:each) do @taggable = TaggableModel.create('language_list' => 'awesome, epic') end context 'when language_list changed' do before(:each) do expect(@taggable.changes).to be_empty @taggable.language_list = 'one' end it 'should show changes of dirty object' do expect(@taggable.changes).to eq({'language_list' => ['awesome, epic', ['one']]}) end it 'flags language_list as changed' do expect(@taggable.language_list_changed?).to be_truthy end it 'preserves original value' do expect(@taggable.language_list_was).to eq('awesome, epic') end it 'shows what the change was' do expect(@taggable.language_list_change).to eq(['awesome, epic', ['one']]) end it 'shows what the changes were' do expect(@taggable.language_list_changes).to eq(['awesome, epic', ['one']]) end end context 'when language_list is the same' do before(:each) do @taggable.language_list = 'awesome, epic' end it 'is not flagged as changed' do expect(@taggable.language_list_changed?).to be_falsy end it 'does not show any changes to the taggable item' do expect(@taggable.changes).to be_empty end end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/utils_spec.rb0000644000004100000410000000163712503660023024654 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::Utils do describe '#like_operator' do it 'should return \'ILIKE\' when the adapter is PostgreSQL' do allow(ActsAsTaggableOn::Utils.connection).to receive(:adapter_name) { 'PostgreSQL' } expect(ActsAsTaggableOn::Utils.like_operator).to eq('ILIKE') end it 'should return \'LIKE\' when the adapter is not PostgreSQL' do allow(ActsAsTaggableOn::Utils.connection).to receive(:adapter_name) { 'MySQL' } expect(ActsAsTaggableOn::Utils.like_operator).to eq('LIKE') end end describe '#sha_prefix' do it 'should return a consistent prefix for a given word' do expect(ActsAsTaggableOn::Utils.sha_prefix('kittens')).to eq(ActsAsTaggableOn::Utils.sha_prefix('kittens')) expect(ActsAsTaggableOn::Utils.sha_prefix('puppies')).not_to eq(ActsAsTaggableOn::Utils.sha_prefix('kittens')) end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/default_parser_spec.rb0000644000004100000410000000315312503660023026507 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::DefaultParser do it '#parse should return empty array if empty array is passed' do parser = ActsAsTaggableOn::DefaultParser.new([]) expect(parser.parse).to be_empty end describe 'Multiple Delimiter' do before do @old_delimiter = ActsAsTaggableOn.delimiter end after do ActsAsTaggableOn.delimiter = @old_delimiter end it 'should separate tags by delimiters' do ActsAsTaggableOn.delimiter = [',', ' ', '\|'] parser = ActsAsTaggableOn::DefaultParser.new('cool, data|I have') expect(parser.parse.to_s).to eq('cool, data, I, have') end it 'should escape quote' do ActsAsTaggableOn.delimiter = [',', ' ', '\|'] parser = ActsAsTaggableOn::DefaultParser.new("'I have'|cool, data") expect(parser.parse.to_s).to eq('"I have", cool, data') parser = ActsAsTaggableOn::DefaultParser.new('"I, have"|cool, data') expect(parser.parse.to_s).to eq('"I, have", cool, data') end it 'should work for utf8 delimiter and long delimiter' do ActsAsTaggableOn.delimiter = [',', '的', '可能是'] parser = ActsAsTaggableOn::DefaultParser.new('我的东西可能是不见了,还好有备份') expect(parser.parse.to_s).to eq('我, 东西, 不见了, 还好有备份') end it 'should work for multiple quoted tags' do ActsAsTaggableOn.delimiter = [','] parser = ActsAsTaggableOn::DefaultParser.new('"Ruby Monsters","eat Katzenzungen"') expect(parser.parse.to_s).to eq('Ruby Monsters, eat Katzenzungen') end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/tag_list_spec.rb0000644000004100000410000001133612503660023025317 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe ActsAsTaggableOn::TagList do let(:tag_list) { ActsAsTaggableOn::TagList.new('awesome', 'radical') } let(:another_tag_list) { ActsAsTaggableOn::TagList.new('awesome','crazy', 'alien') } it { should be_kind_of Array } describe '#add' do it 'should be able to be add a new tag word' do tag_list.add('cool') expect(tag_list.include?('cool')).to be_truthy end it 'should be able to add delimited lists of words' do tag_list.add('cool, wicked', parse: true) expect(tag_list).to include('cool', 'wicked') end it 'should be able to add delimited list of words with quoted delimiters' do tag_list.add("'cool, wicked', \"really cool, really wicked\"", parse: true) expect(tag_list).to include('cool, wicked', 'really cool, really wicked') end it 'should be able to handle other uses of quotation marks correctly' do tag_list.add("john's cool car, mary's wicked toy", parse: true) expect(tag_list).to include("john's cool car", "mary's wicked toy") end it 'should be able to add an array of words' do tag_list.add(%w(cool wicked), parse: true) expect(tag_list).to include('cool', 'wicked') end it 'should quote escape tags with commas in them' do tag_list.add('cool', 'rad,bodacious') expect(tag_list.to_s).to eq("awesome, radical, cool, \"rad,bodacious\"") end end describe '#remove' do it 'should be able to remove words' do tag_list.remove('awesome') expect(tag_list).to_not include('awesome') end it 'should be able to remove delimited lists of words' do tag_list.remove('awesome, radical', parse: true) expect(tag_list).to be_empty end it 'should be able to remove an array of words' do tag_list.remove(%w(awesome radical), parse: true) expect(tag_list).to be_empty end end describe '#+' do it 'should not have duplicate tags' do new_tag_list = tag_list + another_tag_list expect(tag_list).to eq(%w[awesome radical]) expect(another_tag_list).to eq(%w[awesome crazy alien]) expect(new_tag_list).to eq(%w[awesome radical crazy alien]) end it 'should have class : ActsAsTaggableOn::TagList' do new_tag_list = tag_list + another_tag_list expect(new_tag_list.class).to eq(ActsAsTaggableOn::TagList) end end describe '#concat' do it 'should not have duplicate tags' do expect(tag_list.concat(another_tag_list)).to eq(%w[awesome radical crazy alien]) end it 'should have class : ActsAsTaggableOn::TagList' do new_tag_list = tag_list.concat(another_tag_list) expect(new_tag_list.class).to eq(ActsAsTaggableOn::TagList) end end describe '#to_s' do it 'should give a delimited list of words when converted to string' do expect(tag_list.to_s).to eq('awesome, radical') end it 'should be able to call to_s on a frozen tag list' do tag_list.freeze expect(-> { tag_list.add('cool', 'rad,bodacious') }).to raise_error expect(-> { tag_list.to_s }).to_not raise_error end end describe 'cleaning' do it 'should parameterize if force_parameterize is set to true' do ActsAsTaggableOn.force_parameterize = true tag_list = ActsAsTaggableOn::TagList.new('awesome()', 'radical)(cc') expect(tag_list.to_s).to eq('awesome, radical-cc') ActsAsTaggableOn.force_parameterize = false end it 'should lowercase if force_lowercase is set to true' do ActsAsTaggableOn.force_lowercase = true tag_list = ActsAsTaggableOn::TagList.new('aweSomE', 'RaDicaL', 'Entrée') expect(tag_list.to_s).to eq('awesome, radical, entrée') ActsAsTaggableOn.force_lowercase = false end end describe 'custom parser' do let(:parser) { double(parse: %w(cool wicked)) } let(:parser_class) { stub_const('MyParser', Class) } it 'should use a the default parser if none is set as parameter' do allow(ActsAsTaggableOn.default_parser).to receive(:new).and_return(parser) ActsAsTaggableOn::TagList.new('cool, wicked', parse: true) expect(parser).to have_received(:parse) end it 'should use the custom parser passed as parameter' do allow(parser_class).to receive(:new).and_return(parser) ActsAsTaggableOn::TagList.new('cool, wicked', parser: parser_class) expect(parser).to have_received(:parse) end it 'should use the parser setted as attribute' do allow(parser_class).to receive(:new).with('new, tag').and_return(parser) tag_list = ActsAsTaggableOn::TagList.new('example') tag_list.parser = parser_class tag_list.add('new, tag', parse: true) expect(parser).to have_received(:parse) end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/taggable_spec.rb0000644000004100000410000007715712503660023025274 0ustar www-datawww-data# encoding: utf-8 require 'spec_helper' describe 'Taggable To Preserve Order' do before(:each) do @taggable = OrderedTaggableModel.new(name: 'Bob Jones') end it 'should have tag associations' do [:tags, :colours].each do |type| expect(@taggable.respond_to?(type)).to be_truthy expect(@taggable.respond_to?("#{type.to_s.singularize}_taggings")).to be_truthy end end it 'should have tag methods' do [:tags, :colours].each do |type| expect(@taggable.respond_to?("#{type.to_s.singularize}_list")).to be_truthy expect(@taggable.respond_to?("#{type.to_s.singularize}_list=")).to be_truthy expect(@taggable.respond_to?("all_#{type}_list")).to be_truthy end end it 'should return tag list in the order the tags were created' do # create @taggable.tag_list = 'rails, ruby, css' expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy expect(-> { @taggable.save }).to change(ActsAsTaggableOn::Tag, :count).by(3) @taggable.reload expect(@taggable.tag_list).to eq(%w(rails ruby css)) # update @taggable.tag_list = 'pow, ruby, rails' @taggable.save @taggable.reload expect(@taggable.tag_list).to eq(%w(pow ruby rails)) # update with no change @taggable.tag_list = 'pow, ruby, rails' @taggable.save @taggable.reload expect(@taggable.tag_list).to eq(%w(pow ruby rails)) # update to clear tags @taggable.tag_list = '' @taggable.save @taggable.reload expect(@taggable.tag_list).to be_empty end it 'should return tag objects in the order the tags were created' do # create @taggable.tag_list = 'pow, ruby, rails' expect(@taggable.instance_variable_get('@tag_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy expect(-> { @taggable.save }).to change(ActsAsTaggableOn::Tag, :count).by(3) @taggable.reload expect(@taggable.tags.map { |t| t.name }).to eq(%w(pow ruby rails)) # update @taggable.tag_list = 'rails, ruby, css, pow' @taggable.save @taggable.reload expect(@taggable.tags.map { |t| t.name }).to eq(%w(rails ruby css pow)) end it 'should return tag objects in tagging id order' do # create @taggable.tag_list = 'pow, ruby, rails' @taggable.save @taggable.reload ids = @taggable.tags.map { |t| t.taggings.first.id } expect(ids).to eq(ids.sort) # update @taggable.tag_list = 'rails, ruby, css, pow' @taggable.save @taggable.reload ids = @taggable.tags.map { |t| t.taggings.first.id } expect(ids).to eq(ids.sort) end end describe 'Taggable' do before(:each) do @taggable = TaggableModel.new(name: 'Bob Jones') @taggables = [@taggable, TaggableModel.new(name: 'John Doe')] end it 'should have tag types' do [:tags, :languages, :skills, :needs, :offerings].each do |type| expect(TaggableModel.tag_types).to include type end expect(@taggable.tag_types).to eq(TaggableModel.tag_types) end it 'should have tag_counts_on' do expect(TaggableModel.tag_counts_on(:tags)).to be_empty @taggable.tag_list = %w(awesome epic) @taggable.save expect(TaggableModel.tag_counts_on(:tags).length).to eq(2) expect(@taggable.tag_counts_on(:tags).length).to eq(2) end it 'should have tags_on' do expect(TaggableModel.tags_on(:tags)).to be_empty @taggable.tag_list = %w(awesome epic) @taggable.save expect(TaggableModel.tags_on(:tags).length).to eq(2) expect(@taggable.tags_on(:tags).length).to eq(2) end it 'should return [] right after create' do blank_taggable = TaggableModel.new(name: 'Bob Jones') expect(blank_taggable.tag_list).to be_empty end it 'should be able to create tags' do @taggable.skill_list = 'ruby, rails, css' expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy expect(-> { @taggable.save }).to change(ActsAsTaggableOn::Tag, :count).by(3) @taggable.reload expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort) end it 'should be able to create tags through the tag list directly' do @taggable.tag_list_on(:test).add('hello') expect(@taggable.tag_list_cache_on(:test)).to_not be_empty expect(@taggable.tag_list_on(:test)).to eq(['hello']) @taggable.save @taggable.save_tags @taggable.reload expect(@taggable.tag_list_on(:test)).to eq(['hello']) end it 'should differentiate between contexts' do @taggable.skill_list = 'ruby, rails, css' @taggable.tag_list = 'ruby, bob, charlie' @taggable.save @taggable.reload expect(@taggable.skill_list).to include('ruby') expect(@taggable.skill_list).to_not include('bob') end it 'should be able to remove tags through list alone' do @taggable.skill_list = 'ruby, rails, css' @taggable.save @taggable.reload expect(@taggable.skills.count).to eq(3) @taggable.skill_list = 'ruby, rails' @taggable.save @taggable.reload expect(@taggable.skills.count).to eq(2) end it 'should be able to select taggables by subset of tags using ActiveRelation methods' do @taggables[0].tag_list = 'bob' @taggables[1].tag_list = 'charlie' @taggables[0].skill_list = 'ruby' @taggables[1].skill_list = 'css' @taggables.each { |taggable| taggable.save } @found_taggables_by_tag = TaggableModel.joins(:tags).where(tags: {name: ['bob']}) @found_taggables_by_skill = TaggableModel.joins(:skills).where(tags: {name: ['ruby']}) expect(@found_taggables_by_tag).to include @taggables[0] expect(@found_taggables_by_tag).to_not include @taggables[1] expect(@found_taggables_by_skill).to include @taggables[0] expect(@found_taggables_by_skill).to_not include @taggables[1] end it 'should be able to find by tag' do @taggable.skill_list = 'ruby, rails, css' @taggable.save expect(TaggableModel.tagged_with('ruby').first).to eq(@taggable) end it 'should be able to get a count with find by tag when using a group by' do @taggable.skill_list = 'ruby' @taggable.save expect(TaggableModel.tagged_with('ruby').group(:created_at).count.count).to eq(1) end it 'can be used as scope' do @taggable.skill_list = 'ruby' @taggable.save untaggable_model = @taggable.untaggable_models.create!(name:'foobar') scope_tag = TaggableModel.tagged_with('ruby', any: 'distinct', order: 'taggable_models.name asc') expect(UntaggableModel.joins(:taggable_model).merge(scope_tag).except(:select)).to eq([untaggable_model]) end it "shouldn't generate a query with DISTINCT by default" do @taggable.skill_list = 'ruby, rails, css' @taggable.save expect(TaggableModel.tagged_with('ruby').to_sql).to_not match /DISTINCT/ end it "should be able to find a tag using dates" do @taggable.skill_list = "ruby" @taggable.save expect(TaggableModel.tagged_with("ruby", :start_at => Date.today, :end_at => Date.tomorrow).count).to eq(1) end it "shouldn't be able to find a tag outside date range" do @taggable.skill_list = "ruby" @taggable.save expect(TaggableModel.tagged_with("ruby", :start_at => Date.today - 2.days, :end_at => Date.today - 1.day).count).to eq(0) end it 'should be able to find by tag with context' do @taggable.skill_list = 'ruby, rails, css' @taggable.tag_list = 'bob, charlie' @taggable.save expect(TaggableModel.tagged_with('ruby').first).to eq(@taggable) expect(TaggableModel.tagged_with('ruby, css').first).to eq(@taggable) expect(TaggableModel.tagged_with('bob', on: :skills).first).to_not eq(@taggable) expect(TaggableModel.tagged_with('bob', on: :tags).first).to eq(@taggable) end it 'should not care about case' do TaggableModel.create(name: 'Bob', tag_list: 'ruby') TaggableModel.create(name: 'Frank', tag_list: 'Ruby') expect(ActsAsTaggableOn::Tag.all.size).to eq(1) expect(TaggableModel.tagged_with('ruby').to_a).to eq(TaggableModel.tagged_with('Ruby').to_a) end it 'should be able to find by tags with other joins in the query' do @taggable.skill_list = 'ruby, rails, css' @taggable.tag_list = 'bob, charlie' @taggable.save expect(TaggableModel.tagged_with(['bob', 'css'], :any => true).to_a).to eq([@taggable]) bob = TaggableModel.create(:name => 'Bob', :tag_list => 'ruby, rails, css') frank = TaggableModel.create(:name => 'Frank', :tag_list => 'ruby, rails') charlie = TaggableModel.create(:name => 'Charlie', :skill_list => 'ruby, java') # Test for explicit distinct in select bob.untaggable_models.create! frank.untaggable_models.create! charlie.untaggable_models.create! expect(TaggableModel.select('distinct(taggable_models.id), taggable_models.*').joins(:untaggable_models).tagged_with(['css', 'java'], :any => true).to_a.sort).to eq([bob, charlie].sort) expect(TaggableModel.select('distinct(taggable_models.id), taggable_models.*').joins(:untaggable_models).tagged_with(['rails', 'ruby'], :any => false).to_a.sort).to eq([bob, frank].sort) end it 'should not care about case for unicode names', unless: using_sqlite? do ActsAsTaggableOn.strict_case_match = false TaggableModel.create(name: 'Anya', tag_list: 'ПРИВЕТ') TaggableModel.create(name: 'Igor', tag_list: 'привет') TaggableModel.create(name: 'Katia', tag_list: 'ПРИВЕТ') expect(ActsAsTaggableOn::Tag.all.size).to eq(1) expect(TaggableModel.tagged_with('привет').to_a).to eq(TaggableModel.tagged_with('ПРИВЕТ').to_a) end context 'should be able to create and find tags in languages without capitalization :' do ActsAsTaggableOn.strict_case_match = false { japanese: {name: 'Chihiro', tag_list: '日本の'}, hebrew: {name: 'Salim', tag_list: 'עברית'}, chinese: {name: 'Ieie', tag_list: '中国的'}, arabic: {name: 'Yasser', tag_list: 'العربية'}, emo: {name: 'Emo', tag_list: '✏'} }.each do |language, values| it language do TaggableModel.create(values) expect(TaggableModel.tagged_with(values[:tag_list]).count).to eq(1) end end end it 'should be able to get tag counts on model as a whole' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Charlie', skill_list: 'ruby') expect(TaggableModel.tag_counts).to_not be_empty expect(TaggableModel.skill_counts).to_not be_empty end it 'should be able to get all tag counts on model as whole' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Charlie', skill_list: 'ruby') expect(TaggableModel.all_tag_counts).to_not be_empty expect(TaggableModel.all_tag_counts(order: 'tags.id').first.count).to eq(3) # ruby end it 'should be able to get all tags on model as whole' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Charlie', skill_list: 'ruby') expect(TaggableModel.all_tags).to_not be_empty expect(TaggableModel.all_tags(order: 'tags.id').first.name).to eq('ruby') end it 'should be able to use named scopes to chain tag finds by any tags by context' do bob = TaggableModel.create(name: 'Bob', need_list: 'rails', offering_list: 'c++') TaggableModel.create(name: 'Frank', need_list: 'css', offering_list: 'css') TaggableModel.create(name: 'Steve', need_list: 'c++', offering_list: 'java') # Let's only find those who need rails or css and are offering c++ or java expect(TaggableModel.tagged_with(['rails, css'], on: :needs, any: true).tagged_with(['c++', 'java'], on: :offerings, any: true).to_a).to eq([bob]) end it 'should not return read-only records' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') expect(TaggableModel.tagged_with('ruby').first).to_not be_readonly end it 'should be able to get scoped tag counts' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Charlie', skill_list: 'ruby') expect(TaggableModel.tagged_with('ruby').tag_counts(order: 'tags.id').first.count).to eq(2) # ruby expect(TaggableModel.tagged_with('ruby').skill_counts.first.count).to eq(1) # ruby end it 'should be able to get all scoped tag counts' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Charlie', skill_list: 'ruby') expect(TaggableModel.tagged_with('ruby').all_tag_counts(order: 'tags.id').first.count).to eq(3) # ruby end it 'should be able to get all scoped tags' do TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Charlie', skill_list: 'ruby') expect(TaggableModel.tagged_with('ruby').all_tags(order: 'tags.id').first.name).to eq('ruby') end it 'should only return tag counts for the available scope' do frank = TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Charlie', skill_list: 'ruby, java') expect(TaggableModel.tagged_with('rails').all_tag_counts.size).to eq(3) expect(TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.name == 'java' }).to be_falsy # Test specific join syntaxes: frank.untaggable_models.create! expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tag_counts.size).to eq(2) expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tag_counts.size).to eq(2) expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tag_counts.size).to eq(2) end it 'should only return tags for the available scope' do frank = TaggableModel.create(name: 'Frank', tag_list: 'ruby, rails') TaggableModel.create(name: 'Bob', tag_list: 'ruby, rails, css') TaggableModel.create(name: 'Charlie', skill_list: 'ruby, java') expect(TaggableModel.tagged_with('rails').all_tags.count).to eq(3) expect(TaggableModel.tagged_with('rails').all_tags.any? { |tag| tag.name == 'java' }).to be_falsy # Test specific join syntaxes: frank.untaggable_models.create! expect(TaggableModel.tagged_with('rails').joins(:untaggable_models).all_tags.size).to eq(2) expect(TaggableModel.tagged_with('rails').joins(untaggable_models: :taggable_model).all_tags.size).to eq(2) expect(TaggableModel.tagged_with('rails').joins([:untaggable_models]).all_tags.size).to eq(2) end it 'should be able to set a custom tag context list' do bob = TaggableModel.create(name: 'Bob') bob.set_tag_list_on(:rotors, 'spinning, jumping') expect(bob.tag_list_on(:rotors)).to eq(%w(spinning jumping)) bob.save bob.reload expect(bob.tags_on(:rotors)).to_not be_empty end it 'should be able to find tagged' do bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css') frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css') steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby') expect(TaggableModel.tagged_with('ruby', order: 'taggable_models.name').to_a).to eq([bob, frank, steve]) expect(TaggableModel.tagged_with('ruby, rails', order: 'taggable_models.name').to_a).to eq([bob, frank]) expect(TaggableModel.tagged_with(%w(ruby rails), order: 'taggable_models.name').to_a).to eq([bob, frank]) end it 'should be able to find tagged with quotation marks' do bob = TaggableModel.create(name: 'Bob', tag_list: "fitter, happier, more productive, 'I love the ,comma,'") expect(TaggableModel.tagged_with("'I love the ,comma,'")).to include(bob) end it 'should be able to find tagged with invalid tags' do bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive') expect(TaggableModel.tagged_with('sad, happier')).to_not include(bob) end it 'should be able to find tagged with any tag' do bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css') frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css') steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby') expect(TaggableModel.tagged_with(%w(ruby java), order: 'taggable_models.name', any: true).to_a).to eq([bob, frank, steve]) expect(TaggableModel.tagged_with(%w(c++ fitter), order: 'taggable_models.name', any: true).to_a).to eq([bob, steve]) expect(TaggableModel.tagged_with(%w(depressed css), order: 'taggable_models.name', any: true).to_a).to eq([bob, frank]) end it 'should be able to order by number of matching tags when matching any' do bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css') frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css') steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, ruby') expect(TaggableModel.tagged_with(%w(ruby java), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank]) expect(TaggableModel.tagged_with(%w(c++ fitter), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob]) expect(TaggableModel.tagged_with(%w(depressed css), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([frank, bob]) expect(TaggableModel.tagged_with(['fitter', 'happier', 'more productive', 'c++', 'java', 'ruby'], any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank]) expect(TaggableModel.tagged_with(%w(c++ java ruby fitter), any: true, order_by_matching_tag_count: true, order: 'taggable_models.name').to_a).to eq([steve, bob, frank]) end context 'wild: true' do it 'should use params as wildcards' do bob = TaggableModel.create(name: 'Bob', tag_list: 'bob, tricia') frank = TaggableModel.create(name: 'Frank', tag_list: 'bobby, jim') steve = TaggableModel.create(name: 'Steve', tag_list: 'john, patricia') jim = TaggableModel.create(name: 'Jim', tag_list: 'jim, steve') expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, any: true).to_a.sort_by { |o| o.id }).to eq([bob, frank, steve]) expect(TaggableModel.tagged_with(%w(bob tricia), wild: true, exclude: true).to_a).to eq([jim]) expect(TaggableModel.tagged_with('ji', wild: true, any: true).to_a).to eq([frank, jim]) end end it 'should be able to find tagged on a custom tag context' do bob = TaggableModel.create(name: 'Bob') bob.set_tag_list_on(:rotors, 'spinning, jumping') expect(bob.tag_list_on(:rotors)).to eq(%w(spinning jumping)) bob.save expect(TaggableModel.tagged_with('spinning', on: :rotors).to_a).to eq([bob]) end it 'should be able to use named scopes to chain tag finds' do bob = TaggableModel.create(name: 'Bob', tag_list: 'fitter, happier, more productive', skill_list: 'ruby, rails, css') frank = TaggableModel.create(name: 'Frank', tag_list: 'weaker, depressed, inefficient', skill_list: 'ruby, rails, css') steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier, more productive', skill_list: 'c++, java, python') # Let's only find those productive Rails developers expect(TaggableModel.tagged_with('rails', on: :skills, order: 'taggable_models.name').to_a).to eq([bob, frank]) expect(TaggableModel.tagged_with('happier', on: :tags, order: 'taggable_models.name').to_a).to eq([bob, steve]) expect(TaggableModel.tagged_with('rails', on: :skills).tagged_with('happier', on: :tags).to_a).to eq([bob]) expect(TaggableModel.tagged_with('rails').tagged_with('happier', on: :tags).to_a).to eq([bob]) end it 'should be able to find tagged with only the matching tags' do TaggableModel.create(name: 'Bob', tag_list: 'lazy, happier') TaggableModel.create(name: 'Frank', tag_list: 'fitter, happier, inefficient') steve = TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier') expect(TaggableModel.tagged_with('fitter, happier', match_all: true).to_a).to eq([steve]) end it 'should be able to find tagged with only the matching tags for a context' do TaggableModel.create(name: 'Bob', tag_list: 'lazy, happier', skill_list: 'ruby, rails, css') frank = TaggableModel.create(name: 'Frank', tag_list: 'fitter, happier, inefficient', skill_list: 'css') TaggableModel.create(name: 'Steve', tag_list: 'fitter, happier', skill_list: 'ruby, rails, css') expect(TaggableModel.tagged_with('css', on: :skills, match_all: true).to_a).to eq([frank]) end it 'should be able to find tagged with some excluded tags' do TaggableModel.create(name: 'Bob', tag_list: 'happier, lazy') frank = TaggableModel.create(name: 'Frank', tag_list: 'happier') steve = TaggableModel.create(name: 'Steve', tag_list: 'happier') expect(TaggableModel.tagged_with('lazy', exclude: true)).to include(frank, steve) expect(TaggableModel.tagged_with('lazy', exclude: true).size).to eq(2) end it 'should return an empty scope for empty tags' do ['', ' ', nil, []].each do |tag| expect(TaggableModel.tagged_with(tag)).to be_empty end end it 'should options key not be deleted' do options = {:exclude => true} TaggableModel.tagged_with("foo", options) expect(options).to eq({:exclude => true}) end it 'should not delete tags if not updated' do model = TaggableModel.create(name: 'foo', tag_list: 'ruby, rails, programming') model.update_attributes(name: 'bar') model.reload expect(model.tag_list.sort).to eq(%w(ruby rails programming).sort) end context 'Duplicates' do context 'should not create duplicate taggings' do let(:bob) { TaggableModel.create(name: 'Bob') } context 'case sensitive' do it '#add' do expect(lambda { bob.tag_list.add 'happier' bob.tag_list.add 'happier' bob.tag_list.add 'happier', 'rich', 'funny' bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(3) end it '#<<' do expect(lambda { bob.tag_list << 'social' bob.tag_list << 'social' bob.tag_list << 'social' << 'wow' bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(2) end it 'unicode' do expect(lambda { bob.tag_list.add 'ПРИВЕТ' bob.tag_list.add 'ПРИВЕТ' bob.tag_list.add 'ПРИВЕТ', 'ПРИВЕТ' bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(1) end it '#=' do expect(lambda { bob.tag_list = ['Happy', 'Happy'] bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(1) end end context 'case insensitive' do before(:all) { ActsAsTaggableOn.force_lowercase = true } after(:all) { ActsAsTaggableOn.force_lowercase = false } it '#<<' do expect(lambda { bob.tag_list << 'Alone' bob.tag_list << 'AloNe' bob.tag_list << 'ALONE' << 'In The dark' bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(2) end it '#add' do expect(lambda { bob.tag_list.add 'forever' bob.tag_list.add 'ForEver' bob.tag_list.add 'FOREVER', 'ALONE' bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(2) end it 'unicode' do expect(lambda { bob.tag_list.add 'ПРИВЕТ' bob.tag_list.add 'привет', 'Привет' bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(1) end it '#=' do expect(lambda { bob.tag_list = ['Happy', 'HAPPY'] bob.save }).to change(ActsAsTaggableOn::Tagging, :count).by(1) end end end xit 'should not duplicate tags added on different threads', if: supports_concurrency?, skip: 'FIXME, Deadlocks in travis' do #TODO, try with more threads and fix deadlock thread_count = 4 barrier = Barrier.new thread_count expect { thread_count.times.map do |idx| Thread.start do connor = TaggableModel.first_or_create(name: 'Connor') connor.tag_list = 'There, can, be, only, one' barrier.wait begin connor.save rescue ActsAsTaggableOn::DuplicateTagError # second save should succeed connor.save end end end.map(&:join) }.to change(ActsAsTaggableOn::Tag, :count).by(5) end end describe 'Associations' do before(:each) do @taggable = TaggableModel.create(tag_list: 'awesome, epic') end it 'should not remove tags when creating associated objects' do @taggable.untaggable_models.create! @taggable.reload expect(@taggable.tag_list.size).to eq(2) end end describe 'grouped_column_names_for method' do it 'should return all column names joined for Tag GROUP clause' do # NOTE: type column supports an STI Tag subclass in the test suite, though # isn't included by default in the migration generator expect(@taggable.grouped_column_names_for(ActsAsTaggableOn::Tag)) .to eq('tags.id, tags.name, tags.taggings_count, tags.type') end it 'should return all column names joined for TaggableModel GROUP clause' do expect(@taggable.grouped_column_names_for(TaggableModel)).to eq('taggable_models.id, taggable_models.name, taggable_models.type') end it 'should return all column names joined for NonStandardIdTaggableModel GROUP clause' do expect(@taggable.grouped_column_names_for(TaggableModel)).to eq("taggable_models.#{TaggableModel.primary_key}, taggable_models.name, taggable_models.type") end end describe 'NonStandardIdTaggable' do before(:each) do @taggable = NonStandardIdTaggableModel.new(name: 'Bob Jones') @taggables = [@taggable, NonStandardIdTaggableModel.new(name: 'John Doe')] end it 'should have tag types' do [:tags, :languages, :skills, :needs, :offerings].each do |type| expect(NonStandardIdTaggableModel.tag_types).to include type end expect(@taggable.tag_types).to eq(NonStandardIdTaggableModel.tag_types) end it 'should have tag_counts_on' do expect(NonStandardIdTaggableModel.tag_counts_on(:tags)).to be_empty @taggable.tag_list = %w(awesome epic) @taggable.save expect(NonStandardIdTaggableModel.tag_counts_on(:tags).length).to eq(2) expect(@taggable.tag_counts_on(:tags).length).to eq(2) end it 'should have tags_on' do expect(NonStandardIdTaggableModel.tags_on(:tags)).to be_empty @taggable.tag_list = %w(awesome epic) @taggable.save expect(NonStandardIdTaggableModel.tags_on(:tags).length).to eq(2) expect(@taggable.tags_on(:tags).length).to eq(2) end it 'should be able to create tags' do @taggable.skill_list = 'ruby, rails, css' expect(@taggable.instance_variable_get('@skill_list').instance_of?(ActsAsTaggableOn::TagList)).to be_truthy expect(-> { @taggable.save }).to change(ActsAsTaggableOn::Tag, :count).by(3) @taggable.reload expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort) end it 'should be able to create tags through the tag list directly' do @taggable.tag_list_on(:test).add('hello') expect(@taggable.tag_list_cache_on(:test)).to_not be_empty expect(@taggable.tag_list_on(:test)).to eq(['hello']) @taggable.save @taggable.save_tags @taggable.reload expect(@taggable.tag_list_on(:test)).to eq(['hello']) end end describe 'Autogenerated methods' do it 'should be overridable' do expect(TaggableModel.create(tag_list: 'woo').tag_list_submethod_called).to be_truthy end end # See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details context 'tag_counts and aggreating scopes, compatability with MySQL ' do before(:each) do TaggableModel.new(:name => 'Barb Jones').tap { |t| t.tag_list = %w(awesome fun) }.save TaggableModel.new(:name => 'John Doe').tap { |t| t.tag_list = %w(cool fun hella) }.save TaggableModel.new(:name => 'Jo Doe').tap { |t| t.tag_list = %w(curious young naive sharp) }.save TaggableModel.all.each { |t| t.save } end context 'Model.limit(x).tag_counts.sum(:tags_count)' do it 'should not break on Mysql' do # Activerecord 3.2 return a string expect(TaggableModel.limit(2).tag_counts.sum('tags_count').to_i).to eq(5) end end context 'regression prevention, just making sure these esoteric queries still work' do context 'Model.tag_counts.limit(x)' do it 'should limit the tag objects (not very useful, of course)' do array_of_tag_counts = TaggableModel.tag_counts.limit(2) expect(array_of_tag_counts.count).to eq(2) end end context 'Model.tag_counts.sum(:tags_count)' do it 'should limit the total tags used' do expect(TaggableModel.tag_counts.sum(:tags_count).to_i).to eq(9) end end context 'Model.tag_counts.limit(2).sum(:tags_count)' do it 'limit should have no effect; this is just a sanity check' do expect(TaggableModel.tag_counts.limit(2).sum(:tags_count).to_i).to eq(9) end end end end end describe 'Taggable model with json columns', if: postgresql_support_json? do before(:each) do @taggable = TaggableModelWithJson.new(:name => 'Bob Jones') @taggables = [@taggable, TaggableModelWithJson.new(:name => 'John Doe')] end it 'should be able to find by tag with context' do @taggable.skill_list = 'ruby, rails, css' @taggable.tag_list = 'bob, charlie' @taggable.save expect(TaggableModelWithJson.tagged_with('ruby').first).to eq(@taggable) expect(TaggableModelWithJson.tagged_with('ruby, css').first).to eq(@taggable) expect(TaggableModelWithJson.tagged_with('bob', :on => :skills).first).to_not eq(@taggable) expect(TaggableModelWithJson.tagged_with('bob', :on => :tags).first).to eq(@taggable) end it 'should be able to find tagged with any tag' do bob = TaggableModelWithJson.create(:name => 'Bob', :tag_list => 'fitter, happier, more productive', :skill_list => 'ruby, rails, css') frank = TaggableModelWithJson.create(:name => 'Frank', :tag_list => 'weaker, depressed, inefficient', :skill_list => 'ruby, rails, css') steve = TaggableModelWithJson.create(:name => 'Steve', :tag_list => 'fitter, happier, more productive', :skill_list => 'c++, java, ruby') expect(TaggableModelWithJson.tagged_with(%w(ruby java), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, frank, steve]) expect(TaggableModelWithJson.tagged_with(%w(c++ fitter), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, steve]) expect(TaggableModelWithJson.tagged_with(%w(depressed css), :order => 'taggable_model_with_jsons.name', :any => true).to_a).to eq([bob, frank]) end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/related_spec.rb0000644000004100000410000001045312503660023025130 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe 'Acts As Taggable On' do describe 'Related Objects' do #TODO, shared example it 'should find related objects based on tag names on context' do taggable1 = TaggableModel.create!(name: 'Taggable 1',tag_list: 'one, two') taggable2 = TaggableModel.create!(name: 'Taggable 2',tag_list: 'three, four') taggable3 = TaggableModel.create!(name: 'Taggable 3',tag_list: 'one, four') expect(taggable1.find_related_tags).to include(taggable3) expect(taggable1.find_related_tags).to_not include(taggable2) end it 'finds related tags for ordered taggable on' do taggable1 = OrderedTaggableModel.create!(name: 'Taggable 1',colour_list: 'one, two') taggable2 = OrderedTaggableModel.create!(name: 'Taggable 2',colour_list: 'three, four') taggable3 = OrderedTaggableModel.create!(name: 'Taggable 3',colour_list: 'one, four') expect(taggable1.find_related_colours).to include(taggable3) expect(taggable1.find_related_colours).to_not include(taggable2) end it 'should find related objects based on tag names on context - non standard id' do taggable1 = NonStandardIdTaggableModel.create!(name: 'Taggable 1',tag_list: 'one, two') taggable2 = NonStandardIdTaggableModel.create!(name: 'Taggable 2',tag_list: 'three, four') taggable3 = NonStandardIdTaggableModel.create!(name: 'Taggable 3',tag_list: 'one, four') expect(taggable1.find_related_tags).to include(taggable3) expect(taggable1.find_related_tags).to_not include(taggable2) end it 'should find other related objects based on tag names on context' do taggable1 = TaggableModel.create!(name: 'Taggable 1',tag_list: 'one, two') taggable2 = OtherTaggableModel.create!(name: 'Taggable 2',tag_list: 'three, four') taggable3 = OtherTaggableModel.create!(name: 'Taggable 3',tag_list: 'one, four') expect(taggable1.find_related_tags_for(OtherTaggableModel)).to include(taggable3) expect(taggable1.find_related_tags_for(OtherTaggableModel)).to_not include(taggable2) end it 'should find other related objects based on tags only from particular context' do taggable1 = TaggableModel.create!(name: 'Taggable 1',tag_list: 'one, two') taggable2 = TaggableModel.create!(name: 'Taggable 2',tag_list: 'three, four', skill_list: 'one, two') taggable3 = TaggableModel.create!(name: 'Taggable 3',tag_list: 'one, four') expect(taggable1.find_related_tags).to include(taggable3) expect(taggable1.find_related_tags).to_not include(taggable2) end shared_examples "a collection" do it do taggable1 = described_class.create!(name: 'Taggable 1', tag_list: 'one') taggable2 = described_class.create!(name: 'Taggable 2', tag_list: 'one, two') expect(taggable1.find_related_tags).to include(taggable2) expect(taggable1.find_related_tags).to_not include(taggable1) end end # it 'should not include the object itself in the list of related objects' do describe TaggableModel do it_behaves_like "a collection" end # it 'should not include the object itself in the list of related objects - non standard id' do describe NonStandardIdTaggableModel do it_behaves_like "a collection" end context 'Ignored Tags' do let(:taggable1) { TaggableModel.create!(name: 'Taggable 1', tag_list: 'one, two, four') } let(:taggable2) { TaggableModel.create!(name: 'Taggable 2', tag_list: 'two, three') } let(:taggable3) { TaggableModel.create!(name: 'Taggable 3', tag_list: 'one, three') } it 'should not include ignored tags in related search' do expect(taggable1.find_related_tags(ignore: 'two')).to_not include(taggable2) expect(taggable1.find_related_tags(ignore: 'two')).to include(taggable3) end it 'should accept array of ignored tags' do taggable4 = TaggableModel.create!(name: 'Taggable 4', tag_list: 'four') expect(taggable1.find_related_tags(ignore: ['two', 'four'])).to_not include(taggable2) expect(taggable1.find_related_tags(ignore: ['two', 'four'])).to_not include(taggable4) end it 'should accept symbols as ignored tags' do expect(taggable1.find_related_tags(ignore: :two)).to_not include(taggable2) end end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/single_table_inheritance_spec.rb0000644000004100000410000002004012503660023030502 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe 'Single Table Inheritance' do let(:taggable) { TaggableModel.new(name: 'taggable model') } let(:inheriting_model) { InheritingTaggableModel.new(name: 'Inheriting Taggable Model') } let(:altered_inheriting) { AlteredInheritingTaggableModel.new(name: 'Altered Inheriting Model') } 1.upto(4) do |n| let(:"inheriting_#{n}") { InheritingTaggableModel.new(name: "Inheriting Model #{n}") } end let(:student) { Student.create! } describe 'tag contexts' do it 'should pass on to STI-inherited models' do expect(inheriting_model).to respond_to(:tag_list, :skill_list, :language_list) expect(altered_inheriting).to respond_to(:tag_list, :skill_list, :language_list) end it 'should pass on to altered STI models' do expect(altered_inheriting).to respond_to(:part_list) end end context 'matching contexts' do before do inheriting_1.offering_list = 'one, two' inheriting_1.need_list = 'one, two' inheriting_1.save! inheriting_2.need_list = 'one, two' inheriting_2.save! inheriting_3.offering_list = 'one, two' inheriting_3.save! inheriting_4.tag_list = 'one, two, three, four' inheriting_4.save! taggable.need_list = 'one, two' taggable.save! end it 'should find objects with tags of matching contexts' do expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to include(inheriting_2) expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(inheriting_3) expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(inheriting_4) expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(taggable) expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to include(inheriting_2) expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(inheriting_3) expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(inheriting_4) expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to include(taggable) end it 'should not include the object itself in the list of related objects with tags of matching contexts' do expect(inheriting_1.find_matching_contexts(:offerings, :needs)).to_not include(inheriting_1) expect(inheriting_1.find_matching_contexts_for(InheritingTaggableModel, :offerings, :needs)).to_not include(inheriting_1) expect(inheriting_1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(inheriting_1) end end context 'find related tags' do before do inheriting_1.tag_list = 'one, two' inheriting_1.save inheriting_2.tag_list = 'three, four' inheriting_2.save inheriting_3.tag_list = 'one, four' inheriting_3.save taggable.tag_list = 'one, two, three, four' taggable.save end it 'should find related objects based on tag names on context' do expect(inheriting_1.find_related_tags).to include(inheriting_3) expect(inheriting_1.find_related_tags).to_not include(inheriting_2) expect(inheriting_1.find_related_tags).to_not include(taggable) expect(inheriting_1.find_related_tags_for(TaggableModel)).to include(inheriting_3) expect(inheriting_1.find_related_tags_for(TaggableModel)).to_not include(inheriting_2) expect(inheriting_1.find_related_tags_for(TaggableModel)).to include(taggable) end it 'should not include the object itself in the list of related objects' do expect(inheriting_1.find_related_tags).to_not include(inheriting_1) expect(inheriting_1.find_related_tags_for(InheritingTaggableModel)).to_not include(inheriting_1) expect(inheriting_1.find_related_tags_for(TaggableModel)).to_not include(inheriting_1) end end describe 'tag list' do before do @inherited_same = InheritingTaggableModel.new(name: 'inherited same') @inherited_different = AlteredInheritingTaggableModel.new(name: 'inherited different') end #TODO, shared example it 'should be able to save tags for inherited models' do inheriting_model.tag_list = 'bob, kelso' inheriting_model.save expect(InheritingTaggableModel.tagged_with('bob').first).to eq(inheriting_model) end it 'should find STI tagged models on the superclass' do inheriting_model.tag_list = 'bob, kelso' inheriting_model.save expect(TaggableModel.tagged_with('bob').first).to eq(inheriting_model) end it 'should be able to add on contexts only to some subclasses' do altered_inheriting.part_list = 'fork, spoon' altered_inheriting.save expect(InheritingTaggableModel.tagged_with('fork', on: :parts)).to be_empty expect(AlteredInheritingTaggableModel.tagged_with('fork', on: :parts).first).to eq(altered_inheriting) end it 'should have different tag_counts_on for inherited models' do inheriting_model.tag_list = 'bob, kelso' inheriting_model.save! altered_inheriting.tag_list = 'fork, spoon' altered_inheriting.save! expect(InheritingTaggableModel.tag_counts_on(:tags, order: 'tags.id').map(&:name)).to eq(%w(bob kelso)) expect(AlteredInheritingTaggableModel.tag_counts_on(:tags, order: 'tags.id').map(&:name)).to eq(%w(fork spoon)) expect(TaggableModel.tag_counts_on(:tags, order: 'tags.id').map(&:name)).to eq(%w(bob kelso fork spoon)) end it 'should have different tags_on for inherited models' do inheriting_model.tag_list = 'bob, kelso' inheriting_model.save! altered_inheriting.tag_list = 'fork, spoon' altered_inheriting.save! expect(InheritingTaggableModel.tags_on(:tags, order: 'tags.id').map(&:name)).to eq(%w(bob kelso)) expect(AlteredInheritingTaggableModel.tags_on(:tags, order: 'tags.id').map(&:name)).to eq(%w(fork spoon)) expect(TaggableModel.tags_on(:tags, order: 'tags.id').map(&:name)).to eq(%w(bob kelso fork spoon)) end it 'should store same tag without validation conflict' do taggable.tag_list = 'one' taggable.save! inheriting_model.tag_list = 'one' inheriting_model.save! inheriting_model.update_attributes! name: 'foo' end end describe 'ownership' do it 'should have taggings' do student.tag(taggable, with: 'ruby,scheme', on: :tags) expect(student.owned_taggings.count).to eq(2) end it 'should have tags' do student.tag(taggable, with: 'ruby,scheme', on: :tags) expect(student.owned_tags.count).to eq(2) end it 'should return tags for the inheriting tagger' do student.tag(taggable, with: 'ruby, scheme', on: :tags) expect(taggable.tags_from(student)).to eq(%w(ruby scheme)) end it 'returns owner tags on the tagger' do student.tag(taggable, with: 'ruby, scheme', on: :tags) expect(taggable.owner_tags_on(student, :tags).count).to eq(2) end it 'should scope objects returned by tagged_with by owners' do student.tag(taggable, with: 'ruby, scheme', on: :tags) expect(TaggableModel.tagged_with(%w(ruby scheme), owned_by: student).count).to eq(1) end end describe 'a subclass of Tag' do let(:company) { Company.new(:name => 'Dewey, Cheatham & Howe') } let(:user) { User.create! } subject { Market.create! :name => 'finance' } its(:type) { should eql 'Market' } it 'sets STI type through string list' do company.market_list = 'law, accounting' company.save! expect(Market.count).to eq(2) end it 'does not interfere with a normal Tag context on the same model' do company.location_list = 'cambridge' company.save! expect(ActsAsTaggableOn::Tag.where(name: 'cambridge', type: nil)).to_not be_empty end it 'is returned with proper type through ownership' do user.tag(company, :with => 'ripoffs, rackets', :on => :markets) tags = company.owner_tags_on(user, :markets) expect(tags.all? { |tag| tag.is_a? Market }).to be_truthy end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/tag_spec.rb0000644000004100000410000002431112503660023024261 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' require 'db/migrate/2_add_missing_unique_indices.rb' shared_examples_for 'without unique index' do prepend_before(:all) { AddMissingUniqueIndices.down } append_after(:all) do ActsAsTaggableOn::Tag.delete_all AddMissingUniqueIndices.up end end describe ActsAsTaggableOn::Tag do before(:each) do @tag = ActsAsTaggableOn::Tag.new @user = TaggableModel.create(name: 'Pablo') end describe 'named like any' do context 'case insensitive collation and unique index on tag name', if: using_case_insensitive_collation? do before(:each) do ActsAsTaggableOn::Tag.create(name: 'Awesome') ActsAsTaggableOn::Tag.create(name: 'epic') end it 'should find both tags' do expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(2) end end context 'case insensitive collation without indexes or case sensitive collation with indexes' do if using_case_insensitive_collation? include_context 'without unique index' end before(:each) do ActsAsTaggableOn::Tag.create(name: 'Awesome') ActsAsTaggableOn::Tag.create(name: 'awesome') ActsAsTaggableOn::Tag.create(name: 'epic') end it 'should find both tags' do expect(ActsAsTaggableOn::Tag.named_like_any(%w(awesome epic)).count).to eq(3) end end end describe 'named any' do context 'with some special characters combinations', if: using_mysql? do it 'should not raise an invalid encoding exception' do expect{ActsAsTaggableOn::Tag.named_any(["holä", "hol'ä"])}.not_to raise_error end end end describe 'find or create by name' do before(:each) do @tag.name = 'awesome' @tag.save end it 'should find by name' do expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag) end it 'should find by name case insensitive' do expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME')).to eq(@tag) end it 'should create by name' do expect(-> { ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('epic') }).to change(ActsAsTaggableOn::Tag, :count).by(1) end end describe 'find or create by unicode name', unless: using_sqlite? do before(:each) do @tag.name = 'привет' @tag.save end it 'should find by name' do expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('привет')).to eq(@tag) end it 'should find by name case insensitive' do expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('ПРИВЕТ')).to eq(@tag) end it 'should find by name accent insensitive', if: using_case_insensitive_collation? do @tag.name = 'inupiat' @tag.save expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('Iñupiat')).to eq(@tag) end end describe 'find or create all by any name' do before(:each) do @tag.name = 'awesome' @tag.save end it 'should find by name' do expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome')).to eq([@tag]) end it 'should find by name case insensitive' do expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME')).to eq([@tag]) end context 'case sensitive' do if using_case_insensitive_collation? include_context 'without unique index' end it 'should find by name case sensitive' do ActsAsTaggableOn.strict_case_match = true expect { ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME') }.to change(ActsAsTaggableOn::Tag, :count).by(1) end end it 'should create by name' do expect { ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('epic') }.to change(ActsAsTaggableOn::Tag, :count).by(1) end context 'case sensitive' do if using_case_insensitive_collation? include_context 'without unique index' end it 'should find or create by name case sensitive' do ActsAsTaggableOn.strict_case_match = true expect { expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('AWESOME', 'awesome').map(&:name)).to eq(%w(AWESOME awesome)) }.to change(ActsAsTaggableOn::Tag, :count).by(1) end end it 'should find or create by name' do expect { expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name('awesome', 'epic').map(&:name)).to eq(%w(awesome epic)) }.to change(ActsAsTaggableOn::Tag, :count).by(1) end it 'should return an empty array if no tags are specified' do expect(ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name([])).to be_empty end end it 'should require a name' do @tag.valid? #TODO, we should find another way to check this expect(@tag.errors[:name]).to eq(["can't be blank"]) @tag.name = 'something' @tag.valid? expect(@tag.errors[:name]).to be_empty end it 'should limit the name length to 255 or less characters' do @tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxranr' @tag.valid? #TODO, we should find another way to check this expect(@tag.errors[:name]).to eq(['is too long (maximum is 255 characters)']) @tag.name = 'fgkgnkkgjymkypbuozmwwghblmzpqfsgjasflblywhgkwndnkzeifalfcpeaeqychjuuowlacmuidnnrkprgpcpybarbkrmziqihcrxirlokhnzfvmtzixgvhlxzncyywficpraxfnjptxxhkqmvicbcdcynkjvziefqzyndxkjmsjlvyvbwraklbalykyxoliqdlreeykuphdtmzfdwpphmrqvwvqffojkqhlzvinqajsxbszyvrqqyzusxran' @tag.valid? expect(@tag.errors[:name]).to be_empty end it 'should equal a tag with the same name' do @tag.name = 'awesome' new_tag = ActsAsTaggableOn::Tag.new(name: 'awesome') expect(new_tag).to eq(@tag) end it 'should return its name when to_s is called' do @tag.name = 'cool' expect(@tag.to_s).to eq('cool') end it 'have named_scope named(something)' do @tag.name = 'cool' @tag.save! expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag) end it 'have named_scope named_like(something)' do @tag.name = 'cool' @tag.save! @another_tag = ActsAsTaggableOn::Tag.create!(name: 'coolip') expect(ActsAsTaggableOn::Tag.named_like('cool')).to include(@tag, @another_tag) end describe 'escape wildcard symbols in like requests' do before(:each) do @tag.name = 'cool' @tag.save @another_tag = ActsAsTaggableOn::Tag.create!(name: 'coo%') @another_tag2 = ActsAsTaggableOn::Tag.create!(name: 'coolish') end it "return escaped result when '%' char present in tag" do expect(ActsAsTaggableOn::Tag.named_like('coo%')).to_not include(@tag) expect(ActsAsTaggableOn::Tag.named_like('coo%')).to include(@another_tag) end end describe 'when using strict_case_match' do before do ActsAsTaggableOn.strict_case_match = true @tag.name = 'awesome' @tag.save! end after do ActsAsTaggableOn.strict_case_match = false end it 'should find by name' do expect(ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('awesome')).to eq(@tag) end context 'case sensitive' do if using_case_insensitive_collation? include_context 'without unique index' end it 'should find by name case sensitively' do expect { ActsAsTaggableOn::Tag.find_or_create_with_like_by_name('AWESOME') }.to change(ActsAsTaggableOn::Tag, :count) expect(ActsAsTaggableOn::Tag.last.name).to eq('AWESOME') end end context 'case sensitive' do if using_case_insensitive_collation? include_context 'without unique index' end it 'should have a named_scope named(something) that matches exactly' do uppercase_tag = ActsAsTaggableOn::Tag.create(name: 'Cool') @tag.name = 'cool' @tag.save! expect(ActsAsTaggableOn::Tag.named('cool')).to include(@tag) expect(ActsAsTaggableOn::Tag.named('cool')).to_not include(uppercase_tag) end end it 'should not change encoding' do name = "\u3042" original_encoding = name.encoding record = ActsAsTaggableOn::Tag.find_or_create_with_like_by_name(name) record.reload expect(record.name.encoding).to eq(original_encoding) end context 'named any with some special characters combinations', if: using_mysql? do it 'should not raise an invalid encoding exception' do expect{ActsAsTaggableOn::Tag.named_any(["holä", "hol'ä"])}.not_to raise_error end end end describe 'name uniqeness validation' do let(:duplicate_tag) { ActsAsTaggableOn::Tag.new(name: 'ror') } before { ActsAsTaggableOn::Tag.create(name: 'ror') } context "when don't need unique names" do include_context 'without unique index' it 'should not run uniqueness validation' do allow(duplicate_tag).to receive(:validates_name_uniqueness?) { false } duplicate_tag.save expect(duplicate_tag).to be_persisted end end context 'when do need unique names' do it 'should run uniqueness validation' do expect(duplicate_tag).to_not be_valid end it 'add error to name' do duplicate_tag.save expect(duplicate_tag.errors.size).to eq(1) expect(duplicate_tag.errors.messages[:name]).to include('has already been taken') end end end describe 'popular tags' do before do %w(sports rails linux tennis golden_syrup).each_with_index do |t, i| tag = ActsAsTaggableOn::Tag.new(name: t) tag.taggings_count = i tag.save! end end it 'should find the most popular tags' do expect(ActsAsTaggableOn::Tag.most_used(3).first.name).to eq("golden_syrup") expect(ActsAsTaggableOn::Tag.most_used(3).length).to eq(3) end it 'should find the least popular tags' do expect(ActsAsTaggableOn::Tag.least_used(3).first.name).to eq("sports") expect(ActsAsTaggableOn::Tag.least_used(3).length).to eq(3) end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/acts_as_tagger_spec.rb0000644000004100000410000000736212503660023026463 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe 'acts_as_tagger' do describe 'Tagger Method Generation' do before(:each) do @tagger = User.new end it 'should add #is_tagger? query method to the class-side' do expect(User).to respond_to(:is_tagger?) end it 'should return true from the class-side #is_tagger?' do expect(User.is_tagger?).to be_truthy end it 'should return false from the base #is_tagger?' do expect(ActiveRecord::Base.is_tagger?).to be_falsy end it 'should add #is_tagger? query method to the singleton' do expect(@tagger).to respond_to(:is_tagger?) end it 'should add #tag method on the instance-side' do expect(@tagger).to respond_to(:tag) end it 'should generate an association for #owned_taggings and #owned_tags' do expect(@tagger).to respond_to(:owned_taggings, :owned_tags) end end describe '#tag' do context 'when called with a non-existent tag context' do before(:each) do @tagger = User.new @taggable = TaggableModel.new(name: 'Richard Prior') end it 'should by default not throw an exception ' do expect(@taggable.tag_list_on(:foo)).to be_empty expect(-> { @tagger.tag(@taggable, with: 'this, and, that', on: :foo) }).to_not raise_error end it 'should by default create the tag context on-the-fly' do expect(@taggable.tag_list_on(:here_ond_now)).to be_empty @tagger.tag(@taggable, with: 'that', on: :here_ond_now) expect(@taggable.tag_list_on(:here_ond_now)).to_not include('that') expect(@taggable.all_tags_list_on(:here_ond_now)).to include('that') end it 'should show all the tag list when both public and owned tags exist' do @taggable.tag_list = 'ruby, python' @tagger.tag(@taggable, with: 'java, lisp', on: :tags) expect(@taggable.all_tags_on(:tags).map(&:name).sort).to eq(%w(ruby python java lisp).sort) end it 'should not add owned tags to the common list' do @taggable.tag_list = 'ruby, python' @tagger.tag(@taggable, with: 'java, lisp', on: :tags) expect(@taggable.tag_list).to eq(%w(ruby python)) @tagger.tag(@taggable, with: '', on: :tags) expect(@taggable.tag_list).to eq(%w(ruby python)) end it 'should throw an exception when the default is over-ridden' do expect(@taggable.tag_list_on(:foo_boo)).to be_empty expect(-> { @tagger.tag(@taggable, with: 'this, and, that', on: :foo_boo, force: false) }).to raise_error end it 'should not create the tag context on-the-fly when the default is over-ridden' do expect(@taggable.tag_list_on(:foo_boo)).to be_empty @tagger.tag(@taggable, with: 'this, and, that', on: :foo_boo, force: false) rescue expect(@taggable.tag_list_on(:foo_boo)).to be_empty end end describe "when called by multiple tagger's" do before(:each) do @user_x = User.create(name: 'User X') @user_y = User.create(name: 'User Y') @taggable = TaggableModel.create(name: 'acts_as_taggable_on', tag_list: 'plugin') @user_x.tag(@taggable, with: 'ruby, rails', on: :tags) @user_y.tag(@taggable, with: 'ruby, plugin', on: :tags) @user_y.tag(@taggable, with: '', on: :tags) @user_y.tag(@taggable, with: '', on: :tags) end it 'should delete owned tags' do expect(@user_y.owned_tags).to be_empty end it 'should not delete other taggers tags' do expect(@user_x.owned_tags.count).to eq(2) end it 'should not delete original tags' do expect(@taggable.all_tags_list_on(:tags)).to include('plugin') end end end end acts-as-taggable-on-3.5.0/spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb0000644000004100000410000002317212503660023027451 0ustar www-datawww-data# -*- encoding : utf-8 -*- require 'spec_helper' describe 'Acts As Taggable On' do it "should provide a class method 'taggable?' that is false for untaggable models" do expect(UntaggableModel).to_not be_taggable end describe 'Taggable Method Generation To Preserve Order' do before(:each) do TaggableModel.tag_types = [] TaggableModel.preserve_tag_order = false TaggableModel.acts_as_ordered_taggable_on(:ordered_tags) @taggable = TaggableModel.new(name: 'Bob Jones') end it "should respond 'true' to preserve_tag_order?" do expect(@taggable.class.preserve_tag_order?).to be_truthy end end describe 'Taggable Method Generation' do before(:each) do TaggableModel.tag_types = [] TaggableModel.acts_as_taggable_on(:tags, :languages, :skills, :needs, :offerings) @taggable = TaggableModel.new(name: 'Bob Jones') end it "should respond 'true' to taggable?" do expect(@taggable.class).to be_taggable end it 'should create a class attribute for tag types' do expect(@taggable.class).to respond_to(:tag_types) end it 'should create an instance attribute for tag types' do expect(@taggable).to respond_to(:tag_types) end it 'should have all tag types' do expect(@taggable.tag_types).to eq([:tags, :languages, :skills, :needs, :offerings]) end it 'should create a class attribute for preserve tag order' do expect(@taggable.class).to respond_to(:preserve_tag_order?) end it 'should create an instance attribute for preserve tag order' do expect(@taggable).to respond_to(:preserve_tag_order?) end it "should respond 'false' to preserve_tag_order?" do expect(@taggable.class.preserve_tag_order?).to be_falsy end it 'should generate an association for each tag type' do expect(@taggable).to respond_to(:tags, :skills, :languages) end it 'should add tagged_with and tag_counts to singleton' do expect(TaggableModel).to respond_to(:tagged_with, :tag_counts) end it 'should generate a tag_list accessor/setter for each tag type' do expect(@taggable).to respond_to(:tag_list, :skill_list, :language_list) expect(@taggable).to respond_to(:tag_list=, :skill_list=, :language_list=) end it 'should generate a tag_list accessor, that includes owned tags, for each tag type' do expect(@taggable).to respond_to(:all_tags_list, :all_skills_list, :all_languages_list) end end describe 'Reloading' do it 'should save a model instantiated by Model.find' do taggable = TaggableModel.create!(name: 'Taggable') found_taggable = TaggableModel.find(taggable.id) found_taggable.save end end describe 'Matching Contexts' do it 'should find objects with tags of matching contexts' do taggable1 = TaggableModel.create!(name: 'Taggable 1') taggable2 = TaggableModel.create!(name: 'Taggable 2') taggable3 = TaggableModel.create!(name: 'Taggable 3') taggable1.offering_list = 'one, two' taggable1.save! taggable2.need_list = 'one, two' taggable2.save! taggable3.offering_list = 'one, two' taggable3.save! expect(taggable1.find_matching_contexts(:offerings, :needs)).to include(taggable2) expect(taggable1.find_matching_contexts(:offerings, :needs)).to_not include(taggable3) end it 'should find other related objects with tags of matching contexts' do taggable1 = TaggableModel.create!(name: 'Taggable 1') taggable2 = OtherTaggableModel.create!(name: 'Taggable 2') taggable3 = OtherTaggableModel.create!(name: 'Taggable 3') taggable1.offering_list = 'one, two' taggable1.save taggable2.need_list = 'one, two' taggable2.save taggable3.offering_list = 'one, two' taggable3.save expect(taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs)).to include(taggable2) expect(taggable1.find_matching_contexts_for(OtherTaggableModel, :offerings, :needs)).to_not include(taggable3) end it 'should not include the object itself in the list of related objects with tags of matching contexts' do taggable1 = TaggableModel.create!(name: 'Taggable 1') taggable2 = TaggableModel.create!(name: 'Taggable 2') taggable1.offering_list = 'one, two' taggable1.need_list = 'one, two' taggable1.save taggable2.need_list = 'one, two' taggable2.save expect(taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to include(taggable2) expect(taggable1.find_matching_contexts_for(TaggableModel, :offerings, :needs)).to_not include(taggable1) end it 'should ensure joins to multiple taggings maintain their contexts when aliasing' do taggable1 = TaggableModel.create!(name: 'Taggable 1') taggable1.offering_list = 'one' taggable1.need_list = 'two' taggable1.save column = TaggableModel.connection.quote_column_name("context") offer_alias = TaggableModel.connection.quote_table_name("taggings") need_alias = TaggableModel.connection.quote_table_name("need_taggings_taggable_models_join") expect(TaggableModel.joins(:offerings, :needs).to_sql).to include "#{offer_alias}.#{column}" expect(TaggableModel.joins(:offerings, :needs).to_sql).to include "#{need_alias}.#{column}" end end describe 'Tagging Contexts' do it 'should eliminate duplicate tagging contexts ' do TaggableModel.acts_as_taggable_on(:skills, :skills) expect(TaggableModel.tag_types.freq[:skills]).to_not eq(3) end it 'should not contain embedded/nested arrays' do TaggableModel.acts_as_taggable_on([:array], [:array]) expect(TaggableModel.tag_types.freq[[:array]]).to eq(0) end it 'should _flatten_ the content of arrays' do TaggableModel.acts_as_taggable_on([:array], [:array]) expect(TaggableModel.tag_types.freq[:array]).to eq(1) end it 'should not raise an error when passed nil' do expect(-> { TaggableModel.acts_as_taggable_on }).to_not raise_error end it 'should not raise an error when passed [nil]' do expect(-> { TaggableModel.acts_as_taggable_on([nil]) }).to_not raise_error end end context 'when tagging context ends in an "s" when singular (ex. "status", "glass", etc.)' do describe 'caching' do before { @taggable = OtherCachedModel.new(name: 'John Smith') } subject { @taggable } it { should respond_to(:save_cached_tag_list) } its(:cached_language_list) { should be_blank } its(:cached_status_list) { should be_blank } its(:cached_glass_list) { should be_blank } context 'language taggings cache after update' do before { @taggable.update_attributes(language_list: 'ruby, .net') } subject { @taggable } its(:language_list) { should == ['ruby', '.net']} its(:cached_language_list) { should == 'ruby, .net' } # passes its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@language_list' : :@language_list)) } end context 'status taggings cache after update' do before { @taggable.update_attributes(status_list: 'happy, married') } subject { @taggable } its(:status_list) { should == ['happy', 'married'] } its(:cached_status_list) { should == 'happy, married' } # fails its(:cached_status_list) { should_not == '' } # fails, is blank its(:instance_variables) { should include((RUBY_VERSION < '1.9' ? '@status_list' : :@status_list)) } its(:instance_variables) { should_not include((RUBY_VERSION < '1.9' ? '@statu_list' : :@statu_list)) } # fails, note: one "s" end context 'glass taggings cache after update' do before do @taggable.update_attributes(glass_list: 'rectangle, aviator') end subject { @taggable } its(:glass_list) { should == ['rectangle', 'aviator'] } its(:cached_glass_list) { should == 'rectangle, aviator' } # fails its(:cached_glass_list) { should_not == '' } # fails, is blank if RUBY_VERSION < '1.9' its(:instance_variables) { should include('@glass_list') } its(:instance_variables) { should_not include('@glas_list') } # fails, note: one "s" else its(:instance_variables) { should include(:@glass_list) } its(:instance_variables) { should_not include(:@glas_list) } # fails, note: one "s" end end end end describe 'taggings' do before(:each) do @taggable = TaggableModel.new(name: 'Art Kram') end it 'should return no taggings' do expect(@taggable.taggings).to be_empty end end describe '@@remove_unused_tags' do before do @taggable = TaggableModel.create(name: 'Bob Jones') @tag = ActsAsTaggableOn::Tag.create(name: 'awesome') @tagging = ActsAsTaggableOn::Tagging.create(taggable: @taggable, tag: @tag, context: 'tags') end context 'if set to true' do before do ActsAsTaggableOn.remove_unused_tags = true end it 'should remove unused tags after removing taggings' do @tagging.destroy expect(ActsAsTaggableOn::Tag.find_by_name('awesome')).to be_nil end end context 'if set to false' do before do ActsAsTaggableOn.remove_unused_tags = false end it 'should not remove unused tags after removing taggings' do @tagging.destroy expect(ActsAsTaggableOn::Tag.find_by_name('awesome')).to eq(@tag) end end end end acts-as-taggable-on-3.5.0/spec/support/0000755000004100000410000000000012503660023017723 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/spec/support/database_cleaner.rb0000644000004100000410000000054512503660023023511 0ustar www-datawww-dataRSpec.configure do |config| config.before(:suite) do DatabaseCleaner.clean_with(:truncation) DatabaseCleaner.strategy = :transaction DatabaseCleaner.clean end config.after(:suite) do DatabaseCleaner.clean end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end acts-as-taggable-on-3.5.0/spec/support/database.rb0000644000004100000410000000335512503660023022022 0ustar www-datawww-data# set adapter to use, default is sqlite3 # to use an alternative adapter run => rake spec DB='postgresql' db_name = ENV['DB'] || 'sqlite3' database_yml = File.expand_path('../../internal/config/database.yml', __FILE__) if File.exist?(database_yml) ActiveRecord::Migration.verbose = false ActiveRecord::Base.default_timezone = :utc ActiveRecord::Base.configurations = YAML.load_file(database_yml) ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), '../debug.log')) ActiveRecord::Base.logger.level = ENV['TRAVIS'] ? ::Logger::ERROR : ::Logger::DEBUG config = ActiveRecord::Base.configurations[db_name] begin #activerecord 4 uses symbol #TODO, remove when activerecord 3 support is dropped if ActsAsTaggableOn::Utils.active_record4? ActiveRecord::Base.establish_connection(db_name.to_sym) else ActiveRecord::Base.establish_connection(db_name) end ActiveRecord::Base.connection rescue case db_name when /mysql/ ActiveRecord::Base.establish_connection(config.merge('database' => nil)) ActiveRecord::Base.connection.create_database(config['database'], {charset: 'utf8', collation: 'utf8_unicode_ci'}) when 'postgresql' ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => 'utf8')) end ActiveRecord::Base.establish_connection(config) end load(File.dirname(__FILE__) + '/../internal/db/schema.rb') load(File.dirname(__FILE__) + '/../internal/app/models/models.rb') else fail "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample" endacts-as-taggable-on-3.5.0/spec/support/0-helpers.rb0000644000004100000410000000121512503660023022046 0ustar www-datawww-datadef using_sqlite? ActsAsTaggableOn::Utils.connection && ActsAsTaggableOn::Utils.connection.adapter_name == 'SQLite' end def supports_concurrency? !using_sqlite? end def using_postgresql? ActsAsTaggableOn::Utils.using_postgresql? end def postgresql_version if using_postgresql? ActsAsTaggableOn::Utils.connection.execute('SHOW SERVER_VERSION').first['server_version'].to_f else 0.0 end end def postgresql_support_json? postgresql_version >= 9.2 end def using_mysql? ActsAsTaggableOn::Utils.using_mysql? end def using_case_insensitive_collation? using_mysql? && ActsAsTaggableOn::Utils.connection.collation =~ /_ci\Z/ end acts-as-taggable-on-3.5.0/spec/support/array.rb0000644000004100000410000000020012503660023021356 0ustar www-datawww-dataunless [].respond_to?(:freq) class Array def freq k=Hash.new(0) each { |e| k[e]+=1 } k end end endacts-as-taggable-on-3.5.0/LICENSE.md0000644000004100000410000000207012503660023016660 0ustar www-datawww-data__Copyright (c) 2007 Michael Bleigh and Intridea Inc.__ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. acts-as-taggable-on-3.5.0/.travis.yml0000644000004100000410000000136212503660023017370 0ustar www-datawww-datalanguage: ruby rvm: - 2.1 - 2.2 - 2.0.0 - 1.9.3 - rbx-2 env: - DB=sqlite3 - DB=mysql - DB=postgresql gemfile: - gemfiles/activerecord_3.2.gemfile - gemfiles/activerecord_4.0.gemfile - gemfiles/activerecord_4.1.gemfile sudo: false bundler_args: '--without local_development --jobs 3 --retry 3' script: bundle exec rake matrix: fast_finish: true allow_failures: - gemfile: gemfiles/activerecord_edge.gemfile - rvm: rbx-2 exclude: - rvm: 2.2 gemfile: gemfiles/activerecord_3.2.gemfile - rvm: 1.9.3 gemfile: gemfiles/activerecord_4.0.gemfile - rvm: 1.9.3 gemfile: gemfiles/activerecord_4.1.gemfile - rvm: rbx-2 gemfile: gemfiles/activerecord_3.2.gemfile acts-as-taggable-on-3.5.0/lib/0000755000004100000410000000000012503660023016023 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/lib/acts_as_taggable_on.rb0000644000004100000410000000027612503660023022314 0ustar www-datawww-data$stderr.puts < [:environment] do |t, args| ActsAsTaggableOn::Configuration.apply_binary_collation(true) end desc "Forcing collate of tag names to utf8_general_ci" task :collate_ci => [:environment] do |t, args| ActsAsTaggableOn::Configuration.apply_binary_collation(false) end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/0000755000004100000410000000000012503660023021762 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/utils.rb0000644000004100000410000000166112503660023023453 0ustar www-datawww-data# This module is deprecated and will be removed in the incoming versions module ActsAsTaggableOn module Utils class << self # Use ActsAsTaggableOn::Tag connection def connection ActsAsTaggableOn::Tag.connection end def using_postgresql? connection && connection.adapter_name == 'PostgreSQL' end def using_mysql? #We should probably use regex for mysql to support prehistoric adapters connection && connection.adapter_name == 'Mysql2' end def sha_prefix(string) Digest::SHA1.hexdigest(string)[0..6] end def active_record4? ::ActiveRecord::VERSION::MAJOR == 4 end def like_operator using_postgresql? ? 'ILIKE' : 'LIKE' end # escape _ and % characters in strings, since these are wildcards in SQL. def escape_like(str) str.gsub(/[!%_]/) { |x| '!' + x } end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/tags_helper.rb0000644000004100000410000000064112503660023024605 0ustar www-datawww-datamodule ActsAsTaggableOn module TagsHelper # See the wiki for an example using tag_cloud. def tag_cloud(tags, classes) return [] if tags.empty? max_count = tags.sort_by(&:taggings_count).last.taggings_count.to_f tags.each do |tag| index = ((tag.taggings_count / max_count) * (classes.size - 1)) yield tag, classes[index.nan? ? 0 : index.round] end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/tagger.rb0000644000004100000410000000527612503660023023572 0ustar www-datawww-datamodule ActsAsTaggableOn module Tagger def self.included(base) base.extend ClassMethods end module ClassMethods ## # Make a model a tagger. This allows an instance of a model to claim ownership # of tags. # # Example: # class User < ActiveRecord::Base # acts_as_tagger # end def acts_as_tagger(opts={}) class_eval do has_many_with_taggable_compatibility :owned_taggings, opts.merge( as: :tagger, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging' ) has_many_with_taggable_compatibility :owned_tags, through: :owned_taggings, source: :tag, class_name: '::ActsAsTaggableOn::Tag', uniq: true end include ActsAsTaggableOn::Tagger::InstanceMethods extend ActsAsTaggableOn::Tagger::SingletonMethods end def tagger? false end def is_tagger? tagger? end end module InstanceMethods ## # Tag a taggable model with tags that are owned by the tagger. # # @param taggable The object that will be tagged # @param [Hash] options An hash with options. Available options are: # * :with - The tags that you want to # * :on - The context on which you want to tag # # Example: # @user.tag(@photo, :with => "paris, normandy", :on => :locations) def tag(taggable, opts={}) opts.reverse_merge!(force: true) skip_save = opts.delete(:skip_save) return false unless taggable.respond_to?(:is_taggable?) && taggable.is_taggable? fail 'You need to specify a tag context using :on' unless opts.key?(:on) fail 'You need to specify some tags using :with' unless opts.key?(:with) fail "No context :#{opts[:on]} defined in #{taggable.class}" unless opts[:force] || taggable.tag_types.include?(opts[:on]) taggable.set_owner_tag_list_on(self, opts[:on].to_s, opts[:with]) taggable.save unless skip_save end def tagger? self.class.is_tagger? end def is_tagger? tagger? end end module SingletonMethods def tagger? true end def is_tagger? tagger? end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/tagging.rb0000644000004100000410000000261012503660023023726 0ustar www-datawww-datamodule ActsAsTaggableOn class Tagging < ::ActiveRecord::Base #:nodoc: #TODO, remove from 4.0.0 attr_accessible :tag, :tag_id, :context, :taggable, :taggable_type, :taggable_id, :tagger, :tagger_type, :tagger_id if defined?(ActiveModel::MassAssignmentSecurity) belongs_to :tag, class_name: '::ActsAsTaggableOn::Tag', counter_cache: ActsAsTaggableOn.tags_counter belongs_to :taggable, polymorphic: true belongs_to :tagger, polymorphic: true scope :owned_by, ->(owner) { where(tagger: owner) } scope :not_owned, -> { where(tagger_id: nil, tagger_type: nil) } scope :by_contexts, ->(contexts = ['tags']) { where(context: contexts) } scope :by_context, ->(context= 'tags') { by_contexts(context.to_s) } validates_presence_of :context validates_presence_of :tag_id validates_uniqueness_of :tag_id, scope: [:taggable_type, :taggable_id, :context, :tagger_id, :tagger_type] after_destroy :remove_unused_tags private def remove_unused_tags if ActsAsTaggableOn.remove_unused_tags if ActsAsTaggableOn.tags_counter tag.destroy if tag.reload.taggings_count.zero? else tag.destroy if tag.reload.taggings.count.zero? end end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/engine.rb0000644000004100000410000000013012503660023023546 0ustar www-datawww-datarequire 'rails/engine' module ActsAsTaggableOn class Engine < Rails::Engine end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable.rb0000644000004100000410000000552212503660023024061 0ustar www-datawww-datamodule ActsAsTaggableOn module Taggable def taggable? false end ## # This is an alias for calling acts_as_taggable_on :tags. # # Example: # class Book < ActiveRecord::Base # acts_as_taggable # end def acts_as_taggable acts_as_taggable_on :tags end ## # This is an alias for calling acts_as_ordered_taggable_on :tags. # # Example: # class Book < ActiveRecord::Base # acts_as_ordered_taggable # end def acts_as_ordered_taggable acts_as_ordered_taggable_on :tags end ## # Make a model taggable on specified contexts. # # @param [Array] tag_types An array of taggable contexts # # Example: # class User < ActiveRecord::Base # acts_as_taggable_on :languages, :skills # end def acts_as_taggable_on(*tag_types) taggable_on(false, tag_types) end ## # Make a model taggable on specified contexts # and preserves the order in which tags are created # # @param [Array] tag_types An array of taggable contexts # # Example: # class User < ActiveRecord::Base # acts_as_ordered_taggable_on :languages, :skills # end def acts_as_ordered_taggable_on(*tag_types) taggable_on(true, tag_types) end private # Make a model taggable on specified contexts # and optionally preserves the order in which tags are created # # Separate methods used above for backwards compatibility # so that the original acts_as_taggable_on method is unaffected # as it's not possible to add another argument to the method # without the tag_types being enclosed in square brackets # # NB: method overridden in core module in order to create tag type # associations and methods after this logic has executed # def taggable_on(preserve_tag_order, *tag_types) tag_types = tag_types.to_a.flatten.compact.map(&:to_sym) if taggable? self.tag_types = (self.tag_types + tag_types).uniq self.preserve_tag_order = preserve_tag_order else class_attribute :tag_types self.tag_types = tag_types class_attribute :preserve_tag_order self.preserve_tag_order = preserve_tag_order class_eval do has_many :taggings, as: :taggable, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging' has_many :base_tags, through: :taggings, source: :tag, class_name: '::ActsAsTaggableOn::Tag' def self.taggable? true end end end # each of these add context-specific methods and must be # called on each call of taggable_on include Core include Collection include Cache include Ownership include Related include Dirty end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/tag_list_parser.rb0000644000004100000410000000106412503660023025472 0ustar www-datawww-datamodule ActsAsTaggableOn ## # Returns a new TagList using the given tag string. # # Example: # tag_list = ActsAsTaggableOn::TagListParser.parse("One , Two, Three") # tag_list # ["One", "Two", "Three"] module TagListParser class << self ## DEPRECATED def parse(string) ActiveRecord::Base.logger.warn < { scope_opts.inject(self) { |result, hash| result.send(*hash) } } return [scope, opts] end [nil, opts] end def parse_taggable_options(opts) scope_opts = {} [:order, :having, :select, :group, :limit, :offset, :readonly].each do |o| scope_opts[o] = opts.delete o if opts[o] end scope_opts[:where] = opts.delete :conditions if opts[:conditions] scope_opts[:joins] = opts.delete :include if opts [:include] scope_opts[:distinct] = opts.delete :uniq if opts[:uniq] [scope_opts, opts] end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/tag.rb0000644000004100000410000000750012503660023023064 0ustar www-datawww-data# encoding: utf-8 module ActsAsTaggableOn class Tag < ::ActiveRecord::Base attr_accessible :name if defined?(ActiveModel::MassAssignmentSecurity) ### ASSOCIATIONS: has_many :taggings, dependent: :destroy, class_name: '::ActsAsTaggableOn::Tagging' ### VALIDATIONS: validates_presence_of :name validates_uniqueness_of :name, if: :validates_name_uniqueness? validates_length_of :name, maximum: 255 # monkey patch this method if don't need name uniqueness validation def validates_name_uniqueness? true end ### SCOPES: scope :most_used, ->(limit = 20) { order('taggings_count desc').limit(limit) } scope :least_used, ->(limit = 20) { order('taggings_count asc').limit(limit) } def self.named(name) if ActsAsTaggableOn.strict_case_match where(["name = #{binary}?", as_8bit_ascii(name)]) else where(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(name))]) end end def self.named_any(list) clause = list.map { |tag| sanitize_sql_for_named_any(tag).force_encoding('BINARY') }.join(' OR ') where(clause) end def self.named_like(name) clause = ["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(name)}%"] where(clause) end def self.named_like_any(list) clause = list.map { |tag| sanitize_sql(["name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(tag.to_s)}%"]) }.join(' OR ') where(clause) end ### CLASS METHODS: def self.find_or_create_with_like_by_name(name) if ActsAsTaggableOn.strict_case_match self.find_or_create_all_with_like_by_name([name]).first else named_like(name).first || create(name: name) end end def self.find_or_create_all_with_like_by_name(*list) list = Array(list).flatten return [] if list.empty? existing_tags = named_any(list) list.map do |tag_name| comparable_tag_name = comparable_name(tag_name) existing_tag = existing_tags.find { |tag| comparable_name(tag.name) == comparable_tag_name } begin existing_tag || create(name: tag_name) rescue ActiveRecord::RecordNotUnique # Postgres aborts the current transaction with # PG::InFailedSqlTransaction: ERROR: current transaction is aborted, commands ignored until end of transaction block # so we have to rollback this transaction raise DuplicateTagError.new("'#{tag_name}' has already been taken") end end end ### INSTANCE METHODS: def ==(object) super || (object.is_a?(Tag) && name == object.name) end def to_s name end def count read_attribute(:count).to_i end class << self private def comparable_name(str) if ActsAsTaggableOn.strict_case_match str else unicode_downcase(str.to_s) end end def binary ActsAsTaggableOn::Utils.using_mysql? ? 'BINARY ' : nil end def unicode_downcase(string) if ActiveSupport::Multibyte::Unicode.respond_to?(:downcase) ActiveSupport::Multibyte::Unicode.downcase(string) else ActiveSupport::Multibyte::Chars.new(string).downcase.to_s end end def as_8bit_ascii(string) if defined?(Encoding) string.to_s.dup.force_encoding('BINARY') else string.to_s.mb_chars end end def sanitize_sql_for_named_any(tag) if ActsAsTaggableOn.strict_case_match sanitize_sql(["name = #{binary}?", as_8bit_ascii(tag)]) else sanitize_sql(['LOWER(name) = LOWER(?)', as_8bit_ascii(unicode_downcase(tag))]) end end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/0000755000004100000410000000000012503660023023530 5ustar www-datawww-dataacts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/cache.rb0000644000004100000410000000547212503660023025130 0ustar www-datawww-datamodule ActsAsTaggableOn::Taggable module Cache def self.included(base) # When included, conditionally adds tag caching methods when the model # has any "cached_#{tag_type}_list" column base.instance_eval do # @private def _has_tags_cache_columns?(db_columns) db_column_names = db_columns.map(&:name) tag_types.any? do |context| db_column_names.include?("cached_#{context.to_s.singularize}_list") end end # @private def _add_tags_caching_methods send :include, ActsAsTaggableOn::Taggable::Cache::InstanceMethods extend ActsAsTaggableOn::Taggable::Cache::ClassMethods before_save :save_cached_tag_list initialize_tags_cache end # ActiveRecord::Base.columns makes a database connection and caches the # calculated columns hash for the record as @columns. Since we don't # want to add caching methods until we confirm the presence of a # caching column, and we don't want to force opening a database # connection when the class is loaded, here we intercept and cache # the call to :columns as @acts_as_taggable_on_cache_columns # to mimic the underlying behavior. While processing this first # call to columns, we do the caching column check and dynamically add # the class and instance methods # FIXME: this method cannot compile in rubinius def columns @acts_as_taggable_on_cache_columns ||= begin db_columns = super _add_tags_caching_methods if _has_tags_cache_columns?(db_columns) db_columns end end def reset_column_information super @acts_as_taggable_on_cache_columns = nil end end end module ClassMethods def initialize_tags_cache tag_types.map(&:to_s).each do |tag_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.caching_#{tag_type.singularize}_list? caching_tag_list_on?("#{tag_type}") end RUBY end end def acts_as_taggable_on(*args) super(*args) initialize_tags_cache end def caching_tag_list_on?(context) column_names.include?("cached_#{context.to_s.singularize}_list") end end module InstanceMethods def save_cached_tag_list tag_types.map(&:to_s).each do |tag_type| if self.class.send("caching_#{tag_type.singularize}_list?") if tag_list_cache_set_on(tag_type) list = tag_list_cache_on(tag_type).to_a.flatten.compact.join(', ') self["cached_#{tag_type.singularize}_list"] = list end end end true end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/ownership.rb0000644000004100000410000001054112503660023026074 0ustar www-datawww-datamodule ActsAsTaggableOn::Taggable module Ownership def self.included(base) base.extend ActsAsTaggableOn::Taggable::Ownership::ClassMethods base.class_eval do after_save :save_owned_tags end base.initialize_acts_as_taggable_on_ownership end module ClassMethods def acts_as_taggable_on(*args) initialize_acts_as_taggable_on_ownership super(*args) end def initialize_acts_as_taggable_on_ownership tag_types.map(&:to_s).each do |tag_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{tag_type}_from(owner) owner_tag_list_on(owner, '#{tag_type}') end RUBY end end end def owner_tags_on(owner, context) if owner.nil? scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ?), context.to_s]) else scope = base_tags.where([%(#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = ?), context.to_s, owner.id, owner.class.base_class.to_s]) end # when preserving tag order, return tags in created order # if we added the order to the association this would always apply if self.class.preserve_tag_order? scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") else scope end end def cached_owned_tag_list_on(context) variable_name = "@owned_#{context}_list" (instance_variable_defined?(variable_name) && instance_variable_get(variable_name)) || instance_variable_set(variable_name, {}) end def owner_tag_list_on(owner, context) add_custom_context(context) cache = cached_owned_tag_list_on(context) cache[owner] ||= ActsAsTaggableOn::TagList.new(*owner_tags_on(owner, context).map(&:name)) end def set_owner_tag_list_on(owner, context, new_list) add_custom_context(context) cache = cached_owned_tag_list_on(context) cache[owner] = ActsAsTaggableOn.default_parser.new(new_list).parse end def reload(*args) self.class.tag_types.each do |context| instance_variable_set("@owned_#{context}_list", nil) end super(*args) end def save_owned_tags tagging_contexts.each do |context| cached_owned_tag_list_on(context).each do |owner, tag_list| # Find existing tags or create non-existing tags: tags = find_or_create_tags_from_list_with_context(tag_list.uniq, context) # Tag objects for owned tags owned_tags = owner_tags_on(owner, context).to_a # Tag maintenance based on whether preserving the created order of tags if self.class.preserve_tag_order? old_tags, new_tags = owned_tags - tags, tags - owned_tags shared_tags = owned_tags & tags if shared_tags.any? && tags[0...shared_tags.size] != shared_tags index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] } # Update arrays of tag objects old_tags |= owned_tags.from(index) new_tags |= owned_tags.from(index) & shared_tags # Order the array of tag objects to match the tag list new_tags = tags.map { |t| new_tags.find { |n| n.name.downcase == t.name.downcase } }.compact end else # Delete discarded tags and create new tags old_tags = owned_tags - tags new_tags = tags - owned_tags end # Find all taggings that belong to the taggable (self), are owned by the owner, # have the correct context, and are removed from the list. ActsAsTaggableOn::Tagging.destroy_all(taggable_id: id, taggable_type: self.class.base_class.to_s, tagger_type: owner.class.base_class.to_s, tagger_id: owner.id, tag_id: old_tags, context: context) if old_tags.present? # Create new taggings: new_tags.each do |tag| taggings.create!(tag_id: tag.id, context: context.to_s, tagger: owner, taggable: self) end end end true end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/core.rb0000644000004100000410000005022612503660023025012 0ustar www-datawww-datamodule ActsAsTaggableOn::Taggable module Core def self.included(base) base.extend ActsAsTaggableOn::Taggable::Core::ClassMethods base.class_eval do attr_writer :custom_contexts after_save :save_tags end base.initialize_acts_as_taggable_on_core end module ClassMethods def initialize_acts_as_taggable_on_core include taggable_mixin tag_types.map(&:to_s).each do |tags_type| tag_type = tags_type.to_s.singularize context_taggings = "#{tag_type}_taggings".to_sym context_tags = tags_type.to_sym taggings_order = (preserve_tag_order? ? "#{ActsAsTaggableOn::Tagging.table_name}.id" : []) class_eval do # when preserving tag order, include order option so that for a 'tags' context # the associations tag_taggings & tags are always returned in created order has_many_with_taggable_compatibility context_taggings, as: :taggable, dependent: :destroy, class_name: 'ActsAsTaggableOn::Tagging', order: taggings_order, conditions: {context: tags_type}, include: :tag has_many_with_taggable_compatibility context_tags, through: context_taggings, source: :tag, class_name: 'ActsAsTaggableOn::Tag', order: taggings_order end taggable_mixin.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{tag_type}_list tag_list_on('#{tags_type}') end def #{tag_type}_list=(new_tags) set_tag_list_on('#{tags_type}', new_tags) end def all_#{tags_type}_list all_tags_list_on('#{tags_type}') end RUBY end end def taggable_on(preserve_tag_order, *tag_types) super(preserve_tag_order, *tag_types) initialize_acts_as_taggable_on_core end # all column names are necessary for PostgreSQL group clause def grouped_column_names_for(object) object.column_names.map { |column| "#{object.table_name}.#{column}" }.join(', ') end ## # Return a scope of objects that are tagged with the specified tags. # # @param tags The tags that we want to query for # @param [Hash] options A hash of options to alter you query: # * :exclude - if set to true, return objects that are *NOT* tagged with the specified tags # * :any - if set to true, return objects that are tagged with *ANY* of the specified tags # * :order_by_matching_tag_count - if set to true and used with :any, sort by objects matching the most tags, descending # * :match_all - if set to true, return objects that are *ONLY* tagged with the specified tags # * :owned_by - return objects that are *ONLY* owned by the owner # * :start_at - Restrict the tags to those created after a certain time # * :end_at - Restrict the tags to those created before a certain time # # Example: # User.tagged_with(["awesome", "cool"]) # Users that are tagged with awesome and cool # User.tagged_with(["awesome", "cool"], :exclude => true) # Users that are not tagged with awesome or cool # User.tagged_with(["awesome", "cool"], :any => true) # Users that are tagged with awesome or cool # User.tagged_with(["awesome", "cool"], :any => true, :order_by_matching_tag_count => true) # Sort by users who match the most tags, descending # User.tagged_with(["awesome", "cool"], :match_all => true) # Users that are tagged with just awesome and cool # User.tagged_with(["awesome", "cool"], :owned_by => foo ) # Users that are tagged with just awesome and cool by 'foo' # User.tagged_with(["awesome", "cool"], :owned_by => foo, :start_at => Date.today ) # Users that are tagged with just awesome, cool by 'foo' and starting today def tagged_with(tags, options = {}) tag_list = ActsAsTaggableOn.default_parser.new(tags).parse options = options.dup empty_result = where('1 = 0') return empty_result if tag_list.empty? joins = [] conditions = [] having = [] select_clause = [] order_by = [] context = options.delete(:on) owned_by = options.delete(:owned_by) alias_base_name = undecorated_table_name.gsub('.', '_') # FIXME use ActiveRecord's connection quote_column_name quote = ActsAsTaggableOn::Utils.using_postgresql? ? '"' : '' if options.delete(:exclude) if options.delete(:wild) tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{ActsAsTaggableOn::Utils.like_operator} ? ESCAPE '!'", "%#{ActsAsTaggableOn::Utils.escape_like(t)}%"]) }.join(' OR ') else tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{ActsAsTaggableOn::Utils.like_operator} ?", t]) }.join(' OR ') end 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}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)})" if owned_by joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" + " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" + " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{quote_value(owned_by.id, nil)}" + " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}" joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] end elsif any = options.delete(:any) # get tags, drop out if nothing returned (we need at least one) tags = if options.delete(:wild) ActsAsTaggableOn::Tag.named_like_any(tag_list) else ActsAsTaggableOn::Tag.named_any(tag_list) end return empty_result if tags.length == 0 # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123 # avoid ambiguous column name taggings_context = context ? "_#{context}" : '' taggings_alias = adjust_taggings_alias( "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tags.map(&:name).join('_'))}" ) tagging_cond = "#{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + " WHERE #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] tagging_cond << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context # don't need to sanitize sql, map all ids and join with OR logic tag_ids = tags.map { |t| quote_value(t.id, nil) }.join(', ') tagging_cond << " AND #{taggings_alias}.tag_id in (#{tag_ids})" select_clause << " #{table_name}.*" unless context and tag_types.one? if owned_by tagging_cond << ' AND ' + sanitize_sql([ "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?", owned_by.id, owned_by.class.base_class.to_s ]) end conditions << "EXISTS (SELECT 1 FROM #{tagging_cond})" if options.delete(:order_by_matching_tag_count) order_by << "(SELECT count(*) FROM #{tagging_cond}) desc" end else tags = ActsAsTaggableOn::Tag.named_any(tag_list) return empty_result unless tags.length == tag_list.length tags.each do |tag| taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{ActsAsTaggableOn::Utils.sha_prefix(tag.name)}") tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \ " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" + " AND #{taggings_alias}.tag_id = #{quote_value(tag.id, nil)}" tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context if owned_by tagging_join << ' AND ' + sanitize_sql([ "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?", owned_by.id, owned_by.class.base_class.to_s ]) end joins << tagging_join end end group ||= [] # Rails interprets this as a no-op in the group() call below if options.delete(:order_by_matching_tag_count) select_clause << "#{table_name}.*, COUNT(#{taggings_alias}.tag_id) AS #{taggings_alias}_count" group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" group = group_columns order_by << "#{taggings_alias}_count DESC" elsif options.delete(:match_all) taggings_alias, _ = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group" joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" \ " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" \ " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" joins << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] joins << " AND " + sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] group_columns = ActsAsTaggableOn::Utils.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" group = group_columns having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}" end order_by << options[:order] if options[:order].present? query = self query = self.select(select_clause.join(',')) unless select_clause.empty? query.joins(joins.join(' ')) .where(conditions.join(' AND ')) .group(group) .having(having) .order(order_by.join(', ')) .readonly(false) end def is_taggable? true end def adjust_taggings_alias(taggings_alias) if taggings_alias.size > 75 taggings_alias = 'taggings_alias_' + Digest::SHA1.hexdigest(taggings_alias) end taggings_alias end def taggable_mixin @taggable_mixin ||= Module.new end end # all column names are necessary for PostgreSQL group clause def grouped_column_names_for(object) self.class.grouped_column_names_for(object) end def custom_contexts @custom_contexts ||= [] end def is_taggable? self.class.is_taggable? end def add_custom_context(value) custom_contexts << value.to_s unless custom_contexts.include?(value.to_s) or self.class.tag_types.map(&:to_s).include?(value.to_s) end def cached_tag_list_on(context) self["cached_#{context.to_s.singularize}_list"] end def tag_list_cache_set_on(context) variable_name = "@#{context.to_s.singularize}_list" instance_variable_defined?(variable_name) && instance_variable_get(variable_name) end def tag_list_cache_on(context) variable_name = "@#{context.to_s.singularize}_list" if instance_variable_get(variable_name) instance_variable_get(variable_name) elsif cached_tag_list_on(context) && self.class.caching_tag_list_on?(context) instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(cached_tag_list_on(context)).parse) else instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(tags_on(context).map(&:name))) end end def tag_list_on(context) add_custom_context(context) tag_list_cache_on(context) end def all_tags_list_on(context) variable_name = "@all_#{context.to_s.singularize}_list" return instance_variable_get(variable_name) if instance_variable_defined?(variable_name) && instance_variable_get(variable_name) instance_variable_set(variable_name, ActsAsTaggableOn::TagList.new(all_tags_on(context).map(&:name)).freeze) end ## # Returns all tags of a given context def all_tags_on(context) tagging_table_name = ActsAsTaggableOn::Tagging.table_name opts = ["#{tagging_table_name}.context = ?", context.to_s] scope = base_tags.where(opts) if ActsAsTaggableOn::Utils.using_postgresql? group_columns = grouped_column_names_for(ActsAsTaggableOn::Tag) scope.order("max(#{tagging_table_name}.created_at)").group(group_columns) else scope.group("#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}") end.to_a end ## # Returns all tags that are not owned of a given context def tags_on(context) scope = base_tags.where(["#{ActsAsTaggableOn::Tagging.table_name}.context = ? AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id IS NULL", context.to_s]) # when preserving tag order, return tags in created order # if we added the order to the association this would always apply scope = scope.order("#{ActsAsTaggableOn::Tagging.table_name}.id") if self.class.preserve_tag_order? scope end def set_tag_list_on(context, new_list) add_custom_context(context) variable_name = "@#{context.to_s.singularize}_list" process_dirty_object(context, new_list) unless custom_contexts.include?(context.to_s) instance_variable_set(variable_name, ActsAsTaggableOn.default_parser.new(new_list).parse) end def tagging_contexts custom_contexts + self.class.tag_types.map(&:to_s) end def process_dirty_object(context, new_list) value = new_list.is_a?(Array) ? ActsAsTaggableOn::TagList.new(new_list) : new_list attrib = "#{context.to_s.singularize}_list" if changed_attributes.include?(attrib) # The attribute already has an unsaved change. old = changed_attributes[attrib] @changed_attributes.delete(attrib) if old.to_s == value.to_s else old = tag_list_on(context) if self.class.preserve_tag_order @changed_attributes[attrib] = old if old.to_s != value.to_s else @changed_attributes[attrib] = old.to_s if old.sort != ActsAsTaggableOn.default_parser.new(value).parse.sort end end end def reload(*args) self.class.tag_types.each do |context| instance_variable_set("@#{context.to_s.singularize}_list", nil) instance_variable_set("@all_#{context.to_s.singularize}_list", nil) end super(*args) end ## # Find existing tags or create non-existing tags def load_tags(tag_list) ActsAsTaggableOn::Tag.find_or_create_all_with_like_by_name(tag_list) end def save_tags tagging_contexts.each do |context| next unless tag_list_cache_set_on(context) # List of currently assigned tag names tag_list = tag_list_cache_on(context).uniq # Find existing tags or create non-existing tags: tags = find_or_create_tags_from_list_with_context(tag_list, context) # Tag objects for currently assigned tags current_tags = tags_on(context) # Tag maintenance based on whether preserving the created order of tags if self.class.preserve_tag_order? old_tags, new_tags = current_tags - tags, tags - current_tags shared_tags = current_tags & tags if shared_tags.any? && tags[0...shared_tags.size] != shared_tags index = shared_tags.each_with_index { |_, i| break i unless shared_tags[i] == tags[i] } # Update arrays of tag objects old_tags |= current_tags[index...current_tags.size] new_tags |= current_tags[index...current_tags.size] & shared_tags # Order the array of tag objects to match the tag list new_tags = tags.map do |t| new_tags.find { |n| n.name.downcase == t.name.downcase } end.compact end else # Delete discarded tags and create new tags old_tags = current_tags - tags new_tags = tags - current_tags end # Destroy old taggings: if old_tags.present? taggings.not_owned.by_context(context).destroy_all(tag_id: old_tags) end # Create new taggings: new_tags.each do |tag| taggings.create!(tag_id: tag.id, context: context.to_s, taggable: self) end end true end private # Filters the tag lists from the attribute names. def attributes_for_update(attribute_names) tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"} super.delete_if {|attr| tag_lists.include? attr } end # Filters the tag lists from the attribute names. def attributes_for_create(attribute_names) tag_lists = tag_types.map {|tags_type| "#{tags_type.to_s.singularize}_list"} super.delete_if {|attr| tag_lists.include? attr } end ## # Override this hook if you wish to subclass {ActsAsTaggableOn::Tag} -- # context is provided so that you may conditionally use a Tag subclass # only for some contexts. # # @example Custom Tag class for one context # class Company < ActiveRecord::Base # acts_as_taggable_on :markets, :locations # # def find_or_create_tags_from_list_with_context(tag_list, context) # if context.to_sym == :markets # MarketTag.find_or_create_all_with_like_by_name(tag_list) # else # super # end # end # # @param [Array] tag_list Tags to find or create # @param [Symbol] context The tag context for the tag_list def find_or_create_tags_from_list_with_context(tag_list, _context) load_tags(tag_list) end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/collection.rb0000644000004100000410000002107212503660023026212 0ustar www-datawww-datamodule ActsAsTaggableOn::Taggable module Collection def self.included(base) base.extend ActsAsTaggableOn::Taggable::Collection::ClassMethods base.initialize_acts_as_taggable_on_collection end module ClassMethods def initialize_acts_as_taggable_on_collection tag_types.map(&:to_s).each do |tag_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.#{tag_type.singularize}_counts(options={}) tag_counts_on('#{tag_type}', options) end def #{tag_type.singularize}_counts(options = {}) tag_counts_on('#{tag_type}', options) end def top_#{tag_type}(limit = 10) tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i) end def self.top_#{tag_type}(limit = 10) tag_counts_on('#{tag_type}', order: 'count desc', limit: limit.to_i) end RUBY end end def acts_as_taggable_on(*args) super(*args) initialize_acts_as_taggable_on_collection end def tag_counts_on(context, options = {}) all_tag_counts(options.merge({on: context.to_s})) end def tags_on(context, options = {}) all_tags(options.merge({on: context.to_s})) end ## # Calculate the tag names. # To be used when you don't need tag counts and want to avoid the taggable joins. # # @param [Hash] options Options: # * :start_at - Restrict the tags to those created after a certain time # * :end_at - Restrict the tags to those created before a certain time # * :conditions - A piece of SQL conditions to add to the query. Note we don't join the taggable objects for performance reasons. # * :limit - The maximum number of tags to return # * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc' # * :on - Scope the find to only include a certain context def all_tags(options = {}) options = options.dup options.assert_valid_keys :start_at, :end_at, :conditions, :order, :limit, :on ## Generate conditions: options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions] ## Generate scope: tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id") tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*").order(options[:order]).limit(options[:limit]) # Joins and conditions tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) } tag_scope = tag_scope.where(options[:conditions]) group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id" # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore: tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key).group(group_columns) tag_scope_joins(tag_scope, tagging_scope) end ## # Calculate the tag counts for all tags. # # @param [Hash] options Options: # * :start_at - Restrict the tags to those created after a certain time # * :end_at - Restrict the tags to those created before a certain time # * :conditions - A piece of SQL conditions to add to the query # * :limit - The maximum number of tags to return # * :order - A piece of SQL to order by. Eg 'tags.count desc' or 'taggings.created_at desc' # * :at_least - Exclude tags with a frequency less than the given value # * :at_most - Exclude tags with a frequency greater than the given value # * :on - Scope the find to only include a certain context def all_tag_counts(options = {}) options = options.dup options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :on, :id ## Generate conditions: options[:conditions] = sanitize_sql(options[:conditions]) if options[:conditions] ## Generate joins: taggable_join = "INNER JOIN #{table_name} ON #{table_name}.#{primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id" 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 ## Generate scope: tagging_scope = ActsAsTaggableOn::Tagging.select("#{ActsAsTaggableOn::Tagging.table_name}.tag_id, COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) AS tags_count") tag_scope = ActsAsTaggableOn::Tag.select("#{ActsAsTaggableOn::Tag.table_name}.*, #{ActsAsTaggableOn::Tagging.table_name}.tags_count AS count").order(options[:order]).limit(options[:limit]) # Joins and conditions tagging_scope = tagging_scope.joins(taggable_join) tagging_conditions(options).each { |condition| tagging_scope = tagging_scope.where(condition) } tag_scope = tag_scope.where(options[:conditions]) # GROUP BY and HAVING clauses: having = ["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) > 0"] having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) >= ?", options.delete(:at_least)]) if options[:at_least] having.push sanitize_sql(["COUNT(#{ActsAsTaggableOn::Tagging.table_name}.tag_id) <= ?", options.delete(:at_most)]) if options[:at_most] having = having.compact.join(' AND ') group_columns = "#{ActsAsTaggableOn::Tagging.table_name}.tag_id" unless options[:id] # Append the current scope to the scope, because we can't use scope(:find) in RoR 3.0 anymore: tagging_scope = generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key) end tagging_scope = tagging_scope.group(group_columns).having(having) tag_scope_joins(tag_scope, tagging_scope) end def safe_to_sql(relation) connection.respond_to?(:unprepared_statement) ? connection.unprepared_statement { relation.to_sql } : relation.to_sql end private def generate_tagging_scope_in_clause(tagging_scope, table_name, primary_key) table_name_pkey = "#{table_name}.#{primary_key}" if ActsAsTaggableOn::Utils.using_mysql? # See https://github.com/mbleigh/acts-as-taggable-on/pull/457 for details scoped_ids = pluck(table_name_pkey) tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN (?)", scoped_ids) else tagging_scope = tagging_scope.where("#{ActsAsTaggableOn::Tagging.table_name}.taggable_id IN(#{safe_to_sql(select(table_name_pkey))})") end tagging_scope end def tagging_conditions(options) tagging_conditions = [] tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] tagging_conditions.push sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] taggable_conditions = sanitize_sql(["#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = ?", base_class.name]) taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", options.delete(:on).to_s]) if options[:on] taggable_conditions << sanitize_sql([" AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = ?", options[:id]]) if options[:id] tagging_conditions.push taggable_conditions tagging_conditions end def tag_scope_joins(tag_scope, tagging_scope) 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") tag_scope.extending(CalculationMethods) end end def tag_counts_on(context, options={}) self.class.tag_counts_on(context, options.merge(id: id)) end module CalculationMethods def count(column_name=:all, options = {}) # https://github.com/rails/rails/commit/da9b5d4a8435b744fcf278fffd6d7f1e36d4a4f2 super end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/related.rb0000644000004100000410000000652412503660023025504 0ustar www-datawww-datamodule ActsAsTaggableOn::Taggable module Related def self.included(base) base.extend ActsAsTaggableOn::Taggable::Related::ClassMethods base.initialize_acts_as_taggable_on_related end module ClassMethods def initialize_acts_as_taggable_on_related tag_types.map(&:to_s).each do |tag_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def find_related_#{tag_type}(options = {}) related_tags_for('#{tag_type}', self.class, options) end alias_method :find_related_on_#{tag_type}, :find_related_#{tag_type} def find_related_#{tag_type}_for(klass, options = {}) related_tags_for('#{tag_type}', klass, options) end RUBY end end def acts_as_taggable_on(*args) super(*args) initialize_acts_as_taggable_on_related end end def find_matching_contexts(search_context, result_context, options = {}) matching_contexts_for(search_context.to_s, result_context.to_s, self.class, options) end def find_matching_contexts_for(klass, search_context, result_context, options = {}) matching_contexts_for(search_context.to_s, result_context.to_s, klass, options) end def matching_contexts_for(search_context, result_context, klass, options = {}) tags_to_find = tags_on(search_context).map { |t| t.name } related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, result_context]) end def related_tags_for(context, klass, options = {}) tags_to_ignore = Array.wrap(options[:ignore]).map(&:to_s) || [] tags_to_find = tags_on(context).map { |t| t.name }.reject { |t| tags_to_ignore.include? t } related_where(klass, ["#{exclude_self(klass, id)} #{klass.table_name}.#{klass.primary_key} = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{klass.base_class}' AND #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND #{ActsAsTaggableOn::Tag.table_name}.name IN (?) AND #{ActsAsTaggableOn::Tagging.table_name}.context = ?", tags_to_find, context]) end private def exclude_self(klass, id) "#{klass.table_name}.#{klass.primary_key} != #{id} AND" if [self.class.base_class, self.class].include? klass end def group_columns(klass) if ActsAsTaggableOn::Utils.using_postgresql? grouped_column_names_for(klass) else "#{klass.table_name}.#{klass.primary_key}" end end def related_where(klass, conditions) klass.select("#{klass.table_name}.*, COUNT(#{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key}) AS count") .from("#{klass.table_name}, #{ActsAsTaggableOn::Tag.table_name}, #{ActsAsTaggableOn::Tagging.table_name}") .group(group_columns(klass)) .order('count DESC') .where(conditions) end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/taggable/dirty.rb0000644000004100000410000000223712503660023025214 0ustar www-datawww-datamodule ActsAsTaggableOn::Taggable module Dirty def self.included(base) base.extend ActsAsTaggableOn::Taggable::Dirty::ClassMethods base.initialize_acts_as_taggable_on_dirty end module ClassMethods def initialize_acts_as_taggable_on_dirty tag_types.map(&:to_s).each do |tags_type| tag_type = tags_type.to_s.singularize class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{tag_type}_list_changed? changed_attributes.include?("#{tag_type}_list") end def #{tag_type}_list_was changed_attributes.include?("#{tag_type}_list") ? changed_attributes["#{tag_type}_list"] : __send__("#{tag_type}_list") end def #{tag_type}_list_change [changed_attributes['#{tag_type}_list'], __send__('#{tag_type}_list')] if changed_attributes.include?("#{tag_type}_list") end def #{tag_type}_list_changes [changed_attributes['#{tag_type}_list'], __send__('#{tag_type}_list')] if changed_attributes.include?("#{tag_type}_list") end RUBY end end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/generic_parser.rb0000644000004100000410000000070512503660023025301 0ustar www-datawww-datamodule ActsAsTaggableOn ## # Returns a new TagList using the given tag string. # # Example: # tag_list = ActsAsTaggableOn::GenericParser.new.parse("One , Two, Three") # tag_list # ["One", "Two", "Three"] class GenericParser def initialize(tag_list) @tag_list = tag_list end def parse TagList.new.tap do |tag_list| tag_list.add @tag_list.split(',').map(&:strip).reject(&:empty?) end end end end acts-as-taggable-on-3.5.0/lib/acts_as_taggable_on/tag_list.rb0000644000004100000410000000576312503660023024130 0ustar www-datawww-data require 'active_support/core_ext/module/delegation' module ActsAsTaggableOn class TagList < Array attr_accessor :owner attr_accessor :parser def initialize(*args) @parser = ActsAsTaggableOn.default_parser add(*args) end ## # Add tags to the tag_list. Duplicate or blank tags will be ignored. # Use the :parse option to add an unparsed tag string. # # Example: # tag_list.add("Fun", "Happy") # tag_list.add("Fun, Happy", :parse => true) def add(*names) extract_and_apply_options!(names) concat(names) clean! self end # Append---Add the tag to the tag_list. This # expression returns the tag_list itself, so several appends # may be chained together. def <<(obj) add(obj) end # Concatenation --- Returns a new tag list built by concatenating the # two tag lists together to produce a third tag list. def +(other_tag_list) TagList.new.add(self).add(other_tag_list) end # Appends the elements of +other_tag_list+ to +self+. def concat(other_tag_list) super(other_tag_list).send(:clean!) end ## # Remove specific tags from the tag_list. # Use the :parse option to add an unparsed tag string. # # Example: # tag_list.remove("Sad", "Lonely") # tag_list.remove("Sad, Lonely", :parse => true) def remove(*names) extract_and_apply_options!(names) delete_if { |name| names.include?(name) } self end ## # Transform the tag_list into a tag string suitable for editing in a form. # The tags are joined with TagList.delimiter and quoted if necessary. # # Example: # tag_list = TagList.new("Round", "Square,Cube") # tag_list.to_s # 'Round, "Square,Cube"' def to_s tags = frozen? ? self.dup : self tags.send(:clean!) tags.map do |name| d = ActsAsTaggableOn.delimiter d = Regexp.new d.join('|') if d.kind_of? Array name.index(d) ? "\"#{name}\"" : name end.join(ActsAsTaggableOn.glue) end private # Convert everything to string, remove whitespace, duplicates, and blanks. def clean! reject!(&:blank?) map!(&:to_s) map!(&:strip) map! { |tag| tag.mb_chars.downcase.to_s } if ActsAsTaggableOn.force_lowercase map!(&:parameterize) if ActsAsTaggableOn.force_parameterize uniq! end def extract_and_apply_options!(args) options = args.last.is_a?(Hash) ? args.pop : {} options.assert_valid_keys :parse, :parser parser = options[:parser] ? options[:parser] : @parser args.map! { |a| parser.new(a).parse } if options[:parse] || options[:parser] args.flatten! end ## DEPRECATED def self.from(string) ActiveRecord::Base.logger.warn < "rails/rails", :branch => "4-1-stable" group :local_development do gem "guard" gem "guard-rspec" gem "appraisal" gem "rake" gem "byebug", :platform => :mri_21 end gemspec :path => "../" acts-as-taggable-on-3.5.0/gemfiles/activerecord_4.0.gemfile0000644000004100000410000000045412503660023023440 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "activerecord", :github => "rails/rails", :branch => "4-0-stable" group :local_development do gem "guard" gem "guard-rspec" gem "appraisal" gem "rake" gem "byebug", :platform => :mri_21 end gemspec :path => "../" acts-as-taggable-on-3.5.0/gemfiles/activerecord_4.2.gemfile0000644000004100000410000000055612503660023023445 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "railties", :github => "rails/rails", :branch => "4-2-stable" gem "activerecord", :github => "rails/rails", :branch => "4-2-stable" group :local_development do gem "guard" gem "guard-rspec" gem "appraisal" gem "rake" gem "byebug", :platform => :mri_21 end gemspec :path => "../" acts-as-taggable-on-3.5.0/gemfiles/activerecord_3.2.gemfile0000644000004100000410000000045412503660023023441 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "activerecord", :github => "rails/rails", :branch => "3-2-stable" group :local_development do gem "guard" gem "guard-rspec" gem "appraisal" gem "rake" gem "byebug", :platform => :mri_21 end gemspec :path => "../" acts-as-taggable-on-3.5.0/metadata.yml0000644000004100000410000002243412503660213017566 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: acts-as-taggable-on version: !ruby/object:Gem::Version version: 3.5.0 platform: ruby authors: - Michael Bleigh - Joost Baaij autorequire: bindir: bin cert_chain: [] date: 2015-03-03 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activerecord requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '3.2' - - "<" - !ruby/object:Gem::Version version: '5' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '3.2' - - "<" - !ruby/object:Gem::Version version: '5' - !ruby/object:Gem::Dependency name: sqlite3 requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: mysql2 requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.3.7 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 0.3.7 - !ruby/object:Gem::Dependency name: pg requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec-rails requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec-its requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: barrier requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: database_cleaner requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: With ActsAsTaggableOn, you can tag a single model on several contexts, such as skills, interests, and awards. It also provides other advanced functionality. email: - michael@intridea.com - joost@spacebabies.nl executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".rspec" - ".travis.yml" - Appraisals - CHANGELOG.md - CONTRIBUTING.md - Gemfile - Guardfile - LICENSE.md - README.md - Rakefile - UPGRADING.md - acts-as-taggable-on.gemspec - db/migrate/1_acts_as_taggable_on_migration.rb - db/migrate/2_add_missing_unique_indices.rb - db/migrate/3_add_taggings_counter_cache_to_tags.rb - db/migrate/4_add_missing_taggable_index.rb - db/migrate/5_change_collation_for_tag_names.rb - gemfiles/activerecord_3.2.gemfile - gemfiles/activerecord_4.0.gemfile - gemfiles/activerecord_4.1.gemfile - gemfiles/activerecord_4.2.gemfile - lib/acts-as-taggable-on.rb - lib/acts_as_taggable_on.rb - lib/acts_as_taggable_on/compatibility.rb - lib/acts_as_taggable_on/default_parser.rb - lib/acts_as_taggable_on/engine.rb - lib/acts_as_taggable_on/generic_parser.rb - lib/acts_as_taggable_on/tag.rb - lib/acts_as_taggable_on/tag_list.rb - lib/acts_as_taggable_on/tag_list_parser.rb - lib/acts_as_taggable_on/taggable.rb - lib/acts_as_taggable_on/taggable/cache.rb - lib/acts_as_taggable_on/taggable/collection.rb - lib/acts_as_taggable_on/taggable/core.rb - lib/acts_as_taggable_on/taggable/dirty.rb - lib/acts_as_taggable_on/taggable/ownership.rb - lib/acts_as_taggable_on/taggable/related.rb - lib/acts_as_taggable_on/tagger.rb - lib/acts_as_taggable_on/tagging.rb - lib/acts_as_taggable_on/tags_helper.rb - lib/acts_as_taggable_on/utils.rb - lib/acts_as_taggable_on/version.rb - lib/tasks/tags_collate_utf8.rake - spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb - spec/acts_as_taggable_on/acts_as_tagger_spec.rb - spec/acts_as_taggable_on/caching_spec.rb - spec/acts_as_taggable_on/default_parser_spec.rb - spec/acts_as_taggable_on/generic_parser_spec.rb - spec/acts_as_taggable_on/related_spec.rb - spec/acts_as_taggable_on/single_table_inheritance_spec.rb - spec/acts_as_taggable_on/tag_list_parser_spec.rb - spec/acts_as_taggable_on/tag_list_spec.rb - spec/acts_as_taggable_on/tag_spec.rb - spec/acts_as_taggable_on/taggable/dirty_spec.rb - spec/acts_as_taggable_on/taggable_spec.rb - spec/acts_as_taggable_on/tagger_spec.rb - spec/acts_as_taggable_on/tagging_spec.rb - spec/acts_as_taggable_on/tags_helper_spec.rb - spec/acts_as_taggable_on/utils_spec.rb - spec/internal/app/models/altered_inheriting_taggable_model.rb - spec/internal/app/models/cached_model.rb - spec/internal/app/models/cached_model_with_array.rb - spec/internal/app/models/company.rb - spec/internal/app/models/inheriting_taggable_model.rb - spec/internal/app/models/market.rb - spec/internal/app/models/models.rb - spec/internal/app/models/non_standard_id_taggable_model.rb - spec/internal/app/models/ordered_taggable_model.rb - spec/internal/app/models/other_cached_model.rb - spec/internal/app/models/other_taggable_model.rb - spec/internal/app/models/student.rb - spec/internal/app/models/taggable_model.rb - spec/internal/app/models/untaggable_model.rb - spec/internal/app/models/user.rb - spec/internal/config/database.yml.sample - spec/internal/db/schema.rb - spec/spec_helper.rb - spec/support/0-helpers.rb - spec/support/array.rb - spec/support/database.rb - spec/support/database_cleaner.rb homepage: https://github.com/mbleigh/acts-as-taggable-on licenses: - MIT metadata: {} post_install_message: |- When upgrading Re-run the migrations generator rake acts_as_taggable_on_engine:install:migrations This will create any new migrations and skip existing ones Version 3.5.0 has a migration for mysql adapter rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.9.3 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5 signing_key: specification_version: 4 summary: Advanced tagging for Rails. test_files: - spec/acts_as_taggable_on/acts_as_taggable_on_spec.rb - spec/acts_as_taggable_on/acts_as_tagger_spec.rb - spec/acts_as_taggable_on/caching_spec.rb - spec/acts_as_taggable_on/default_parser_spec.rb - spec/acts_as_taggable_on/generic_parser_spec.rb - spec/acts_as_taggable_on/related_spec.rb - spec/acts_as_taggable_on/single_table_inheritance_spec.rb - spec/acts_as_taggable_on/tag_list_parser_spec.rb - spec/acts_as_taggable_on/tag_list_spec.rb - spec/acts_as_taggable_on/tag_spec.rb - spec/acts_as_taggable_on/taggable/dirty_spec.rb - spec/acts_as_taggable_on/taggable_spec.rb - spec/acts_as_taggable_on/tagger_spec.rb - spec/acts_as_taggable_on/tagging_spec.rb - spec/acts_as_taggable_on/tags_helper_spec.rb - spec/acts_as_taggable_on/utils_spec.rb - spec/internal/app/models/altered_inheriting_taggable_model.rb - spec/internal/app/models/cached_model.rb - spec/internal/app/models/cached_model_with_array.rb - spec/internal/app/models/company.rb - spec/internal/app/models/inheriting_taggable_model.rb - spec/internal/app/models/market.rb - spec/internal/app/models/models.rb - spec/internal/app/models/non_standard_id_taggable_model.rb - spec/internal/app/models/ordered_taggable_model.rb - spec/internal/app/models/other_cached_model.rb - spec/internal/app/models/other_taggable_model.rb - spec/internal/app/models/student.rb - spec/internal/app/models/taggable_model.rb - spec/internal/app/models/untaggable_model.rb - spec/internal/app/models/user.rb - spec/internal/config/database.yml.sample - spec/internal/db/schema.rb - spec/spec_helper.rb - spec/support/0-helpers.rb - spec/support/array.rb - spec/support/database.rb - spec/support/database_cleaner.rb acts-as-taggable-on-3.5.0/.gitignore0000644000004100000410000000015712503660023017250 0ustar www-datawww-data*.log *.sqlite3 /pkg/* .bundle .ruby-version spec/internal/config/database.yml tmp*.sw? *.sw? tmp *.gem *.lock acts-as-taggable-on-3.5.0/CONTRIBUTING.md0000644000004100000410000000410712503660023017510 0ustar www-datawww-data# How to contribute: ## Bug reports / Issues * Is something broken or not working as expected? Check for an existing issue or [create a new one](https://github.com/mbleigh/acts-as-taggable-on/issues/new) * IMPORTANT: Include the version of the gem, if you've install from git, what Ruby and Rails you are running, etc. ## Code 1. [Fork and clone the repo](https://help.github.com/articles/fork-a-repo) 2. Install the gem dependencies: `bundle install` 3. Make the changes you want and back them up with tests. * [Run the tests](https://github.com/mbleigh/acts-as-taggable-on#testing) (`bundle exec rake spec`) 4. Update the CHANGELOG.md file with your changes and give yourself credit 5. Commit and create a pull request with details as to what has been changed and why * Use well-described, small (atomic) commits. * Include links to any relevant github issues. * *Don't* change the VERSION file. 6. Extra Credit: [Confirm it runs and tests pass on the rubies specified in the travis config](.travis.yml). I will otherwise confirm it runs on these. How I handle pull requests: * If the tests pass and the pull request looks good, I will merge it. * If the pull request needs to be changed, * you can change it by updating the branch you generated the pull request from * either by adding more commits, or * by force pushing to it * I can make any changes myself and manually merge the code in. ### Commit Messages * [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) * [http://stopwritingramblingcommitmessages.com/](http://stopwritingramblingcommitmessages.com/) * [ThoughtBot style guide](https://github.com/thoughtbot/guides/tree/master/style#git) ### About Pull Requests (PR's) * [All Your Open Source Code Are Belong To Us](http://www.benjaminfleischer.com/2013/07/30/all-your-open-source-code-are-belong-to-us/) * [Using Pull Requests](https://help.github.com/articles/using-pull-requests) * [Github pull requests made easy](http://www.element84.com/github-pull-requests-made-easy.html) ## Documentation * Update the wiki acts-as-taggable-on-3.5.0/Appraisals0000644000004100000410000000073312503660023017302 0ustar www-datawww-dataappraise "activerecord-3.2" do gem "activerecord", github: "rails/rails" , branch: '3-2-stable' end appraise "activerecord-4.0" do gem "activerecord", github: "rails/rails" , branch: '4-0-stable' end appraise "activerecord-4.1" do gem "activerecord", github: "rails/rails" , branch: '4-1-stable' end appraise "activerecord-4.2" do gem "railties", github: "rails/rails" , branch: '4-2-stable' gem "activerecord", github: "rails/rails" , branch: '4-2-stable' end acts-as-taggable-on-3.5.0/CHANGELOG.md0000644000004100000410000003104712503660023017073 0ustar www-datawww-dataChanges are below categorized as `Features, Fixes, or Misc`. Each change should fall into categories that would affect whether the release is major (breaking changes), minor (new behavior), or patch (bug fix). See [semver](http://semver.org/) and [pessimistic versioning](http://guides.rubygems.org/patterns/#pessimistic_version_constraint) As such, a _Feature_ would map to either major or minor. A _bug fix_ to a patch. And _misc_ is either minor or patch, the difference being kind of fuzzy for the purposes of history. Adding tests would be patch level. ### [3.5.0 / 2015-02-11](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.4.4...v3.5.0) * Fixes * [@rikettsie Fixed collation for MySql via rake rule or config parameter](https://github.com/mbleigh/acts-as-taggable-on/pull/634) ### [3.4.4 / 2015-02-11](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.4.3...v3.4.4) * Fixes * [@d4rky-pl Add context constraint to find_related_* methods](https://github.com/mbleigh/acts-as-taggable-on/pull/629) ### [3.4.3 / 2014-09-26](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.4.2...v3.4.3) * Fixes * [@warp clears column cache on reset_column_information resolves](https://github.com/mbleigh/acts-as-taggable-on/pull/621) ### [3.4.2 / 2014-09-26](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.4.1...v3.4.2) * Fixes * [@stiff fixed tagged_with :any in postgresql](https://github.com/mbleigh/acts-as-taggable-on/pull/570) * [@jerefrer fixed encoding in mysql](https://github.com/mbleigh/acts-as-taggable-on/pull/588) * [@markedmondson Ensure taggings context aliases are maintained when joining multiple taggables](https://github.com/mbleigh/acts-as-taggable-on/pull/589) ### [3.4.1 / 2014-09-01](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.4.0...v3.4.1) * Fixes * [@konukhov fix owned ordered taggable bug](https://github.com/mbleigh/acts-as-taggable-on/pull/585) ### [3.4.0 / 2014-08-29](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.3.0...v3.4.0) * Features * [@ProGM Support for custom parsers for tags](https://github.com/mbleigh/acts-as-taggable-on/pull/579) * [@damzcodes #577 Popular feature](https://github.com/mbleigh/acts-as-taggable-on/pull/577) * Fixes * [@twalpole Update for rails edge (4.2)](https://github.com/mbleigh/acts-as-taggable-on/pull/583) * Performance * [@dontfidget #587 Use pluck instead of select](https://github.com/mbleigh/acts-as-taggable-on/pull/587) ### [3.3.0 / 2014-07-08](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.6...v3.3.0) * Features * [@felipeclopes #488 Support for `start_at` and `end_at` restrictions when selecting tags](https://github.com/mbleigh/acts-as-taggable-on/pull/488) * Fixes * [@tonytonyjan #560 Fix for `ActsAsTaggableOn.remove_unused_tags` doesn't work](https://github.com/mbleigh/acts-as-taggable-on/pull/560) * [@ThearkInn #555 Fix for `tag_cloud` helper to generate correct css tags](https://github.com/mbleigh/acts-as-taggable-on/pull/555) * Performance * [@pcai #556 Add back taggables index in the taggins table](https://github.com/mbleigh/acts-as-taggable-on/pull/556) ### [3.2.6 / 2014-05-28](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.5...v3.2.6) * Fixes * [@seuros #548 Fix dirty marking when tags are not ordered](https://github.com/mbleigh/acts-as-taggable-on/issues/548) * Misc * [@seuros Remove actionpack dependency](https://github.com/mbleigh/acts-as-taggable-on/commit/5d20e0486c892fbe21af42fdcd79d0b6ebe87ed4) * [@seuros #547 Add tests for update_attributes](https://github.com/mbleigh/acts-as-taggable-on/issues/547) ### [3.2.5 / 2014-05-25](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.4...v3.2.5) * Fixes * [@seuros #546 Fix autoload bug. Now require engine file instead of autoloading it](https://github.com/mbleigh/acts-as-taggable-on/issues/546) ### [3.2.4 / 2014-05-24](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.3...v3.2.4) * Fixes * [@seuros #544 Fix incorrect query generation related to `GROUP BY` SQL statement](https://github.com/mbleigh/acts-as-taggable-on/issues/544) * Misc * [@seuros #545 Remove `ammeter` development dependency](https://github.com/mbleigh/acts-as-taggable-on/pull/545) * [@seuros #545 Deprecate `TagList.from` in favor of `TagListParser.parse`](https://github.com/mbleigh/acts-as-taggable-on/pull/545) * [@seuros #543 Introduce lazy loading](https://github.com/mbleigh/acts-as-taggable-on/pull/543) * [@seuros #541 Deprecate ActsAsTaggableOn::Utils](https://github.com/mbleigh/acts-as-taggable-on/pull/541) ### [3.2.3 / 2014-05-16](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.2...v3.2.3) * Fixes * [@seuros #540 Fix for tags removal (it was affecting all records with the same tag)](https://github.com/mbleigh/acts-as-taggable-on/pull/540) * [@akcho8 #535 Fix for `options` Hash passed to methods from being deleted by those methods](https://github.com/mbleigh/acts-as-taggable-on/pull/535) ### [3.2.2 / 2014-05-07](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.1...v3.2.2) * Breaking Changes * [@seuros #526 Taggable models are not extended with ActsAsTaggableOn::Utils anymore](https://github.com/mbleigh/acts-as-taggable-on/pull/526) * Fixes * [@seuros #536 Add explicit conversion of tags to strings (when assigning tags)](https://github.com/mbleigh/acts-as-taggable-on/pull/536) * Misc * [@seuros #526 Delete outdated benchmark script](https://github.com/mbleigh/acts-as-taggable-on/pull/526) * [@seuros #525 Fix tests so that they pass with MySQL](https://github.com/mbleigh/acts-as-taggable-on/pull/525) ### [3.2.1 / 2014-05-06](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.2.0...v3.2.1) * Misc * [@seuros #523 Run tests loading only ActiveRecord (without the full Rails stack)](https://github.com/mbleigh/acts-as-taggable-on/pull/523) * [@seuros #523 Remove activesupport dependency](https://github.com/mbleigh/acts-as-taggable-on/pull/523) * [@seuros #523 Introduce database_cleaner in specs](https://github.com/mbleigh/acts-as-taggable-on/pull/523) * [@seuros #520 Tag_list cleanup](https://github.com/mbleigh/acts-as-taggable-on/pull/520) ### [3.2.0 / 2014-05-01](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.1.1...v3.2.0) * Breaking Changes * ActsAsTaggableOn::Tag is not extend with ActsAsTaggableOn::Utils anymore * Features * [@chess #413 Hook to support STI subclasses of Tag in save_tags](https://github.com/mbleigh/acts-as-taggable-on/pull/413) * Fixes * [@jdelStrother #515 Rename Compatibility methods to reduce chance of conflicts ](https://github.com/mbleigh/acts-as-taggable-on/pull/515) * [@seuros #512 fix for << method](https://github.com/mbleigh/acts-as-taggable-on/pull/512) * [@sonots #510 fix IN subquery error for mysql](https://github.com/mbleigh/acts-as-taggable-on/pull/510) * [@jonseaberg #499 fix for race condition when multiple processes try to add the same tag](https://github.com/mbleigh/acts-as-taggable-on/pull/499) * [@leklund #496 Fix for distinct and postgresql json columns errors](https://github.com/mbleigh/acts-as-taggable-on/pull/496) * [@thatbettina & @plexus #394 Multiple quoted tags](https://github.com/mbleigh/acts-as-taggable-on/pull/496) * Performance * Misc * [@seuros #511 Rspec 3](https://github.com/mbleigh/acts-as-taggable-on/pull/511) ### [3.1.0 / 2014-03-31](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...v3.1.0) * Fixes * [@mikehale #487 Match_all respects context](https://github.com/mbleigh/acts-as-taggable-on/pull/487) * Performance * [@dgilperez #390 Add taggings counter cache](https://github.com/mbleigh/acts-as-taggable-on/pull/390) * Misc * [@jonseaberg Add missing indexes to schema used in specs #474](https://github.com/mbleigh/acts-as-taggable-on/pull/474) * [@seuros Specify Ruby >= 1.9.3 required in gemspec](https://github.com/mbleigh/acts-as-taggable-on/pull/502) * [@kiasaki Add missing quotes to code example](https://github.com/mbleigh/acts-as-taggable-on/pull/501) ### [3.1.0.rc1 / 2014-02-26](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.1...v3.1.0.rc1) * Features * [@jamesburke-examtime #467 Add :order_by_matching_tag_count option](https://github.com/mbleigh/acts-as-taggable-on/pull/469) * Fixes * [@rafael #406 Dirty attributes not correctly derived](https://github.com/mbleigh/acts-as-taggable-on/pull/406) * [@bzbnhang #440 Did not respect strict_case_match](https://github.com/mbleigh/acts-as-taggable-on/pull/440) * [@znz #456 Fix breaking encoding of tag](https://github.com/mbleigh/acts-as-taggable-on/pull/456) * [@rgould #417 Let '.count' work when tagged_with is accompanied by a group clause](https://github.com/mbleigh/acts-as-taggable-on/pull/417) * [@developer88 #461 Move 'Distinct' out of select string and use .uniq instead](https://github.com/mbleigh/acts-as-taggable-on/pull/461) * [@gerard-leijdekkers #473 Fixed down migration index name](https://github.com/mbleigh/acts-as-taggable-on/pull/473) * [@leo-souza #498 Use database's lower function for case-insensitive match](https://github.com/mbleigh/acts-as-taggable-on/pull/498) * Misc * [@billychan #463 Thread safe support](https://github.com/mbleigh/acts-as-taggable-on/pull/463) * [@billychan #386 Add parse:true instructions to README](https://github.com/mbleigh/acts-as-taggable-on/pull/386) * [@seuros #449 Improve README/UPGRADING/post install docs](https://github.com/mbleigh/acts-as-taggable-on/pull/449) * [@seuros #452 Remove I18n deprecation warning in specs](https://github.com/mbleigh/acts-as-taggable-on/pull/452) * [@seuros #453 Test against Ruby 2.1 on Travis CI](https://github.com/mbleigh/acts-as-taggable-on/pull/453) * [@takashi #454 Clarify example in docs](https://github.com/mbleigh/acts-as-taggable-on/pull/454) ### [3.0.1 / 2014-01-08](https://github.com/mbleigh/acts-as-taggable-on/compare/v3.0.0...v3.0.1) * Fixes * [@rafael #406 Dirty attributes not correctly derived](https://github.com/mbleigh/acts-as-taggable-on/pull/406) * [@bzbnhang #440 Did not respect strict_case_match](https://github.com/mbleigh/acts-as-taggable-on/pull/440) * [@znz #456 Fix breaking encoding of tag](https://github.com/mbleigh/acts-as-taggable-on/pull/456) * Misc * [@billychan #386 Add parse:true instructions to README](https://github.com/mbleigh/acts-as-taggable-on/pull/386) * [@seuros #449 Improve README/UPGRADING/post install docs](https://github.com/mbleigh/acts-as-taggable-on/pull/449) * [@seuros #452 Remove I18n deprecation warning in specs](https://github.com/mbleigh/acts-as-taggable-on/pull/452) * [@seuros #453 Test against Ruby 2.1 on Travis CI](https://github.com/mbleigh/acts-as-taggable-on/pull/453) * [@takashi #454 Clarify example in docs](https://github.com/mbleigh/acts-as-taggable-on/pull/454) ### [3.0.0 / 2014-01-01](https://github.com/mbleigh/acts-as-taggable-on/compare/v2.4.1...v3.0.0) * Breaking Changes * No longer supports Ruby 1.8. * Features * Supports Rails 4.1. * Misc (TODO: expand) * [zquest #359](https://github.com/mbleigh/acts-as-taggable-on/pull/359) * [rsl #367](https://github.com/mbleigh/acts-as-taggable-on/pull/367) * [ktdreyer #383](https://github.com/mbleigh/acts-as-taggable-on/pull/383) * [cwoodcox #346](https://github.com/mbleigh/acts-as-taggable-on/pull/346) * [mrb #421](https://github.com/mbleigh/acts-as-taggable-on/pull/421) * [bf4 #430](https://github.com/mbleigh/acts-as-taggable-on/pull/430) * [sanemat #368](https://github.com/mbleigh/acts-as-taggable-on/pull/368) * [bf4 #343](https://github.com/mbleigh/acts-as-taggable-on/pull/343) * [marclennox #429](https://github.com/mbleigh/acts-as-taggable-on/pull/429) * [shekibobo #403](https://github.com/mbleigh/acts-as-taggable-on/pull/403) * [ches ktdreyer #410](https://github.com/mbleigh/acts-as-taggable-on/pull/410) * [makaroni4 #371](https://github.com/mbleigh/acts-as-taggable-on/pull/371) * [kenzai dstosik awt #431](https://github.com/mbleigh/acts-as-taggable-on/pull/431) * [bf4 joelcogen shekibobo aaronchi #438](https://github.com/mbleigh/acts-as-taggable-on/pull/438) * [seuros #442](https://github.com/mbleigh/acts-as-taggable-on/pull/442) * [bf4 #445](https://github.com/mbleigh/acts-as-taggable-on/pull/445) * [eaglemt #446](https://github.com/mbleigh/acts-as-taggable-on/pull/446) ### 3.0.0.rc2 [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/fork-v3.0.0.rc1...fork-v3.0.0.rc2) ### 3.0.0.rc1 [changes](https://github.com/mbleigh/acts-as-taggable-on/compare/v2.4.1...fork-v3.0.0.rc1) ### [2.4.1 / 2013-05-07](https://github.com/mbleigh/acts-as-taggable-on/compare/v2.4.0...v2.4.1) * Features * Fixes * Misc acts-as-taggable-on-3.5.0/README.md0000644000004100000410000003257012503660023016543 0ustar www-datawww-data# ActsAsTaggableOn [![Gem Version](https://badge.fury.io/rb/acts-as-taggable-on.svg)](http://badge.fury.io/rb/acts-as-taggable-on) [![Build Status](https://secure.travis-ci.org/mbleigh/acts-as-taggable-on.png)](http://travis-ci.org/mbleigh/acts-as-taggable-on) [![Code Climate](https://codeclimate.com/github/mbleigh/acts-as-taggable-on.png)](https://codeclimate.com/github/mbleigh/acts-as-taggable-on) [![Inline docs](http://inch-ci.org/github/mbleigh/acts-as-taggable-on.png)](http://inch-ci.org/github/mbleigh/acts-as-taggable-on) This plugin was originally based on Acts as Taggable on Steroids by Jonathan Viney. It has evolved substantially since that point, but all credit goes to him for the initial tagging functionality that so many people have used. For instance, in a social network, a user might have tags that are called skills, interests, sports, and more. There is no real way to differentiate between tags and so an implementation of this type is not possible with acts as taggable on steroids. Enter Acts as Taggable On. Rather than tying functionality to a specific keyword (namely `tags`), acts as taggable on allows you to specify an arbitrary number of tag "contexts" that can be used locally or in combination in the same way steroids was used. ## Compatibility Versions 2.x are compatible with Ruby 1.8.7+ and Rails 3. Versions 2.4.1 and up are compatible with Rails 4 too (thanks to arabonradar and cwoodcox). Versions >= 3.x are compatible with Ruby 1.9.3+ and Rails 3 and 4. For an up-to-date roadmap, see https://github.com/mbleigh/acts-as-taggable-on/milestones ## Installation To use it, add it to your Gemfile: ```ruby gem 'acts-as-taggable-on', '~> 3.4' ``` and bundle: ```shell bundle ``` #### Post Installation Install migrations ```shell # For the latest versions : rake acts_as_taggable_on_engine:install:migrations # For versions 2.4.1 and earlier : rails generate acts_as_taggable_on:migration ``` Review the generated migrations then migrate : ```shell rake db:migrate ``` #### For MySql users You can circumvent at any time the problem of special characters [issue 623](https://github.com/mbleigh/acts-as-taggable-on/issues/623) by setting in an initializer file: ```ruby ActsAsTaggableOn.force_binary_collation = true ``` Or by running this rake task: ```shell rake acts_as_taggable_on_engine:tag_names:collate_bin ``` See the Configuration section for more details, and a general note valid for older version of the gem. #### Upgrading see [UPGRADING](UPGRADING.md) ## Usage Setup ```ruby class User < ActiveRecord::Base acts_as_taggable # Alias for acts_as_taggable_on :tags acts_as_taggable_on :skills, :interests end class UsersController < ApplicationController def user_params params.require(:user).permit(:name, :tag_list => []) ## Rails 4 strong params usage end end @user = User.new(:name => "Bobby") ``` Add and remove a single tag ```ruby @user.tag_list.add("awesome") # add a single tag. alias for << @user.tag_list.remove("awesome") # remove a single tag ``` Add and remove multiple tags in an array ```ruby @user.tag_list.add("awesome", "slick") @user.tag_list.remove("awesome", "slick") ``` You can also add and remove tags in format of String. This would be convenient in some cases such as handling tag input param in a String. Pay attention you need to add `parse: true` as option in this case. You may also want to take a look at delimiter in the string. The default is comma `,` so you don't need to do anything here. However, if you made a change on delimiter setting, make sure the string will match. See [configuration](#configuration) for more about delimiter. ```ruby @user.tag_list.add("awesome, slick", parse: true) @user.tag_list.remove("awesome, slick", parse: true) ``` You can also add and remove tags by direct assignment. Note this will remove existing tags so use it with attention. ```ruby @user.tag_list = "awesome, slick, hefty" @user.save @user.reload @user.tags => [#, #, #] ``` With the defined context in model, you have multiple new methods at disposal to manage and view the tags in the context. For example, with `:skill` context these methods are added to the model: `skill_list`(and `skill_list.add`, `skill_list.remove` `skill_list=`), `skills`(plural), `skill_counts`. ```ruby @user.skill_list = "joking, clowning, boxing" @user.save @user.reload @user.skills => [#, #, #] @user.skill_list.add("coding") @user.skill_list # => ["joking", "clowning", "boxing", "coding"] @another_user = User.new(:name => "Alice") @another_user.skill_list.add("clowning") @another_user.save User.skill_counts => [#, #, #] ``` To preserve the order in which tags are created use `acts_as_ordered_taggable`: ```ruby class User < ActiveRecord::Base # Alias for acts_as_ordered_taggable_on :tags acts_as_ordered_taggable acts_as_ordered_taggable_on :skills, :interests end @user = User.new(:name => "Bobby") @user.tag_list = "east, south" @user.save @user.tag_list = "north, east, south, west" @user.save @user.reload @user.tag_list # => ["north", "east", "south", "west"] ``` ### Finding most or least used tags You can find the most or least used tags by using: ```ruby ActsAsTaggableOn::Tag.most_used ActsAsTaggableOn::Tag.least_used ``` You can also filter the results by passing the method a limit, however the default limit is 20. ```ruby ActsAsTaggableOn::Tag.most_used(10) ActsAsTaggableOn::Tag.least_used(10) ``` ### Finding Tagged Objects Acts As Taggable On uses scopes to create an association for tags. This way you can mix and match to filter down your results. ```ruby class User < ActiveRecord::Base acts_as_taggable_on :tags, :skills scope :by_join_date, order("created_at DESC") end User.tagged_with("awesome").by_join_date User.tagged_with("awesome").by_join_date.paginate(:page => params[:page], :per_page => 20) # Find users that matches all given tags: User.tagged_with(["awesome", "cool"], :match_all => true) # Find users with any of the specified tags: User.tagged_with(["awesome", "cool"], :any => true) # Find users that has not been tagged with awesome or cool: User.tagged_with(["awesome", "cool"], :exclude => true) # Find users with any of the tags based on context: User.tagged_with(['awesome', 'cool'], :on => :tags, :any => true).tagged_with(['smart', 'shy'], :on => :skills, :any => true) ``` You can also use `:wild => true` option along with `:any` or `:exclude` option. It will be looking for `%awesome%` and `%cool%` in SQL. __Tip:__ `User.tagged_with([])` or `User.tagged_with('')` will return `[]`, an empty set of records. ### Relationships You can find objects of the same type based on similar tags on certain contexts. Also, objects will be returned in descending order based on the total number of matched tags. ```ruby @bobby = User.find_by_name("Bobby") @bobby.skill_list # => ["jogging", "diving"] @frankie = User.find_by_name("Frankie") @frankie.skill_list # => ["hacking"] @tom = User.find_by_name("Tom") @tom.skill_list # => ["hacking", "jogging", "diving"] @tom.find_related_skills # => [, ] @bobby.find_related_skills # => [] @frankie.find_related_skills # => [] ``` ### Dynamic Tag Contexts In addition to the generated tag contexts in the definition, it is also possible to allow for dynamic tag contexts (this could be user generated tag contexts!) ```ruby @user = User.new(:name => "Bobby") @user.set_tag_list_on(:customs, "same, as, tag, list") @user.tag_list_on(:customs) # => ["same", "as", "tag", "list"] @user.save @user.tags_on(:customs) # => [,...] @user.tag_counts_on(:customs) User.tagged_with("same", :on => :customs) # => [@user] ``` ### Tag Parsers If you want to change how tags are parsed, you can define a your own implementation: ```ruby class MyParser < ActsAsTaggableOn::GenericParser def parse ActsAsTaggableOn::TagList.new.tap do |tag_list| tag_list.add @tag_list.split('|') end end end ``` Now you can use this parser, passing it as parameter: ```ruby @user = User.new(:name => "Bobby") @user.tag_list = "east, south" @user.tag_list.add("north|west", parser: MyParser) @user.tag_list # => ["north", "east", "south", "west"] # Or also: @user.tag_list.parser = MyParser @user.tag_list.add("north|west") @user.tag_list # => ["north", "east", "south", "west"] ``` Or change it globally: ```ruby ActsAsTaggable.default_parser = MyParser @user = User.new(:name => "Bobby") @user.tag_list = "east|south" @user.tag_list # => ["east", "south"] ``` ### Tag Ownership Tags can have owners: ```ruby class User < ActiveRecord::Base acts_as_tagger end class Photo < ActiveRecord::Base acts_as_taggable_on :locations end @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations) @some_user.owned_taggings @some_user.owned_tags Photo.tagged_with("paris", :on => :locations, :owned_by => @some_user) @some_photo.locations_from(@some_user) # => ["paris", "normandy"] @some_photo.owner_tags_on(@some_user, :locations) # => [#...] @some_photo.owner_tags_on(nil, :locations) # => Ownerships equivalent to saying @some_photo.locations @some_user.tag(@some_photo, :with => "paris, normandy", :on => :locations, :skip_save => true) #won't save @some_photo object ``` ### Dirty objects ```ruby @bobby = User.find_by_name("Bobby") @bobby.skill_list # => ["jogging", "diving"] @bobby.skill_list_changed? #=> false @bobby.changes #=> {} @bobby.skill_list = "swimming" @bobby.changes.should == {"skill_list"=>["jogging, diving", ["swimming"]]} @bobby.skill_list_changed? #=> true @bobby.skill_list_change.should == ["jogging, diving", ["swimming"]] ``` ### Tag cloud calculations To construct tag clouds, the frequency of each tag needs to be calculated. Because we specified `acts_as_taggable_on` on the `User` class, we can get a calculation of all the tag counts by using `User.tag_counts_on(:customs)`. But what if we wanted a tag count for a single user's posts? To achieve this we call tag_counts on the association: ```ruby User.find(:first).posts.tag_counts_on(:tags) ``` A helper is included to assist with generating tag clouds. Here is an example that generates a tag cloud. Helper: ```ruby module PostsHelper include ActsAsTaggableOn::TagsHelper end ``` Controller: ```ruby class PostController < ApplicationController def tag_cloud @tags = Post.tag_counts_on(:tags) end end ``` View: ```erb <% tag_cloud(@tags, %w(css1 css2 css3 css4)) do |tag, css_class| %> <%= link_to tag.name, { :action => :tag, :id => tag.name }, :class => css_class %> <% end %> ``` CSS: ```css .css1 { font-size: 1.0em; } .css2 { font-size: 1.2em; } .css3 { font-size: 1.4em; } .css4 { font-size: 1.6em; } ``` ## Configuration If you would like to remove unused tag objects after removing taggings, add: ```ruby ActsAsTaggableOn.remove_unused_tags = true ``` If you want force tags to be saved downcased: ```ruby ActsAsTaggableOn.force_lowercase = true ``` If you want tags to be saved parametrized (you can redefine to_param as well): ```ruby ActsAsTaggableOn.force_parameterize = true ``` If you would like tags to be case-sensitive and not use LIKE queries for creation: ```ruby ActsAsTaggableOn.strict_case_match = true ``` If you would like to have an exact match covering special characters with MySql: ```ruby ActsAsTaggableOn.force_binary_collation = true ``` If you want to change the default delimiter (it defaults to ','). You can also pass in an array of delimiters such as ([',', '|']): ```ruby ActsAsTaggableOn.delimiter = ',' ``` *NOTE 1: SQLite by default can't upcase or downcase multibyte characters, resulting in unwanted behavior. Load the SQLite ICU extension for proper handle of such characters. [See docs](http://www.sqlite.org/src/artifact?ci=trunk&filename=ext/icu/README.txt)* *NOTE 2: the option `force_binary_collation` is strongest than `strict_case_match` and when set to true, the `strict_case_match` is ignored. To roughly apply the `force_binary_collation` behaviour with a version of the gem <= 3.4.4, execute the following commands in the MySql console:* ```shell USE my_wonderful_app_db; ALTER TABLE tags MODIFY name VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin; ``` ## Contributors We have a long list of valued contributors. [Check them all](https://github.com/mbleigh/acts-as-taggable-on/contributors) ## Maintainer * [Joost Baaij](https://github.com/tilsammans) ## TODO - Write benchmark script - Resolve concurrency issues ## Testing Acts As Taggable On uses RSpec for its test coverage. Inside the gem directory, you can run the specs with: ```shell bundle rake spec ``` You can run all the tests across all the Rails versions by running `rake appraise`. If you'd also like to [run the tests across all rubies and databases as configured for Travis CI, install and run `wwtd`](https://github.com/grosser/wwtd). ## License See [LICENSE](https://github.com/mbleigh/acts-as-taggable-on/blob/master/LICENSE.md) acts-as-taggable-on-3.5.0/metadata.gz0000444000004100000410000000300012503660213017367 0ustar www-datawww-dataqTYmo6_KԒmVm,Ei{{Hogn#;w4 W&A}؄Tۚb90*R2=z`D(zIi *~jyC u[˟v-+9PBzjiv9:9JIR;7Z7S1fR`ұq q/;|&UbQ2F&e+,*lY\n~tgLhdLKF'ečPٙapHUdHESM[Q|ly^ŗP '*2ͧ1E"'ܣ[cTn*b>SL{{~*LK=wrR"F1@货[u9ئs{`{`e3vy'&k;,ӏk E^ IRzLJ8geK7x.; ŢxOz6Ernɺ'y'ٌ[ҍ 3MZ9}!#xc G# [hթg^b8zHdGbyB&tnX١[-lm$b{O-6K2wBq\=lJ+q):yw,&[C JG#&l+7j~!M Tڃ A}e(\/"VchӀHmrV*Qm%eXٺעsE]B,OuL0]zEE;mY]Si!YUP-R̘ZA5&AaA/GI]o> 2j0e,