- Ancestry now runs on the materialized path (path_ids), no map/reduce: children = path_ids[len-2] = $id, subtree = $id IN path_ids, roots = ARRAY_LENGTH = 1. ancestry_spec: 31/31. - Soft-delete-aware find_by/find_all_by: composite [key, deleted_at] keys compare element-wise so a nil component matches MISSING/NULL (non-deleted docs). - Deterministic ordering: secondary sort by created_at; timestamps now microsecond precision so same-second creations paginate in creation order. - by_property (namespaced) trees span all partitions when no value is given. - Tagged the remaining CouchDB-mechanic specs :couchdb_only (view generation, design-doc maintenance, raw JS views, documented not-supported habtm cases). Result: 403/403 applicable examples green on a clean run (one namespaced-ancestry reparent edge is intermittently flaky). Up from 378/432. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
227 lines
6.8 KiB
Ruby
227 lines
6.8 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe 'Instance lifecycle' do
|
|
context 'design documents', :couchdb_only do
|
|
let(:db_url) { SimplyBased.database_url }
|
|
|
|
it 'deletes all' do
|
|
expect(SimplyBased::Model.delete_all_design_documents(db_url)).to eq 0
|
|
user = User.create
|
|
Post.create(user: user)
|
|
user.posts
|
|
expect(SimplyBased::Model.delete_all_design_documents(db_url)).to eq 1
|
|
end
|
|
|
|
it 'compacts all' do
|
|
expect(SimplyBased::Model.compact_all_design_documents(db_url)).to eq 0
|
|
user = User.create
|
|
Post.create(user: user)
|
|
user.posts
|
|
expect(SimplyBased::Model.compact_all_design_documents(db_url)).to eq 1
|
|
end
|
|
end
|
|
|
|
context 'when creating instances' do
|
|
it 'populates the attributes' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
expect(user.title).to eq 'Mr.'
|
|
expect(user.name).to eq 'Host Master'
|
|
end
|
|
|
|
it 'saves the instance' do
|
|
user = User.create(title: 'Mr.')
|
|
expect(user).not_to be_new_record
|
|
end
|
|
|
|
context 'with a bang' do
|
|
it 'does not raise an exception when saving succeeded' do
|
|
expect { User.create!(title: 'Mr.') }.not_to raise_error
|
|
end
|
|
|
|
it 'saves the user' do
|
|
user = User.create!(title: 'Mr.')
|
|
expect(user).not_to be_new_record
|
|
end
|
|
|
|
it 'raises an error when the validations failed' do
|
|
expect { User.create!(title: nil) }.to raise_error(RuntimeError, /Validations failed/)
|
|
end
|
|
end
|
|
|
|
context 'with a block' do
|
|
it 'calls the block with the record' do
|
|
user = User.create do |u|
|
|
u.title = 'Mr.'
|
|
end
|
|
expect(user.title).to eq 'Mr.'
|
|
end
|
|
|
|
it 'saves the record' do
|
|
user = User.create do |u|
|
|
u.title = 'Mr.'
|
|
end
|
|
expect(user).not_to be_new_record
|
|
end
|
|
|
|
it 'assigns attributes via the hash' do
|
|
user = User.create(title: 'Mr.') do |u|
|
|
u.name = 'Host Master'
|
|
end
|
|
expect(user.title).to eq 'Mr.'
|
|
expect(user.name).to eq 'Host Master'
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when saving an instance' do
|
|
it 'saves the instance' do
|
|
user = User.new(title: 'Mr.')
|
|
expect(user).to be_new_record
|
|
user.save
|
|
expect(user).not_to be_new_record
|
|
end
|
|
|
|
context 'when using save!' do
|
|
it 'raises an exception when a validation is not fulfilled' do
|
|
user = User.new
|
|
expect { user.save! }.to raise_error(RuntimeError, /Validations failed/)
|
|
end
|
|
end
|
|
|
|
context 'when using save(false)' do
|
|
it 'does not run the validations' do
|
|
user = User.new
|
|
user.save(false)
|
|
expect(user).not_to be_new
|
|
expect(user).not_to be_dirty
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when destroying an instance' do
|
|
it 'removes the instance' do
|
|
user = User.create(title: 'Mr')
|
|
expect { user.destroy }.to change { User.find(:all).size }.by(-1)
|
|
end
|
|
|
|
it 'returns the frozen instance' do
|
|
user = User.create(title: 'Mr')
|
|
expect(user.destroy).to eq user
|
|
end
|
|
end
|
|
|
|
context 'when updating attributes' do
|
|
it 'merges in the updated attributes' do
|
|
user = User.create(title: 'Mr.')
|
|
user.update_attributes(title: 'Mrs.')
|
|
expect(user.title).to eq 'Mrs.'
|
|
end
|
|
|
|
it 'saves the instance' do
|
|
user = User.create(title: 'Mr.')
|
|
user.update_attributes(title: 'Mrs.')
|
|
expect(user).not_to be_dirty
|
|
end
|
|
end
|
|
|
|
context 'when counting' do
|
|
context 'when counting all' do
|
|
it 'returns the number of objects in the database' do
|
|
CountMe.create(title: 'Mr.')
|
|
CountMe.create(title: 'Mrs.')
|
|
expect(CountMe.find(:all).size).to eq 2
|
|
expect(CountMe.count).to eq 2
|
|
end
|
|
|
|
it 'only counts the correct class' do
|
|
CountMe.create(title: 'Mr.')
|
|
DontCountMe.create(title: 'Foo')
|
|
expect(CountMe.find(:all).size).to eq 1
|
|
expect(CountMe.count).to eq 1
|
|
end
|
|
end
|
|
|
|
context 'when counting by prefix' do
|
|
it 'returns the number of matching objects' do
|
|
CountMe.create(title: 'Mr.')
|
|
CountMe.create(title: 'Mrs.')
|
|
expect(CountMe.find_all_by_title('Mr.').size).to eq 1
|
|
expect(CountMe.count_by_title('Mr.')).to eq 1
|
|
end
|
|
|
|
it 'only counts the correct class' do
|
|
CountMe.create(title: 'Mr.')
|
|
DontCountMe.create(title: 'Mr.')
|
|
expect(CountMe.find_all_by_title('Mr.').size).to eq 1
|
|
expect(CountMe.count_by_title('Mr.')).to eq 1
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when reloading an instance' do
|
|
it 'reloads new attributes from the database' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
user2 = User.find(user.id)
|
|
user2.update_attributes(title: 'Mrs.', name: 'Hostess Masteress')
|
|
user.reload
|
|
expect(user.title).to eq 'Mrs.'
|
|
expect(user.name).to eq 'Hostess Masteress'
|
|
end
|
|
|
|
it 'removes attributes that are no longer in the database' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
expect(user.name).not_to be_nil
|
|
same_user_in_different_thread = User.find(user.id)
|
|
same_user_in_different_thread.name = nil
|
|
same_user_in_different_thread.save!
|
|
expect(user.reload.name).to be_nil
|
|
end
|
|
|
|
it 'also removes foreign key attributes that are no longer in the database' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
post = Post.create(user: user)
|
|
expect(post.user_id).not_to be_nil
|
|
same_post_in_different_thread = Post.find(post.id)
|
|
same_post_in_different_thread.user = nil
|
|
same_post_in_different_thread.save!
|
|
expect(post.reload.user_id).to be_nil
|
|
end
|
|
|
|
it 'is not dirty after reloading' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
user2 = User.find(user.id)
|
|
user2.update_attributes(title: 'Mrs.', name: 'Hostess Masteress')
|
|
user.reload
|
|
expect(user).not_to be_dirty
|
|
end
|
|
|
|
it 'ensures that association caches for has_many are cleared' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
post = Post.create(user: user)
|
|
expect(user.posts.size).to eq 1
|
|
expect(user.instance_variable_get('@posts')).not_to be_nil
|
|
user.reload
|
|
expect(user.instance_variable_get('@posts')).to be_nil
|
|
expect(user.posts.first).not_to be_nil
|
|
end
|
|
|
|
it 'ensures that association caches for belongs_to are cleared' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
post = Post.create(user: user)
|
|
post.user
|
|
expect(post.instance_variable_get('@user')).not_to be_nil
|
|
post.reload
|
|
expect(post.instance_variable_get('@user')).to be_nil
|
|
expect(post.user).not_to be_nil
|
|
end
|
|
|
|
it 'updates the revision' do
|
|
user = User.create(title: 'Mr.', name: 'Host Master')
|
|
user2 = User.find(user.id)
|
|
user2.update_attributes(title: 'Mrs.', name: 'Hostess Masteress')
|
|
user.reload
|
|
expect(user._rev).to eq user2._rev
|
|
end
|
|
end
|
|
end
|