Project

General

Profile

Feature #17330

Updated by nobu (Nobuyoshi Nakada) about 1 year ago

(As always "with core" method proposals, I don't expect quick success, but hope for a fruitful discussion) 

 ### Reasons: **Reasons:** 

 Ruby always tried to be very chainability-friendly. Recently, with introduction of `.then` and `=>`, even more so. But one pattern that frequently emerges and doesn't have good idiomatic expression: calculate something, and if it is not a "good" value, return `nil` (or provide default value with `||`). There are currently two partial solutions: 

 1. `nonzero?` in Ruby core (frequently mocked for "inadequate" behavior, as it is looking like predicate method, but instead of `true`/`false` returns an original value or `nil`) 
 2. ActiveSupport `Object#presence`, which also returns an original value or `nil` if it is not "present" (e.g. `nil` or `empty?` in AS-speak) 

 Both of them prove themselves quite useful in some domains, but they are targeting only those particular domains, look unlike each other, and can be confusing. 

 ### Proposal: **Proposal:** 

 Method `Object#non` (or `Kernel#non`), which receives a block, calls it with receiver and returns `nil` (if block matched) or receiver otherwise. 

 Prototype implementation: 

 ```ruby 
 class Object 
   def non 
     self unless yield(self) 
   end 
 end 
 ``` 

 Usage examples: 

 ```  
 # 1. With number: 

     ```ruby 
     

 limit = calculate.some.limit 
     
 limit.zero? ? DEFAULT_LIMIT : limit 
     
 # or, with nonzero? 
     
 calculate.some.limit.nonzero? || DEFAULT_LIMIT 
     
 # with non: 
     
 calculate.some.limit.non(&:zero?) || DEFAULT_LIMIT 
     
 # ^ Note here, how, unlike `nonzero?`, we see predicate-y ?, but it is INSIDE the `non()` and less confusing 
     ``` 

 # 2. With string: 

     ```ruby 
     

 name = params[:name] if params[:name] && !params[:name].empty? 
     
 # or, with ActiveSupport: 
     
 name = params[:name].presence 
     
 # with non: 
     
 name = params[:name]&.non(&:empty?) 
     ``` 

 # 3. More complicated example 

     ```ruby 
     

 action = payload.dig('action', 'type') 
     
 return if PROHIBITED_ACTIONS.include?(action) 
     
 send("do_#{action}") 
     
 # with non & then: 
     
 payload.dig('action', 'type') 
       
   .non { |action| PROHIBITED_ACTIONS.include?(action) } 
       
   &.then { |action| send("do_#{action}") } 
     

 ``` 

 ### Possible **Possible extensions of the idea idea** 

 It is quite tempting to define the symmetric method named -- as we already have `Object#then` -- `Object#when`: 
 ```ruby 
 some.long.calculation.when { |val| val < 10 } # returns nil if value >= 10 
 # or even... with support for === 
 some.long.calculation.when(..10)&.then { continue to do something } 
 ``` 
 ...but I am afraid I've overstayed my welcome :) 

Back