diff --git a/README.md b/README.md index d2ad0c3..5127597 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ The `Day` class will gain the following methods: `nil` when a record does not exist. - `.find_by`: finds the first record matching the specified conditions. If no record is found, returns `nil`. +- `find_by!` behaves like `find_by` but raises a + `StaticAssociation::RecordNotFound` error if no record is found. - `.where`: accepts an array of ids and returns all records with matching ids. ### Associations diff --git a/lib/static_association.rb b/lib/static_association.rb index 1293db3..6f409f3 100644 --- a/lib/static_association.rb +++ b/lib/static_association.rb @@ -62,6 +62,13 @@ def find_by(**args) all.find { |record| matches_attributes?(record: record, attributes: args) } end + def find_by!(**args) + find_by(**args) or raise RecordNotFound.new( + "Couldn't find #{name} with " + + args.map { |k, v| "#{k}=#{v}" }.join(", ") + ) + end + def record(settings, &block) settings.assert_valid_keys(:id) id = settings.fetch(:id) diff --git a/spec/static_association_spec.rb b/spec/static_association_spec.rb index e54f2de..51df8a2 100644 --- a/spec/static_association_spec.rb +++ b/spec/static_association_spec.rb @@ -119,6 +119,16 @@ class AssociationClass ) end end + + context "when argument is numeric string" do + it "returns the record" do + record = DummyClass.record(id: 1) + + found_record = DummyClass.find("1") + + expect(found_record).to eq(record) + end + end end describe ".find_by_id" do @@ -264,7 +274,7 @@ class AssociationClass end context "with undefined attributes" do - it "raises a StaticAssociation::UndefinedAttribute" do + it "raises an error" do DummyClass.record(id: 1) expect { @@ -286,6 +296,105 @@ class AssociationClass end end + describe ".find_by!" do + context "when record exists with the specified attribute value" do + it "returns the record" do + record1 = DummyClass.record(id: 1) do |r| + r.name = "foo" + end + _record2 = DummyClass.record(id: 2) do |r| + r.name = "bar" + end + + found_record = DummyClass.find_by!(name: "foo") + + expect(found_record).to eq(record1) + end + end + + context "when no record exists that matches the specified attribute value" do + it "raises an error" do + DummyClass.record(id: 1) do |r| + r.name = "foo" + end + + expect { + DummyClass.find_by!(name: "bar") + }.to raise_error( + StaticAssociation::RecordNotFound, + "Couldn't find DummyClass with name=bar" + ) + end + end + + context "when multiple records match the specified attribute value" do + it "returns the first matching record" do + record1 = DummyClass.record(id: 1) do |r| + r.name = "foo" + end + _record2 = DummyClass.record(id: 2) do |r| + r.name = "foo" + end + + found_record = DummyClass.find_by!(name: "foo") + + expect(found_record).to eq(record1) + end + end + + context "when specifying multiple attribute values" do + it "returns the record matching all attributes" do + _record1 = DummyClass.record(id: 1) do |r| + r.name = "foo" + end + record2 = DummyClass.record(id: 2) do |r| + r.name = "foo" + end + + found_record = DummyClass.find_by!(id: 2, name: "foo") + + expect(found_record).to eq(record2) + end + end + + context "when specifying multiple attribute values but no record " \ + "matches all attributes" do + it "raises an error" do + _record1 = DummyClass.record(id: 1) do |r| + r.name = "foo" + end + + expect { + DummyClass.find_by!(id: 1, name: "bar") + }.to raise_error( + StaticAssociation::RecordNotFound, + "Couldn't find DummyClass with id=1, name=bar" + ) + end + end + + context "with undefined attributes" do + it "raises a StaticAssociation::UndefinedAttribute" do + DummyClass.record(id: 1) + + expect { + DummyClass.find_by!(undefined_attribute: 1) + }.to raise_error( + StaticAssociation::UndefinedAttribute, + "Undefined attribute 'undefined_attribute'" + ) + end + end + + context "with no attributes" do + it "raises a StaticAssociation::ArgumentError" do + expect { + DummyClass.find_by! + }.to raise_error(StaticAssociation::ArgumentError) + end + end + end + describe ".belongs_to_static" do it "defines a reader method for the association" do associated_class = AssociationClass.new