From 870f7afb79f0a4857f92f399fdc5f00b3341f474 Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Wed, 2 Oct 2019 15:58:27 -0400 Subject: [PATCH] Add #one to HasMany (#550) Per https://github.com/hanami/model/issues/550, the HasMany interface does not line up with Hanami's guides. Prior to this commit, the following is invalid as HasMany does not implement #one. ```ruby class AuthorRepository < Hanami::Repository associations do has_many :books end def find_book(author, id) book_for(author, id).one end private def book_for(author, id) assoc(:books, author).where(id: id) end end ``` This implementation is based on a couple of others, notably ROM's and Ecto's (Elixir). I opted to name the raised error MultipleResultsError, which is the exact name used by Ecto, because I feel it bears a semantic relationship to the name of the method from which raises it. --- lib/hanami/model/associations/has_many.rb | 10 +++++++ lib/hanami/model/error.rb | 3 ++ .../model/associations/has_many_spec.rb | 29 +++++++++++++++++++ spec/support/fixtures.rb | 8 +++++ 4 files changed, 50 insertions(+) diff --git a/lib/hanami/model/associations/has_many.rb b/lib/hanami/model/associations/has_many.rb index f8724237..e8bb161b 100644 --- a/lib/hanami/model/associations/has_many.rb +++ b/lib/hanami/model/associations/has_many.rb @@ -46,6 +46,16 @@ def initialize(repository, source, target, subject, scope = nil) freeze end + # @raise [MultipleResultsError] if more than one record is available + # + # @since 1.3.3 + # @api private + def one + raise Hanami::Model::MultipleResultsError, "#{count} results returned" if count > 1 + + scope.one + end + # @since 0.7.0 # @api private def create(data) diff --git a/lib/hanami/model/error.rb b/lib/hanami/model/error.rb index 21fe379f..231f3c35 100644 --- a/lib/hanami/model/error.rb +++ b/lib/hanami/model/error.rb @@ -127,5 +127,8 @@ def initialize(url) super("Unknown database adapter for URL: #{url.inspect}. Please check your database configuration (hint: ENV['DATABASE_URL']).") end end + + class MultipleResultsError < Error + end end end diff --git a/spec/integration/hanami/model/associations/has_many_spec.rb b/spec/integration/hanami/model/associations/has_many_spec.rb index 617aba21..827a969c 100644 --- a/spec/integration/hanami/model/associations/has_many_spec.rb +++ b/spec/integration/hanami/model/associations/has_many_spec.rb @@ -192,4 +192,33 @@ # skipped spec it '#remove' end + + describe '#one' do + it 'raises an error if more than one record exists' do + author = authors.create(name: 'Umberto Eco') + book = books.create(author_id: author.id, title: 'Foucault Pendulum') + book = books.create(author_id: author.id, title: 'Foucault Pendulum') + + expect do + authors.find_book_by_title(author, book.title) + end.to raise_error(Hanami::Model::MultipleResultsError) + end + + it 'returns an individual record if only one record exists' do + author = authors.create(name: 'Umberto Eco') + book = books.create(author_id: author.id, title: 'Foucault Pendulum') + + found = authors.find_book(author, book.id) + + expect(found).to eq(book) + end + + it 'returns nil if no records exist' do + author = authors.create(name: 'Aspiring Author') + + found = authors.find_book(author, nil) + + expect(found).to eq(nil) + end + end end diff --git a/spec/support/fixtures.rb b/spec/support/fixtures.rb index 1b75c949..260571ac 100644 --- a/spec/support/fixtures.rb +++ b/spec/support/fixtures.rb @@ -258,12 +258,20 @@ def find_book(author, id) book_for(author, id).one end + def find_book_by_title(author, title) + book_by_title(author, title).one + end + def book_exists?(author, id) book_for(author, id).exists? end private + def book_by_title(author, title) + assoc(:books, author).where(title: title) + end + def book_for(author, id) assoc(:books, author).where(id: id) end