├── .gitignore ├── Gemfile ├── lib ├── null_plus │ └── version.rb └── null_plus.rb ├── CHANGELOG.md ├── spec └── null_plus_spec.rb ├── .github └── workflows │ └── test.yml ├── null_plus.gemspec ├── MIT-LICENSE.txt ├── Rakefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'minitest' 6 | gem 'rake' 7 | -------------------------------------------------------------------------------- /lib/null_plus/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module NullPlus 4 | VERSION = "1.0.1" 5 | end 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### 1.0.1 4 | 5 | * Relax Ruby version requirement to allow Ruby 3.0 6 | 7 | ### 1.0.0 8 | 9 | * Initial release 10 | 11 | -------------------------------------------------------------------------------- /lib/null_plus.rb: -------------------------------------------------------------------------------- 1 | require_relative "null_plus/version" 2 | 3 | require "null_question" 4 | 5 | class Object 6 | def +@ 7 | null? ? nil : self 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/null_plus_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../lib/null_plus" 2 | require "minitest/autorun" 3 | 4 | describe NullPlus do 5 | describe "Object#+@" do 6 | it "returns itself for non-null objects" do 7 | object = Object.new 8 | assert_equal object, +object 9 | end 10 | 11 | it "returns nil for nil" do 12 | assert_equal nil, +nil 13 | end 14 | 15 | it "returns nil for custom null objects" do 16 | null_object = Object.new 17 | def null_object.null?() true end 18 | assert_equal nil, +null_object 19 | end 20 | end 21 | end 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Ruby ${{ matrix.ruby }} (${{ matrix.os }}) 8 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 9 | strategy: 10 | matrix: 11 | ruby: 12 | - 3.0 13 | - 2.7 14 | - 2.6 15 | - 2.5 16 | - jruby-9.2.13.0 17 | - truffleruby-20.3.0 18 | os: 19 | - ubuntu-latest 20 | runs-on: ${{matrix.os}} 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Ruby 24 | uses: ruby/setup-ruby@v1 25 | with: 26 | ruby-version: ${{matrix.ruby}} 27 | bundler-cache: true 28 | - name: Run tests 29 | run: bundle exec rake 30 | -------------------------------------------------------------------------------- /null_plus.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require File.dirname(__FILE__) + "/lib/null_plus/version" 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = "null_plus" 7 | gem.version = NullPlus::VERSION 8 | gem.summary = "+nil" 9 | gem.description = "This gem redefines Ruby's unary + operator to turn null objects into nil. By default, the unary + operator is not used by Ruby, so overloading it is not so dangerous as it might have sounded to you when you read it. Every object that returns true for null? is considered a null object." 10 | gem.authors = ["Jan Lelis"] 11 | gem.email = ["hi@ruby.consulting"] 12 | gem.homepage = "https://github.com/janlelis/null_plus" 13 | gem.license = "MIT" 14 | 15 | gem.files = Dir["{**/}{.*,*}"].select{ |path| File.file?(path) && path !~ /^pkg/ } 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.required_ruby_version = ">= 2.0" 21 | gem.add_dependency "null_question", "~> 1.0" 22 | end 23 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Jan Lelis, https://janlelis.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # # # 2 | # Get gemspec info 3 | 4 | gemspec_file = Dir['*.gemspec'].first 5 | gemspec = eval File.read(gemspec_file), binding, gemspec_file 6 | info = "#{gemspec.name} | #{gemspec.version} | " \ 7 | "#{gemspec.runtime_dependencies.size} dependencies | " \ 8 | "#{gemspec.files.size} files" 9 | 10 | 11 | # # # 12 | # Gem build and install task 13 | 14 | desc info 15 | task :gem do 16 | puts info + "\n\n" 17 | print " "; sh "gem build #{gemspec_file}" 18 | FileUtils.mkdir_p 'pkg' 19 | FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg' 20 | puts; sh %{gem install --no-document pkg/#{gemspec.name}-#{gemspec.version}.gem} 21 | end 22 | 23 | 24 | # # # 25 | # Start an IRB session with the gem loaded 26 | 27 | desc "#{gemspec.name} | IRB" 28 | task :irb do 29 | sh "irb -I ./lib -r #{gemspec.name.gsub '-','/'}" 30 | end 31 | 32 | 33 | # # # 34 | # Run specs 35 | 36 | desc "#{gemspec.name} | Spec" 37 | task :spec do 38 | if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ 39 | sh "for %f in (spec/\*.rb) do ruby spec/%f" 40 | else 41 | sh "for file in spec/*.rb; do ruby $file; done" 42 | end 43 | end 44 | task default: :spec 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # +`nil` [![[version]](https://badge.fury.io/rb/null_plus.svg)](https://badge.fury.io/rb/null_plus) [![[ci]](https://github.com/janlelis/null_plus/workflows/Test/badge.svg)](https://github.com/janlelis/null_plus/actions?query=workflow%3ATest) 2 | 3 | This gem redefines Ruby's unary `+` operator to turn null objects into nil. By default, the unary `+` operator is rarely¹ used by Ruby, so overloading it is not so dangerous as it might have sounded to you when you read it. 4 | 5 | Every object that returns [true for `null?`](https://github.com/janlelis/null_question) is considered a null object. 6 | 7 | ¹ (Ruby 2.3 introduced `+` for String: It will return an unfrozen version of the string) 8 | 9 | ## Setup 10 | 11 | Add to your **Gemfile**: 12 | 13 | ```ruby 14 | gem "null_plus" 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```ruby 20 | class NullObject 21 | def null? 22 | true 23 | end 24 | end 25 | 26 | null = NullObject.new 27 | 28 | +nil #=> nil 29 | +null #=> nil 30 | +"some object" #=> "some object" 31 | 32 | # Useful for settings defaults or checking conditions: 33 | if +null 34 | fail # will not run 35 | end 36 | 37 | value = +null || 42 #=> 42 38 | 39 | ``` 40 | 41 | ### Hint 42 | 43 | Pay attention to proper operator precedence when chaining method class: 44 | 45 | ```ruby 46 | class NullObject 47 | def null? 48 | true 49 | end 50 | end 51 | 52 | null = NullObject.new 53 | 54 | +null.class #=> NullObject 55 | (+null).class #=> NilClass 56 | ``` 57 | 58 | ## J-_-L 59 | 60 | Copyright (C) 2015 Jan Lelis . Released under the MIT license. 61 | --------------------------------------------------------------------------------