Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid calling count on Enumerable #3

Merged
merged 3 commits into from
Jun 23, 2016

Conversation

schneiderderek
Copy link
Collaborator

@mikegee we talked about this awhile back so I thought I would put something together.

There could be Enumerable objects where it takes a very long time in order to get a count. first_and_only should avoid calling count especially since first_and_only only needs to know whether there are 0, 1, or 2 objects to do its job.

@dapplebeforedawn What do you think?

@mikegee
Copy link
Contributor

mikegee commented Jun 21, 2016

Is it easy to still include zero or more than one in the error message?

@schneiderderek
Copy link
Collaborator Author

@mikegee Its somewhat easy. I've been going back and forth between two implementations, let me know what you think is better (or if they are both bad):

Option 1:

require "first_and_only/version"

module Enumerable
  def first_and_only!
    first_two_count = first(2).count
    fail(FirstAndOnly::CountNotOne.new(first_two_count)) if first_two_count != 1
    first
  end

  module FirstAndOnly
    class CountNotOne < StandardError
      COUNT_ZERO_ERROR_MESSAGE = 'Expected the count to be 1, was 0.'.freeze
      COUNT_GREATER_THAN_ONE_ERROR_MESSAGE = 'Expected the count to be 1, was at least 2.'.freeze

      def initialize(first_two_count)
        if first_two_count == 0
          message = COUNT_ZERO_ERROR_MESSAGE
        else
          message = COUNT_GREATER_THAN_ONE_ERROR_MESSAGE
        end
        super(message)
      end
    end
  end
end

Option 2:

require "first_and_only/version"

module Enumerable
  def first_and_only!
    case first(2).count
    when 0
      fail FirstAndOnly::CountNotOne, FirstAndOnly::COUNT_ZERO_ERROR_MESSAGE
    when 1
      first
    else
      fail FirstAndOnly::CountNotOne, FirstAndOnly::COUNT_GREATER_THAN_ONE_ERROR_MESSAGE
    end
  end

  module FirstAndOnly
    CountNotOne = Class.new(StandardError)
    COUNT_ZERO_ERROR_MESSAGE = 'Expected the count to be 1, was 0.'.freeze
    COUNT_GREATER_THAN_ONE_ERROR_MESSAGE = 'Expected the count to be 1, was at least 2.'.freeze
  end
end

As i was writing these out I thought of a third implementation where there are two separate error classes, one for the zero count case and one for the greater than one case.

What do you think?

@mikegee
Copy link
Contributor

mikegee commented Jun 22, 2016

I have an allergic reaction to case statements, but I must admit that option seems better here.

Splitting the two errors into their own classes is probably an improvement too.

@schneiderderek
Copy link
Collaborator Author

@mikegee I agree, I usually dislike case statements, but I think in this case it worked well.

I split out the error into two classes, but I made the two specific classes inherit from the CountNotOneError class in case a client was rescuing that exception.

What do you think?

@mikegee
Copy link
Contributor

mikegee commented Jun 23, 2016

As much as I would love to continue to engineer the hell out of this miniscule detail of a gem basically nobody uses, let's ship it. Thanks Derek!

@schneiderderek schneiderderek merged commit be6bc9e into markjlorenz:master Jun 23, 2016
@schneiderderek schneiderderek deleted the avoid-calling-count branch June 23, 2016 19:41
@markjlorenz
Copy link
Owner

@schneiderderek I've added you to the project! Please merge you change, and then merge a version bump and tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants