test: cover OrmHasOneThroughTest and teardown
This commit is contained in:
1 parent
b3ae5aca76
commit
ed452e46f6
1 file changed
+178
@@ -0,0 +1,178 @@
|
|||||||
|
require 'minitest/autorun'
|
||||||
|
require 'prism'
|
||||||
|
require 'tmpdir'
|
||||||
|
|
||||||
|
class OrmHasOneThroughTest < Minitest::Test
|
||||||
|
def setup
|
||||||
|
@dir = Dir.mktmpdir('prism-has-one-through-store')
|
||||||
|
@store = CountingHasOneThroughStore.new(File.join(@dir, 'test.pstore'))
|
||||||
|
@store.create_table(:suppliers, %w[id name])
|
||||||
|
@store.create_table(:accounts, %w[id supplier_id plan])
|
||||||
|
@store.create_table(:account_histories, %w[id account_id notes])
|
||||||
|
|
||||||
|
supplier_class = Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :suppliers
|
||||||
|
has_one :account, class_name: 'Account', foreign_key: 'supplier_id'
|
||||||
|
has_one :account_history, through: :account
|
||||||
|
end
|
||||||
|
account_class = Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :accounts
|
||||||
|
belongs_to :supplier, class_name: 'Supplier', foreign_key: 'supplier_id'
|
||||||
|
has_one :account_history, class_name: 'AccountHistory', foreign_key: 'account_id'
|
||||||
|
end
|
||||||
|
history_class = Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :account_histories
|
||||||
|
belongs_to :account, class_name: 'Account', foreign_key: 'account_id'
|
||||||
|
has_one :supplier, through: :account
|
||||||
|
end
|
||||||
|
[supplier_class, account_class, history_class].each { |klass| klass.store = @store }
|
||||||
|
Object.const_set(:Supplier, supplier_class)
|
||||||
|
Object.const_set(:Account, account_class)
|
||||||
|
Object.const_set(:AccountHistory, history_class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
FileUtils.remove_entry(@dir)
|
||||||
|
%i[Supplier Account AccountHistory].each { |name| Object.send(:remove_const, name) if Object.const_defined?(name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_through_reads_source_off_a_has_one_through_record
|
||||||
|
supplier = Supplier.create!(name: 'acme')
|
||||||
|
account = Account.create!(supplier_id: supplier.id, plan: 'gold')
|
||||||
|
history = AccountHistory.create!(account_id: account.id, notes: 'upgraded')
|
||||||
|
|
||||||
|
assert_equal history.id, supplier.account_history.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_through_reads_source_off_a_belongs_to_through_record
|
||||||
|
supplier = Supplier.create!(name: 'acme')
|
||||||
|
account = Account.create!(supplier_id: supplier.id, plan: 'gold')
|
||||||
|
history = AccountHistory.create!(account_id: account.id, notes: 'upgraded')
|
||||||
|
|
||||||
|
assert_equal supplier.id, history.supplier.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_through_returns_nil_without_through_record
|
||||||
|
supplier = Supplier.create!(name: 'lonely')
|
||||||
|
assert_nil supplier.account_history
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_through_returns_nil_when_through_exists_but_source_missing
|
||||||
|
supplier = Supplier.create!(name: 'acme')
|
||||||
|
Account.create!(supplier_id: supplier.id, plan: 'gold')
|
||||||
|
assert_nil supplier.account_history
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_through_honors_explicit_source_when_name_differs
|
||||||
|
history_with_alias = Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :account_histories
|
||||||
|
belongs_to :account, class_name: 'Account', foreign_key: 'account_id'
|
||||||
|
has_one :owner, through: :account, source: :supplier
|
||||||
|
end
|
||||||
|
history_with_alias.store = @store
|
||||||
|
|
||||||
|
supplier = Supplier.create!(name: 'acme')
|
||||||
|
account = Account.create!(supplier_id: supplier.id, plan: 'gold')
|
||||||
|
record = history_with_alias.create!(account_id: account.id, notes: 'n/a')
|
||||||
|
|
||||||
|
assert_equal supplier.id, record.owner.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_has_one_through_rejects_dependent_option
|
||||||
|
error = assert_raises(ArgumentError) do
|
||||||
|
Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :suppliers
|
||||||
|
has_one :account, class_name: 'Account', foreign_key: 'supplier_id'
|
||||||
|
has_one :account_history, through: :account, dependent: :destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert_match(/dependent/, error.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_includes_has_one_through_avoids_n_plus_one_queries
|
||||||
|
3.times do |i|
|
||||||
|
supplier = Supplier.create!(name: "supplier#{i}")
|
||||||
|
account = Account.create!(supplier_id: supplier.id, plan: 'gold')
|
||||||
|
AccountHistory.create!(account_id: account.id, notes: "notes#{i}")
|
||||||
|
end
|
||||||
|
lonely = Supplier.create!(name: 'no account')
|
||||||
|
|
||||||
|
@store.reset_query_count!
|
||||||
|
suppliers = Supplier.all.includes(:account_history).to_a
|
||||||
|
baseline = @store.query_count
|
||||||
|
|
||||||
|
notes = suppliers.map { |supplier| supplier.account_history&.notes }
|
||||||
|
assert_equal baseline, @store.query_count,
|
||||||
|
"expected preloaded #account_history to be free, but it issued #{@store.query_count - baseline} more queries"
|
||||||
|
assert_equal ['notes0', 'notes1', 'notes2', nil], notes.compact.sort + [nil]
|
||||||
|
assert_includes suppliers.map(&:name), lonely.name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Mirrors test_includes_has_one_through_avoids_n_plus_one_queries above,
|
||||||
|
# but from the AccountHistory side where the through step (:account) is a
|
||||||
|
# belongs_to rather than a has_one - exercises the `when :belongs_to then
|
||||||
|
# preload_belongs_to(...)` branch in preload_has_one_through specifically,
|
||||||
|
# which the has_one-through variant above never touches.
|
||||||
|
def test_includes_has_one_through_by_belongs_to_avoids_n_plus_one_queries
|
||||||
|
3.times do |i|
|
||||||
|
supplier = Supplier.create!(name: "supplier#{i}")
|
||||||
|
account = Account.create!(supplier_id: supplier.id, plan: 'gold')
|
||||||
|
AccountHistory.create!(account_id: account.id, notes: "notes#{i}")
|
||||||
|
end
|
||||||
|
|
||||||
|
@store.reset_query_count!
|
||||||
|
histories = AccountHistory.all.includes(:supplier).to_a
|
||||||
|
baseline = @store.query_count
|
||||||
|
|
||||||
|
names = histories.map { |history| history.supplier.name }.sort
|
||||||
|
assert_equal baseline, @store.query_count,
|
||||||
|
"expected preloaded #supplier to be free, but it issued #{@store.query_count - baseline} more queries"
|
||||||
|
assert_equal %w[supplier0 supplier1 supplier2], names
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_includes_has_one_through_unknown_through_raises
|
||||||
|
broken_class = Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :suppliers
|
||||||
|
has_one :account_history, through: :nonexistent
|
||||||
|
end
|
||||||
|
broken_class.store = @store
|
||||||
|
broken_class.create!(name: 'x')
|
||||||
|
assert_raises(ArgumentError) { broken_class.all.includes(:account_history).to_a }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_includes_has_one_through_rejects_has_many_through_association
|
||||||
|
broken_class = Class.new(Prism::Orm::Model) do
|
||||||
|
table_name :suppliers
|
||||||
|
has_many :accounts, class_name: 'Account', foreign_key: 'supplier_id'
|
||||||
|
has_one :account_history, through: :accounts
|
||||||
|
end
|
||||||
|
broken_class.store = @store
|
||||||
|
broken_class.create!(name: 'x')
|
||||||
|
assert_raises(ArgumentError) { broken_class.all.includes(:account_history).to_a }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wraps Store#all/#find to count reads, mirroring CountingThroughStore in
|
||||||
|
# test_orm_has_many_through.rb so this file has no load-order dependency.
|
||||||
|
class CountingHasOneThroughStore < Prism::Orm::Store
|
||||||
|
def initialize(*)
|
||||||
|
super
|
||||||
|
@query_count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :query_count
|
||||||
|
|
||||||
|
def reset_query_count!
|
||||||
|
@query_count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def all(table)
|
||||||
|
@query_count += 1
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(table, id)
|
||||||
|
@query_count += 1
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user