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.
Files changed (68) hide show
  1. data/lib/quality_extensions/all.rb +1 -1
  2. data/lib/quality_extensions/array/average.rb +2 -1
  3. data/lib/quality_extensions/array/classify.rb +3 -1
  4. data/lib/quality_extensions/array/expand_ranges.rb +3 -1
  5. data/lib/quality_extensions/array/group_by.rb +3 -1
  6. data/lib/quality_extensions/array/sequence.rb +2 -0
  7. data/lib/quality_extensions/array/shell_escape.rb +11 -6
  8. data/lib/quality_extensions/array/to_a_recursive.rb +1 -0
  9. data/lib/quality_extensions/array/to_query_string.rb +2 -0
  10. data/lib/quality_extensions/colored/toggleability.rb +2 -2
  11. data/lib/quality_extensions/console/command.rb +4 -3
  12. data/lib/quality_extensions/date/deprecated.rb +2 -0
  13. data/lib/quality_extensions/date/iso8601.rb +4 -1
  14. data/lib/quality_extensions/date/month_ranges.rb +2 -0
  15. data/lib/quality_extensions/dir/each_child.rb +1 -0
  16. data/lib/quality_extensions/exception/inspect_with_backtrace.rb +4 -2
  17. data/lib/quality_extensions/file/exact_match_regexp.rb +1 -0
  18. data/lib/quality_extensions/hash/assert_has_only_keys.rb +48 -0
  19. data/lib/quality_extensions/hash/to_date.rb +2 -0
  20. data/lib/quality_extensions/hash/to_query_string.rb +8 -2
  21. data/lib/quality_extensions/kernel/backtrace.rb +4 -2
  22. data/lib/quality_extensions/kernel/capture_output.rb +3 -2
  23. data/lib/quality_extensions/kernel/die.rb +4 -1
  24. data/lib/quality_extensions/kernel/example_printer.rb +3 -3
  25. data/lib/quality_extensions/kernel/remove_const.rb +3 -2
  26. data/lib/quality_extensions/kernel/remove_module.rb +3 -2
  27. data/lib/quality_extensions/kernel/require_all.rb +5 -3
  28. data/lib/quality_extensions/kernel/require_once.rb +1 -0
  29. data/lib/quality_extensions/kernel/simulate_input.rb +1 -0
  30. data/lib/quality_extensions/kernel/trap_chain.rb +3 -0
  31. data/lib/quality_extensions/module/alias_method_chain.rb +3 -15
  32. data/lib/quality_extensions/module/ancestry_of_instance_method.rb +19 -0
  33. data/lib/quality_extensions/module/basename.rb +5 -8
  34. data/lib/quality_extensions/module/bool_attr_accessor.rb +3 -1
  35. data/lib/quality_extensions/module/by_name.rb +76 -0
  36. data/lib/quality_extensions/module/class_methods.rb +2 -0
  37. data/lib/quality_extensions/module/create.rb +6 -5
  38. data/lib/quality_extensions/module/guard_method.rb +4 -1
  39. data/lib/quality_extensions/module/join.rb +2 -2
  40. data/lib/quality_extensions/module/malias_method_chain.rb +3 -2
  41. data/lib/quality_extensions/module/namespace.rb +1 -1
  42. data/lib/quality_extensions/module/parents.rb +2 -2
  43. data/lib/quality_extensions/module/remove_const.rb +3 -2
  44. data/lib/quality_extensions/module/split.rb +2 -1
  45. data/lib/quality_extensions/month.rb +2 -0
  46. data/lib/quality_extensions/mutex/if_available.rb +2 -0
  47. data/lib/quality_extensions/nil_method_missing.rb +227 -0
  48. data/lib/quality_extensions/object/ancestry_of_method.rb +2 -0
  49. data/lib/quality_extensions/object/if_else.rb +1 -1
  50. data/lib/quality_extensions/object/ignore_access.rb +3 -2
  51. data/lib/quality_extensions/object/mcall.rb +3 -1
  52. data/lib/quality_extensions/object/methods.rb +3 -2
  53. data/lib/quality_extensions/object/send_if.rb +2 -1
  54. data/lib/quality_extensions/object/singleton_send.rb +2 -0
  55. data/lib/quality_extensions/safe_nil.rb +3 -5
  56. data/lib/quality_extensions/string/digits_only.rb +2 -0
  57. data/lib/quality_extensions/string/each_char_with_index.rb +1 -0
  58. data/lib/quality_extensions/string/md5.rb +2 -0
  59. data/lib/quality_extensions/string/shell_escape.rb +4 -2
  60. data/lib/quality_extensions/string/to_underscored_label.rb +2 -0
  61. data/lib/quality_extensions/string/with_knowledge_of_color.rb +3 -0
  62. data/lib/quality_extensions/symbol/constantize.rb +2 -1
  63. data/lib/quality_extensions/symbol/match.rb +2 -1
  64. data/lib/quality_extensions/test/assert_anything.rb +1 -0
  65. data/lib/quality_extensions/test/difference_highlighting.rb +2 -2
  66. data/lib/quality_extensions/time/deprecated.rb +2 -0
  67. data/test/all.rb +8 -2
  68. 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 'facets/core/module/by_name'
13
- require 'facets/core/module/modspace'
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 'facets/core/module/by_name'
14
- require 'facets/core/module/modspace'
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/more/filelist'
10
+ require 'facets/filelist'
10
11
 
11
- require 'facets/core/kernel/require_local'
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 ['flip.rb', @deep_dir + "/flop.rb"], $loaded
122
+ assert_equal [@deep_dir + "/flop.rb", 'flip.rb'], $loaded
121
123
  end
122
124
 
123
125
  def test_exclude_pattern
@@ -5,6 +5,7 @@
5
5
  # Submit to Facets?:: No, not yet
6
6
  #++
7
7
 
8
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
8
9
  require 'rubygems'
9
10
 
10
11
  module Kernel
@@ -6,6 +6,7 @@
6
6
  # Developer notes:
7
7
  #++
8
8
 
9
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
9
10
  require 'stringio'
10
11
  require 'rubygems'
11
12
 
@@ -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
- #require 'facets/core/module/alias_method_chain' # Doesn't support blocks or I'd use it.
13
- require 'facets/core/kernel/singleton_class'
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/core/module/basename'
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/core/string/basename') except that:
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.basename
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/core/module/attr_tester.rb
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
@@ -6,6 +6,8 @@
6
6
  # Developer notes::
7
7
  #++
8
8
 
9
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
10
+
9
11
  class Object
10
12
 
11
13
  def class_methods(include_super = true)
@@ -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 'facets/core/hash/assert_has_only_keys'
28
- require 'facets/core/hash/reverse_merge'
29
- require 'facets/core/kernel/constant'
30
- require 'facets/core/symbol/to_proc'
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/core/module/basename'
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
- require 'facets/core/kernel/require_local'
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/core/symbol/to_proc'
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/core/kernel/singleton_class'
13
- require 'quality_extensions/module/alias_method_chain'
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'
@@ -9,9 +9,9 @@
9
9
  # * Copied from ActiveSupport.
10
10
  #++
11
11
 
12
-
12
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..')
13
13
  require 'rubygems'
14
- require 'facets/core/kernel/constant'
14
+ require 'facets/kernel/constant'
15
15
 
16
16
 
17
17
  class Module
@@ -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/core/module/by_name'
13
- require 'facets/core/module/modspace'
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/core/symbol/to_proc'
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
@@ -46,6 +46,8 @@ end
46
46
  # |_|\___||___/\__|
47
47
  #
48
48
  =begin test
49
+ require 'test/unit'
50
+
49
51
  class TheTest < Test::Unit::TestCase
50
52
  def test_months_range
51
53
  range = Month.new(2006, 6)..Month.new(2006, 9)
@@ -42,6 +42,8 @@ end
42
42
  # |_|\___||___/\__|
43
43
  #
44
44
  =begin test
45
+ require 'test/unit'
46
+
45
47
  class TheTest < Test::Unit::TestCase
46
48
  def setup
47
49
  @semaphore = Mutex.new
@@ -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
+