├── .document ├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── its-it.gemspec ├── lib ├── its-it.rb └── its-it │ ├── it.rb │ ├── kernel.rb │ └── version.rb └── spec ├── it_spec.rb ├── main_spec.rb ├── rspec_compatibility_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # don't commit Gemfile.lock for gems 18 | Gemfile.lock 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format d 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.7.1 4 | 5 | before_install: gem install bundler -v ">= 1.10.0" --conservative 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Original code Copyright (c) 2007 Jay Phillips 2 | Updates Copyright (c) 2011 Ronen Barzel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # its-it 2 | 3 | 4 | ## Overview 5 | 6 | This gem defines kernel methods `its` and `it` that queue and defer method 7 | calls. This is handy for list comprehension and case statements. 8 | 9 | [![Gem Version](https://badge.fury.io/rb/its-it.png)](http://badge.fury.io/rb/its-it) 10 | [![Build Status](https://secure.travis-ci.org/ronen/its-it.png)](http://travis-ci.org/ronen/its-it) 11 | 12 | ## List Comprehension 13 | 14 | `its` and `it` extend the Symbol#to_proc idiom to support chaining multiple 15 | methods. 16 | 17 | When performing a list comprehension ruby, you can use a block argument: 18 | 19 | 20 | ```ruby 21 | users.map{ |user| user.contact } 22 | ``` 23 | 24 | Or, to avoid needing the block and and extra parameter, you can use the `Symbol#to_proc` shortcut: 25 | 26 | ```ruby 27 | users.map &:contact 28 | ``` 29 | 30 | But if you want to chain several methods, such as: 31 | 32 | ```ruby 33 | users.map{ |user| user.contact.last_name.capitalize } 34 | ``` 35 | 36 | The `Symbol#to_proc` shortcut doesn't help much. At best, if you're willing to accept intermediate arrays, you can do: 37 | 38 | ```ruby 39 | users.map(&:contact).map(&:last_name).map(&:capitalize) 40 | ``` 41 | 42 | To improve the situation, this gem provides a Kernel method `its`, which lets you get the same shortcut advantages as `Symbol#to_proc` but supports chaining: 43 | 44 | ```ruby 45 | users.map &its.contact.last_name.capitalize 46 | ``` 47 | 48 | Also, `its` supports arguments and blocks, allowing you to do things like 49 | 50 | ```ruby 51 | users.map &its.contact.last_name[0,3].capitalize 52 | users.select &its.contact.last_name.length > 10 53 | users.select(&its.addresses.any? { |address| airline.flies_to address.city }) 54 | ``` 55 | 56 | As a syntactic sugar, `it` is an alias for `its`, to use with methods that describe actions rather than posessives. For example: 57 | 58 | ```ruby 59 | items.map &it.to_s.capitalize 60 | ``` 61 | 62 | ### Hash comprehensions 63 | 64 | When used with hash comprehensions, the `|key, val|` pair of arguments are presented to `its` as a tuple that can be accessed array-like via `[0]` or `[1]` and/or struct-like via `#key` and `#value` methods. E.g. 65 | 66 | ```ruby 67 | {dogs: 1, cats: 2, goats:3}.select &its.key =~ /^c/ # => {cats: 2} 68 | {dogs: 1, cats: 2, goats:3}.select &its.value.even? # => {cats: 2} 69 | {dogs: 1, cats: 2, goats:3}.select &its[1].even? # => {cats: 2} 70 | ``` 71 | 72 | ## Case statements 73 | 74 | `its` and `it` similarly extend Ruby's `case` mechanism to support testing 75 | arbitrary methods, minimizing the need to create temporary variables. That is, instead of: 76 | 77 | ```ruby 78 | maxlen = arrays.map(&size).max 79 | case 80 | when maxlen > 10000 then "too big" 81 | when maxlen < 10 then "too small" 82 | else "okay" 83 | end 84 | ``` 85 | 86 | You can use `it`: 87 | 88 | ```ruby 89 | case arrays.map(&size).max 90 | when it > 1000 then "too big" 91 | when it < 10 then "too small" 92 | else "okay" 93 | end 94 | ``` 95 | 96 | Of course method chanining can be used here too: 97 | 98 | ```ruby 99 | case users.first 100 | when its.name == "Gimme Cookie" then ... 101 | when its.name.length > 10 then ... 102 | else ... 103 | end 104 | ``` 105 | 106 | ## Under the hood 107 | 108 | The `it` method creates an instance of the `ItsIt::It` class, which uses `method_missing` to capture and queue up all 109 | methods and their arguments except for `:to_proc` and `:===` (and 110 | also excepting `:respond_to? :to_proc` and `:respond_to? :===`). 111 | 112 | `:to_proc` returns a proc that will evaluate the method queue on a given 113 | argument. `:===` takes an argument and evaluates that proc, returning the 114 | result. 115 | 116 | ## Installation 117 | 118 | Install as usual from http://rubygems.org via 119 | 120 | ```bash 121 | $ gem install "its-it" 122 | ``` 123 | 124 | or in a Gemfile 125 | 126 | ```ruby 127 | gem "its-it" 128 | ``` 129 | 130 | ## Compatibility 131 | 132 | Tested on MRI ruby 2.7.1 133 | 134 | (MRI ruby 1.9.3, 2.1.9, 2.2.5, and 2.3.1 were supported up through version 1.3.0) 135 | 136 | (MRI ruby 1.8.7 was supported up through version 1.1.1) 137 | 138 | ## History 139 | 140 | Release Notes 141 | 142 | * 2.0.0 Switch to Ruby 2.7 keyword syntax. No other breaking changes. 143 | * 1.3.0 Add `#key` and `#value` for Hash comprehensions; plus minor internal cleanup. 144 | * 1.2.1 Don't leak all of ItsIt into main, just ItsIt::Kernel. Thanks to [klg](https://github.com/kjg) 145 | * 1.2.0 Add support for Hash comprehensions; drop support for ruby 1.8.7 146 | * 1.1.1 Remove dependency on BlankSlate 147 | 148 | This gem is orignally based on Jay Philips' 149 | [methodphitamine](https://github.com/jicksta/methodphitamine) gem. It has been made available on [rubygems.org](http://rubygems.org), and updated over the years. 150 | 151 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require "bundler/gem_tasks" 3 | 4 | require 'rspec/core/rake_task' 5 | RSpec::Core::RakeTask.new(:spec) do |spec| 6 | spec.rspec_opts = '-Ispec' 7 | end 8 | 9 | task :default => :spec 10 | 11 | require 'rdoc/task' 12 | Rake::RDocTask.new do |rdoc| 13 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 14 | 15 | rdoc.main = 'README.rdoc' 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = "enumerable_hashify #{version}" 18 | rdoc.rdoc_files.include('README*') 19 | rdoc.rdoc_files.include('lib/**/*.rb') 20 | end 21 | -------------------------------------------------------------------------------- /its-it.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require 'its-it/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'its-it' 6 | s.version = ItsIt::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.date = "2011-04-26" 9 | s.authors = ['Ronen Barzel'] 10 | s.email = 'ronen@barzel.org' 11 | s.homepage = 'http://github.com/ronen/its-it' 12 | s.summary = %q{Defines its() and it() method-chain proxies.} 13 | s.description = %q{ 14 | This gem defines the Kernel method "it" that queue and defer method calls. 15 | This extends the Symbol#to_proc idiom to support chaining multiple methods. 16 | For example, items.collect(&it.to_s.capitalize). This also allows 17 | conditionals in case statements, such as: case ... when it > 3 then [etc.]. 18 | The method is also aliased as "its", for methods that describe possessives 19 | rather than actions, such as items.collect(&its.name.capitalize) 20 | 21 | [This gem is an extension of Jay Philips' "methodphitamine" gem, updated 22 | for ruby 1.9 and gemspec compatibility and adding the case statement functionality.] 23 | } 24 | s.extra_rdoc_files = [ 25 | "LICENSE.txt", 26 | ] 27 | 28 | s.required_ruby_version = '~> 2.7' 29 | s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7') 30 | s.rubygems_version = '1.3.7' 31 | s.specification_version = 3 32 | 33 | s.files = `git ls-files`.split("\n") 34 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 35 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 36 | s.require_paths = ['lib'] 37 | 38 | s.add_development_dependency 'rake' 39 | s.add_development_dependency 'rdoc' 40 | s.add_development_dependency 'rspec', "~> 3.0" 41 | s.add_development_dependency 'bundler' 42 | s.add_development_dependency 'simplecov' 43 | s.add_development_dependency 'simplecov-gem-profile' 44 | 45 | end 46 | 47 | -------------------------------------------------------------------------------- /lib/its-it.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path(File.dirname(__FILE__)) 2 | 3 | require 'its-it/it' 4 | require 'its-it/kernel' 5 | require 'its-it/version' 6 | 7 | include ItsIt::Kernel 8 | -------------------------------------------------------------------------------- /lib/its-it/it.rb: -------------------------------------------------------------------------------- 1 | # This module contains an It class which queues any methods called on it 2 | # and can be converted into a Proc. The Proc it generates executes the queued 3 | # methods in the order received on the argument passed to the block, allowing 4 | # something like the following: 5 | # 6 | # (1..10).select &it % 2 == 0 7 | # 8 | module ItsIt 9 | 10 | # The class instantiated by the it and its kernel methods. 11 | class It < BasicObject 12 | 13 | instance_methods.map(&:to_s).each do |method| 14 | undef_method method unless method.start_with? "__" 15 | end 16 | 17 | def initialize #:nodoc: 18 | @queue = [] 19 | end 20 | 21 | def method_missing(method, *args, **kw, &block) 22 | @queue << [method, args, kw, block] unless method == :respond_to? and [:to_proc, :===].include?(args.first.to_sym) 23 | self 24 | end 25 | 26 | module KeyValAccessors 27 | def key 28 | self[0] 29 | end 30 | def value 31 | self[1] 32 | end 33 | end 34 | 35 | def to_proc 36 | Kernel.send :proc do |*obj| 37 | case obj.size 38 | when 1 then obj = obj.shift # array comprehension, gets one arg 39 | when 2 then obj.extend(KeyValAccessors) # hash comprehension, gets two args 40 | end 41 | @queue.inject(obj) { |chain,(method, args, kw, block)| chain.send(method, *args, **kw, &block) } 42 | end 43 | end 44 | 45 | def ===(obj) 46 | self.to_proc.call(obj) 47 | end 48 | 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/its-it/kernel.rb: -------------------------------------------------------------------------------- 1 | module ItsIt 2 | module Kernel 3 | protected 4 | def it() It.new end 5 | alias its it 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/its-it/version.rb: -------------------------------------------------------------------------------- 1 | module ItsIt 2 | VERSION = "2.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /spec/it_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/spec_helper" 2 | 3 | describe ItsIt::It do 4 | 5 | TestString = "This is a test" 6 | 7 | it "should start with identity via to_proc" do 8 | expect(it.to_proc.call(TestString)).to eq(TestString) 9 | end 10 | 11 | it "should start with identity via ===" do 12 | expect(it === TestString).to eq(TestString) 13 | end 14 | 15 | it "should work with a simple method via to_proc" do 16 | expect((it.length).to_proc.call(TestString)).to eq(TestString.length) 17 | end 18 | 19 | it "should work with a simple method using ===" do 20 | expect((it.length) === TestString).to eq(TestString.length) 21 | end 22 | 23 | it "should work with arguments" do 24 | expect((it.sub(/test/, 'kumquat')).call(TestString)).to eq('This is a kumquat') 25 | end 26 | 27 | it "should work with keyword arguments" do 28 | expect((it.lines(chomp:true)).call(TestString + "\n")).to eq([TestString]) 29 | end 30 | 31 | it "should work with a block" do 32 | expect((it.sub(/test/) {"balloon"}).to_proc.call(TestString)).to eq('This is a balloon') 33 | end 34 | 35 | it "should chain methods" do 36 | expect((it.reverse.swapcase.succ).to_proc.call(TestString)).to eq("TSET A SI SIHu") 37 | end 38 | 39 | it "should respond to to_proc()" do 40 | expect(it).to respond_to(:to_proc) 41 | end 42 | 43 | it "should respond to ===" do 44 | expect(it).to respond_to(:===) 45 | end 46 | 47 | it "should work with numbers" do 48 | expect((it < 1) === 0).to be_truthy 49 | expect((it < 1) === 1).to be_falsey 50 | expect((it < 1) === 2).to be_falsey 51 | end 52 | 53 | context "hash comprehension" do 54 | it "presents `key` accessor" do 55 | expect({a: 1, b:2}.select(&(it.key == :b))).to eq({b:2}) 56 | end 57 | 58 | it "presents `value` accessor" do 59 | expect({a: 1, b:2}.select(&(it.value == 2))).to eq({b:2}) 60 | end 61 | 62 | it "presents as an array" do 63 | expect({a: 1, b:2}.select(&(it[1] == 2))).to eq({b:2}) 64 | end 65 | 66 | end 67 | 68 | 69 | it "should work in a case statement" do 70 | [0,1,2].each do |i| 71 | case i 72 | when it < 1 then expect(i).to eq(0) 73 | when it == 1 then expect(i).to eq(1) 74 | else expect(i).to eq(2) 75 | end 76 | end 77 | end 78 | 79 | it "should not queue the method respond_to? when given :to_proc as an arg" do 80 | expect(it.respond_to? :to_proc).to be_true 81 | end 82 | 83 | it "should not queue the method respond_to? when given :=== as an arg" do 84 | expect(it.respond_to? :===).to be_true 85 | end 86 | 87 | it "should queue the method respond_to? when given generic arg" do 88 | expect((it.respond_to? :size).to_proc.call(TestString)).to be_truthy 89 | end 90 | 91 | end 92 | -------------------------------------------------------------------------------- /spec/main_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/spec_helper" 2 | 3 | describe Object do 4 | it "doesn't get ItsIt's version" do 5 | expect(defined?(::VERSION)).to be_nil 6 | end 7 | 8 | it "does't get the It class" do 9 | expect(defined?(::It)).to be_nil 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/rspec_compatibility_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/spec_helper" 2 | 3 | describe "RSpec compatibility" do 4 | 5 | # Surprisingly, RSpec's it() method isn't even defined within the context 6 | # of each expectation block. Man, that's some crazy voodoo. 7 | 8 | it "should make available the it and its methods" do 9 | expect(method(:it)).to eq method(:its) # Ensure it's not RSpec's it() method 10 | expect { 11 | it.should be_kind_of(ItsIt::It) 12 | }.to_not raise_error 13 | end 14 | 15 | it "should work with RSpec's old :should syntax" do 16 | [1,2,3].each &it.should(be_kind_of(Integer)) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'simplecov-gem-profile' 3 | SimpleCov.start 'gem' 4 | 5 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 6 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 7 | require 'rspec' 8 | require 'its-it' 9 | 10 | # Requires supporting files with custom matchers and macros, etc, 11 | # in ./support/ and its subdirectories. 12 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 13 | 14 | RSpec.configure do |config| 15 | config.expect_with(:rspec) do |c| 16 | c.syntax = [:expect, :should] 17 | end 18 | end 19 | --------------------------------------------------------------------------------