require 'spec_helper'

def model_scoped(klass)
  if klass.respond_to? :scoped
    klass.scoped
  else
    klass.all
  end
end

describe 'calculated_attributes' do
  it 'includes calculated attributes' do
    expect(model_scoped(Post).calculated(:comments_count).first.comments_count).to eq(1)
  end

  it 'includes parametric calculated attributes' do
    expect(model_scoped(Post).calculated(comments_by_user: [User.where(username: 'unused').first])
             .first.comments_by_user).to eq(0)
    expect(model_scoped(Post).calculated(comments_by_user: [User.where(username: 'test').first])
             .first.comments_by_user).to eq(1)
  end

  it 'includes multiple calculated attributes' do
    post = model_scoped(Post).calculated(:comments_count, :comments_two).first
    expect(post.comments_count).to eq(1)
    expect(post.comments_two).to eq(1)
  end

  it 'includes chained calculated attributes' do
    post = model_scoped(Post).calculated(:comments_count).calculated(:comments_two).first
    expect(post.comments_count).to eq(1)
    expect(post.comments_two).to eq(1)
  end

  it 'nests with where query' do
    expect(Post.where(id: 1).calculated(:comments_count).first.comments_count).to eq(1)
  end

  it 'nests with order query' do
    expect(Post.order('id DESC').calculated(:comments_count).first.id).to eq(Post.count)
  end

  it 'allows access via model instance method' do
    expect(Post.first.calculated(:comments_count).comments_count).to eq(1)
  end

  it 'allows access via model instance method with parameters' do
    expect(Post.first.calculated(comments_by_user: [User.where(username: 'test').first]).comments_by_user).to eq(1)
  end

  it 'allows anonymous access via model instance method' do
    expect(Post.first.comments_count).to eq(1)
  end

  it 'allows access via model instance method with parameters' do
    expect(Post.first.comments_by_user(User.where(username: 'test').first)).to eq(1)
  end

  it 'allows anonymous access via model instance method with STI and lambda on base class' do
    expect(Tutorial.first.comments_count).to eq(1)
  end

  it 'allows anonymous access via model instance method with STI and lambda on subclass' do
    expect(Article.first.sub_comments).to eq(1)
  end

  it 'does not allow anonymous access for nonexisting calculated block' do
    expect { Post.first.some_attr }.to raise_error(NoMethodError)
  end

  it 'does not allow anonymous access for nonexisting calculated block with STI' do
    expect { Tutorial.first.some_attr }.to raise_error(NoMethodError)
  end

  it 'allows access to multiple calculated attributes via model instance method' do
    post = Post.first.calculated(:comments_count, :comments_two)
    expect(post.comments_count).to eq(1)
    expect(post.comments_two).to eq(1)
  end

  it 'allows attributes to be defined using AREL' do
    expect(model_scoped(Post).calculated(:comments_arel).first.comments_arel).to eq(1)
  end

  it 'allows attributes to be defined using class that responds to to_sql' do
    expect(model_scoped(Post).calculated(:comments_to_sql).first.comments_to_sql).to eq(1)
  end

  it 'maintains previous scope' do
    expect(Post.where(text: 'First post!').calculated(:comments_count).first.comments_count).to eq(1)
    expect(Post.where("posts.text = 'First post!'").calculated(:comments_count).first.comments_count).to eq(1)
  end

  it 'maintains subsequent scope' do
    expect(model_scoped(Post).calculated(:comments_count).where(text: 'First post!').first.comments_count).to eq(1)
    expect(model_scoped(Post).calculated(:comments_count).where("posts.text = 'First post!'").first.comments_count).to eq(1)
  end

  it 'includes calculated attributes with STI and lambda on base class' do
    expect(model_scoped(Tutorial).calculated(:comments_count).first.comments_count).to eq(1)
  end

  it 'includes calculated attributes with STI and lambda on subclass' do
    expect(model_scoped(Article).calculated(:sub_comments).first.sub_comments).to eq(1)
  end

  context 'when joining models' do
    it 'includes calculated attributes' do
      expect(model_scoped(Post).joins(:comments).calculated(:comments_count).first.comments_count).to eq(1)
    end

    context 'when conditions are specified on the joined table' do
      it 'includes calculated models' do
        scope = model_scoped(Post).joins(:comments)
        expect(scope.first.comments_count).to eq(1)
      end
    end
  end

  context 'when eager loading models' do
    it 'includes calculated attributes' do
      scope = case ActiveRecord::VERSION::MAJOR
      when 4 then model_scoped(Post).includes(:comments).references(:comments)
      when 3 then model_scoped(Post).eager_load(:comments)
      end
      expect(scope.first.comments_count).to eq(1)
    end
  end
end