quality_extensions 0.1.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|