# frozen_string_literal: true require "spec_helper" # standard:disable RSpec/NestedGroups describe PgSearch::Multisearch::Rebuilder do with_table "pg_search_documents", &DOCUMENTS_SCHEMA describe "when initialized with a model that is not multisearchable" do with_model :not_multisearchable it "raises an exception" do expect { described_class.new(NotMultisearchable) }.to raise_exception( PgSearch::Multisearch::ModelNotMultisearchable, "NotMultisearchable is not multisearchable. See PgSearch::ClassMethods#multisearchable" ) end end describe "#rebuild" do context "when the model defines .rebuild_pg_search_documents" do context "when multisearchable is not conditional" do with_model :Model do model do include PgSearch::Model multisearchable def rebuild_pg_search_documents end end end it "calls .rebuild_pg_search_documents" do rebuilder = described_class.new(Model) without_partial_double_verification do allow(Model).to receive(:rebuild_pg_search_documents) rebuilder.rebuild expect(Model).to have_received(:rebuild_pg_search_documents) end end end context "when multisearchable is conditional" do %i[if unless].each do |conditional_key| context "via :#{conditional_key}" do with_model :Model do table do |t| t.boolean :active end model do include PgSearch::Model multisearchable conditional_key => :active? def rebuild_pg_search_documents end end end it "calls .rebuild_pg_search_documents" do rebuilder = described_class.new(Model) without_partial_double_verification do allow(Model).to receive(:rebuild_pg_search_documents) rebuilder.rebuild expect(Model).to have_received(:rebuild_pg_search_documents) end end end end end end context "when the model does not define .rebuild_pg_search_documents" do context "when multisearchable is not conditional" do context "when :against only includes columns" do with_model :Model do table do |t| t.string :name end model do include PgSearch::Model multisearchable against: :name end end it "does not call :rebuild_pg_search_documents" do rebuilder = described_class.new(Model) # stub respond_to? to return false since should_not_receive defines the method original_respond_to = Model.method(:respond_to?) allow(Model).to receive(:respond_to?) do |method_name, *args| if method_name == :rebuild_pg_search_documents false else original_respond_to.call(method_name, *args) end end without_partial_double_verification do allow(Model).to receive(:rebuild_pg_search_documents) rebuilder.rebuild expect(Model).not_to have_received(:rebuild_pg_search_documents) end end # standard:disable RSpec/ExampleLength it "executes the default SQL" do time = Time.utc(2001, 1, 1, 0, 0, 0) rebuilder = described_class.new(Model, -> { time }) expected_sql = <<~SQL.squish INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at) SELECT 'Model' AS searchable_type, #{Model.quoted_table_name}.#{Model.primary_key} AS searchable_id, ( coalesce(#{Model.quoted_table_name}."name"::text, '') ) AS content, '2001-01-01 00:00:00' AS created_at, '2001-01-01 00:00:00' AS updated_at FROM #{Model.quoted_table_name} SQL executed_sql = [] notifier = ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start, _finish, _id, payload| executed_sql << payload[:sql] if payload[:sql].include?(%(INSERT INTO "pg_search_documents")) end rebuilder.rebuild ActiveSupport::Notifications.unsubscribe(notifier) expect(executed_sql.length).to eq(1) expect(executed_sql.first.strip).to eq(expected_sql.strip) end # standard:enable RSpec/ExampleLength context "with a model with a camel case column" do with_model :ModelWithCamelCaseColumn do table do |t| t.string :camelName end model do include PgSearch::Model multisearchable against: :name end end it "rebuilds without error" do time = Time.utc(2001, 1, 1, 0, 0, 0) rebuilder = described_class.new(Model, -> { time }) expect { rebuilder.rebuild }.not_to raise_error end end context "with a model with a non-standard primary key" do with_model :ModelWithNonStandardPrimaryKey do table primary_key: :non_standard_primary_key do |t| t.string :name end model do include PgSearch::Model multisearchable against: :name end end # standard:disable RSpec/ExampleLength it "generates SQL with the correct primary key" do time = Time.utc(2001, 1, 1, 0, 0, 0) rebuilder = described_class.new(ModelWithNonStandardPrimaryKey, -> { time }) expected_sql = <<~SQL.squish INSERT INTO "pg_search_documents" (searchable_type, searchable_id, content, created_at, updated_at) SELECT 'ModelWithNonStandardPrimaryKey' AS searchable_type, #{ModelWithNonStandardPrimaryKey.quoted_table_name}.non_standard_primary_key AS searchable_id, ( coalesce(#{ModelWithNonStandardPrimaryKey.quoted_table_name}."name"::text, '') ) AS content, '2001-01-01 00:00:00' AS created_at, '2001-01-01 00:00:00' AS updated_at FROM #{ModelWithNonStandardPrimaryKey.quoted_table_name} SQL executed_sql = [] notifier = ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start, _finish, _id, payload| executed_sql << payload[:sql] if payload[:sql].include?(%(INSERT INTO "pg_search_documents")) end rebuilder.rebuild ActiveSupport::Notifications.unsubscribe(notifier) expect(executed_sql.length).to eq(1) expect(executed_sql.first.strip).to eq(expected_sql.strip) end # standard:enable RSpec/ExampleLength end end context "when :against includes non-column dynamic methods" do with_model :Model do model do include PgSearch::Model multisearchable against: [:foo] def foo "bar" end end end # standard:disable RSpec/ExampleLength it "calls update_pg_search_document on each record" do record = Model.create! rebuilder = described_class.new(Model) # stub respond_to? to return false since should_not_receive defines the method original_respond_to = Model.method(:respond_to?) allow(Model).to receive(:respond_to?) do |method_name, *args| if method_name == :rebuild_pg_search_documents false else original_respond_to.call(method_name, *args) end end without_partial_double_verification do allow(Model).to receive(:rebuild_pg_search_documents) rebuilder.rebuild expect(Model).not_to have_received(:rebuild_pg_search_documents) end expect(record.pg_search_document).to be_present end # standard:enable RSpec/ExampleLength end context "when only additional_attributes is set" do with_model :Model do table do |t| t.string :name end model do include PgSearch::Model multisearchable against: :name, additional_attributes: ->(obj) { {additional_attribute_column: "#{obj.class}::#{obj.id}"} } end end it "calls update_pg_search_document on each record" do record_1 = Model.create!(name: "record_1", id: 1) record_2 = Model.create!(name: "record_2", id: 2) PgSearch::Document.delete_all rebuilder = described_class.new(Model) rebuilder.rebuild expect(record_1.reload.pg_search_document.additional_attribute_column).to eq("Model::1") expect(record_2.reload.pg_search_document.additional_attribute_column).to eq("Model::2") end end end context "when multisearchable is conditional" do context "via :if" do with_model :Model do table do |t| t.boolean :active end model do include PgSearch::Model multisearchable if: :active? end end # standard:disable RSpec/ExampleLength it "calls update_pg_search_document on each record" do record_1 = Model.create!(active: true) record_2 = Model.create!(active: false) rebuilder = described_class.new(Model) # stub respond_to? to return false since should_not_receive defines the method original_respond_to = Model.method(:respond_to?) allow(Model).to receive(:respond_to?) do |method_name, *args| if method_name == :rebuild_pg_search_documents false else original_respond_to.call(method_name, *args) end end without_partial_double_verification do allow(Model).to receive(:rebuild_pg_search_documents) rebuilder.rebuild expect(Model).not_to have_received(:rebuild_pg_search_documents) end expect(record_1.pg_search_document).to be_present expect(record_2.pg_search_document).not_to be_present end # standard:enable RSpec/ExampleLength end context "via :unless" do with_model :Model do table do |t| t.boolean :inactive end model do include PgSearch::Model multisearchable unless: :inactive? end end # standard:disable RSpec/ExampleLength it "calls update_pg_search_document on each record" do record_1 = Model.create!(inactive: true) record_2 = Model.create!(inactive: false) rebuilder = described_class.new(Model) # stub respond_to? to return false since should_not_receive defines the method original_respond_to = Model.method(:respond_to?) allow(Model).to receive(:respond_to?) do |method_name, *args| if method_name == :rebuild_pg_search_documents false else original_respond_to.call(method_name, *args) end end without_partial_double_verification do allow(Model).to receive(:rebuild_pg_search_documents) rebuilder.rebuild expect(Model).not_to have_received(:rebuild_pg_search_documents) end expect(record_1.pg_search_document).not_to be_present expect(record_2.pg_search_document).to be_present end # standard:enable RSpec/ExampleLength end end end end end # standard:enable RSpec/NestedGroups