Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Improve emoji lookup fallback behavior

Instead of an unfriendly LocalJumpError, raise the Emoji::NotFound
exception with a helpful message if the block was not given.

If a fallback block was given, yield the value for which the lookup failed.
  • Loading branch information...
commit 5d2401222ae6d97a0241b3f5f719fa98c150c925 1 parent 70db910
@mislav mislav authored
Showing with 21 additions and 9 deletions.
  1. +12 −2 lib/emoji.rb
  2. +9 −7 test/emoji_test.rb
View
14 lib/emoji.rb
@@ -4,6 +4,8 @@
module Emoji
extend self
+ NotFound = Class.new(IndexError)
@jeremy Admin
jeremy added a note

IndexError is used for out of bounds indexes on Arrays. Probably want to subclass KeyError for a map lookup, or just use StandardError.

@mislav Admin
mislav added a note

I didn't want to subclass KeyError because it doesn't exist on 1.8, but later I realized gemoji isn't 1.8 compatible anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
def data_file
File.expand_path('../../db/emoji.json', __FILE__)
end
@@ -17,11 +19,19 @@ def all
end
def find_by_alias(name)
- names_index.fetch(name) { yield }
+ names_index.fetch(name) {
+ if block_given? then yield name
+ else raise NotFound, "Emoji not found by name: %s" % name.inspect
+ end
+ }
end
@jeremy Admin
jeremy added a note

Example of a more familiar API style, including a second arg for default value:

def find_by_alias(name)
  fetch_by_alias name, nil
end

ALIAS_MISSING = lambda { |name| raise NotFound, "Emoji not found by name: %s" % name.inspect }
def fetch_by_alias(name, *default_value, &default_block)
  default_block ||= ALIAS_MISSING
  names_index.fetch(name, *default_value, &default_block)
end
@mislav Admin
mislav added a note

Example of a more familiar API style

I can be on board with this, sure. Please send a PR and we could make it v2.1 or something

@jeremy Admin
jeremy added a note

Done: #53

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
def find_by_unicode(unicode)
- unicodes_index.fetch(unicode) { yield }
+ unicodes_index.fetch(unicode) {
+ if block_given? then yield unicode
+ else raise NotFound, "Emoji not found from unicode: %s" % Emoji::Character.hex_inspect(unicode)
+ end
+ }
end
def names
View
16 test/emoji_test.rb
@@ -23,14 +23,15 @@ class EmojiTest < TestCase
end
test "emoji alias not found" do
- assert_raises LocalJumpError do
+ error = assert_raises Emoji::NotFound do
Emoji.find_by_alias('$$$')
end
+ assert_equal %(Emoji not found by name: "$$$"), error.message
end
test "emoji by alias fallback block" do
- emoji = Emoji.find_by_alias('$$$') { :not_found }
- assert_equal :not_found, emoji
+ emoji = Emoji.find_by_alias('hello') { |name| name.upcase }
+ assert_equal 'HELLO', emoji
end
test "fetching emoji by unicode" do
@@ -39,14 +40,15 @@ class EmojiTest < TestCase
end
test "emoji unicode not found" do
- assert_raises LocalJumpError do
- Emoji.find_by_unicode('$$$')
+ error = assert_raises Emoji::NotFound do
+ Emoji.find_by_unicode("\u{1234}\u{abcd}")
end
+ assert_equal %(Emoji not found from unicode: 1234-abcd), error.message
end
test "emoji by unicode fallback block" do
- emoji = Emoji.find_by_unicode('$$$') { :not_found }
- assert_equal :not_found, emoji
+ emoji = Emoji.find_by_unicode("\u{1234}") { |u| "not-#{u}-found" }
+ assert_equal "not-\u{1234}-found", emoji
end
test "emojis have tags" do

3 comments on commit 5d24012

@jeremy
Admin

I found this API style pretty surprising, since I expect a missing lookup to return nil for nice operation with truthy conditionals, like

if emoji = Emoji.find_by_alias($1)
  ...
else
  ...
end

Raising an exception when the alias is missing does not feel actually exceptional—it's an expected common case. By raising, developers will often end up rescuing the exception for control flow rather than looking deeper to see that find_by_* has Hash#fetch-like semantics.

@jeremy

IndexError is used for out of bounds indexes on Arrays. Probably want to subclass KeyError for a map lookup, or just use StandardError.

@josh
Admin

@jeremy yeah, I agree, this is kinda a smell.

@jeremy

Example of a more familiar API style, including a second arg for default value:

def find_by_alias(name)
  fetch_by_alias name, nil
end

ALIAS_MISSING = lambda { |name| raise NotFound, "Emoji not found by name: %s" % name.inspect }
def fetch_by_alias(name, *default_value, &default_block)
  default_block ||= ALIAS_MISSING
  names_index.fetch(name, *default_value, &default_block)
end
@mislav

I didn't want to subclass KeyError because it doesn't exist on 1.8, but later I realized gemoji isn't 1.8 compatible anyway.

@mislav
Admin

I wanted a nil-free API. Agreed that it makes the truthy conditional a little more awkward by forcing you to append a { nil } block. I don't expect developers to start using these exceptions as control flow, because if they do, that's their own fault. I think in some cases an exception is in order if the user think they're sure that an emoji by that name exists, but for some reason it doesn't (or gets removed in the future).

Suggestions for another API are welcome, but we'd have to do another major or minor release with this. v2.0 is already out.

@mislav

Example of a more familiar API style

I can be on board with this, sure. Please send a PR and we could make it v2.1 or something

@jeremy

Done: #53

Please sign in to comment.
Something went wrong with that request. Please try again.