Skip to content

Commit 1f87b22

Browse files
committed
Treat blank item parameters as nil
1 parent 0da1e6d commit 1f87b22

4 files changed

Lines changed: 144 additions & 1 deletion

File tree

app/controllers/items_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def set_item
6767

6868
# Only allow a list of trusted parameters through.
6969
def item_params
70-
@item_params ||= jsonapi_deserialize(params, only: Item::EDIT_ATTRS)
70+
@item_params ||= jsonapi_deserialize(params, only: Item::EDIT_ATTRS).transform_values(&:presence)
7171
end
7272

7373
def ensure_image_param!

app/models/item.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Item < ApplicationRecord
2626
validate :multiple_terms_allowed
2727
validate :unsuppressed_item_has_image
2828
validate :unsuppressed_item_has_mms_id
29+
validate :non_nil_mms_id_is_not_blank
2930

3031
def multiple_terms_allowed
3132
terms_by_facet = terms.group_by(&:facet)
@@ -49,6 +50,12 @@ def unsuppressed_item_has_mms_id
4950
errors.add(:mms_id, 'unsuppressed item must have an MMS ID')
5051
end
5152

53+
def non_nil_mms_id_is_not_blank
54+
return if mms_id.nil? || mms_id.present?
55+
56+
errors.add(:mms_id, 'MMS ID, if present, cannot be blank')
57+
end
58+
5259
# ------------------------------------------------------------
5360
# Scopes
5461

spec/models/item_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,50 @@
197197
expect(item.suppressed).to eq(true)
198198
expect(item.mms_id).to be_nil
199199
end
200+
201+
it 'does not allow an empty mms_id even for suppressed items' do
202+
item = Item.take
203+
mms_id_before = item.mms_id
204+
updated_at_before = item.updated_at
205+
206+
item.suppressed = true
207+
item.mms_id = ''
208+
expect(item).not_to be_valid
209+
210+
expect { item.save! }.to raise_error(ActiveRecord::RecordInvalid)
211+
item.reload
212+
expect(item.updated_at).to eq(updated_at_before)
213+
expect(item.mms_id).to eq(mms_id_before)
214+
end
215+
216+
it 'does not allow a blank, non-empty mms_id even for suppressed items' do
217+
item = Item.take
218+
mms_id_before = item.mms_id
219+
updated_at_before = item.updated_at
220+
221+
item.suppressed = true
222+
item.mms_id = ' '
223+
expect(item).not_to be_valid
224+
225+
expect { item.save! }.to raise_error(ActiveRecord::RecordInvalid)
226+
item.reload
227+
expect(item.updated_at).to eq(updated_at_before)
228+
expect(item.mms_id).to eq(mms_id_before)
229+
end
230+
231+
it 'allows multiple suppressed items with nil mms_id' do
232+
aggregate_failures do
233+
(0..1).each do |i|
234+
title = "item #{i}"
235+
item = Item.create!(title: title, suppressed: true)
236+
expect(item).to be_persisted
237+
item.reload
238+
expect(item.mms_id).to be_nil
239+
expect(item.title).to eq(title)
240+
expect(item.suppressed).to eq(true)
241+
end
242+
end
243+
end
200244
end
201245

202246
describe 'synthetic accessors' do

spec/requests/items_spec.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,30 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item
437437
expect(response.headers['Location']).to eq(item_url(item))
438438
end
439439

440+
it 'treats a blank MMS ID as nil' do
441+
post_attributes = valid_attributes.except(:mms_id).merge(suppressed: true, mms_id: ' ')
442+
443+
payload = { data: { type: 'item', attributes: post_attributes, relationships: to_relationships(image: valid_image) } }
444+
expect { post items_url, params: payload, as: :jsonapi }.to change(Item, :count).by(1)
445+
446+
expect(response).to have_http_status(:created)
447+
expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE)
448+
449+
parsed_response = JSON.parse(response.body)
450+
item_id = parsed_response['data']['id'].to_i
451+
452+
item = Item.find(item_id)
453+
expect(item).not_to be_nil
454+
expected_attributes = post_attributes.merge(mms_id: nil)
455+
expected_attributes.each { |attr, val| expect(item.send(attr)).to eq(val) }
456+
457+
links = parsed_response.delete('links')
458+
expect(links['self']).to eq(items_url)
459+
460+
expect(parsed_response).to contain_jsonapi_for(item)
461+
expect(response.headers['Location']).to eq(item_url(item))
462+
end
463+
440464
it 'accepts a suppressed item without an image' do
441465
payload = {
442466
data: {
@@ -487,6 +511,24 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item
487511
expect(actual_errors).to contain_exactly(*expected_json)
488512
end
489513

514+
it 'treats a blank title as missing' do
515+
post_attributes = valid_attributes.merge(title: ' ')
516+
payload = { data: { type: 'item', attributes: post_attributes, relationships: valid_relationships } }
517+
518+
allow(Rails.logger).to receive(:error)
519+
expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count)
520+
expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid))
521+
522+
expect(response).to have_http_status(:unprocessable_entity)
523+
expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE)
524+
525+
actual_errors = JSON.parse(response.body)['errors']
526+
invalid_attributes = post_attributes.merge(title: nil)
527+
expected_errors = expected_errors_for(invalid_attributes)
528+
expected_json = expected_errors.map { |err| jsonapi_for(err) }
529+
expect(actual_errors).to contain_exactly(*expected_json)
530+
end
531+
490532
it 'fails with 422 Unprocessable Entity for a missing image' do
491533
payload = {
492534
data: {
@@ -542,6 +584,24 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item
542584
expect(actual_errors).to contain_exactly(*expected_json)
543585
end
544586

587+
it 'treats a blank MMS ID as missing' do
588+
post_attributes = valid_attributes.merge(mms_id: ' ')
589+
payload = { data: { type: 'item', attributes: post_attributes, relationships: valid_relationships } }
590+
591+
allow(Rails.logger).to receive(:error)
592+
expect { post items_url, params: payload, as: :jsonapi }.not_to change(Item, :count)
593+
expect(Rails.logger).to have_received(:error).with(kind_of(ActiveRecord::RecordInvalid))
594+
595+
expect(response).to have_http_status(:unprocessable_entity)
596+
expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE)
597+
598+
actual_errors = JSON.parse(response.body)['errors']
599+
invalid_attributes = post_attributes.merge(mms_id: nil)
600+
expected_errors = expected_errors_for(invalid_attributes)
601+
expected_json = expected_errors.map { |err| jsonapi_for(err) }
602+
expect(actual_errors).to contain_exactly(*expected_json)
603+
end
604+
545605
it 'fails with 404 Not Found for an invalid term' do
546606
invalid_id = Term.maximum(:id) + 999
547607
payload = {
@@ -741,6 +801,38 @@ def expected_errors_for(attributes, terms: valid_terms, image: valid_image, item
741801

742802
expect(parsed_response).to contain_jsonapi_for(item)
743803
end
804+
805+
it 'treats a blank MMS ID as nil' do
806+
item = Item.take
807+
808+
old_attributes = item.attributes.slice(*Item::EDIT_ATTRS)
809+
patch_attributes = old_attributes.merge(suppressed: true, mms_id: '')
810+
expected_terms = item.terms.to_a
811+
812+
payload = { data: { type: 'item', id: item.id.to_s, attributes: patch_attributes,
813+
relationships: to_relationships(terms: expected_terms, image: item.image) } }
814+
patch item_url(item), params: payload, as: :jsonapi
815+
816+
expect(response).to have_http_status(:ok)
817+
expect(response.content_type).to start_with(JSONAPI::MEDIA_TYPE)
818+
819+
item.reload
820+
821+
expected_attributes = patch_attributes.merge(mms_id: nil)
822+
expected_attributes.each do |attr, val|
823+
actual = item.send(attr)
824+
expect(actual).to eq(val), "Wrong value for #{attr}; expected #{val.inspect}, was #{actual.inspect}"
825+
end
826+
827+
expect(item.terms).to contain_exactly(*expected_terms)
828+
829+
parsed_response = JSON.parse(response.body)
830+
831+
links = parsed_response.delete('links')
832+
expect(links['self']).to eq(item_url(item))
833+
834+
expect(parsed_response).to contain_jsonapi_for(item)
835+
end
744836
end
745837

746838
describe 'failure' do

0 commit comments

Comments
 (0)