require File.expand_path('../../spec_helper', __FILE__)
describe Tagtical::Taggable do
  before do
    clean_database!
    @taggable = TaggableModel.new(:name => "Bob Jones")
    @taggables = [@taggable, TaggableModel.new(:name => "John Doe")]
  end
  subject { @taggable }

  it "should not use not registered tags" do
    Tag::Skill.create!(:value => "chess")
    taggable = OtherTaggableModel.create!
    taggable.tag_list = "chess, basketball"
    taggable.save!
    expect(taggable.tags.any? { |t| t.is_a?(Tag::Skill) }).to eq(false)
  end

  it "should have tag types" do
    expect(TaggableModel.tag_types).to include(Tagtical::Tag::Type::BASE, "language", "skill", "craft", "need", "offering")
    expect(@taggable.tag_types).to eq(TaggableModel.tag_types)
  end

  describe ".custom_tag_types" do
    subject { TaggableModel.custom_tag_types }
    it { should include("language", "skill", "craft", "need", "offering") }
    it { should_not include(Tagtical::Tag::Type::BASE) }
    it { should == @taggable.custom_tag_types }
  end

  describe ".tag_types" do
    subject { TaggableModel.tag_types }
    it { should be_a(Tagtical::Tag::Type::Collection) }
    specify { expect(subject.first).to be_a(Tagtical::Tag::Type) }
  end

  describe "tag types collection" do

    it "should return its class instance after executing Array's methods" do
      expect(TaggableModel.tag_types.select { |t| t == "tag" }).to be_a(Tagtical::Tag::Type::Collection)
    end

    context "get tag types" do
      specify "by array" do
        expect(TaggableModel.tag_types.get(["language", "skill"])).to match_array(["skill", "language"])
      end

      specify "by item" do
        expect(TaggableModel.tag_types.get("language")).to eq("language")
      end
    end
  end

  it "should have tag_counts_on" do
    expect(TaggableModel.tag_counts_on(:tags).to_a).to be_empty

    @taggable.tag_list = ["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

  describe ".short_tags" do
    subject { @taggable }
    before { subject.update_attributes!(skill_list: %w(ruby rails), tag_list: %w(red)) }

    specify { expect(subject.short_tags).to eq(%w(skill_ruby skill_rails tag_red tag_ruby tag_rails)) }
    specify { expect(subject.short_tags(only: :skill)).to eq(%w(skill_ruby skill_rails)) }
    specify { expect(subject.short_tags(exclude: :skill)).to eq(%w(tag_red tag_ruby tag_rails)) }
  end

  it "should be able to create tags" do
    @taggable.skill_list = "ruby, rails, css"
    expect(@taggable.tag_list_on(:skills)).to be_an_instance_of(Tagtical::TagList)

    expect { @taggable.save }.to change(Tagtical::Tag, :count).by(3)

    @taggable.reload
    expect(@taggable.skill_list.sort).to eq(%w(ruby rails css).sort)
    expect(@taggable.tag_list.sort).to eq(%w(ruby rails css).sort)
  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.size).to eq(3)
    @taggable.skill_list = "ruby, rails"
    @taggable.save
    @taggable.reload
    expect(@taggable.skills.size).to eq(2)
  end

  describe "Tag Type Scopes" do
    before do
      @taggable.update_attributes!(:tag_list => "tree, train", :skill_list => "basketball", :craft_list => "pottery")
      @taggable.reload
    end

    describe "inherited tags scope optimizations" do
      before do
        @taggable.tags.to_a # load the target
      end

      it "should not access the database when top level tags are already loaded" do
        ActiveRecord::Base.connection.expects(:execute).never
        @taggable.skills.to_a
        expect(@taggable.skills).to have_only_tag_values %w{basketball pottery}
        expect(@taggable.crafts(:current)).to have_only_tag_values %w{pottery}
        expect(@taggable.skills(:current)).to have_only_tag_values %w{basketball}
        expect(@taggable.skills(:children)).to have_only_tag_values %w{pottery}
        expect(@taggable.tags).to have_only_tag_values %w{train tree basketball pottery}
      end

      it "should select the correct tags" do
        @taggable.skills.each { |tag| expect(tag).to be_skill }
        @taggable.crafts.each { |tag| expect(tag).to be_craft }
      end

    end
  end

  describe "Has and Has Not Scopes" do
    before do
      @taggables[0].update_attributes!(:language_list => "Spanish, French", :craft_list => "Pottery", :skill_list => "Fighting")
      @taggables[1].update_attributes!(:language_list => "Spanish, French", :craft_list => "Pottery", :need_list => "Food")
    end

    # These *should* be working, but we are having trouble in sqlite3, but we've confirmed it works in mysql
#    it "should omit the classes we don't want" do
#      TaggableModel.has_no_needs.all.should==[@taggables[0]]
#      TaggableModel.has_no_tags(:needs, :<).all.should==[]
#    end

  end

  when_possible_values_specified(:values => %w{Knitting Ruby Pottery}) do
    
    before do
      @taggable.craft_list = "knitting, ruby"
      @taggable.save!
      @taggable.reload
    end

    it { should have_only_tag_values %w{Knitting Ruby} }

    it "should save only one tag with a value from the possible_values list" do
      @taggable.craft_list.add("ruby", "pottery")
      @taggable.save!
      @taggable.reload
      expect(@taggable).to have_only_tag_values %w{Knitting Ruby Pottery}
    end

    it "should raise an error when saved with value not in possible_values" do
      @taggable.craft_list << "not-in-list"
      expect { @taggable.save! }.to raise_error(ActiveRecord::RecordInvalid)
    end

  end

  describe "#cascade_set_tag_list!" do
    when_possible_values_specified(:values => %w{Knitting Ruby Pottery Basketball}, :klass => Tag::Skill) do
      before do
        @taggable.update_attributes!(:tag_list => "tree, train", :skill_list => "basketball", :craft_list => "pottery")
        @taggable.reload
      end

      context "when :cascade => true" do
        before do
          @taggable.set_tag_list(["ruby : 7 ", "plain  "], :cascade => true)
          @taggable.save!
          @taggable.reload
        end

        specify do
          expect(@taggable.tag_list).to have_same_elements %w{Ruby plain}
        end

        it "should set value on skill even if different case" do
          expect(@taggable.skills).to have_only_tag_values %w{Ruby}
        end

        it "should keep the tag's relevance" do
          expect(@taggable.skills[0].relevance).to eq(7)
        end

        it "should remove all elements from craft" do
          expect(@taggable.crafts).to be_empty
        end

      end
      context "when :cascade only on :craft" do
        before do
          @taggable.set_tag_list(["ruby", "plain"], :cascade => true, :types => :craft)
          @taggable.save!
          @taggable.reload
        end

        specify do
          expect(@taggable.tag_list).to have_same_elements %w{Ruby plain}
        end

        it "should not have any tags on skills directly" do
          expect(@taggable.skills(:current)).to be_empty
        end

        it "should have tags on crafts" do
          expect(@taggable.craft_list).to have_same_elements %w{Ruby}
        end

      end
      context "Adding tags with Exclusion" do

        before do
          @taggable.set_tag_list "Ruby, plain", :cascade => true, :except => :skill
          @taggable.save!
          @taggable.reload
        end

        it "should not change the tags in skill" do
          expect(@taggable.skill_list).to have_same_elements ["Basketball", "Pottery"]
        end

        it "should have set the values at the tag level" do
          expect(@taggable.tag_list(:current)).to have_same_elements ["Ruby", "plain"]
        end

      end
      context "Getting tag_list with :except" do

        before do
          @taggable.set_tag_list "Ruby, plain", :cascade => true
          @taggable.save!
          @taggable.reload
        end

        it "should exclude all defined types" do
          expect(@taggable.tag_list(:except => :skill).size).to eq(1)
        end

      end

    end
  end

  describe "Eager Loading Tags" do
    before do
      @taggables[0].update_attributes!(:craft_list => "foo:0.9, bar, car")
      @taggables[1].update_attributes!(:craft_list => "foo:0.3")

      @taggables = TaggableModel.includes(:tags).load
    end

    it "should leverage eager loaded tags for tag_list" do
      ActiveRecord::Base.connection.expects(:execute).never
      @taggables[0].tag_list
      @taggables[0].craft_list
      @taggables[0].skill_list
    end

    it "should populate relevance on tags and preserve different relevances" do
      ActiveRecord::Base.connection.expects(:execute).never
      expect(@taggables[0].tags.detect { |t| t.value=="foo" }.relevance).to eq(0.9)
      expect(@taggables[1].tags.detect { |t| t.value=="foo" }.relevance).to eq(0.3)
    end
    
  end

  describe "tag_list scoping behavior" do
    before do
      @taggables[0].tag_list = "bob"
      @taggables[1].tag_list = "charlie"
      @taggables[0].skill_list = "ruby"
      @taggables[1].skill_list = "css"
      @taggables[0].craft_list = "knitting"
      @taggables[1].craft_list = "pottery"
      @taggables.each(&:save!)
      @taggables.each(&:reload)
    end

    it "should empty out inheriting tags" do
      @taggables[0].tag_list = []
      @taggables[0].save!
      @taggables[0].reload

      expect(@taggables[0].crafts).to be_empty
    end

    it "should not empty out 'tag' type when :current scope" do
      @taggables[0].set_tag_list([], :current)
      @taggables[0].save!
      @taggables[0].reload

      expect(@taggables[0].tags).to_not be_empty
      expect(@taggables[0].tags(:current)).to be_empty
      expect(@taggables[0].skills).to_not be_empty
      expect(@taggables[0].crafts).to_not be_empty
    end

    it "should be able to query tags" do
      expect(@taggables[0].tags(:scope => :current)).to have_only_tag_values %w{bob}
      expect(@taggables[0].tags(:==)).to have_only_tag_values %w{bob}
      expect(@taggables[0].tags).to have_only_tag_values %w{bob knitting ruby}
      expect(@taggables[0].tags(:scope => :children)).to have_only_tag_values %w{knitting ruby}
      expect(@taggables[0].tags(:scope => :<)).to have_only_tag_values %w{knitting ruby}
      expect(@taggables[1].crafts(:parents)).to have_only_tag_values %w{charlie css}
      expect(@taggables[1].crafts(:scope => :>)).to have_only_tag_values %w{charlie css}

      expect(@taggables[1].crafts(:scope => [:parents, :current])).to have_only_tag_values %w{charlie css pottery}
      expect(@taggables[1].crafts(:scope => :>=)).to have_only_tag_values %w{charlie css pottery}
      expect(@taggables[1].skills(:scope => [:parents, :children])).to have_only_tag_values %w{charlie pottery}
      expect(@taggables[1].skills(:scope => :"><")).to have_only_tag_values %w{charlie pottery}
    end

    it "should be able to select taggables by subset of tags using ActiveRelation methods" do
      expect(TaggableModel.with_tags("bob")).to eq([@taggables[0]])
      expect(TaggableModel.with_skills("ruby")).to eq([@taggables[0]])
      expect(TaggableModel.with_tags("rUBy")).to eq([@taggables[0]])
      expect(TaggableModel.with_tags("ruby", :scope => :current)).to eq([])
      expect(TaggableModel.with_tags("ruby", :scope => :==)).to eq([])
      expect(TaggableModel.with_skills("knitting")).to eq([@taggables[0]])
      expect(TaggableModel.with_skills("KNITTING", :scope => :current)).to eq([])
      expect(TaggableModel.with_skills("KNITTING", :scope => :==)).to eq([])
      expect(TaggableModel.with_skills("knitting", :scope => :parents)).to eq([])
      expect(TaggableModel.with_skills("knitting", :scope => :>)).to eq([])
      expect(TaggableModel.with_tags("bob", :scope => :current)).to eq([@taggables[0]])
      expect(TaggableModel.with_tags("bob", :scope => :==)).to eq([@taggables[0]])
      expect(TaggableModel.with_skills("bob", :scope => :parents)).to eq([@taggables[0]])
      expect(TaggableModel.with_skills("bob", :scope => :>)).to eq([@taggables[0]])
      expect(TaggableModel.with_crafts("knitting")).to eq([@taggables[0]])
    end
  end

  it "should be able to find by tag" do
    @taggable.skill_list = "ruby, rails, css"
    @taggable.save

    expect(TaggableModel.tagged_with("ruby")).to eq([@taggable])
  end

  describe "finding records with empty tags" do
    before do
      TaggableModel.create!(:name => "Ted")
      TaggableModel.create!(:name => "Tom", :skill_list => "ruby, rails, css")
      TaggableModel.create!(:name => "Fiona", :language_list => "english, spanish")
    end

    it "should be able to find them by passing the tag type name" do
      expect(TaggableModel.filter_by_empty_tags(:skill).map(&:name)).to eq(%w{Ted Fiona})
    end

    it "should be able to find them by passing the array of tag type names" do
      expect(TaggableModel.filter_by_empty_tags([:skill, "language"]).map(&:name)).to eq(%w{Ted})
    end
  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")).to eq([@taggable])
    expect(TaggableModel.tagged_with("ruby, css")).to eq([@taggable])
    expect(TaggableModel.tagged_with("bob", :on => :skills)).to_not eq([@taggable])
    expect(TaggableModel.tagged_with("bob", :on => :tags)).to eq([@taggable])
  end

  it "should be able to search by tag type" do
    TaggableModel.create!(:name => "Ted", :skill_list => "ruby")
    TaggableModel.create!(:name => "Tom", :skill_list => "ruby, rails, css")
    TaggableModel.create!(:name => "Fiona", :skill_list => "html, ruby, rails, css")

    expect(TaggableModel.tagged_with("ruby", :on => :skills).sort_by(&:id)).to eq(TaggableModel.with_skills("ruby").sort_by(&:id))
    expect(TaggableModel.tagged_with(["ruby", "rails", "css"], :on => :skills).sort_by(&:id)).to eq(TaggableModel.with_skills("ruby", "rails", "css").sort_by(&:id))
    expect(TaggableModel.with_skills("ruby", "rails").size).to eq(2)
  end

  it "should not duplicate tags" do
    @taggable = TaggableModel.create!(:name => "Gary", :skill_list => ["Ruby", "ruby", "RUBY", "rails"])

    expect(@taggable.skill_list.size).to eq(2)
  end

  describe "Tag Scope" do
    it "should proxy argument from tag scope to tagged_with" do
      { ["ruby", "rails", {:any => true}] => [['ruby', 'rails'], {:any => true, :on => :skill}],
        ["ruby", "rails"] => [['ruby', 'rails'], {:on => :skill}],
        [] => [[], {:on => :skill}],
        [["ruby", "rails"]] => [['ruby', 'rails'], {:on => :skill}]
      }.each do |input, output|
        TaggableModel.expects(:tagged_with).with(*output)
        TaggableModel.with_skills(*input)
      end
    end
  end

  it "should not care about case" do
    bob = TaggableModel.create!(:name => "Bob", :tag_list => "ruby")
    frank = TaggableModel.create!(:name => "Frank", :tag_list => "Ruby")

    expect(Tagtical::Tag.count).to eq(1)
    expect(TaggableModel.tagged_with("ruby").to_a).to eq(TaggableModel.tagged_with("Ruby").to_a)
  end

  it "should be able to get tag counts on model as a whole" do
    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")
    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
    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")

    expect(TaggableModel.all_tag_counts).to_not be_empty
    expect(TaggableModel.all_tag_counts(:order => 'tags.id').map { |tag| [tag.class, tag.value, tag.count] }).to eq([
      [Tagtical::Tag, "ruby", 2],
      [Tagtical::Tag, "rails", 2],
      [Tagtical::Tag, "css", 1],
      [Tag::Skill, "ruby", 1]
    ])
  end

  if ActiveRecord::VERSION::MAJOR >= 3
    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
  else
    it "should not return read-only records" do
      # apparantly, there is no way to set readonly to false in a scope if joins are made
    end

    it "should be possible to return writable records" do
      TaggableModel.create(:name => "Bob", :tag_list => "ruby, rails, css")
      expect(TaggableModel.tagged_with("ruby").first(:readonly => false)).to_not be_readonly
    end
  end

  context "with inheriting tags classes" do
    before do
      @taggable.tag_list = "bob"
      @taggable.skill_list = "ruby"
      @taggable.craft_list = "knitting"
      @taggable.save!
      @taggable.reload
    end

    context "with tag_list options" do
      it "should ignore tag subclasses with :scope => :current" do
        @taggable.set_tag_list([], :scope => :current)
        @taggable.save!
        @taggable.reload

        expect(@taggable.tags(:current)).to be_empty
        expect(@taggable.tags).to_not be_empty
        expect(@taggable.skills).to_not be_empty
        expect(@taggable.crafts).to_not be_empty
      end
    end

    it "should have tag_lists with inheriting tags" do
      expect(@taggable.tag_list).to have_same_elements %w{bob ruby knitting}
      expect(@taggable.skill_list).to have_same_elements %w{ruby knitting}
    end

    it "should nullify out inheriting tags on tag_list setter" do
      @taggable.tag_list = []
      @taggable.save!
      @taggable.reload

      expect(@taggable.tags).to be_empty
      expect(@taggable.skills).to be_empty
      expect(@taggable.crafts).to be_empty
    end
    
    it "should nullify out inheriting tags on skill_list setter but keep the tags in the super class" do
      @taggable.skill_list = []
      @taggable.save!
      @taggable.reload

      expect(@taggable.tags).to have_only_tag_values %w{bob}
      expect(@taggable.skills).to be_empty
      expect(@taggable.crafts).to be_empty
    end

    it "should not create tags on parent if children have the value" do
      Tagtical::Tag.delete_all
      expect {
        @taggable.skill_list = "pottery"
        @taggable.save!
        @taggable.reload
        @taggable.craft_list = "pottery"
        @taggable.save!
      }.to change(Tagtical::Tagging, :count).by(1)

      @taggable.reload
      expect(@taggable.skills.size).to eq(1)
      expect(@taggable.skills.first).to be_an_instance_of(Tag::FooCraft)
    end

  end

  context "with multiple taggable models" do

    before do
      @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")
    end

    RSpec::Matchers.define :have_tags_counts_of do |expected|
      def breakdown(tags)
        tags.map { |tag| [tag.class, tag.value, tag.count] }
      end
      match do |actual|
        breakdown(actual) == expected
      end
      failure_message do |actual|
        "expected #{breakdown(actual)} to have the breakdown #{expected}"
      end
    end

    it "should be able to get scoped tag counts" do
      expect(TaggableModel.tagged_with("ruby").tag_counts(:order => 'tags.id')).to have_tags_counts_of [
        [Tagtical::Tag, "ruby", 2],
        [Tagtical::Tag, "rails", 2],
        [Tagtical::Tag, "css", 1],
        [Tag::Skill, "ruby", 1],
        [Tag::Skill, "java", 1] ]
      expect(TaggableModel.tagged_with("ruby").skill_counts.first.count).to eq(1)
    end

    it "should be able to get all scoped tag counts" do
      expect(TaggableModel.tagged_with("ruby").all_tag_counts(:order => 'tags.id')).to have_tags_counts_of [
        [Tagtical::Tag, "ruby", 2],
        [Tagtical::Tag, "rails", 2],
        [Tagtical::Tag, "css", 1],
        [Tag::Skill, "ruby", 1],
        [Tag::Skill, "java", 1] ]
    end

    it 'should only return tag counts for the available scope' do
      expect(TaggableModel.tagged_with('rails').all_tag_counts).to have_tags_counts_of [
        [Tagtical::Tag, "css", 1],
        [Tagtical::Tag, "rails", 2],
        [Tagtical::Tag, "ruby", 2]
      ]
      expect(TaggableModel.tagged_with('rails').all_tag_counts.any? { |tag| tag.value == 'java' }).to eq(false)

      # 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
  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

  context "with multiple tag lists per taggable model" do
    before 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')
    end

    it "should be able to find tagged" do
      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(["ruby", "rails"], :order => 'taggable_models.name').to_a).to eq([@bob, @frank])
    end

    it "should be able to find tagged with any tag" do
      expect(TaggableModel.tagged_with(["ruby", "java"], :order => 'taggable_models.name', :any => true).to_a).to eq([@bob, @frank, @steve])
      expect(TaggableModel.tagged_with(["c++", "fitter"], :order => 'taggable_models.name', :any => true).to_a).to eq([@bob, @steve])
      expect(TaggableModel.tagged_with(["fitter", "css"], :order => 'taggable_models.name', :any => true, :on => :skills).to_a).to eq([@bob, @frank])
    end

    it "should be able to use named scopes to chain tag finds" do
      # 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
  end

  it "should be able to find tagged with only the matching tags" do
    bob = TaggableModel.create(:name => "Bob", :tag_list => "lazy, happier")
    frank = 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 eq([steve])
  end

  it "should be able to find tagged with some excluded tags" do
    bob = 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 eq([frank, steve])
  end

  it "should not create duplicate taggings" do
    bob = TaggableModel.create(:name => "Bob")
    expect {
      bob.tag_list << "happier"
      bob.tag_list << "happier"
      bob.save
    }.to change(Tagtical::Tagging, :count).by(1)
  end

  describe "Associations" do
    before(:each) do
      @taggable = TaggableModel.create(:tag_list => "awesome, epic", :skill_list => "basketball, hiking, boxing")
    end

    it "should not remove tags when creating associated objects" do
      @taggable.untaggable_models.create!
      @taggable.reload
      expect(@taggable.tag_list.size).to eq(5)
    end

    specify "tag_list methods should accept scope arguments" do
      expect(@taggable.tag_list(:current).size).to eq(2)
      expect(@taggable.skill_list(:parents).size).to eq(2)
      expect(@taggable.skill_list(:current).size).to eq(3)
      expect(@taggable.tag_list(:current, :children).size).to eq(5)
    end

  end

  describe "grouped_column_names_for method" do
    it "should return all column names joined for Tag GROUP clause" do
      expect(@taggable.grouped_column_names_for(Tagtical::Tag)).to eq("tags.id, tags.value, 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
  end

  describe "Single Table Inheritance for tags" do
    before do
      @taggable = TaggableModel.new(:name => "taggable")
    end

  end

  describe "Single Table Inheritance" do
    before do
      @taggable = TaggableModel.new(:name => "taggable")
      @inherited_same = InheritingTaggableModel.new(:name => "inherited same")
      @inherited_different = AlteredInheritingTaggableModel.new(:name => "inherited different")
    end

    it "should be able to save tags for inherited models" do
      @inherited_same.tag_list = "bob, kelso"
      @inherited_same.save
      expect(InheritingTaggableModel.tagged_with("bob").first).to eq(@inherited_same)
    end

    it "should find STI tagged models on the superclass" do
      @inherited_same.tag_list = "bob, kelso"
      @inherited_same.save
      expect(TaggableModel.tagged_with("bob").first).to eq(@inherited_same)
    end

    it "should be able to add on contexts only to some subclasses" do
      @inherited_different.part_list = "fork, spoon"
      @inherited_different.save
      expect(AlteredInheritingTaggableModel.tagged_with("fork", :on => :parts).first).to eq(@inherited_different)
    end

    it "should have different tag_counts_on for inherited models" do
      @inherited_same.tag_list = "bob, kelso"
      @inherited_same.save!
      @inherited_different.tag_list = "fork, spoon"
      @inherited_different.save!

      expect(InheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:value)).to eq(%w(bob kelso))
      expect(AlteredInheritingTaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:value)).to eq(%w(fork spoon))
      expect(TaggableModel.tag_counts_on(:tags, :order => 'tags.id').map(&:value)).to eq(%w(bob kelso fork spoon))
    end

    it 'should store same tag without validation conflict' do
      @taggable.tag_list = 'one'
      @taggable.save!

      @inherited_same.tag_list = 'one'
      @inherited_same.save!

      @inherited_same.update_attributes! :name => 'foo'
    end
  end

  describe "#owner_tags_on" do
    before do
      @user = TaggableUser.create!
      @user1 = TaggableUser.create!
      @model = TaggableModel.create!(:name => "Bob", :tag_list => "fitter, happier, more productive")
      @user.tag(@model, :with => "martial arts", :on => :skills)
      @user1.tag(@model, :with => "pottery", :on => :crafts)
      @user1.tag(@model, :with => ["spoon", "pottery"], :on => :tags)
    end

    it "should ignore different contexts" do
      expect(@model.owner_tags_on(@user, :languages)).to be_empty
    end

    it "should return for only the specified context" do
      expect(@model.owner_tags_on(@user, :skills).size).to eq(1)

      expect(@model.owner_tags_on(@user, :tags).size).to eq(1)
      expect(@model.owner_tags_on(@user1, :tags).size).to eq(2)
    end

    it "should preserve the tag type even though we tag on :tags" do
      expect(@model.tags.where(value: "pottery").first).to be_an_instance_of(Tag::FooCraft)
    end

    it "should support STI" do
      tag = @model.crafts.where(value: "pottery").first
      expect(@model.owner_tags_on(@user1, :crafts)).to eq([tag])
      expect(@model.owner_tags_on(@user1, :skills)).to eq([tag])
      expect(@model.owner_tags_on(@user1, :tags)).to include(tag)

    end
  end


  it "should be able to create tags through the tag list directly" do
    @taggable.tag_list_on(:skills).add("hello")
    expect(@taggable.tag_list_cache_on(:skills)).to_not be_empty
    expect(@taggable.tag_list_on(:skills)).to eq(["hello"])

    @taggable.save
    @taggable.save_tags

    @taggable.reload
    expect(@taggable.tag_list_on(:skills)).to eq(["hello"])
  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")
  #  bob.tag_list_on(:rotors).should == ["spinning","jumping"]
  #  bob.save
  #  bob.reload
  #  bob.tags_on(:rotors).should_not be_empty
  #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")
  #  bob.tag_list_on(:rotors).should == ["spinning","jumping"]
  #  bob.save
  #
  #  TaggableModel.tagged_with("spinning", :on => :rotors).to_a.should == [bob]
  #end

end
