quality_extensions 0.1.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/quality_extensions/all.rb +1 -1
- data/lib/quality_extensions/array/average.rb +2 -1
- data/lib/quality_extensions/array/classify.rb +3 -1
- data/lib/quality_extensions/array/expand_ranges.rb +3 -1
- data/lib/quality_extensions/array/group_by.rb +3 -1
- data/lib/quality_extensions/array/sequence.rb +2 -0
- data/lib/quality_extensions/array/shell_escape.rb +11 -6
- data/lib/quality_extensions/array/to_a_recursive.rb +1 -0
- data/lib/quality_extensions/array/to_query_string.rb +2 -0
- data/lib/quality_extensions/colored/toggleability.rb +2 -2
- data/lib/quality_extensions/console/command.rb +4 -3
- data/lib/quality_extensions/date/deprecated.rb +2 -0
- data/lib/quality_extensions/date/iso8601.rb +4 -1
- data/lib/quality_extensions/date/month_ranges.rb +2 -0
- data/lib/quality_extensions/dir/each_child.rb +1 -0
- data/lib/quality_extensions/exception/inspect_with_backtrace.rb +4 -2
- data/lib/quality_extensions/file/exact_match_regexp.rb +1 -0
- data/lib/quality_extensions/hash/assert_has_only_keys.rb +48 -0
- data/lib/quality_extensions/hash/to_date.rb +2 -0
- data/lib/quality_extensions/hash/to_query_string.rb +8 -2
- data/lib/quality_extensions/kernel/backtrace.rb +4 -2
- data/lib/quality_extensions/kernel/capture_output.rb +3 -2
- data/lib/quality_extensions/kernel/die.rb +4 -1
- data/lib/quality_extensions/kernel/example_printer.rb +3 -3
- data/lib/quality_extensions/kernel/remove_const.rb +3 -2
- data/lib/quality_extensions/kernel/remove_module.rb +3 -2
- data/lib/quality_extensions/kernel/require_all.rb +5 -3
- data/lib/quality_extensions/kernel/require_once.rb +1 -0
- data/lib/quality_extensions/kernel/simulate_input.rb +1 -0
- data/lib/quality_extensions/kernel/trap_chain.rb +3 -0
- data/lib/quality_extensions/module/alias_method_chain.rb +3 -15
- data/lib/quality_extensions/module/ancestry_of_instance_method.rb +19 -0
- data/lib/quality_extensions/module/basename.rb +5 -8
- data/lib/quality_extensions/module/bool_attr_accessor.rb +3 -1
- data/lib/quality_extensions/module/by_name.rb +76 -0
- data/lib/quality_extensions/module/class_methods.rb +2 -0
- data/lib/quality_extensions/module/create.rb +6 -5
- data/lib/quality_extensions/module/guard_method.rb +4 -1
- data/lib/quality_extensions/module/join.rb +2 -2
- data/lib/quality_extensions/module/malias_method_chain.rb +3 -2
- data/lib/quality_extensions/module/namespace.rb +1 -1
- data/lib/quality_extensions/module/parents.rb +2 -2
- data/lib/quality_extensions/module/remove_const.rb +3 -2
- data/lib/quality_extensions/module/split.rb +2 -1
- data/lib/quality_extensions/month.rb +2 -0
- data/lib/quality_extensions/mutex/if_available.rb +2 -0
- data/lib/quality_extensions/nil_method_missing.rb +227 -0
- data/lib/quality_extensions/object/ancestry_of_method.rb +2 -0
- data/lib/quality_extensions/object/if_else.rb +1 -1
- data/lib/quality_extensions/object/ignore_access.rb +3 -2
- data/lib/quality_extensions/object/mcall.rb +3 -1
- data/lib/quality_extensions/object/methods.rb +3 -2
- data/lib/quality_extensions/object/send_if.rb +2 -1
- data/lib/quality_extensions/object/singleton_send.rb +2 -0
- data/lib/quality_extensions/safe_nil.rb +3 -5
- data/lib/quality_extensions/string/digits_only.rb +2 -0
- data/lib/quality_extensions/string/each_char_with_index.rb +1 -0
- data/lib/quality_extensions/string/md5.rb +2 -0
- data/lib/quality_extensions/string/shell_escape.rb +4 -2
- data/lib/quality_extensions/string/to_underscored_label.rb +2 -0
- data/lib/quality_extensions/string/with_knowledge_of_color.rb +3 -0
- data/lib/quality_extensions/symbol/constantize.rb +2 -1
- data/lib/quality_extensions/symbol/match.rb +2 -1
- data/lib/quality_extensions/test/assert_anything.rb +1 -0
- data/lib/quality_extensions/test/difference_highlighting.rb +2 -2
- data/lib/quality_extensions/time/deprecated.rb +2 -0
- data/test/all.rb +8 -2
- metadata +86 -83
@@ -6,11 +6,12 @@
|
|
6
6
|
# Developer notes::
|
7
7
|
#++
|
8
8
|
|
9
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
9
10
|
require 'rubygems'
|
10
11
|
require 'quality_extensions/object/ignore_access'
|
11
12
|
require 'quality_extensions/module/split'
|
12
|
-
require '
|
13
|
-
require 'facets/
|
13
|
+
require 'quality_extensions/module/by_name'
|
14
|
+
require 'facets/module/modspace'
|
14
15
|
|
15
16
|
class Module
|
16
17
|
alias_method :remove_const_before_was_added_to_Kernel, :remove_const
|
@@ -7,11 +7,12 @@
|
|
7
7
|
# * Deprecated by quality_extensions/module/remove.rb
|
8
8
|
#++
|
9
9
|
|
10
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
10
11
|
require 'rubygems'
|
11
12
|
require 'quality_extensions/object/ignore_access'
|
12
13
|
require 'quality_extensions/module/split'
|
13
|
-
require '
|
14
|
-
require 'facets/
|
14
|
+
require 'quality_extensions/module/by_name'
|
15
|
+
require 'facets/module/modspace'
|
15
16
|
|
16
17
|
module Kernel
|
17
18
|
# This is similar to +remove_const+, but it _only_ works for modules/classes.
|
@@ -5,10 +5,11 @@
|
|
5
5
|
# Submit to Facets?:: Yes
|
6
6
|
#++
|
7
7
|
|
8
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
8
9
|
require 'rubygems'
|
9
|
-
require 'facets/
|
10
|
+
require 'facets/filelist'
|
10
11
|
|
11
|
-
require 'facets/
|
12
|
+
require 'facets/kernel/load'
|
12
13
|
require_local '../file/exact_match_regexp'
|
13
14
|
|
14
15
|
module Kernel
|
@@ -71,6 +72,7 @@ end
|
|
71
72
|
# |_|\___||___/\__|
|
72
73
|
#
|
73
74
|
=begin test
|
75
|
+
require 'test/unit'
|
74
76
|
require 'tmpdir'
|
75
77
|
require 'fileutils'
|
76
78
|
require 'English'
|
@@ -117,7 +119,7 @@ class TheTest < Test::Unit::TestCase
|
|
117
119
|
create_ruby_file @deep_dir + "/flop.rb"
|
118
120
|
|
119
121
|
require_all File.dirname(@base_dir)
|
120
|
-
assert_equal [
|
122
|
+
assert_equal [@deep_dir + "/flop.rb", 'flip.rb'], $loaded
|
121
123
|
end
|
122
124
|
|
123
125
|
def test_exclude_pattern
|
@@ -5,6 +5,8 @@
|
|
5
5
|
# Submit to Facets?:: Yes!
|
6
6
|
#++
|
7
7
|
|
8
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
9
|
+
|
8
10
|
module Kernel
|
9
11
|
# Calling <tt>Kernel#trap()</tt> by itself will _replace_ any previously registered handler code.
|
10
12
|
# <tt>Kernel#trap_chain()</tt>, on the other hand, will _add_ the block you supply to the existing "list" of registered handler blocks.
|
@@ -26,6 +28,7 @@ end
|
|
26
28
|
# |_|\___||___/\__|
|
27
29
|
#
|
28
30
|
=begin test
|
31
|
+
require 'test/unit'
|
29
32
|
require 'rubygems'
|
30
33
|
require 'quality_extensions/kernel/capture_output'
|
31
34
|
require 'fileutils'
|
@@ -7,25 +7,13 @@
|
|
7
7
|
# Changes::
|
8
8
|
#++
|
9
9
|
|
10
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
10
11
|
require 'rubygems'
|
11
12
|
gem 'facets'
|
12
|
-
|
13
|
-
require 'facets/
|
13
|
+
require 'facets/module/alias'
|
14
|
+
require 'facets/kernel/singleton_class'
|
14
15
|
|
15
16
|
|
16
|
-
# /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/module/aliasing.rb
|
17
|
-
class Module
|
18
|
-
def alias_method_chain(target, feature)
|
19
|
-
# Strip out punctuation on predicates or bang methods since
|
20
|
-
# e.g. target?_without_feature is not a valid method name.
|
21
|
-
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
22
|
-
yield(aliased_target, punctuation) if block_given?
|
23
|
-
alias_method "#{aliased_target}_without_#{feature}#{punctuation}", target
|
24
|
-
alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
# end /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/module/aliasing.rb
|
28
|
-
|
29
17
|
class Module
|
30
18
|
|
31
19
|
def alias_method_chain_with_prevent_repeat_aliasing(target, feature, &block)
|
@@ -41,3 +41,22 @@ class Module
|
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
# _____ _
|
48
|
+
# |_ _|__ ___| |_
|
49
|
+
# | |/ _ \/ __| __|
|
50
|
+
# | | __/\__ \ |_
|
51
|
+
# |_|\___||___/\__|
|
52
|
+
#
|
53
|
+
=begin test
|
54
|
+
require 'test/unit'
|
55
|
+
|
56
|
+
class TheTest < Test::Unit::TestCase
|
57
|
+
def test_1
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
=end
|
62
|
+
|
@@ -7,17 +7,16 @@
|
|
7
7
|
# Changes::
|
8
8
|
#++
|
9
9
|
|
10
|
-
|
10
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
11
11
|
require 'rubygems'
|
12
|
-
require 'facets/
|
13
|
-
require 'facets/core/string/basename'
|
12
|
+
require 'facets/module/basename'
|
14
13
|
require 'quality_extensions/module/namespace'
|
15
14
|
|
16
15
|
class Module
|
17
16
|
# Gets the basename of a "module path" (the name of the module without any of the namespace modules that it is contained in),
|
18
17
|
# in the same sense that <tt>File.basename</tt> returns the basename of a _filesystem_ path.
|
19
18
|
#
|
20
|
-
# This is identical to Facets' String#basename ('facets/
|
19
|
+
# This is identical to Facets' String#basename ('facets/string/basename') except that:
|
21
20
|
# * it is a class method instead of an instance method of String,
|
22
21
|
# * it accepts modules, strings, and symbols.
|
23
22
|
#
|
@@ -30,10 +29,8 @@ class Module
|
|
30
29
|
case module_or_name
|
31
30
|
when Module
|
32
31
|
module_or_name.basename
|
33
|
-
when Symbol
|
34
|
-
module_or_name.to_s.
|
35
|
-
when String
|
36
|
-
module_or_name.basename
|
32
|
+
when Symbol,String
|
33
|
+
module_or_name.to_s.gsub(/^.*::/, '')
|
37
34
|
else
|
38
35
|
raise ArgumentError
|
39
36
|
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# License:: Ruby License
|
5
5
|
# Submit to Facets?:: Yes
|
6
6
|
# Developer notes::
|
7
|
-
# * Based on /usr/lib/ruby/gems/1.8/gems/facets-1.8.54/lib/facets/
|
7
|
+
# * Based on /usr/lib/ruby/gems/1.8/gems/facets-1.8.54/lib/facets/module/attr_tester.rb
|
8
8
|
# * Hey Thomas, don't you think Module#attr_tester should only create the read-only a? method and have another method that creates the writer (like there how we have attr_reader, _writer, and _accessor?) ? "tester" does not imply "setter" in my mind...
|
9
9
|
# * I'm going to rename this one to bool_attr_accessor, which calls both bool_attr_reader and bool_attr_writer
|
10
10
|
# * Then you also have the option to use bool_attr_reader and bool_attr_writer separately if you so desire.
|
@@ -12,6 +12,8 @@
|
|
12
12
|
# * Changed it so that if you call a!(false) it would actually set @a to false rather than leaving it unchanged. (I assume that was a bug.)
|
13
13
|
#++
|
14
14
|
|
15
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
16
|
+
|
15
17
|
# bool_attr_accessor
|
16
18
|
|
17
19
|
class Module
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#--
|
2
|
+
# Author:: Gavin Sinclair
|
3
|
+
# Copyright:: Copyright (c) Gavin Sinclair
|
4
|
+
# License:: Ruby License
|
5
|
+
# Submit to Facets?:: No. Copied from facets-1.8.54/lib/facets/core/module/by_name.rb. No longer exists in 2.4.1.
|
6
|
+
# Developer notes::
|
7
|
+
#++
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'facets/kernel/constant'
|
11
|
+
|
12
|
+
class Module
|
13
|
+
|
14
|
+
# <em>Note: the following documentation uses "class" because it's more common, but it
|
15
|
+
# applies to modules as well.</em>
|
16
|
+
#
|
17
|
+
# Given the _name_ of a class, returns the class itself (i.e. instance of Class). The
|
18
|
+
# dereferencing starts at Object. That is,
|
19
|
+
#
|
20
|
+
# Class.by_name("String")
|
21
|
+
#
|
22
|
+
# is equivalent to
|
23
|
+
#
|
24
|
+
# Object.const_get("String")
|
25
|
+
#
|
26
|
+
# The parameter _name_ is expected to be a Symbol or String, or at least to respond to
|
27
|
+
# <tt>to_str</tt>.
|
28
|
+
#
|
29
|
+
# An ArgumentError is raised if _name_ does not correspond to an existing class. If _name_
|
30
|
+
# is not even a valid class name, the error you'll get is not defined.
|
31
|
+
#
|
32
|
+
# Examples:
|
33
|
+
#
|
34
|
+
# Class.by_name("String") # -> String
|
35
|
+
# Class.by_name("::String") # -> String
|
36
|
+
# Class.by_name("Process::Sys") # -> Process::Sys
|
37
|
+
# Class.by_name("GorillaZ") # -> (ArgumentError)
|
38
|
+
#
|
39
|
+
# Class.by_name("Enumerable") # -> Enumerable
|
40
|
+
# Module.by_name("Enumerable") # -> Enumerable
|
41
|
+
#
|
42
|
+
#--
|
43
|
+
# Credit for this goes to Gavin Sinclair
|
44
|
+
#++
|
45
|
+
def by_name(name)
|
46
|
+
result = Object.constant(name)
|
47
|
+
return result if result.kind_of?( Module )
|
48
|
+
raise ArgumentError, "#{name} is not a module or class"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
# _____ _
|
56
|
+
# |_ _|__ ___| |_
|
57
|
+
# | |/ _ \/ __| __|
|
58
|
+
# | | __/\__ \ |_
|
59
|
+
# |_|\___||___/\__|
|
60
|
+
#
|
61
|
+
=begin test
|
62
|
+
|
63
|
+
require 'test/unit'
|
64
|
+
|
65
|
+
class TCModule < Test::Unit::TestCase
|
66
|
+
|
67
|
+
def test_by_name
|
68
|
+
c = ::Test::Unit::TestCase.name
|
69
|
+
assert_equal( ::Test::Unit::TestCase, Module.by_name(c) )
|
70
|
+
c = "Test::Unit::TestCase"
|
71
|
+
assert_equal( ::Test::Unit::TestCase, Class.by_name(c) )
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
=end
|
@@ -23,11 +23,12 @@
|
|
23
23
|
# * Added __FILE__, __LINE__ to class_eval so that error messages would be more helpful.
|
24
24
|
#++
|
25
25
|
|
26
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
26
27
|
require 'rubygems'
|
27
|
-
require '
|
28
|
-
require 'facets/
|
29
|
-
require 'facets/
|
30
|
-
require 'facets/
|
28
|
+
require 'quality_extensions/hash/assert_has_only_keys'
|
29
|
+
require 'facets/hash/reverse_merge'
|
30
|
+
require 'facets/kernel/constant'
|
31
|
+
require 'facets/ruby' # to_proc
|
31
32
|
require 'quality_extensions/module/split'
|
32
33
|
require 'quality_extensions/module/basename'
|
33
34
|
require 'quality_extensions/module/dirname'
|
@@ -139,7 +140,7 @@ require 'rubygems'
|
|
139
140
|
require 'quality_extensions/module/attribute_accessors'
|
140
141
|
require 'quality_extensions/module/namespace'
|
141
142
|
require 'quality_extensions/symbol/constantize'
|
142
|
-
require 'facets/
|
143
|
+
require 'facets/module/basename'
|
143
144
|
|
144
145
|
module Namespace
|
145
146
|
end
|
@@ -11,9 +11,12 @@
|
|
11
11
|
# possible to make it thread-safe?
|
12
12
|
#++
|
13
13
|
|
14
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
14
15
|
require 'rubygems'
|
16
|
+
puts '1'
|
15
17
|
require 'quality_extensions/module/attribute_accessors'
|
16
|
-
|
18
|
+
puts '2'
|
19
|
+
require 'facets/kernel/load'
|
17
20
|
require_local 'bool_attr_accessor'
|
18
21
|
#require 'quality_extensions/module/bool_attr_accessor'
|
19
22
|
require 'quality_extensions/symbol/match'
|
@@ -7,9 +7,9 @@
|
|
7
7
|
# Changes::
|
8
8
|
#++
|
9
9
|
|
10
|
-
|
10
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
11
11
|
require 'rubygems'
|
12
|
-
require 'facets/
|
12
|
+
require 'facets/ruby' # to_proc
|
13
13
|
require 'quality_extensions/symbol/constantize'
|
14
14
|
require 'quality_extensions/module/namespace' # dirname
|
15
15
|
require 'quality_extensions/module/basename'
|
@@ -7,10 +7,11 @@
|
|
7
7
|
# Changes::
|
8
8
|
#++
|
9
9
|
|
10
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
10
11
|
require 'rubygems'
|
11
12
|
gem 'facets'
|
12
|
-
require 'facets/
|
13
|
-
require '
|
13
|
+
require 'facets/kernel/singleton_class'
|
14
|
+
require 'facets/module/alias'
|
14
15
|
|
15
16
|
class Module
|
16
17
|
|
@@ -8,7 +8,7 @@
|
|
8
8
|
# * 0.0.52: Renamed namespace to namespace_module to avoid conflicting with Facets' Module#namespace and Rake's namespace
|
9
9
|
#++
|
10
10
|
|
11
|
-
|
11
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
12
12
|
require 'rubygems'
|
13
13
|
require 'quality_extensions/symbol/constantize'
|
14
14
|
require 'quality_extensions/module/split'
|
@@ -6,11 +6,12 @@
|
|
6
6
|
# Developer notes::
|
7
7
|
#++
|
8
8
|
|
9
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
9
10
|
require 'rubygems'
|
10
11
|
require 'quality_extensions/object/ignore_access'
|
11
12
|
require 'quality_extensions/module/split'
|
12
|
-
require 'facets/
|
13
|
-
require 'facets/
|
13
|
+
require 'facets/module/by_name'
|
14
|
+
require 'facets/module/modspace'
|
14
15
|
|
15
16
|
module Kernel
|
16
17
|
# This is similar to +Kernel#remove_const+, but it _only_ works for modules/classes.
|
@@ -6,8 +6,9 @@
|
|
6
6
|
# Developer notes::
|
7
7
|
#++
|
8
8
|
|
9
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
|
9
10
|
require 'rubygems'
|
10
|
-
require 'facets/
|
11
|
+
require 'facets/ruby' # to_proc
|
11
12
|
|
12
13
|
class Module
|
13
14
|
# Very similar to Facets' +Module#nesting+, but, whereas +nesting+ will break <tt>A::B</tt> into an array of _constants_ represting nesting
|
@@ -0,0 +1,227 @@
|
|
1
|
+
#--
|
2
|
+
# Credits::
|
3
|
+
# * Daniel Lucraft (http://www.daniellucraft.com/blog/2007/08/null-objects/) -- for the idea
|
4
|
+
# * Tyler Rick -- Re-wrote it more simply so that the method_missing was in NilClass directly; packaged it, added some documention, and added some tests
|
5
|
+
# Copyright:: Tyler Rick
|
6
|
+
# License:: Ruby License
|
7
|
+
# Submit to Facets?:: No
|
8
|
+
# Developer notes::
|
9
|
+
#++
|
10
|
+
|
11
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..')
|
12
|
+
|
13
|
+
class NilClass
|
14
|
+
# This allows you to call undefined methods on nil without an exception being raised.
|
15
|
+
#
|
16
|
+
# This gives us a much conciser alternative to this common pattern:
|
17
|
+
# might_be_nil ? might_be_nil.some_method : nil
|
18
|
+
# or
|
19
|
+
# (might_be_nil && might_be_nil.some_method)
|
20
|
+
#
|
21
|
+
# ... where might_be_nil is something that you hope is not nil most of the time, but which may on accosion be nil (and when it is, you don't want an exception
|
22
|
+
# to be raised!).
|
23
|
+
#
|
24
|
+
# For example, accessing a key from a hash:
|
25
|
+
# (hash[:a] && hash[:a][:b] && hash[:a][:b].some_method)
|
26
|
+
#
|
27
|
+
# With NilClass#method_missing, that simply becomes
|
28
|
+
# hash[:a][:b].some_method)
|
29
|
+
#
|
30
|
+
# The caveat with this approach is that it requires changing the behavior of a core class, NilClass, which could potentially have undesirable effects on
|
31
|
+
# code that expects the original behavior. Don't require this file unless you are sure that you want *all* nils everywhere to have this behavior.
|
32
|
+
#
|
33
|
+
# For a safer alternative that doesn't require monkey-patching NilClass, consider using the _? method.
|
34
|
+
#
|
35
|
+
def method_missing(*args)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# _____ _
|
42
|
+
# |_ _|__ ___| |_
|
43
|
+
# | |/ _ \/ __| __|
|
44
|
+
# | | __/\__ \ |_
|
45
|
+
# |_|\___||___/\__|
|
46
|
+
#
|
47
|
+
=begin test
|
48
|
+
require 'test/unit'
|
49
|
+
|
50
|
+
class TheTest < Test::Unit::TestCase
|
51
|
+
|
52
|
+
def test_simple__nil
|
53
|
+
hash = {}
|
54
|
+
assert_equal nil, hash[:a].length
|
55
|
+
end
|
56
|
+
def test_simple__normal_objects
|
57
|
+
hash = {:a => 'abc'}
|
58
|
+
assert_equal 3, hash[:a].length
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_chaining
|
62
|
+
hash = {}
|
63
|
+
|
64
|
+
assert_equal nil, hash[:a].length * 4
|
65
|
+
assert_equal nil, hash[:a][:b][:c]
|
66
|
+
assert_equal nil, hash[:a][:b][:c].some_method
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
=end
|
71
|
+
|
72
|
+
|
73
|
+
# Alternative version #1:
|
74
|
+
# (See note in safe_nil.rb -- this version fails the test_does_not_permanently_modify_nil_class test)
|
75
|
+
=begin
|
76
|
+
class NilClass
|
77
|
+
def __?
|
78
|
+
n = nil
|
79
|
+
def n.method_missing(*args)
|
80
|
+
__?
|
81
|
+
end
|
82
|
+
n
|
83
|
+
end
|
84
|
+
end
|
85
|
+
=end
|
86
|
+
|
87
|
+
|
88
|
+
# Alternative version #2:
|
89
|
+
# Another attempt, which doesn't require modifying NilClass ... but I found it didn't work as well as one might hope... :
|
90
|
+
=begin
|
91
|
+
# The __? method just returns the object itself for all objects except for nil. For nil, __? will return a special version of nil that lets you call undefined
|
92
|
+
# methods.
|
93
|
+
#
|
94
|
+
# If you call an undefined method on nil._?, you will get back an instance of ChainableSafeNil -- rather than raising an exception, which is what would
|
95
|
+
# happen if you called the same method on a _plain_ old nil.
|
96
|
+
#
|
97
|
+
# __? gives us a much conciser alternative to this common pattern:
|
98
|
+
# might_be_nil ? might_be_nil.some_method : nil
|
99
|
+
# or
|
100
|
+
# (might_be_nil && might_be_nil.some_method)
|
101
|
+
#
|
102
|
+
# ... where might_be_nil is something that you hope is not nil most of the time, but which may on accosion be nil (and when it is, you don't want an exception
|
103
|
+
# to be raised!).
|
104
|
+
#
|
105
|
+
# For example, accessing a key from a hash:
|
106
|
+
# (hash[:a] && hash[:a][:b] && hash[:a][:b].some_method)
|
107
|
+
#
|
108
|
+
# With __?, that simply becomes
|
109
|
+
# hash[:a].__?[:b].some_method)
|
110
|
+
#
|
111
|
+
# Unfortunately, this idea fails in two ways:
|
112
|
+
# * nil.__?.whatever will return an instance of ChainableSafeNil, which means you may end up with ChainableSafeNil objects as values of variables and arguments
|
113
|
+
# in your code... which seems messy, undesirable, and undefined (Should ChainableSafeNil act like nil? Should it evaluate to false like nil does? Good luck
|
114
|
+
# with that...)
|
115
|
+
# * If something evaluates to nil further on down the chain, after the __?, then all method calls to that nil will be unsafe. For example:
|
116
|
+
# hash = { :a => {} }
|
117
|
+
# hash[:a].__?[:b][:c]) => NoMethodError
|
118
|
+
#
|
119
|
+
# Conclusion: Chaining is impossible -- Just use _? on any object that might return nil (on which you want to call a method).
|
120
|
+
|
121
|
+
|
122
|
+
require 'singleton'
|
123
|
+
require 'rubygems'
|
124
|
+
require 'facets/more/basicobject'
|
125
|
+
|
126
|
+
|
127
|
+
class Object
|
128
|
+
def __?
|
129
|
+
self
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class NilClass
|
134
|
+
def __?
|
135
|
+
ChainableSafeNil.instance
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class ChainableSafeNil < BasicObject
|
140
|
+
include Singleton
|
141
|
+
|
142
|
+
def inspect
|
143
|
+
"ChainableSafeNil"
|
144
|
+
end
|
145
|
+
|
146
|
+
def method_missing(method, *args, &block)
|
147
|
+
#puts "nil.respond_to?(method)=#{nil.respond_to?(method)}"
|
148
|
+
return ChainableSafeNil.instance unless nil.respond_to?(method)
|
149
|
+
nil.send(method, *args, &block) rescue ChainableSafeNil.instance
|
150
|
+
end
|
151
|
+
|
152
|
+
def ==(other)
|
153
|
+
#other.nil?
|
154
|
+
raise "Why on earth is this line never getting executed?? And yet if I remove this method entirely, equality breaks"
|
155
|
+
end
|
156
|
+
|
157
|
+
def nil?; true; end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
class TheTest < Test::Unit::TestCase
|
165
|
+
|
166
|
+
def test_simple__nil
|
167
|
+
hash = {}
|
168
|
+
assert_equal ChainableSafeNil.instance, hash[:a].__?.length
|
169
|
+
end
|
170
|
+
def test_simple__normal_objects
|
171
|
+
hash = {:a => 'abc'}
|
172
|
+
assert_equal 3, hash[:a].__?.length
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_chaining
|
176
|
+
hash = {}
|
177
|
+
|
178
|
+
assert_raise(NoMethodError) { hash[:a] .length * 4 }
|
179
|
+
assert_raise(NoMethodError) { hash[:a] [:b][:c] }
|
180
|
+
assert_raise(NoMethodError) { hash[:a] [:b][:c].some_method }
|
181
|
+
|
182
|
+
assert_equal ChainableSafeNil.instance, hash[:a].__?.length * 4
|
183
|
+
assert_equal ChainableSafeNil.instance, hash[:a].__?[:b][:c]
|
184
|
+
assert_equal ChainableSafeNil.instance, hash[:a].__?[:b][:c].some_method
|
185
|
+
|
186
|
+
# But it needs to be chainable even if the *receiver* of __? isn't nil but something *later* down the chain is nil...
|
187
|
+
# (in this example, hash[:a] isn't nil, but hash[:a][:b] is, making it unsafe to evaluate hash[:a][:b][:c])
|
188
|
+
hash = { :a => {} }
|
189
|
+
assert_raise(NoMethodError) { hash[:a] [:b] [:c] }
|
190
|
+
# Unfortunately, I don't know any way to make that possible!!!
|
191
|
+
assert_raise(NoMethodError) { hash[:a].__?[:b] [:c] }
|
192
|
+
# So one is left with calling __? for *anything* that might be nil...
|
193
|
+
assert_equal ChainableSafeNil.instance, hash[:a].__?[:b].__?[:c]
|
194
|
+
# ... which is what we were trying to avoid having to do! This means __? is *not* chainable and we may as well use the plain old _? / SafeNil.
|
195
|
+
# We have failed as programmers and may as well go home in shame.
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def test_inspect
|
200
|
+
assert_equal 'ChainableSafeNil', nil.__?.inspect
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_nil?
|
204
|
+
assert ChainableSafeNil.instance.nil?
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_equality
|
208
|
+
assert_equal ChainableSafeNil.instance, ChainableSafeNil.instance
|
209
|
+
assert ChainableSafeNil.instance.eql?( nil.__?.length )
|
210
|
+
assert ChainableSafeNil.instance == nil.__?.length
|
211
|
+
|
212
|
+
assert ChainableSafeNil.instance == nil
|
213
|
+
# But:
|
214
|
+
assert nil != ChainableSafeNil.instance
|
215
|
+
|
216
|
+
# Why isn't this true?? Which == is getting called here?
|
217
|
+
#assert ChainableSafeNil.instance.send(:==, ChainableSafeNil.instance)
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_does_not_permanently_modify_nil_class
|
221
|
+
assert_raise(NoMethodError) { nil.foo }
|
222
|
+
nil.__?
|
223
|
+
assert_raise(NoMethodError) { nil.foo }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
=end
|
227
|
+
|