Skip to content

Commit 403cfb5

Browse files
Ben Abrahamsidane
authored andcommitted
Adds .find_by class method
The `.find_by` method accepts a hash of attributes and returns the first record that matches all of them. If no record was found, the method returns `nil`, this matches the Rails `.find_by` behaviour. If an attribute is not valid for the record, an `UndefinedAttribute` error is raised.
1 parent 3b70631 commit 403cfb5

3 files changed

Lines changed: 119 additions & 1 deletion

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,17 @@ Calling `record` will allow you to create an instance of this static model,
4343
a unique id is mandatory. The newly created object is yielded to the passed
4444
block.
4545

46-
The `Day` class will gain `.all`, `.find`, `.find_by_id`, and `.where` methods.
46+
The `Day` class will gain `.all`, `.find`, `.find_by_id`, `.find_by` and
47+
`.where` methods.
4748

4849
- The `.all` method returns all the static records defined in the class.
4950
- The `.ids` method returns an array of all the ids of the static records.
5051
- The `.find` method accepts a single id and returns the matching record. If the
5152
record does not exist, a `RecordNotFound` error is raised.
5253
- The `.find_by_id` method behaves similarly to the `.find` method, except it
5354
returns `nil` when a record does not exist.
55+
- `find_by` finds the first record matching the specified conditions. If no
56+
record is found, returns `nil`.
5457
- The `.where` method accepts an array of ids and returns all records with
5558
matching ids.
5659

lib/static_association.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
module StaticAssociation
88
extend ActiveSupport::Concern
99

10+
class ArgumentError < StandardError; end
11+
1012
class DuplicateID < StandardError; end
1113

1214
class RecordNotFound < StandardError; end
1315

16+
class UndefinedAttribute < StandardError; end
17+
1418
attr_reader :id
1519

1620
private
@@ -52,6 +56,12 @@ def where(id: [])
5256
all.select { |record| id.include?(record.id) }
5357
end
5458

59+
def find_by(**args)
60+
args.any? or raise ArgumentError
61+
62+
all.find { |record| matches_attributes?(record: record, attributes: args) }
63+
end
64+
5565
def record(settings, &block)
5666
settings.assert_valid_keys(:id)
5767
id = settings.fetch(:id)
@@ -64,6 +74,18 @@ def record(settings, &block)
6474
record.instance_exec(record, &block) if block
6575
index[id] = record
6676
end
77+
78+
private
79+
80+
def matches_attributes?(record:, attributes:)
81+
attributes.all? do |attribute, value|
82+
record.respond_to?(attribute) or raise UndefinedAttribute.new(
83+
"Undefined attribute '#{attribute}'"
84+
)
85+
86+
record.public_send(attribute) == value
87+
end
88+
end
6789
end
6890

6991
module AssociationHelpers

spec/static_association_spec.rb

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,99 @@ class AssociationClass
191191

192192
expect(results).to contain_exactly(record1, record3)
193193
end
194+
195+
describe ".find_by" do
196+
context "when record exists with the specified attribute value" do
197+
it "returns the record" do
198+
record1 = DummyClass.record(id: 1) do |r|
199+
r.name = "foo"
200+
end
201+
_record2 = DummyClass.record(id: 2) do |r|
202+
r.name = "bar"
203+
end
204+
205+
found_record = DummyClass.find_by(name: "foo")
206+
207+
expect(found_record).to eq(record1)
208+
end
209+
end
210+
211+
context "when no record exists that matches the specified attribute value" do
212+
it "returns nil" do
213+
DummyClass.record(id: 1) do |r|
214+
r.name = "foo"
215+
end
216+
217+
found_record = DummyClass.find_by(name: "bar")
218+
219+
expect(found_record).to be_nil
220+
end
221+
end
222+
223+
context "when multiple records match the specified attribute value" do
224+
it "returns the first matching record" do
225+
record1 = DummyClass.record(id: 1) do |r|
226+
r.name = "foo"
227+
end
228+
_record2 = DummyClass.record(id: 2) do |r|
229+
r.name = "foo"
230+
end
231+
232+
found_record = DummyClass.find_by(name: "foo")
233+
234+
expect(found_record).to eq(record1)
235+
end
236+
end
237+
238+
context "when specifying multiple attribute values" do
239+
it "returns the record matching all attributes" do
240+
_record1 = DummyClass.record(id: 1) do |r|
241+
r.name = "foo"
242+
end
243+
record2 = DummyClass.record(id: 2) do |r|
244+
r.name = "foo"
245+
end
246+
247+
found_record = DummyClass.find_by(id: 2, name: "foo")
248+
249+
expect(found_record).to eq(record2)
250+
end
251+
end
252+
253+
context "when specifying multiple attribute values but no record " \
254+
"matches all attributes" do
255+
it "returns nil" do
256+
_record1 = DummyClass.record(id: 1) do |r|
257+
r.name = "foo"
258+
end
259+
260+
found_record = DummyClass.find_by(id: 1, name: "bar")
261+
262+
expect(found_record).to be_nil
263+
end
264+
end
265+
266+
context "with undefined attributes" do
267+
it "raises a StaticAssociation::UndefinedAttribute" do
268+
DummyClass.record(id: 1)
269+
270+
expect {
271+
DummyClass.find_by(undefined_attribute: 1)
272+
}.to raise_error(
273+
StaticAssociation::UndefinedAttribute,
274+
"Undefined attribute 'undefined_attribute'"
275+
)
276+
end
277+
end
278+
279+
context "with no attributes" do
280+
it "raises a StaticAssociation::ArgumentError" do
281+
expect {
282+
DummyClass.find_by
283+
}.to raise_error(StaticAssociation::ArgumentError)
284+
end
285+
end
286+
end
194287
end
195288

196289
describe ".belongs_to_static" do

0 commit comments

Comments
 (0)