Skip to content

Instantly share code, notes, and snippets.

@bryanp
Last active January 24, 2018 12:54
Show Gist options
  • Save bryanp/938d2982c8eb2dafcb5189afbad9b558 to your computer and use it in GitHub Desktop.
Save bryanp/938d2982c8eb2dafcb5189afbad9b558 to your computer and use it in GitHub Desktop.
Detect Optional Keyword Arguments in Ruby
NOT_PASSED = Object.new
def foo(bar: NOT_PASSED)
if bar.equal?(NOT_PASSED)
...
end
end
# For context, this is a validator. Here's what I was trying to make work initially:
module Validations
class Acceptance
NOT_PASSED = Object.new
def self.valid?(value, accepts: NOT_PASSED)
if accepts.equal?(NOT_PASSED)
OtherValidator.valid?(value)
else
value == accepts
end
end
end
end
Acceptance.valid?(true)
# => true
Acceptance.valid?(nil)
# => false
Acceptance.valid?("no", accepts: "yes")
# => false
# BUT, you could do this instead (sort of resembling what @piisalie was suggesting on twitter):
module Validations
class Acceptance
def self.valid?(value, accepts: OtherValidator)
if accepts.respond_to?(:valid?)
accepts.valid?(value)
else
value == accepts
end
end
end
end
@piisalie
Copy link

piisalie commented Jan 24, 2018

I like your solution I think because I'm biased against nil. That said, I would probably use a symbol :not_passed instead of a constant.

Another solution I have often seen people use is like:

 def not_passed(some_option: nil)
   if some_option  
     puts "passed #{some_option}"    
   else    
     puts "nothing passed"    
   end  
 end  

not_passed    # => nothing passed

not_passed(some_option: "hello")    # => passed hello

Edit: I think I misunderstood your question on Twitter. Rethinking my answer... ;-)

@piisalie
Copy link

piisalie commented Jan 24, 2018

Alright, your update is closer to what I was thinking but I would probably go one step further using a generic validator like:

class GenericValidator
  def self.acceptable(value)
    new(value)
  end

  def self.valid?(value)
    true
  end

  def initialize(value)
    @value = value
  end

  def valid?(value)
    @value == value
  end
end

module Validations
  class Acceptance
    def self.valid?(value, accepts: GenericValidator)
      accepts.valid?(value) && !!value
    end
  end
end

Validations::Acceptance.valid?("no", accepts: GenericValidator.acceptable("yes"))
# => false
Validations::Acceptance.valid?("yes", accepts: GenericValidator.acceptable("yes"))
# => true
Validations::Acceptance.valid?(nil)
# => false
Validations::Acceptance.valid?(false)
# => false

The idea being, we should always pass a Validator as the accepts option, and that validator knows how to tell if it is actually valid through the generic valid? interface. In this way the Acceptance class doesn't have to have any knowledge about what validators exist and what to do with them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment