require 'spec_helper'
require 'active_repository'
require 'active_repository/result_set'

describe ActiveRepository::ResultSet, :result_set do
  before do
    class Country < ActiveRepository::Base
      fields :name, :continent
    end
  end

  describe 'initialize' do
    context 'with class only' do
      subject { ActiveRepository::ResultSet.new(Country) }

      it 'must not be nil' do
        expect(subject).not_to be_nil
      end
    end

    context 'with class and attributes' do
      subject { ActiveRepository::ResultSet.new(Country, {a: 'a'}) }

      it 'must not be nil' do
        expect(subject).not_to be_nil
      end
    end
  end

  describe '#count' do
    after do
      Country.delete_all
    end

    context 'without query' do
      it 'returns total records in data store' do
        Country.create
        Country.create
        Country.create
        Country.create

        expect(Country.count).to eq 4
      end
    end

    context 'without query' do
      it 'returns total records in data store' do
        Country.create(name: 'A')
        Country.create
        Country.create(name: 'B')
        Country.create

        expect(Country.where(name: { '$ne' => nil}).count).to eq 2
      end
    end
  end

  describe '#where' do
    subject { ActiveRepository::ResultSet.new(Country) }

    it "returns a ResultSet object" do
      expect(subject.where(name: 'Canada')).to be_a(ActiveRepository::ResultSet)
    end

    it 'creates another ResultSet object' do
      result = subject.where(continent: 'America')

      expect(result).not_to eq subject
    end

    context 'nested ResultSets' do
      it 'returns a new ResultSet' do
        result = subject.where(name: 'Canada').where(continent: 'America')

        expect(result).to be_a(ActiveRepository::ResultSet)
      end

      it 'returns a new ResultSet' do
        result = subject.where(name: 'Canada').where("continent = 'America'")

        expect(result).to be_a(ActiveRepository::ResultSet)
      end
    end
  end

  describe '#and' do
    it 'is an alias for #where' do
      expect(described_class.instance_method(:and)).to eq described_class.instance_method(:where)
    end
  end

  describe '#or' do
    subject { ActiveRepository::ResultSet.new(Country) }

    it "returns a ResultSet object" do
      expect(subject.or(name: 'Canada')).to be_a(ActiveRepository::ResultSet)
    end

    it 'creates another ResultSet object' do
      result = subject.or(continent: 'America')

      expect(result).not_to eq subject
    end

    context 'nested ResultSets' do
      it 'returns a new ResultSet' do
        result = subject.or(name: 'Canada').or(continent: 'America')

        expect(result).to be_a(ActiveRepository::ResultSet)
      end
    end
  end

  describe '#build' do
    subject { ActiveRepository::ResultSet.new(Country) }

    it "returns a Country object" do
      expect(subject.build(name: 'Canada')).to be_a(Country)
    end

    it 'returns a new_record' do
      result = subject.build(continent: 'America')

      expect(result).to be_new_record
    end

    it 'merges attributes' do
      result_set = subject.where(name: 'Canada')
      result = result_set.build(continent: 'America')

      expect(result.attributes).to eq(name: 'Canada', continent: 'America')
    end
  end

  describe '#create' do
    subject { ActiveRepository::ResultSet.new(Country) }

    it "returns a Country object" do
      expect(subject.create(name: 'Canada')).to be_a(Country)
    end

    it 'returns a persisted object' do
      result = subject.create(continent: 'America')

      expect(result).not_to be_new_record
    end

    it 'merges attributes' do
      result_set = subject.where(name: 'Canada')
      result = result_set.create(continent: 'America')

      expect(result.attributes).to eq(name: 'Canada', continent: 'America', id: result.id)
    end
  end

  describe '#all' do
    before do
      Country.delete_all
      Country.create(name: 'Canada', continent: 'America')
      Country.create(name: 'Russia', continent: 'Europe')
      Country.create(name: 'USA', continent: 'America')
      Country.create(name: 'Brazil', continent: 'America')
    end

    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      context 'single ResultSet' do
        it 'returns an array of objects' do
          objects = subject.where(continent: 'America').all

          expect(objects.class).to eq Array
        end

        it 'returns a collection of Countries' do
          objects = subject.where(continent: 'America').all

          expect(objects.map(&:class).uniq).to eq [Country]
        end

        it 'returns filtered objects' do
          objects = subject.where(continent: 'America').all

          expect(objects).to eq (Country.all - [Country.find(2)])
        end
      end

      context 'nested ResultSets' do
        it 'returns an array of objects' do
          objects = subject.where(continent: 'America').where(name: 'Canada').all

          expect(objects.class).to eq Array
        end

        it 'returns a collection of Countries' do
          objects = subject.where(continent: 'America').where(name: 'Canada').all

          expect(objects.map(&:class).uniq).to eq [Country]
        end

        it 'returns filtered objects' do
          objects = subject.where(continent: 'America').where(name: 'Canada').all

          expect(objects).to eq ([Country.first])
        end

        it 'returns filtered objects' do
          objects = subject.where(continent: 'America').where("name = 'Canada'").all

          expect(objects).to eq ([Country.first])
        end
      end
    end

    context 'when result_set is empty' do
      it 'returns an empty array' do
        expect(subject.where('').all).to be_empty
      end
    end
  end

  describe '#each' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      before do
        Country.delete_all
        Country.create(name: 'Canada', continent: 'America')
        Country.create(name: 'Russia', continent: 'Europe')
        Country.create(name: 'USA', continent: 'America')
        Country.create(name: 'Brazil', continent: 'America')
      end

      it 'returns an array of objects' do
        objects = subject.where(continent: 'America').each

        expect(objects.class).to eq Enumerator
      end

      it 'returns a collection of Countries' do
        objects = subject.where(continent: 'America').each

        expect(objects.map(&:class).uniq).to eq [Country]
      end

      it 'executes given method on all objects' do
        objects = subject.where('id is not null').each(&:delete)

        expect(Country.count).to eq 0
      end
    end

    context 'when result_set is empty' do
      it 'returns nil' do
        expect(subject.where(continent: 'America').each).not_to be_any
      end
    end
  end

  describe '#empty?' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      before do
        Country.delete_all
        Country.create(name: 'Canada', continent: 'America')
        Country.create(name: 'Russia', continent: 'Europe')
        Country.create(name: 'USA', continent: 'America')
        Country.create(name: 'Brazil', continent: 'America')
      end

      it 'returns false' do
        result = subject.where(continent: 'America').empty?

        expect(result).not_to be
      end
    end

    context 'when result_set is empty' do
      it 'returns true' do
        result = subject.where(continent: 'Americas').empty?

        expect(result).to be
      end
    end
  end

  describe '#any?' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      before do
        Country.delete_all
        Country.create(name: 'Canada', continent: 'America')
        Country.create(name: 'Russia', continent: 'Europe')
        Country.create(name: 'USA', continent: 'America')
        Country.create(name: 'Brazil', continent: 'America')
      end

      it 'returns true' do
        result = subject.where(continent: 'America').any?

        expect(result).to be
      end
    end

    context 'when result_set is empty' do
      it 'returns false' do
        result = subject.where(continent: 'Americas').any?

        expect(result).not_to be
      end
    end
  end

  describe '#map' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      before do
        Country.delete_all
        Country.create(name: 'Canada', continent: 'America')
        Country.create(name: 'Russia', continent: 'Europe')
        Country.create(name: 'USA', continent: 'America')
        Country.create(name: 'Brazil', continent: 'America')
      end

      it 'returns an array with given attributes' do
        result = subject.where(continent: 'America').map(&:name)

        expect(result).to eq ["Canada", "USA", "Brazil"]
      end
    end

    context 'when result_set is empty' do
      it 'returns an empty array' do
        result = subject.where(continent: 'Americas').map(&:name)

        expect(result).to eq []
      end
    end
  end

  describe '#pluck' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      before do
        Country.delete_all
        Country.create(name: 'Canada', continent: 'America')
        Country.create(name: 'Russia', continent: 'Europe')
        Country.create(name: 'USA', continent: 'America')
        Country.create(name: 'Brazil', continent: 'America')
      end

      it 'returns an array with given attributes' do
        result = subject.where(continent: 'America').pluck(:name)

        expect(result).to eq ["Canada", "USA", "Brazil"]
      end
    end

    context 'when result_set is empty' do
      it 'returns an empty array' do
        result = subject.where(continent: 'Americas').pluck(:name)

        expect(result).to eq []
      end
    end
  end

  describe '#first' do
    before do
      Country.delete_all
      Country.create(name: 'Canada', continent: 'America')
      Country.create(name: 'Russia', continent: 'Europe')
      Country.create(name: 'USA', continent: 'America')
      Country.create(name: 'Brazil', continent: 'America')
    end

    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      it 'returns first object from filtered objects' do
        objects = subject.where(name: 'Russia').or(name: 'USA').first

        expect(objects).to eq Country.find(2)
      end
    end

    context 'when result_set is empty' do
      it 'returns nil' do
        objects = subject.where(name: 'Russia').and(name: 'USA').first

        expect(objects).to be_nil
      end
    end
  end

  describe '#last' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'when result_set is not empty' do
      it 'returns first object from filtered objects' do
        objects = subject.where(name: 'Russia').or(name: 'USA').last

        expect(objects).to eq Country.find(3)
      end
    end

    context 'when result_set is empty' do
      it 'returns nil' do
        objects = subject.where(name: 'Russia').and(name: 'USA').first

        expect(objects).to be_nil
      end
    end
  end

  describe '#first_or_initialize' do
    subject { ActiveRepository::ResultSet.new(Country) }

    context 'single query' do
      context 'when result_set is not empty' do
        it 'returns first filtered object' do
          object = subject.where(name: 'Russia').first_or_initialize

          expect(object).to eq Country.find(2)
        end
      end

      context 'when result_set is empty' do
        context 'query is a Hash' do
          it 'returns an object with specified attributes' do
            object = subject.where(name: 'Poland').first_or_initialize

            expect(object.attributes).to eq(name: 'Poland')
          end
        end

        context 'query is not a Hash' do
          it 'returns a new object with specified attributes' do
            object = subject.where("name = 'Poland'").first_or_initialize

            expect(object.attributes).to eq(name: 'Poland')
          end
        end
      end
    end

    context 'nested _query' do
      context 'when result_set is not empty' do
        it 'returns first filtered object' do
          object = subject.where(name: 'Russia').and(continent: 'Europe').first_or_initialize

          expect(object).to eq Country.find(2)
        end
      end

      context 'when result_set is empty' do
        context 'all queries are Hashes' do
          it 'returns a new object with specified attributes' do
            object = subject.where(name: 'Poland').and(continent: 'Europe').first_or_initialize

            expect(object.attributes).to eq(name: 'Poland', continent: 'Europe')
          end
        end

        context 'not all queries are Hashes' do
          it 'returns a new object with all Hashes as attributes' do
            object = subject.where(name: 'Poland').and("continent = 'Europe'").first_or_initialize

            expect(object.attributes).to eq(name: 'Poland', continent: 'Europe')
          end

          it 'returns a new object with all Hashes as attributes' do
            object = subject.where("name = 'Poland'").and("continent = 'Europe'").first_or_initialize

            expect(object.attributes).to eq(name: 'Poland', continent: 'Europe')
          end
        end
      end
    end
  end

  describe '#first_or_create' do
    subject { ActiveRepository::ResultSet.new(Country) }

    before do
      Country.delete_all
      Country.create(name: 'Canada', continent: 'America')
      Country.create(name: 'Russia', continent: 'Europe')
      Country.create(name: 'USA', continent: 'America')
      Country.create(name: 'Brazil', continent: 'America')
    end

    context 'single query' do
      context 'when result_set is not empty' do
        it 'returns first filtered object' do
          object = subject.where(name: 'Russia').first_or_create

          expect(object).to eq Country.find(2)
        end
      end

      context 'when result_set is empty' do
        context 'query is a Hash' do
          it 'returns an object with id not nil' do
            object = subject.where(name: 'Poland').first_or_create

            expect(object.id).to eq Country.last.id
          end

          it 'returns an object with specified attributes' do
            object = subject.where(name: 'Poland').first_or_create

            expect(object.attributes).to eq(id: 5, name: 'Poland')
          end
        end

        context 'query is not a Hash' do
          it 'returns a new object with specified attributes' do
            object = subject.where("name = 'Poland'").first_or_create

            expect(object.attributes).to eq(name: 'Poland', id: 5)
          end
        end
      end
    end

    context 'nested _query' do
      context 'when result_set is not empty' do
        it 'returns first filtered object' do
          object = subject.where(name: 'Russia').and(continent: 'Europe').first_or_create

          expect(object).to eq Country.find(2)
        end
      end

      context 'when result_set is empty' do
        context 'all queries are Hashes' do
          it 'returns a new object with specified attributes' do
            object = subject.where(name: 'Poland').and(continent: 'Europe').first_or_create

            expect(object.attributes).to eq(id: 5, name: 'Poland', continent: 'Europe')
          end
        end

        context 'not all queries are Hashes' do
          it 'returns a new object with all Hashes as attributes' do
            object = subject.where(name: 'Poland').and("continent = 'Europe'").first_or_create

            expect(object.attributes).to eq(id: 5, name: 'Poland', continent: 'Europe')
          end

          it 'returns a new object with all Hashes as attributes' do
            object = subject.where("name = 'Poland'").and("continent = 'Europe'").first_or_create

            expect(object.attributes).to eq(:name=>"Poland", :continent=>"Europe", :id=>5)
          end
        end
      end
    end
  end
end
