PostgreSQL’s temporary tables
Testing ActiveRecord concerns in isolation without including them in an application model can be challenging.
Consider an ActiveRecord concern that extracts a specific database behavior. For example, a query for tagging:
module TaggableConcern
extend ActiveSupport::Concern
included do
scope :with_tag, ->(tag) {
where('? = ANY(tags)', tag)
}
scope :with_tags, ->(tags) {
where('tags @> ARRAY[?]::varchar[]', Array(tags))
}
end
endTo test this cleanly, you can leverage a specific PostgreSQL feature found in the CREATE TABLE synopsis: temporary tables.
CREATE TEMPORARY TABLE table_name (...)
ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP }Using this in Rails is straightforward. The [create_table]((https://api.rubyonrails.org/v6.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table) method accepts :temporary as a boolean argument to mark the table as ephemeral. It also accepts an options parameter to define extra table instructions, such as when to drop it:
create_table :my_table, temporary: true, options: 'ON COMMIT DROP' do |t|
...
endHere is how to apply this to test the tagging concern in RSpec:
RSpec.describe TaggableConcern, type: :model do
before do
class TempBook < ApplicationRecord
include TaggableConcern
connection.create_table :temp_books, temporary: true, options: 'ON COMMIT DROP' do |t|
t.string :name
t.string :tags, array: true
t.timestamps
end
end
end
let!(:ruby) do
TempBook.create!(
name: 'Programming Rails',
tags: %w[ruby rails web]
)
end
let!(:elixir) do
TempBook.create!(
name: 'Programming Phoenix',
tags: %w[elixir phoenix web]
)
end
let!(:go) do
TempBook.create!(
name: 'Programming Go',
tags: %w[go programming]
)
end
describe '.with_tag' do
subject { TempBook.with_tag(query) }
context 'with nil value' do
let(:query) { nil }
it { is_expected.to be_empty }
end
context 'with one match' do
let(:query) { 'ruby' }
it { is_expected.to match_array([ruby]) }
end
context 'with multiple matches' do
let(:query) { 'web' }
it { is_expected.to match_array([ruby, elixir]) }
end
end
describe '.with_tags' do
subject { TempBook.with_tags(query) }
context 'with nil value' do
let(:query) { nil }
it { is_expected.to be_empty }
end
context 'with empty array' do
let(:query) { [] }
it { is_expected.to be_empty }
end
context 'with a common tag' do
let(:query) { 'web' }
it { is_expected.to match_array([ruby, elixir]) }
end
context 'with a common tag as array' do
let(:query) { ['web'] }
it { is_expected.to match_array([ruby, elixir]) }
end
context 'with mixed tags' do
let(:query) { ['ruby', 'elixir'] }
it { is_expected.to be_empty }
end
context 'with mixed tags' do
let(:query) { ['ruby', 'web'] }
it { is_expected.to match_array([ruby]) }
end
end
endFinished in 0.1783 seconds (files took 0.59146 seconds to load)
9 examples, 0 failures