diff --git a/Gemfile b/Gemfile index eeae1fe..d73febe 100644 --- a/Gemfile +++ b/Gemfile @@ -1,19 +1,16 @@ -source "http://rubygems.org" -# Add dependencies required to use your gem here. -# Example: -# gem "activesupport", ">= 2.3.5" -gem "activerecord", ">= 3.2.0" -gem "squeel", '~> 1.0.11', :require => false +source 'https://rubygems.org' + +gem 'activerecord', '>= 3.2.0' +gem 'squeel', '~> 1.1.1', :require => false # Add dependencies to develop your gem here. # Include everything needed to run rake, tests, features, etc. group :development do - gem "pg" - gem "rspec", "~> 2.10.0" - gem "yard", "~> 0.6.0" - gem "bundler", "~> 1.2.0" - gem "jeweler", "~> 1.6.4" - #gem "rcov", ">= 0" + gem 'pg' + gem 'rspec', '~> 2.10.0' + gem 'yard', '~> 0.6.0' + gem 'bundler', '>= 1.2.0' + gem 'jeweler', '~> 1.6.4' gem 'sqlite3' - gem "rake" + gem 'rake' end diff --git a/Gemfile.lock b/Gemfile.lock index 1cb7b6d..f94fe48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,31 +1,37 @@ GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: - activemodel (3.2.6) - activesupport (= 3.2.6) - builder (~> 3.0.0) - activerecord (3.2.6) - activemodel (= 3.2.6) - activesupport (= 3.2.6) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activesupport (3.2.6) - i18n (~> 0.6) - multi_json (~> 1.0) - arel (3.0.2) - builder (3.0.0) + activemodel (4.0.0) + activesupport (= 4.0.0) + builder (~> 3.1.0) + activerecord (4.0.0) + activemodel (= 4.0.0) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.0) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.0) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) + arel (4.0.0) + atomic (1.1.14) + builder (3.1.4) diff-lcs (1.1.3) - git (1.2.5) - i18n (0.6.0) + git (1.2.6) + i18n (0.6.5) jeweler (1.6.4) bundler (~> 1.0) git (>= 1.2.5) rake - multi_json (1.3.6) - pg (0.14.0) - polyamorous (0.5.0) - activerecord (~> 3.0) - rake (0.9.2.2) + minitest (4.7.5) + multi_json (1.8.0) + pg (0.16.0) + polyamorous (0.6.4) + activerecord (>= 3.0) + rake (10.1.0) rspec (2.10.0) rspec-core (~> 2.10.0) rspec-expectations (~> 2.10.0) @@ -34,12 +40,14 @@ GEM rspec-expectations (2.10.0) diff-lcs (~> 1.1.3) rspec-mocks (2.10.1) - sqlite3 (1.3.6) - squeel (1.0.11) - activerecord (~> 3.0) - activesupport (~> 3.0) - polyamorous (~> 0.5.0) - tzinfo (0.3.33) + sqlite3 (1.3.8) + squeel (1.1.1) + activerecord (>= 3.0) + activesupport (>= 3.0) + polyamorous (~> 0.6.0) + thread_safe (0.1.3) + atomic + tzinfo (0.3.37) yard (0.6.8) PLATFORMS @@ -47,11 +55,11 @@ PLATFORMS DEPENDENCIES activerecord (>= 3.2.0) - bundler (~> 1.2.0) + bundler (>= 1.2.0) jeweler (~> 1.6.4) pg rake rspec (~> 2.10.0) sqlite3 - squeel (~> 1.0.11) + squeel (~> 1.1.1) yard (~> 0.6.0) diff --git a/lib/rocket_tag/tag.rb b/lib/rocket_tag/tag.rb index a1eb2a0..3267826 100644 --- a/lib/rocket_tag/tag.rb +++ b/lib/rocket_tag/tag.rb @@ -1,16 +1,14 @@ module RocketTag class Tag < ActiveRecord::Base has_many :taggings, :dependent => :destroy, :class_name => 'RocketTag::Tagging' - attr_accessible :name validates_presence_of :name validates_uniqueness_of :name - has_and_belongs_to_many :alias, :class_name => "RocketTag::Tag", + has_and_belongs_to_many :alias, lambda { uniq }, :class_name => "RocketTag::Tag", :join_table => "alias_tags", :foreign_key => "tag_id", :association_foreign_key => "alias_id", - :uniq => true, :after_add => :add_reverse_alias, :after_remove => :remove_reverse_alias @@ -18,11 +16,11 @@ def add_reverse_alias(tag) [self.alias, self].flatten.each do |t| tag.alias << t if !tag.alias.include?(t) && t != tag end - end + end def remove_reverse_alias(tag) tag.alias.delete(self) if tag.alias.include?(self) - end + end def alias?(that) return self.alias.include?(that) diff --git a/lib/rocket_tag/taggable.rb b/lib/rocket_tag/taggable.rb index 5712815..2979a2d 100644 --- a/lib/rocket_tag/taggable.rb +++ b/lib/rocket_tag/taggable.rb @@ -35,7 +35,7 @@ def self.parse_tags list require 'csv' if list.kind_of? String # for some reason CSV parser cannot handle - # + # # hello, "foo" # # but must be @@ -106,13 +106,13 @@ def tags_for_context context def tagged_similar options = {} context = options.delete :on - contexts = self.class.normalize_contexts context, + contexts = self.class.normalize_contexts context, self.class.rocket_tag.contexts - q = self.class.tagged_with Hash[*contexts.map{|c| + q = self.class.tagged_with Hash[*contexts.map{|c| [c, tags_for_context(c)] }.flatten(1)] - + # Exclude self from the results q.where{id!=my{id}} @@ -149,7 +149,7 @@ def normalize_contexts(context, default_if_nil = []) def validate_contexts contexts contexts.each do |context| unless is_valid_context? context - raise Exception.new("#{context} is not a valid tag context for #{self}") + raise Exception.new("#{context} is not a valid tag context for #{self}") end end end @@ -176,16 +176,16 @@ def with_tag_context context end def tagged_with tags_list, options = {} - + # Grab table name t = self.table_name - + q = joins{taggings.tag} - - alias_tag_names = lambda do |list| + + alias_tag_names = lambda do |list| names = RocketTag::Tag.select{:name}.where do id.in(RocketTag::Tag.select{'alias_tags.alias_id'}.joins(:alias).where{ - tags.name.in(list) + tags.name.in(list) }) end names.map{|t| t.name} @@ -229,17 +229,17 @@ def tagged_with tags_list, options = {} # select * from ( ..... ) tags # remove `.arel` dependency q = from(q.as(self.table_name)) - + # Restrict by minimum tag counts if required - min = options.delete :min - q = q.where{tags_count>=min} if min + min = options.delete :min + q = q.where{tags_count>=min} if min # Require all the tags if required all, exact = options.delete(:all), options.delete(:exact) q = q.where{tags_count==tags_list.length} if all || exact q = q.joins{taggings.tag}.group("#{self.table_name}.id").having('COUNT(tags.id) = ?', tags_list.length) if exact - # Return the relation + # Return the relation q end @@ -269,10 +269,10 @@ def tags(options = {}) # select * from ( ..... ) tags q = RocketTag::Tag.from(q.as(RocketTag::Tag.table_name)) #q = RocketTag::Tag.from(q.arel.as(RocketTag::Tag.table_name)) - + # Restrict by minimum tag counts if required - min = options.delete :min - q = q.where{tags_count>=min} if min + min = options.delete :min + q = q.where{tags_count>=min} if min # Return the relation q @@ -308,7 +308,7 @@ def setup_for_rocket_tag exisiting_tag_names = exisiting_tags.map &:name # Find missing tags - tags_names_to_create = list - exisiting_tag_names + tags_names_to_create = list - exisiting_tag_names # Create missing tags created_tags = tags_names_to_create.map do |tag_name| @@ -319,9 +319,9 @@ def setup_for_rocket_tag tags_to_assign = exisiting_tags + created_tags tags_to_assign.each do |tag| - tagging = Tagging.new :tag => tag, - :taggable => self, - :context => context, + tagging = Tagging.new :tag => tag, + :taggable => self, + :context => context, :tagger => nil self.taggings << tagging end @@ -346,15 +346,15 @@ def attr_taggable *contexts contexts.each do |context| class_eval do - has_many "#{context}_taggings".to_sym, - :source => :taggable, - :as => :taggable, - :conditions => { :context => context } + has_many "#{context}_taggings".to_sym, + lambda { where(:context => context) }, + :source => :taggable, + :as => :taggable has_many "#{context}_tags".to_sym, + lambda { where(["taggings.context = ?", context]) }, :source => :tag, - :through => :taggings, - :conditions => [ "taggings.context = ?", context ] + :through => :taggings validate context do if not send(context).kind_of? Enumerable diff --git a/lib/rocket_tag/tagging.rb b/lib/rocket_tag/tagging.rb index d99bf83..400d96a 100644 --- a/lib/rocket_tag/tagging.rb +++ b/lib/rocket_tag/tagging.rb @@ -1,15 +1,5 @@ module RocketTag - class Tagging < ::ActiveRecord::Base - - attr_accessible :tag, - :tag_id, - :context, - :taggable, - :taggable_type, - :taggable_id, - :tagger, - :tagger_type, - :tagger_id + class Tagging < ::ActiveRecord::Base belongs_to :tag, :class_name => 'RocketTag::Tag' belongs_to :taggable, :polymorphic => true diff --git a/rocket_tag.gemspec b/rocket_tag.gemspec index 2fb4992..63cd782 100644 --- a/rocket_tag.gemspec +++ b/rocket_tag.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brad Phelan"] - s.date = "2012-07-11" + s.date = "2013-09-12" s.description = "" s.email = "bradphelan@xtargets.com" s.extra_rdoc_files = [ @@ -29,12 +29,16 @@ Gem::Specification.new do |s| "lib/generators/rocket_tag/migration/migration_generator.rb", "lib/generators/rocket_tag/migration/templates/active_record/migration.rb", "lib/rocket_tag.rb", + "lib/rocket_tag/alias_tag.rb", + "lib/rocket_tag/configuration.rb", "lib/rocket_tag/tag.rb", "lib/rocket_tag/taggable.rb", "lib/rocket_tag/tagging.rb", "rocket_tag.gemspec", "spec/database.yml", "spec/models.rb", + "spec/rocket_tag/configuration_spec.rb", + "spec/rocket_tag/tag_alias_spec.rb", "spec/rocket_tag/taggable_spec.rb", "spec/schema.rb", "spec/spec_helper.rb" @@ -42,40 +46,40 @@ Gem::Specification.new do |s| s.homepage = "http://github.com/bradphelan/rocket_tag" s.licenses = ["MIT"] s.require_paths = ["lib"] - s.rubygems_version = "1.8.15" + s.rubygems_version = "2.0.2" s.summary = "A modern fast tagging framework for Rails 3.1+" if s.respond_to? :specification_version then - s.specification_version = 3 + s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, [">= 3.2.0"]) - s.add_runtime_dependency(%q, ["~> 1.0.0"]) + s.add_runtime_dependency(%q, ["~> 1.1.1"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 2.10.0"]) s.add_development_dependency(%q, ["~> 0.6.0"]) - s.add_development_dependency(%q, ["~> 1.1.0"]) + s.add_development_dependency(%q, [">= 1.2.0"]) s.add_development_dependency(%q, ["~> 1.6.4"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) else s.add_dependency(%q, [">= 3.2.0"]) - s.add_dependency(%q, ["~> 1.0.0"]) + s.add_dependency(%q, ["~> 1.1.1"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 2.10.0"]) s.add_dependency(%q, ["~> 0.6.0"]) - s.add_dependency(%q, ["~> 1.1.0"]) + s.add_dependency(%q, [">= 1.2.0"]) s.add_dependency(%q, ["~> 1.6.4"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) end else s.add_dependency(%q, [">= 3.2.0"]) - s.add_dependency(%q, ["~> 1.0.0"]) + s.add_dependency(%q, ["~> 1.1.1"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 2.10.0"]) s.add_dependency(%q, ["~> 0.6.0"]) - s.add_dependency(%q, ["~> 1.1.0"]) + s.add_dependency(%q, [">= 1.2.0"]) s.add_dependency(%q, ["~> 1.6.4"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) diff --git a/spec/rocket_tag/taggable_spec.rb b/spec/rocket_tag/taggable_spec.rb index b869a6e..1f1575c 100644 --- a/spec/rocket_tag/taggable_spec.rb +++ b/spec/rocket_tag/taggable_spec.rb @@ -107,10 +107,10 @@ end it "should generate the correct results" do - TaggableModel.tagged_with(%w[a b], :all=>true).count(:distinct => true).should == 6 - TaggableModel.tagged_with(%w[a b], :all=>true).where{name.like "app%"}.count(:distinct => true).should == 3 - TaggableModel.tagged_with(%w[a b], :all=>true).where{name.like "%1"}.count(:distinct => true).should == 2 - TaggableModel.tagged_with(%w[a b], :all=>true, :on => :skills).where{name.like "%1"}.count(:distinct => true).should == 1 + TaggableModel.tagged_with(%w[a b], :all=>true).distinct.count.should == 6 + TaggableModel.tagged_with(%w[a b], :all=>true).where{name.like "app%"}.distinct.count.should == 3 + TaggableModel.tagged_with(%w[a b], :all=>true).where{name.like "%1"}.distinct.count.should == 2 + TaggableModel.tagged_with(%w[a b], :all=>true, :on => :skills).where{name.like "%1"}.distinct.count.should == 1 end end @@ -173,7 +173,7 @@ it "should return similar items in the correct order with the correct tags_count" do # ---- - similar = @t00.tagged_similar(:on => :skills).all + similar = @t00.tagged_similar(:on => :skills).to_a similar[0].id.should == @t01.id similar[1].id.should == @t10.id similar[2].id.should == @t11.id @@ -183,7 +183,7 @@ similar[2].tags_count.should == 1 # ---- - similar = @t00.tagged_similar(:on => :languages).all.sort + similar = @t00.tagged_similar(:on => :languages).to_a.sort similar[0].id.should == @t01.id similar[1].id.should == @t10.id similar[2].id.should == @t21.id @@ -193,7 +193,7 @@ similar[2].tags_count.should == 1 # ---- - similar = @t00.tagged_similar.all + similar = @t00.tagged_similar.to_a similar[0].id.should == @t01.id similar[1].id.should == @t10.id similar[2].id.should == @t11.id @@ -208,7 +208,7 @@ describe "#tagged_with" do it "should count the number of matched tags" do - r = TaggableModel.tagged_with(["a", "b", "german"]).all + r = TaggableModel.tagged_with(["a", "b", "german"]).to_a r.find{|i|i.name == "00"}.tags_count.should == 3 r.find{|i|i.name == "01"}.tags_count.should == 3 r.find{|i|i.name == "10"}.tags_count.should == 1 @@ -228,7 +228,7 @@ # The min option is a shortcut for a query on tags_count r = TaggableModel.tagged_with(["a", "b", "german"], :min => 2).count.should == 2 - r = TaggableModel.tagged_with(["a", "b", "german"], :on => :skills).all + r = TaggableModel.tagged_with(["a", "b", "german"], :on => :skills).to_a r.find{|i|i.name == "00"}.tags_count.should == 2 r.find{|i|i.name == "01"}.tags_count.should == 2 r.find{|i|i.name == "10"}.tags_count.should == 1 @@ -236,7 +236,7 @@ r.find{|i|i.name == "21"}.should be_nil # It should be possible to narrow scopes with tagged_with - r = @user0.taggable_models.tagged_with(["a", "b", "german"], :on => :skills).all + r = @user0.taggable_models.tagged_with(["a", "b", "german"], :on => :skills).to_a r.find{|i|i.name == "00"}.tags_count.should == 2 r.find{|i|i.name == "01"}.should be_nil r.find{|i|i.name == "10"}.tags_count.should == 1 @@ -246,7 +246,7 @@ describe ":all => true" do it "should return records where *all* tags match on any context" do - q0 = TaggableModel.tagged_with(["a", "german"], :all => true ).all + q0 = TaggableModel.tagged_with(["a", "german"], :all => true ).to_a q0.length.should == 2 q0.should include @t00 q0.should include @t01 @@ -255,7 +255,7 @@ describe ":all => false" do it "should return records where *any* tags match on any context" do - q0 = TaggableModel.tagged_with(["a", "german"] ).all + q0 = TaggableModel.tagged_with(["a", "german"] ).to_a q0.length.should == 5 q0.should include @t00 q0.should include @t01 @@ -270,7 +270,7 @@ describe ":all => false, :on => context" do it "should return records where *any* tags match on the specific context" do - q0 = TaggableModel.tagged_with(["a", "german"], :on => :skills ).all + q0 = TaggableModel.tagged_with(["a", "german"], :on => :skills ).to_a q0.length.should == 4 q0.should include @t00 q0.should include @t01 @@ -285,14 +285,14 @@ describe ":all => true, :on => context" do it "should return records where *all* tags match on the specific context" do q0 = TaggableModel.tagged_with(["a", "german"], :on => :skills, :all => true ) - q0.all.length.should == 0 + q0.to_a.length.should == 0 - q0 = TaggableModel.tagged_with(["a", "b"], :on => :skills, :all => true ).all + q0 = TaggableModel.tagged_with(["a", "b"], :on => :skills, :all => true ).to_a q0.length.should == 2 q0.should include @t00 q0.should include @t01 - q0 = TaggableModel.tagged_with(["a", "b"], :on => :skills, :all => true ).where{foo=="A"}.all + q0 = TaggableModel.tagged_with(["a", "b"], :on => :skills, :all => true ).where{foo=="A"}.to_a q0.length.should == 1 q0.should include @t00 q0.should_not include @t01 @@ -422,8 +422,8 @@ describe "#popular_tags" do it "should return correct list (and correctly ordered) of popular tags for class and context" do - TaggableModel.popular_tags.all.length.should == RocketTag::Tag.all.count - TaggableModel.popular_tags.limit(10).all.length.should == 10 + TaggableModel.popular_tags.to_a.length.should == RocketTag::Tag.all.count + TaggableModel.popular_tags.limit(10).to_a.length.should == 10 TaggableModel.popular_tags.order('tags_count desc, name desc').first.name.should == 'c' TaggableModel.popular_tags.order('id asc').first.name.should == 'a' TaggableModel.popular_tags.order('id asc').last.name.should == 'jinglish' @@ -431,7 +431,7 @@ TaggableModel.popular_tags(:on=>:skills).order('name asc').last.name.should == 'y' TaggableModel.popular_tags(:on=>[:skills, :languages]).order('id asc').first.name.should == 'a' TaggableModel.popular_tags(:on=>[:skills, :languages]).order('id asc').last.name.should == 'jinglish' - TaggableModel.popular_tags(:min=>2).all.length.should == 6 ## dirty! + TaggableModel.popular_tags(:min=>2).to_a.length.should == 6 ## dirty! end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index aa5e4a0..d43b412 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -40,36 +40,33 @@ def freq if File.exists?(database_yml) active_record_configuration = YAML.load_file(database_yml) - + ActiveRecord::Base.configurations = active_record_configuration config = ActiveRecord::Base.configurations[db_name] - + begin ActiveRecord::Base.establish_connection(db_name) ActiveRecord::Base.connection rescue case db_name - when /mysql/ + 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 - + ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log")) ActiveRecord::Base.default_timezone = :utc - - ActiveRecord::Base.silence do - ActiveRecord::Migration.verbose = false - - load(File.dirname(__FILE__) + '/schema.rb') - load(File.dirname(__FILE__) + '/models.rb') - end - + + ActiveRecord::Migration.verbose = false + + load(File.dirname(__FILE__) + '/schema.rb') + load(File.dirname(__FILE__) + '/models.rb') else raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample" end