require 'spec_helper'

describe ThinkingSphinx::ActiveRecord::Interpreter do
  let(:instance) {
    ThinkingSphinx::ActiveRecord::Interpreter.new index, block
  }
  let(:model)   { double('model') }
  let(:index)   { double('index', :append_source => source, :options => {}) }
  let(:source)  {
    Struct.new(:attributes, :fields, :associations, :groupings, :conditions).
      new([], [], [], [], [])
  }
  let(:block)   { Proc.new { } }

  before :each do
    ThinkingSphinx::ActiveRecord::SQLSource.stub! :new => source
    source.stub :model => model
  end

  describe '.translate!' do
    let(:instance) { double('interpreter', :translate! => true) }

    it "creates a new interpreter instance with the given block and index" do
      ThinkingSphinx::ActiveRecord::Interpreter.should_receive(:new).
        with(index, block).and_return(instance)

      ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block
    end

    it "calls translate! on the instance" do
      ThinkingSphinx::ActiveRecord::Interpreter.stub!(:new => instance)
      instance.should_receive(:translate!)

      ThinkingSphinx::ActiveRecord::Interpreter.translate! index, block
    end
  end

  describe '#group_by' do
    it "adds a source to the index" do
      index.should_receive(:append_source).and_return(source)

      instance.group_by 'lat'
    end

    it "only adds a single source for the given context" do
      index.should_receive(:append_source).once.and_return(source)

      instance.group_by 'lat'
      instance.group_by 'lng'
    end

    it "appends a new grouping statement to the source" do
      instance.group_by 'lat'

      source.groupings.should include('lat')
    end
  end

  describe '#has' do
    let(:column)    { double('column') }
    let(:attribute) { double('attribute') }

    before :each do
      ThinkingSphinx::ActiveRecord::Attribute.stub! :new => attribute
    end

    it "adds a source to the index" do
      index.should_receive(:append_source).and_return(source)

      instance.has column
    end

    it "only adds a single source for the given context" do
      index.should_receive(:append_source).once.and_return(source)

      instance.has column
      instance.has column
    end

    it "creates a new attribute with the provided column" do
      ThinkingSphinx::ActiveRecord::Attribute.should_receive(:new).
        with(model, column, {}).and_return(attribute)

      instance.has column
    end

    it "passes through options to the attribute" do
      ThinkingSphinx::ActiveRecord::Attribute.should_receive(:new).
        with(model, column, :as => :other_name).and_return(attribute)

      instance.has column, :as => :other_name
    end

    it "adds an attribute to the source" do
      instance.has column

      source.attributes.should include(attribute)
    end

    it "adds multiple attributes when passed multiple columns" do
      instance.has column, column

      source.attributes.select { |saved_attribute|
        saved_attribute == attribute
      }.length.should == 2
    end
  end

  describe '#indexes' do
    let(:column) { double('column') }
    let(:field)  { double('field') }

    before :each do
      ThinkingSphinx::ActiveRecord::Field.stub! :new => field
    end

    it "adds a source to the index" do
      index.should_receive(:append_source).and_return(source)

      instance.indexes column
    end

    it "only adds a single source for the given context" do
      index.should_receive(:append_source).once.and_return(source)

      instance.indexes column
      instance.indexes column
    end

    it "creates a new field with the provided column" do
      ThinkingSphinx::ActiveRecord::Field.should_receive(:new).
        with(model, column, {}).and_return(field)

      instance.indexes column
    end

    it "passes through options to the field" do
      ThinkingSphinx::ActiveRecord::Field.should_receive(:new).
        with(model, column, :as => :other_name).and_return(field)

      instance.indexes column, :as => :other_name
    end

    it "adds a field to the source" do
      instance.indexes column

      source.fields.should include(field)
    end

    it "adds multiple fields when passed multiple columns" do
      instance.indexes column, column

      source.fields.select { |saved_field|
        saved_field == field
      }.length.should == 2
    end
  end

  describe '#join' do
    let(:column)      { double('column') }
    let(:association) { double('association') }

    before :each do
      ThinkingSphinx::ActiveRecord::Association.stub! :new => association
    end

    it "adds a source to the index" do
      index.should_receive(:append_source).and_return(source)

      instance.join column
    end

    it "only adds a single source for the given context" do
      index.should_receive(:append_source).once.and_return(source)

      instance.join column
      instance.join column
    end

    it "creates a new association with the provided column" do
      ThinkingSphinx::ActiveRecord::Association.should_receive(:new).
        with(column).and_return(association)

      instance.join column
    end

    it "adds an association to the source" do
      instance.join column

      source.associations.should include(association)
    end

    it "adds multiple fields when passed multiple columns" do
      instance.join column, column

      source.associations.select { |saved_assoc|
        saved_assoc == association
      }.length.should == 2
    end
  end

  describe '#method_missing' do
    let(:column) { double('column') }

    before :each do
      ThinkingSphinx::ActiveRecord::Column.stub!(:new => column)
    end

    it "returns a new column for the given method" do
      instance.id.should == column
    end

    it "should initialise the column with the method name and arguments" do
      ThinkingSphinx::ActiveRecord::Column.should_receive(:new).
        with(:users, :posts, :subject).and_return(column)

      instance.users(:posts, :subject)
    end
  end

  describe '#set_database' do
    before :each do
      source.stub :set_database_settings => true

      stub_const 'ActiveRecord::Base',
        double(:configurations => {'other' => {:baz => :qux}})
    end

    it "sends through a hash if provided" do
      source.should_receive(:set_database_settings).with(:foo => :bar)

      instance.set_database :foo => :bar
    end

    it "finds the environment settings if given a string key" do
      source.should_receive(:set_database_settings).with(:baz => :qux)

      instance.set_database 'other'
    end

    it "finds the environment settings if given a symbol key" do
      source.should_receive(:set_database_settings).with(:baz => :qux)

      instance.set_database :other
    end
  end

  describe '#set_property' do
    before :each do
      index.class.stub  :settings => [:morphology]
      source.class.stub :settings => [:mysql_ssl_cert]
    end

    it 'saves other settings as index options' do
      instance.set_property :field_weights => {:name => 10}

      index.options[:field_weights].should == {:name => 10}
    end

    context 'index settings' do
      it "sets the provided setting" do
        index.should_receive(:morphology=).with('stem_en')

        instance.set_property :morphology => 'stem_en'
      end
    end

    context 'source settings' do
      before :each do
        source.stub :mysql_ssl_cert= => true
      end

      it "adds a source to the index" do
        index.should_receive(:append_source).and_return(source)

        instance.set_property :mysql_ssl_cert => 'private.cert'
      end

      it "only adds a single source for the given context" do
        index.should_receive(:append_source).once.and_return(source)

        instance.set_property :mysql_ssl_cert => 'private.cert'
        instance.set_property :mysql_ssl_cert => 'private.cert'
      end

      it "sets the provided setting" do
        source.should_receive(:mysql_ssl_cert=).with('private.cert')

        instance.set_property :mysql_ssl_cert => 'private.cert'
      end
    end
  end

  describe '#translate!' do
    it "returns the block evaluated within the context of the interpreter" do
      block = Proc.new {
        __id__
      }

      interpreter = ThinkingSphinx::ActiveRecord::Interpreter.new index, block
      interpreter.translate!.
        should == interpreter.__id__
    end
  end

  describe '#where' do
    it "adds a source to the index" do
      index.should_receive(:append_source).and_return(source)

      instance.where 'id > 100'
    end

    it "only adds a single source for the given context" do
      index.should_receive(:append_source).once.and_return(source)

      instance.where 'id > 100'
      instance.where 'id < 150'
    end

    it "appends a new grouping statement to the source" do
      instance.where 'id > 100'

      source.conditions.should include('id > 100')
    end
  end
end