can_lookup_examples.rb 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. RSpec.shared_examples 'ApplicationModel::CanLookup' do
  2. describe '.lookup_keys' do
  3. it 'returns a subset of: id, name, login, email, number' do
  4. expect(described_class.lookup_keys)
  5. .to all(be_in(%i[id name login email number]))
  6. end
  7. it 'only includes attributes present on the model' do
  8. expect(described_class.lookup_keys)
  9. .to all(be_in(described_class.attribute_names.map(&:to_sym)))
  10. end
  11. end
  12. describe '.lookup' do
  13. around do |example|
  14. Rails.cache.clear
  15. example.run
  16. Rails.cache.clear
  17. end
  18. let!(:instance) { create(described_class.name.underscore) }
  19. let(:valid_lookup_key) { (described_class.lookup_keys - [:id]).sample }
  20. let(:invalid_lookup_key) { (described_class.attribute_names.map(&:to_sym) - described_class.lookup_keys).sample }
  21. it 'accepts exactly one attribute-value pair' do
  22. expect { described_class.lookup(id: instance.id, valid_lookup_key => 'foo') }
  23. .to raise_error(ArgumentError)
  24. end
  25. it 'only accepts attributes from .lookup_keys' do
  26. expect { described_class.lookup(invalid_lookup_key => 'foo') }
  27. .to raise_error(ArgumentError)
  28. end
  29. shared_examples 'per-attribute examples' do |attribute|
  30. it "finds records by #{attribute} (like .find_by)" do
  31. expect(described_class.lookup(attribute => instance.send(attribute))).to eq(instance)
  32. end
  33. describe "cache storage by #{attribute}" do
  34. context 'inside a DB transaction' do # provided by default RSpec config
  35. it 'leaves the cache untouched' do
  36. expect { described_class.lookup(attribute => instance.send(attribute)) }
  37. .not_to change { described_class.cache_get(instance.send(attribute)) }
  38. end
  39. end
  40. context 'outside a DB transaction' do
  41. before do
  42. allow(ActiveRecord::Base.connection)
  43. .to receive(:transaction_open?).and_return(false)
  44. end
  45. context 'when called for the first time' do
  46. it 'saves the value to the cache' do
  47. expect(Rails.cache)
  48. .to receive(:write)
  49. .with("#{described_class}::#{instance.send(attribute)}", instance, { expires_in: 7.days })
  50. .and_call_original
  51. expect { described_class.lookup(attribute => instance.send(attribute)) }
  52. .to change { described_class.cache_get(instance.send(attribute)) }
  53. .to(instance)
  54. end
  55. end
  56. context 'when called a second time' do
  57. before { described_class.lookup(attribute => instance.send(attribute)) }
  58. it 'retrieves results from cache' do
  59. expect(Rails.cache)
  60. .to receive(:read)
  61. .with("#{described_class}::#{instance.send(attribute)}")
  62. described_class.lookup(attribute => instance.send(attribute))
  63. end
  64. if attribute != :id
  65. context 'after it has been updated' do
  66. let!(:old_attribute_val) { instance.send(attribute) }
  67. let!(:new_attribute_val) { instance.send(attribute).next }
  68. it 'moves the record to a new cache key' do
  69. expect { instance.update(attribute => new_attribute_val) }
  70. .to change { described_class.cache_get(old_attribute_val) }.to(nil)
  71. expect { described_class.lookup({ attribute => instance.send(attribute) }) }
  72. .to change { described_class.cache_get(new_attribute_val) }.to(instance)
  73. end
  74. end
  75. end
  76. context 'after it has been destroyed' do
  77. it 'returns nil' do
  78. expect { instance.destroy }
  79. .to change { described_class.cache_get(instance.send(attribute)) }
  80. .to(nil)
  81. end
  82. end
  83. end
  84. end
  85. end
  86. end
  87. described_class.lookup_keys.each do |key|
  88. include_examples 'per-attribute examples', key
  89. end
  90. end
  91. end