Skip to content

Commit

Permalink
Merge branch 'release/0.6.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
thornomad committed Jul 3, 2020
2 parents 7598f4d + 3aac7f9 commit 6fbb3d0
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
enumbler (0.5.1)
enumbler (0.6.0)
activerecord (>= 5.2.3, < 6.1)
activesupport (>= 5.2.3, < 6.1)

Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,39 @@ Color.black.black? # => true
Color.black.is_black # => true
Color.white.not_black? # => true

# Get attributes without hitting the database
Color.black(:id) # => 1
Color.black(:enum) # => :black
Color.black(:label) # => 'black'
Color.black(:graphql_enum) # => 'BLACK'

# Get an Enumble object from an id, label, enum, or ActiveRecord model
Color.find_enumbles(:black, 'white') # => [Enumbler::Enumble<:black>, Enumbler::Enumble<:white>]
Color.find_enumbles(:black, 'does-not-exist') # => [Enumbler::Enumble<:black>, nil]

Color.find_enumble(:black) # => Enumbler::Enumble<:black>

# raises errors if none found
Color.find_enumbles!!(:black, 'does-no-exist') # => raises Enumbler::Error
Color.find_enumble!(:does_not_exist) # => raises Enumbler::Error

# Get ids flexibly, without raising an error if none found
Color.ids_from_enumbler(:black, 'white') # => [1, 2]
Color.ids_from_enumbler(:black, 'does-no-exist') # => [1, nil]

# Raise an error if none found
Color.ids_from_enumbler!(:black, 'does-no-exist') # => raises Enumbler::Error
Color.id_from_enumbler!(:does_not_exist) # => raises Enumbler::Error

# Get enumble object by id

house = House.create!(color: Color.black)
house.black?
house.not_black?

house2 = House.create!(color: Color.white)
House.color(:black, :white) # => [house, house2]
House.color(:black, :white) # => ActiveRecord::Relation<house, house2>
House.color(Color.black, :white) # => ActiveRecord::Relation<house, house2>
```

### Use a column other than `label`
Expand Down
4 changes: 4 additions & 0 deletions lib/enumbler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def define_dynamic_methods_for_enumbled_to_models(enumbled_model, prefix: false,
where(column_name => enumbled_model.ids_from_enumbler(args))
end

define_singleton_method("#{cmethod}!") do |*args|
where(column_name => enumbled_model.ids_from_enumbler!(args))
end

enumbled_model.enumbles.each do |enumble|
method_name = prefix ? "#{model_name}_#{enumble.enum}?" : "#{enumble.enum}?"
not_method_name = prefix ? "#{model_name}_not_#{enumble.enum}?" : "not_#{enumble.enum}?"
Expand Down
145 changes: 124 additions & 21 deletions lib/enumbler/enabler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,93 @@ def enumbler_label_column_name(label_column_name)
@enumbler_label_column_name = label_column_name
end

# See {.find_enumbles}. Simply returns the first object. Use when you
# want one argument to be found and not returned in an array.
# @raise [Error] when there is no [Enumbler::Enumble] to be found and
# `raise_error: true`
# @param args [Integer, String, Symbol]
# @param case_sensitive [Boolean] should a String search be case sensitive
# (default: false)
# @param raise_error [Boolean] raise an error if not found (default:
# false)
# @return [Enumbler::Enumble]
def find_enumble(arg, case_sensitive: false, raise_error: false)
find_enumbles(arg, case_sensitive: case_sensitive, raise_error: raise_error).first
end

# See {.find_enumbles}. Simply returns the first object. Use when you
# want one argument to be found and not returned in an array. Raises error
# if none found.
# @raise [Error] when there is no [Enumbler::Enumble] to be found and
# `raise_error: true`
# @param args [Integer, String, Symbol]
# @param case_sensitive [Boolean] should a String search be case sensitive
# (default: false)
# @return [Enumbler::Enumble]
def find_enumble!(arg, case_sensitive: false)
find_enumbles(arg, case_sensitive: case_sensitive, raise_error: true).first
end

# Finds an array of {Enumbler::Enumble} objects matching the given
# argument. Accepts an Integer, String, Symbol, or ActiveRecord instance.
#
# This method is designed to let you get information about the record
# without having to hit the database. Returns `nil` when none found
# unless `raise_error` is `true`.
#
# Color.find_enumbles(:black, 'white', 'not-found')
# #=> [Enumbler::Enumble<:black>, Enumbler::Enumble<:white>, nil]
#
# @raise [Error] when there is no [Enumbler::Enumble] to be found and
# `raise_error: true`
# @param args [Integer, String, Symbol]
# @param case_sensitive [Boolean] should a String search be case sensitive
# (default: false)
# @param raise_error [Boolean] raise an error if not found (default:
# false)
# @return [Array<Enumbler::Enumble>]
def find_enumbles(*args, case_sensitive: false, raise_error: false)
args.flatten.compact.uniq.map do |arg|
err = "Unable to find a #{@enumbled_model}#enumble with #{arg}"

begin
arg = Integer(arg) # raises Type error if not a real integer
enumble = @enumbled_model.enumbles.find { |e| e.id == arg }
rescue TypeError, ArgumentError
enumble =
if arg.is_a?(Symbol)
@enumbled_model.enumbles.find { |e| e.enum == arg }
elsif arg.is_a?(String)
@enumbled_model.enumbles.find do |e|
case_sensitive ? e.label == arg : arg.casecmp?(e.label)
end
elsif arg.instance_of?(@enumbled_model)
arg.enumble
end
end

if enumble.present?
enumble
else
raise Error if raise_error

nil
end
rescue Error
raise Error, err
end
end

# See {.find_enumbles}. Same method, only raises error when none found.
# @raise [Error] when there is no [Enumbler::Enumble] to be found
# @param args [Integer, String, Symbol]
# @param case_sensitive [Boolean] should a String search be case sensitive
# (default: false)
# @return [Array<Enumbler::Enumble>]
def find_enumbles!(*args, case_sensitive: false)
find_enumbles(*args, case_sensitive: case_sensitive, raise_error: true)
end

# Return the record id for a given argument. Can accept an Integer, a
# Symbol, or an instance of Enumbled model. This lookup is a database-free
# lookup.
Expand All @@ -107,9 +194,25 @@ def enumbler_label_column_name(label_column_name)
#
# @raise [Error] when there is no enumble to be found
# @param arg [Integer, Symbol, Class]
# @param case_sensitive [Boolean] should a string search be performed with
# case sensitivity (default: false)
# @param raise_error [Boolean] raise an error if not found (default:
# false)
# @return [Integer]
def id_from_enumbler(arg)
ids_from_enumbler(arg).first
def id_from_enumbler(arg, case_sensitive: false, raise_error: false)
ids_from_enumbler(arg, case_sensitive: case_sensitive, raise_error: raise_error).first
end

# See {.ids_from_enumbler}. Raises error if none found.
# @raise [Error] when there is no enumble to be found
# @param arg [Integer, Symbol, Class]
# @param case_sensitive [Boolean] should a string search be performed with
# case sensitivity (default: false)
# @param raise_error [Boolean] raise an error if not found (default:
# false)
# @return [Integer]
def id_from_enumbler!(arg, case_sensitive: false)
ids_from_enumbler(arg, case_sensitive: case_sensitive, raise_error: true).first
end

# Return the record id(s) based on different argument types. Can accept
Expand All @@ -118,30 +221,30 @@ def id_from_enumbler(arg)
#
# Color.ids_from_enumbler(1, 2) # => [1, 2]
# Color.ids_from_enumbler(:black, :white) # => [1, 2]
# Color.ids_from_enumbler('black', :white) # => [1, 2]
# Color.ids_from_enumbler(Color.black, Color.white) # => [1, 2]
#
# @raise [Error] when there is no enumble to be found
# @param *args [Integer, Symbol, Class]
# @param case_sensitive [Boolean] should a string search be performed with
# case sensitivity (default: false)
# @param raise_error [Boolean] raise an error if not found (default:
# false)
# @return [Array<Integer>]
def ids_from_enumbler(*args)
args.flatten.compact.uniq.map do |arg|
err = "Unable to find a #{@enumbled_model}#enumble with #{arg}"

begin
arg = Integer(arg) # raises Type error if not a real integer
enumble = @enumbled_model.enumbles.find { |e| e.id == arg }
rescue TypeError
enumble = if arg.is_a?(Symbol)
@enumbled_model.enumbles.find { |e| e.enum == arg }
elsif arg.instance_of?(@enumbled_model)
arg.enumble
end
end
def ids_from_enumbler(*args, case_sensitive: false, raise_error: false)
enumbles = find_enumbles(*args, case_sensitive: case_sensitive, raise_error: raise_error)
enumbles.map { |e| e&.id }
end

enumble&.id || raise(Error, err)
rescue Error
raise Error, err
end
# See {.ids_from_enumbler}. Raises error when none found.
# @raise [Error] when there is no enumble to be found
# @param *args [Integer, Symbol, Class]
# @param case_sensitive [Boolean] should a string search be performed with
# case sensitivity (default: false)
# @return [Array<Integer>]
def ids_from_enumbler!(*args, case_sensitive: false)
enumbles = find_enumbles!(*args, case_sensitive: case_sensitive)
enumbles.map(&:id)
end

# Seeds the database with the Enumbler data.
Expand All @@ -150,7 +253,7 @@ def ids_from_enumbler(*args)
# @param validate [Boolean] validate on save?
def seed_the_enumbler(delete_missing_records: false, validate: true)
max_database_id = all.order('id desc').take&.id || 0
max_enumble_id = enumbles.map(&:id).max
max_enumble_id = @enumbles.map(&:id).max

# If we are not deleting records, we just need to update each listed
# enumble and skip anything else in the database. If we are deleting
Expand Down
10 changes: 10 additions & 0 deletions lib/enumbler/enumble.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ def attributes
}
end

# Used to return itself from a class method.
#
# ```
# Color.black(:enumble) #=> <Enumble:0x00007fb4396a78c8>
# ```
# @return [Enumbler::Enumble]
def enumble
self
end

def eql?(other)
other.class == self.class &&
(other.id == id || other.enum == enum || other.label == label)
Expand Down
2 changes: 1 addition & 1 deletion lib/enumbler/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Enumbler
VERSION = '0.5.1'
VERSION = '0.6.0'
end
57 changes: 52 additions & 5 deletions spec/enumbler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,12 @@ class House < ApplicationRecord
expect(House.color(:black, :white)).to contain_exactly(house, house2)
expect(House.color(Color.black)).to contain_exactly(house)
end
it 'raises an error when the Enumble is not defined' do
expect { House.color(100, 1) }.to raise_error(Enumbler::Error, /Unable to find/)
it 'raises an error when the Enumble is not defined with bang method' do
expect { House.color!(100, 1) }.to raise_error(Enumbler::Error, /Unable to find/)
end
it 'returns nil when Enumble not defined' do
house = House.create! color: Color.black
expect(House.color(100, 1)).to contain_exactly(house)
end
it 'allows a scope prefix to be set' do
house = House.create! color: Color.black
Expand Down Expand Up @@ -162,27 +166,70 @@ class House < ApplicationRecord
end
end

describe '.find_enumbles', :seed do
it 'returns the correct enumbles' do
expect(Color.find_enumbles(1, 2)).to contain_exactly(Color.enumbles.first, Color.enumbles.second)
expect(Color.find_enumble(1)).to eq Color.enumbles.first
end
it 'can return an enumble based on a ActiveModel record' do
color = Color.find(1)
expect(Color.find_enumbles!(color)).to contain_exactly(color.enumble)
end
it 'raises an error when something that is not an integer is passed' do
expect { Color.find_enumbles('bob') }.not_to raise_error
expect { Color.find_enumbles!('bob') }.to raise_error(Enumbler::Error, /bob/)
end
end

# NOTE: the logic for these methods was moved to `.find_enumbles` but I left
# the tests here. We should refactor the tests into the main method with all
# the logic at some point.
describe '.ids_from_enumbler', :seed do
it 'returns a numeric id' do
expect(Color.id_from_enumbler(1)).to eq 1
expect(Color.ids_from_enumbler(1)).to contain_exactly(1)
expect(Color.ids_from_enumbler(1, 100)).to contain_exactly(1, nil)
end
it 'raises an error when the id is not defined' do
expect { Color.ids_from_enumbler(100, 1) }.to raise_error(Enumbler::Error, /Unable to find/)
expect { Color.ids_from_enumbler!(100, 1) }.to raise_error(Enumbler::Error, /Unable to find/)
end
it 'returns an id from a symbol' do
expect(Color.id_from_enumbler(:black)).to eq 1
expect(Color.ids_from_enumbler(:black)).to contain_exactly(1)
expect(Color.ids_from_enumbler(:black, :white)).to contain_exactly(1, 2)
end

context 'when case_sensitive is false (default)' do
it 'returns an id from a string' do
expect(Color.id_from_enumbler('dark-brown')).to eq 3
expect(Color.ids_from_enumbler('dark-brown')).to contain_exactly(3)
expect(Color.ids_from_enumbler('dark-Brown', 'black')).to contain_exactly(1, 3)
expect(Color.id_from_enumbler('BlaCk')).to eq 1
end
end

context 'when case_sensitive is true' do
it 'returns an id from a string' do
expect(Color.ids_from_enumbler('black', case_sensitive: true)).to contain_exactly(1)
expect(Color.ids_from_enumbler('black', :white, case_sensitive: true)).to contain_exactly(1, 2)
expect { Color.ids_from_enumbler!('Black', case_sensitive: true) }.to raise_error(Enumbler::Error, /Black/)
expect(Color.id_from_enumbler('black', case_sensitive: true)).to eq(1)
expect(Color.id_from_enumbler('black', case_sensitive: true)).to eq(1)
expect { Color.id_from_enumbler!('Black', case_sensitive: true) }.to raise_error(Enumbler::Error, /Black/)
end
end

it 'raises an error when the symbol cannot be found' do
expect { Color.ids_from_enumbler(:Bob) }.to raise_error(Enumbler::Error, /Unable to find/)
expect { Color.ids_from_enumbler!(:Bob) }.to raise_error(Enumbler::Error, /Unable to find/)
expect { Color.ids_from_enumbler(:Bob) }.not_to raise_error
expect { Color.id_from_enumbler!(:Bob) }.to raise_error(Enumbler::Error, /Unable to find/)
expect { Color.id_from_enumbler(:Bob) }.not_to raise_error
end
it 'returns an id from a instance' do
expect(Color.ids_from_enumbler(Color.black)).to contain_exactly(1)
end
it 'raises an error when the symbol cannot be found' do
expect { Color.ids_from_enumbler(Color.new(id: 100, label: 'ok')) }
expect { Color.ids_from_enumbler!(Color.new(id: 100, label: 'ok')) }
.to raise_error(Enumbler::Error, /Unable to find/)
end
end
Expand Down

0 comments on commit 6fbb3d0

Please sign in to comment.