├── .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 | [](http://badge.fury.io/rb/its-it)
10 | [](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 |
--------------------------------------------------------------------------------