# frozen_string_literal: true require 'spec_helper' describe ThinkingSphinx::ActiveRecord::PropertySQLPresenter do let(:adapter) { double 'adapter' } let(:associations) { double 'associations', :alias_for => 'articles' } let(:model) { double :column_names => ['title', 'created_at'] } let(:path) { double :aggregate? => false, :model => model } before :each do allow(adapter).to receive(:quote) { |column| column } stub_const 'Joiner::Path', double(:new => path) end context 'with a field' do let(:presenter) { ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new( field, adapter, associations ) } let(:field) { double('field', :name => 'title', :columns => [column], :type => nil, :multi? => false, :source_type => nil, :model => double) } let(:column) { double('column', :string? => false, :__stack => [], :__name => 'title') } describe '#to_group' do it "returns the column name as a string" do expect(presenter.to_group).to eq('articles.title') end it "gets the column's table alias from the associations object" do allow(column).to receive_messages(:__stack => [:users, :posts]) expect(associations).to receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_group end it "returns nil if the property is an aggregate" do allow(path).to receive_messages :aggregate? => true expect(presenter.to_group).to be_nil end it "returns nil if the field is sourced via a separate query" do allow(field).to receive_messages :source_type => 'query' expect(presenter.to_group).to be_nil end end describe '#to_select' do it "returns the column name as a string" do expect(presenter.to_select).to eq('articles.title AS title') end it "gets the column's table alias from the associations object" do allow(column).to receive_messages(:__stack => [:users, :posts]) expect(associations).to receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_select end it "returns the column name with an alias when provided" do allow(field).to receive_messages(:name => :subject) expect(presenter.to_select).to eq('articles.title AS subject') end it "groups and concatenates aggregated columns" do allow(adapter).to receive :group_concatenate do |clause, separator| "GROUP_CONCAT(#{clause} SEPARATOR '#{separator}')" end allow(path).to receive_messages :aggregate? => true expect(presenter.to_select). to eq("GROUP_CONCAT(articles.title SEPARATOR ' ') AS title") end it "concatenates multiple columns" do allow(adapter).to receive :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end allow(field).to receive_messages(:columns => [column, column]) expect(presenter.to_select). to eq("CONCAT_WS(' ', articles.title, articles.title) AS title") end it "does not include columns that don't exist" do allow(adapter).to receive :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end allow(field).to receive_messages(:columns => [column, double('column', :string? => false, :__stack => [], :__name => 'body')]) expect(presenter.to_select). to eq("CONCAT_WS(' ', articles.title) AS title") end it "returns nil for query sourced fields" do allow(field).to receive_messages :source_type => :query expect(presenter.to_select).to be_nil end it "returns nil for ranged query sourced fields" do allow(field).to receive_messages :source_type => :ranged_query expect(presenter.to_select).to be_nil end end end context 'with an attribute' do let(:presenter) { ThinkingSphinx::ActiveRecord::PropertySQLPresenter.new( attribute, adapter, associations ) } let(:attribute) { double('attribute', :name => 'created_at', :columns => [column], :type => :integer, :multi? => false, :source_type => nil, :model => double) } let(:column) { double('column', :string? => false, :__stack => [], :__name => 'created_at') } before :each do allow(adapter).to receive :cast_to_timestamp do |clause| "UNIX_TIMESTAMP(#{clause})" end end describe '#to_group' do it "returns the column name as a string" do expect(presenter.to_group).to eq('articles.created_at') end it "gets the column's table alias from the associations object" do allow(column).to receive_messages(:__stack => [:users, :posts]) expect(associations).to receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_group end it "returns nil if the column is a string" do allow(column).to receive_messages(:string? => true) expect(presenter.to_group).to be_nil end it "returns nil if the property is an aggregate" do allow(path).to receive_messages :aggregate? => true expect(presenter.to_group).to be_nil end it "returns nil if the attribute is sourced via a separate query" do allow(attribute).to receive_messages :source_type => 'query' expect(presenter.to_group).to be_nil end end describe '#to_select' do it "returns the column name as a string" do expect(presenter.to_select).to eq('articles.created_at AS created_at') end it "gets the column's table alias from the associations object" do allow(column).to receive_messages(:__stack => [:users, :posts]) expect(associations).to receive(:alias_for).with([:users, :posts]). and_return('posts') presenter.to_select end it "returns the column name with an alias when provided" do allow(attribute).to receive_messages(:name => :creation_timestamp) expect(presenter.to_select). to eq('articles.created_at AS creation_timestamp') end it "ensures datetime attributes are converted to timestamps" do allow(attribute).to receive_messages :type => :timestamp expect(presenter.to_select). to eq('UNIX_TIMESTAMP(articles.created_at) AS created_at') end it "does not include columns that don't exist" do allow(adapter).to receive :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end allow(adapter).to receive :cast_to_string do |clause| "CAST(#{clause} AS varchar)" end allow(attribute).to receive_messages(:columns => [column, double('column', :string? => false, :__stack => [], :__name => 'updated_at')]) expect(presenter.to_select).to eq("CONCAT_WS(',', CAST(articles.created_at AS varchar)) AS created_at") end it "casts and concatenates multiple columns for attributes" do allow(adapter).to receive :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end allow(adapter).to receive :cast_to_string do |clause| "CAST(#{clause} AS varchar)" end allow(attribute).to receive_messages(:columns => [column, column]) expect(presenter.to_select).to eq("CONCAT_WS(',', CAST(articles.created_at AS varchar), CAST(articles.created_at AS varchar)) AS created_at") end it "double-casts and concatenates multiple columns for timestamp attributes" do allow(adapter).to receive :concatenate do |clause, separator| "CONCAT_WS('#{separator}', #{clause})" end allow(adapter).to receive :cast_to_string do |clause| "CAST(#{clause} AS varchar)" end allow(attribute).to receive_messages :columns => [column, column], :type => :timestamp expect(presenter.to_select).to eq("CONCAT_WS(',', CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar), CAST(UNIX_TIMESTAMP(articles.created_at) AS varchar)) AS created_at") end it "does not split attribute clause for timestamp casting if it looks like a function call" do allow(column).to receive_messages :__name => "COALESCE(articles.updated_at, articles.created_at)", :string? => true allow(attribute).to receive_messages :name => 'mod_date', :columns => [column], :type => :timestamp expect(presenter.to_select).to eq("UNIX_TIMESTAMP(COALESCE(articles.updated_at, articles.created_at)) AS mod_date") end it "returns nil for query sourced attributes" do allow(attribute).to receive_messages :source_type => :query expect(presenter.to_select).to be_nil end it "returns nil for ranged query sourced attributes" do allow(attribute).to receive_messages :source_type => :ranged_query expect(presenter.to_select).to be_nil end end end end