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.
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
+