core_ext 0.0.1

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 (175) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -0
  3. data/lib/core_ext/array/access.rb +76 -0
  4. data/lib/core_ext/array/conversions.rb +211 -0
  5. data/lib/core_ext/array/extract_options.rb +29 -0
  6. data/lib/core_ext/array/grouping.rb +116 -0
  7. data/lib/core_ext/array/inquiry.rb +17 -0
  8. data/lib/core_ext/array/prepend_and_append.rb +7 -0
  9. data/lib/core_ext/array/wrap.rb +46 -0
  10. data/lib/core_ext/array.rb +7 -0
  11. data/lib/core_ext/array_inquirer.rb +44 -0
  12. data/lib/core_ext/benchmark.rb +14 -0
  13. data/lib/core_ext/benchmarkable.rb +49 -0
  14. data/lib/core_ext/big_decimal/conversions.rb +14 -0
  15. data/lib/core_ext/big_decimal.rb +1 -0
  16. data/lib/core_ext/builder.rb +6 -0
  17. data/lib/core_ext/callbacks.rb +770 -0
  18. data/lib/core_ext/class/attribute.rb +128 -0
  19. data/lib/core_ext/class/attribute_accessors.rb +4 -0
  20. data/lib/core_ext/class/subclasses.rb +42 -0
  21. data/lib/core_ext/class.rb +2 -0
  22. data/lib/core_ext/concern.rb +142 -0
  23. data/lib/core_ext/configurable.rb +148 -0
  24. data/lib/core_ext/date/acts_like.rb +8 -0
  25. data/lib/core_ext/date/blank.rb +12 -0
  26. data/lib/core_ext/date/calculations.rb +143 -0
  27. data/lib/core_ext/date/conversions.rb +93 -0
  28. data/lib/core_ext/date/zones.rb +6 -0
  29. data/lib/core_ext/date.rb +5 -0
  30. data/lib/core_ext/date_and_time/calculations.rb +328 -0
  31. data/lib/core_ext/date_and_time/zones.rb +40 -0
  32. data/lib/core_ext/date_time/acts_like.rb +14 -0
  33. data/lib/core_ext/date_time/blank.rb +12 -0
  34. data/lib/core_ext/date_time/calculations.rb +177 -0
  35. data/lib/core_ext/date_time/conversions.rb +104 -0
  36. data/lib/core_ext/date_time/zones.rb +6 -0
  37. data/lib/core_ext/date_time.rb +5 -0
  38. data/lib/core_ext/deprecation/behaviors.rb +86 -0
  39. data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
  40. data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
  41. data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
  42. data/lib/core_ext/deprecation/reporting.rb +105 -0
  43. data/lib/core_ext/deprecation.rb +43 -0
  44. data/lib/core_ext/digest/uuid.rb +51 -0
  45. data/lib/core_ext/duration.rb +157 -0
  46. data/lib/core_ext/enumerable.rb +106 -0
  47. data/lib/core_ext/file/atomic.rb +68 -0
  48. data/lib/core_ext/file.rb +1 -0
  49. data/lib/core_ext/hash/compact.rb +20 -0
  50. data/lib/core_ext/hash/conversions.rb +261 -0
  51. data/lib/core_ext/hash/deep_merge.rb +38 -0
  52. data/lib/core_ext/hash/except.rb +22 -0
  53. data/lib/core_ext/hash/indifferent_access.rb +23 -0
  54. data/lib/core_ext/hash/keys.rb +170 -0
  55. data/lib/core_ext/hash/reverse_merge.rb +22 -0
  56. data/lib/core_ext/hash/slice.rb +48 -0
  57. data/lib/core_ext/hash/transform_values.rb +29 -0
  58. data/lib/core_ext/hash.rb +9 -0
  59. data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
  60. data/lib/core_ext/inflections.rb +70 -0
  61. data/lib/core_ext/inflector/inflections.rb +244 -0
  62. data/lib/core_ext/inflector/methods.rb +381 -0
  63. data/lib/core_ext/inflector/transliterate.rb +112 -0
  64. data/lib/core_ext/inflector.rb +7 -0
  65. data/lib/core_ext/integer/inflections.rb +29 -0
  66. data/lib/core_ext/integer/multiple.rb +10 -0
  67. data/lib/core_ext/integer/time.rb +29 -0
  68. data/lib/core_ext/integer.rb +3 -0
  69. data/lib/core_ext/json/decoding.rb +67 -0
  70. data/lib/core_ext/json/encoding.rb +127 -0
  71. data/lib/core_ext/json.rb +2 -0
  72. data/lib/core_ext/kernel/agnostics.rb +11 -0
  73. data/lib/core_ext/kernel/concern.rb +10 -0
  74. data/lib/core_ext/kernel/reporting.rb +41 -0
  75. data/lib/core_ext/kernel/singleton_class.rb +6 -0
  76. data/lib/core_ext/kernel.rb +4 -0
  77. data/lib/core_ext/load_error.rb +30 -0
  78. data/lib/core_ext/logger.rb +57 -0
  79. data/lib/core_ext/logger_silence.rb +24 -0
  80. data/lib/core_ext/marshal.rb +19 -0
  81. data/lib/core_ext/module/aliasing.rb +74 -0
  82. data/lib/core_ext/module/anonymous.rb +28 -0
  83. data/lib/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/core_ext/module/concerning.rb +135 -0
  86. data/lib/core_ext/module/delegation.rb +218 -0
  87. data/lib/core_ext/module/deprecation.rb +23 -0
  88. data/lib/core_ext/module/introspection.rb +62 -0
  89. data/lib/core_ext/module/method_transplanting.rb +3 -0
  90. data/lib/core_ext/module/qualified_const.rb +52 -0
  91. data/lib/core_ext/module/reachable.rb +8 -0
  92. data/lib/core_ext/module/remove_method.rb +35 -0
  93. data/lib/core_ext/module.rb +11 -0
  94. data/lib/core_ext/multibyte/chars.rb +231 -0
  95. data/lib/core_ext/multibyte/unicode.rb +388 -0
  96. data/lib/core_ext/multibyte.rb +21 -0
  97. data/lib/core_ext/name_error.rb +31 -0
  98. data/lib/core_ext/numeric/bytes.rb +64 -0
  99. data/lib/core_ext/numeric/conversions.rb +132 -0
  100. data/lib/core_ext/numeric/inquiry.rb +26 -0
  101. data/lib/core_ext/numeric/time.rb +74 -0
  102. data/lib/core_ext/numeric.rb +4 -0
  103. data/lib/core_ext/object/acts_like.rb +10 -0
  104. data/lib/core_ext/object/blank.rb +140 -0
  105. data/lib/core_ext/object/conversions.rb +4 -0
  106. data/lib/core_ext/object/deep_dup.rb +53 -0
  107. data/lib/core_ext/object/duplicable.rb +98 -0
  108. data/lib/core_ext/object/inclusion.rb +27 -0
  109. data/lib/core_ext/object/instance_variables.rb +28 -0
  110. data/lib/core_ext/object/json.rb +199 -0
  111. data/lib/core_ext/object/to_param.rb +1 -0
  112. data/lib/core_ext/object/to_query.rb +84 -0
  113. data/lib/core_ext/object/try.rb +146 -0
  114. data/lib/core_ext/object/with_options.rb +69 -0
  115. data/lib/core_ext/object.rb +14 -0
  116. data/lib/core_ext/option_merger.rb +25 -0
  117. data/lib/core_ext/ordered_hash.rb +48 -0
  118. data/lib/core_ext/ordered_options.rb +81 -0
  119. data/lib/core_ext/range/conversions.rb +34 -0
  120. data/lib/core_ext/range/each.rb +21 -0
  121. data/lib/core_ext/range/include_range.rb +23 -0
  122. data/lib/core_ext/range/overlaps.rb +8 -0
  123. data/lib/core_ext/range.rb +4 -0
  124. data/lib/core_ext/regexp.rb +5 -0
  125. data/lib/core_ext/rescuable.rb +119 -0
  126. data/lib/core_ext/securerandom.rb +23 -0
  127. data/lib/core_ext/security_utils.rb +20 -0
  128. data/lib/core_ext/string/access.rb +104 -0
  129. data/lib/core_ext/string/behavior.rb +6 -0
  130. data/lib/core_ext/string/conversions.rb +56 -0
  131. data/lib/core_ext/string/exclude.rb +11 -0
  132. data/lib/core_ext/string/filters.rb +102 -0
  133. data/lib/core_ext/string/indent.rb +43 -0
  134. data/lib/core_ext/string/inflections.rb +235 -0
  135. data/lib/core_ext/string/inquiry.rb +13 -0
  136. data/lib/core_ext/string/multibyte.rb +53 -0
  137. data/lib/core_ext/string/output_safety.rb +261 -0
  138. data/lib/core_ext/string/starts_ends_with.rb +4 -0
  139. data/lib/core_ext/string/strip.rb +23 -0
  140. data/lib/core_ext/string/zones.rb +14 -0
  141. data/lib/core_ext/string.rb +13 -0
  142. data/lib/core_ext/string_inquirer.rb +26 -0
  143. data/lib/core_ext/tagged_logging.rb +78 -0
  144. data/lib/core_ext/test_case.rb +88 -0
  145. data/lib/core_ext/testing/assertions.rb +99 -0
  146. data/lib/core_ext/testing/autorun.rb +12 -0
  147. data/lib/core_ext/testing/composite_filter.rb +54 -0
  148. data/lib/core_ext/testing/constant_lookup.rb +50 -0
  149. data/lib/core_ext/testing/declarative.rb +26 -0
  150. data/lib/core_ext/testing/deprecation.rb +36 -0
  151. data/lib/core_ext/testing/file_fixtures.rb +34 -0
  152. data/lib/core_ext/testing/isolation.rb +115 -0
  153. data/lib/core_ext/testing/method_call_assertions.rb +41 -0
  154. data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
  155. data/lib/core_ext/testing/stream.rb +42 -0
  156. data/lib/core_ext/testing/tagged_logging.rb +25 -0
  157. data/lib/core_ext/testing/time_helpers.rb +134 -0
  158. data/lib/core_ext/time/acts_like.rb +8 -0
  159. data/lib/core_ext/time/calculations.rb +284 -0
  160. data/lib/core_ext/time/conversions.rb +66 -0
  161. data/lib/core_ext/time/zones.rb +95 -0
  162. data/lib/core_ext/time.rb +20 -0
  163. data/lib/core_ext/time_with_zone.rb +503 -0
  164. data/lib/core_ext/time_zone.rb +464 -0
  165. data/lib/core_ext/uri.rb +25 -0
  166. data/lib/core_ext/version.rb +3 -0
  167. data/lib/core_ext/xml_mini/jdom.rb +181 -0
  168. data/lib/core_ext/xml_mini/libxml.rb +79 -0
  169. data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
  170. data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
  171. data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
  172. data/lib/core_ext/xml_mini/rexml.rb +130 -0
  173. data/lib/core_ext/xml_mini.rb +194 -0
  174. data/lib/core_ext.rb +3 -0
  175. metadata +310 -0
@@ -0,0 +1,84 @@
1
+ require 'cgi'
2
+
3
+ class Object
4
+ # Alias of <tt>to_s</tt>.
5
+ def to_param
6
+ to_s
7
+ end
8
+
9
+ # Converts an object into a string suitable for use as a URL query string,
10
+ # using the given <tt>key</tt> as the param name.
11
+ def to_query(key)
12
+ "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
13
+ end
14
+ end
15
+
16
+ class NilClass
17
+ # Returns +self+.
18
+ def to_param
19
+ self
20
+ end
21
+ end
22
+
23
+ class TrueClass
24
+ # Returns +self+.
25
+ def to_param
26
+ self
27
+ end
28
+ end
29
+
30
+ class FalseClass
31
+ # Returns +self+.
32
+ def to_param
33
+ self
34
+ end
35
+ end
36
+
37
+ class Array
38
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
39
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
40
+ def to_param
41
+ collect(&:to_param).join '/'
42
+ end
43
+
44
+ # Converts an array into a string suitable for use as a URL query string,
45
+ # using the given +key+ as the param name.
46
+ #
47
+ # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
48
+ def to_query(key)
49
+ prefix = "#{key}[]"
50
+
51
+ if empty?
52
+ nil.to_query(prefix)
53
+ else
54
+ collect { |value| value.to_query(prefix) }.join '&'
55
+ end
56
+ end
57
+ end
58
+
59
+ class Hash
60
+ # Returns a string representation of the receiver suitable for use as a URL
61
+ # query string:
62
+ #
63
+ # {name: 'David', nationality: 'Danish'}.to_query
64
+ # # => "name=David&nationality=Danish"
65
+ #
66
+ # An optional namespace can be passed to enclose key names:
67
+ #
68
+ # {name: 'David', nationality: 'Danish'}.to_query('user')
69
+ # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
70
+ #
71
+ # The string pairs "key=value" that conform the query string
72
+ # are sorted lexicographically in ascending order.
73
+ #
74
+ # This method is also aliased as +to_param+.
75
+ def to_query(namespace = nil)
76
+ collect do |key, value|
77
+ unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
78
+ value.to_query(namespace ? "#{namespace}[#{key}]" : key)
79
+ end
80
+ end.compact.sort! * '&'
81
+ end
82
+
83
+ alias_method :to_param, :to_query
84
+ end
@@ -0,0 +1,146 @@
1
+ require 'delegate'
2
+
3
+ module CoreExt
4
+ module Tryable #:nodoc:
5
+ def try(*a, &b)
6
+ try!(*a, &b) if a.empty? || respond_to?(a.first)
7
+ end
8
+
9
+ def try!(*a, &b)
10
+ if a.empty? && block_given?
11
+ if b.arity == 0
12
+ instance_eval(&b)
13
+ else
14
+ yield self
15
+ end
16
+ else
17
+ public_send(*a, &b)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class Object
24
+ include CoreExt::Tryable
25
+
26
+ ##
27
+ # :method: try
28
+ #
29
+ # :call-seq:
30
+ # try(*a, &b)
31
+ #
32
+ # Invokes the public method whose name goes as first argument just like
33
+ # +public_send+ does, except that if the receiver does not respond to it the
34
+ # call returns +nil+ rather than raising an exception.
35
+ #
36
+ # This method is defined to be able to write
37
+ #
38
+ # @person.try(:name)
39
+ #
40
+ # instead of
41
+ #
42
+ # @person.name if @person
43
+ #
44
+ # +try+ calls can be chained:
45
+ #
46
+ # @person.try(:spouse).try(:name)
47
+ #
48
+ # instead of
49
+ #
50
+ # @person.spouse.name if @person && @person.spouse
51
+ #
52
+ # +try+ will also return +nil+ if the receiver does not respond to the method:
53
+ #
54
+ # @person.try(:non_existing_method) # => nil
55
+ #
56
+ # instead of
57
+ #
58
+ # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
59
+ #
60
+ # +try+ returns +nil+ when called on +nil+ regardless of whether it responds
61
+ # to the method:
62
+ #
63
+ # nil.try(:to_i) # => nil, rather than 0
64
+ #
65
+ # Arguments and blocks are forwarded to the method if invoked:
66
+ #
67
+ # @posts.try(:each_slice, 2) do |a, b|
68
+ # ...
69
+ # end
70
+ #
71
+ # The number of arguments in the signature must match. If the object responds
72
+ # to the method the call is attempted and +ArgumentError+ is still raised
73
+ # in case of argument mismatch.
74
+ #
75
+ # If +try+ is called without arguments it yields the receiver to a given
76
+ # block unless it is +nil+:
77
+ #
78
+ # @person.try do |p|
79
+ # ...
80
+ # end
81
+ #
82
+ # You can also call try with a block without accepting an argument, and the block
83
+ # will be instance_eval'ed instead:
84
+ #
85
+ # @person.try { upcase.truncate(50) }
86
+ #
87
+ # Please also note that +try+ is defined on +Object+. Therefore, it won't work
88
+ # with instances of classes that do not have +Object+ among their ancestors,
89
+ # like direct subclasses of +BasicObject+.
90
+
91
+ ##
92
+ # :method: try!
93
+ #
94
+ # :call-seq:
95
+ # try!(*a, &b)
96
+ #
97
+ # Same as #try, but raises a +NoMethodError+ exception if the receiver is
98
+ # not +nil+ and does not implement the tried method.
99
+ #
100
+ # "a".try!(:upcase) # => "A"
101
+ # nil.try!(:upcase) # => nil
102
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
103
+ end
104
+
105
+ class Delegator
106
+ include CoreExt::Tryable
107
+
108
+ ##
109
+ # :method: try
110
+ #
111
+ # :call-seq:
112
+ # try(a*, &b)
113
+ #
114
+ # See Object#try
115
+
116
+ ##
117
+ # :method: try!
118
+ #
119
+ # :call-seq:
120
+ # try!(a*, &b)
121
+ #
122
+ # See Object#try!
123
+ end
124
+
125
+ class NilClass
126
+ # Calling +try+ on +nil+ always returns +nil+.
127
+ # It becomes especially helpful when navigating through associations that may return +nil+.
128
+ #
129
+ # nil.try(:name) # => nil
130
+ #
131
+ # Without +try+
132
+ # @person && @person.children.any? && @person.children.first.name
133
+ #
134
+ # With +try+
135
+ # @person.try(:children).try(:first).try(:name)
136
+ def try(*args)
137
+ nil
138
+ end
139
+
140
+ # Calling +try!+ on +nil+ always returns +nil+.
141
+ #
142
+ # nil.try!(:name) # => nil
143
+ def try!(*args)
144
+ nil
145
+ end
146
+ end
@@ -0,0 +1,69 @@
1
+ require 'core_ext/option_merger'
2
+
3
+ class Object
4
+ # An elegant way to factor duplication out of options passed to a series of
5
+ # method calls. Each method called in the block, with the block variable as
6
+ # the receiver, will have its options merged with the default +options+ hash
7
+ # provided. Each method called on the block variable must take an options
8
+ # hash as its final argument.
9
+ #
10
+ # Without <tt>with_options</tt>, this code contains duplication:
11
+ #
12
+ # class Account < ActiveRecord::Base
13
+ # has_many :customers, dependent: :destroy
14
+ # has_many :products, dependent: :destroy
15
+ # has_many :invoices, dependent: :destroy
16
+ # has_many :expenses, dependent: :destroy
17
+ # end
18
+ #
19
+ # Using <tt>with_options</tt>, we can remove the duplication:
20
+ #
21
+ # class Account < ActiveRecord::Base
22
+ # with_options dependent: :destroy do |assoc|
23
+ # assoc.has_many :customers
24
+ # assoc.has_many :products
25
+ # assoc.has_many :invoices
26
+ # assoc.has_many :expenses
27
+ # end
28
+ # end
29
+ #
30
+ # It can also be used with an explicit receiver:
31
+ #
32
+ # I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n|
33
+ # subject i18n.t :subject
34
+ # body i18n.t :body, user_name: user.name
35
+ # end
36
+ #
37
+ # When you don't pass an explicit receiver, it executes the whole block
38
+ # in merging options context:
39
+ #
40
+ # class Account < ActiveRecord::Base
41
+ # with_options dependent: :destroy do
42
+ # has_many :customers
43
+ # has_many :products
44
+ # has_many :invoices
45
+ # has_many :expenses
46
+ # end
47
+ # end
48
+ #
49
+ # <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
50
+ #
51
+ # NOTE: Each nesting level will merge inherited defaults in addition to their own.
52
+ #
53
+ # class Post < ActiveRecord::Base
54
+ # with_options if: :persisted?, length: { minimum: 50 } do
55
+ # validates :content, if: -> { content.present? }
56
+ # end
57
+ # end
58
+ #
59
+ # The code is equivalent to:
60
+ #
61
+ # validates :content, length: { minimum: 50 }, if: -> { content.present? }
62
+ #
63
+ # Hence the inherited default for `if` key is ignored.
64
+ #
65
+ def with_options(options, &block)
66
+ option_merger = CoreExt::OptionMerger.new(self, options)
67
+ block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
68
+ end
69
+ end
@@ -0,0 +1,14 @@
1
+ require 'core_ext/object/acts_like'
2
+ require 'core_ext/object/blank'
3
+ require 'core_ext/object/duplicable'
4
+ require 'core_ext/object/deep_dup'
5
+ require 'core_ext/object/try'
6
+ require 'core_ext/object/inclusion'
7
+
8
+ require 'core_ext/object/conversions'
9
+ require 'core_ext/object/instance_variables'
10
+
11
+ require 'core_ext/object/json'
12
+ require 'core_ext/object/to_param'
13
+ require 'core_ext/object/to_query'
14
+ require 'core_ext/object/with_options'
@@ -0,0 +1,25 @@
1
+ require 'core_ext/hash/deep_merge'
2
+
3
+ module CoreExt
4
+ class OptionMerger #:nodoc:
5
+ instance_methods.each do |method|
6
+ undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/
7
+ end
8
+
9
+ def initialize(context, options)
10
+ @context, @options = context, options
11
+ end
12
+
13
+ private
14
+ def method_missing(method, *arguments, &block)
15
+ if arguments.first.is_a?(Proc)
16
+ proc = arguments.pop
17
+ arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) }
18
+ else
19
+ arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup)
20
+ end
21
+
22
+ @context.__send__(method, *arguments, &block)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ YAML.add_builtin_type("omap") do |type, val|
4
+ CoreExt::OrderedHash[val.map{ |v| v.to_a.first }]
5
+ end
6
+
7
+ module CoreExt
8
+ # <tt>CoreExt::OrderedHash</tt> implements a hash that preserves
9
+ # insertion order.
10
+ #
11
+ # oh = CoreExt::OrderedHash.new
12
+ # oh[:a] = 1
13
+ # oh[:b] = 2
14
+ # oh.keys # => [:a, :b], this order is guaranteed
15
+ #
16
+ # Also, maps the +omap+ feature for YAML files
17
+ # (See http://yaml.org/type/omap.html) to support ordered items
18
+ # when loading from yaml.
19
+ #
20
+ # <tt>CoreExt::OrderedHash</tt> is namespaced to prevent conflicts
21
+ # with other implementations.
22
+ class OrderedHash < ::Hash
23
+ def to_yaml_type
24
+ "!tag:yaml.org,2002:omap"
25
+ end
26
+
27
+ def encode_with(coder)
28
+ coder.represent_seq '!omap', map { |k,v| { k => v } }
29
+ end
30
+
31
+ def select(*args, &block)
32
+ dup.tap { |hash| hash.select!(*args, &block) }
33
+ end
34
+
35
+ def reject(*args, &block)
36
+ dup.tap { |hash| hash.reject!(*args, &block) }
37
+ end
38
+
39
+ def nested_under_indifferent_access
40
+ self
41
+ end
42
+
43
+ # Returns true to make sure that this hash is extractable via <tt>Array#extract_options!</tt>
44
+ def extractable_options?
45
+ true
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,81 @@
1
+ module CoreExt
2
+ # Usually key value pairs are handled something like this:
3
+ #
4
+ # h = {}
5
+ # h[:boy] = 'John'
6
+ # h[:girl] = 'Mary'
7
+ # h[:boy] # => 'John'
8
+ # h[:girl] # => 'Mary'
9
+ # h[:dog] # => nil
10
+ #
11
+ # Using +OrderedOptions+, the above code could be reduced to:
12
+ #
13
+ # h = CoreExt::OrderedOptions.new
14
+ # h.boy = 'John'
15
+ # h.girl = 'Mary'
16
+ # h.boy # => 'John'
17
+ # h.girl # => 'Mary'
18
+ # h.dog # => nil
19
+ #
20
+ # To raise an exception when the value is blank, append a
21
+ # bang to the key name, like:
22
+ #
23
+ # h.dog! # => raises KeyError: key not found: :dog
24
+ #
25
+ class OrderedOptions < Hash
26
+ alias_method :_get, :[] # preserve the original #[] method
27
+ protected :_get # make it protected
28
+
29
+ def []=(key, value)
30
+ super(key.to_sym, value)
31
+ end
32
+
33
+ def [](key)
34
+ super(key.to_sym)
35
+ end
36
+
37
+ def method_missing(name, *args)
38
+ name_string = name.to_s
39
+ if name_string.chomp!('=')
40
+ self[name_string] = args.first
41
+ else
42
+ bangs = name_string.chomp!('!')
43
+
44
+ if bangs
45
+ fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank."))
46
+ else
47
+ self[name_string]
48
+ end
49
+ end
50
+ end
51
+
52
+ def respond_to_missing?(name, include_private)
53
+ true
54
+ end
55
+ end
56
+
57
+ # +InheritableOptions+ provides a constructor to build an +OrderedOptions+
58
+ # hash inherited from another hash.
59
+ #
60
+ # Use this if you already have some hash and you want to create a new one based on it.
61
+ #
62
+ # h = CoreExt::InheritableOptions.new({ girl: 'Mary', boy: 'John' })
63
+ # h.girl # => 'Mary'
64
+ # h.boy # => 'John'
65
+ class InheritableOptions < OrderedOptions
66
+ def initialize(parent = nil)
67
+ if parent.kind_of?(OrderedOptions)
68
+ # use the faster _get when dealing with OrderedOptions
69
+ super() { |h,k| parent._get(k) }
70
+ elsif parent
71
+ super() { |h,k| parent[k] }
72
+ else
73
+ super()
74
+ end
75
+ end
76
+
77
+ def inheritable_copy
78
+ self.class.new(self)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,34 @@
1
+ class Range
2
+ RANGE_FORMATS = {
3
+ :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
4
+ }
5
+
6
+ # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
7
+ #
8
+ # This method is aliased to <tt>to_s</tt>.
9
+ #
10
+ # range = (1..100) # => 1..100
11
+ #
12
+ # range.to_formatted_s # => "1..100"
13
+ # range.to_s # => "1..100"
14
+ #
15
+ # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
16
+ # range.to_s(:db) # => "BETWEEN '1' AND '100'"
17
+ #
18
+ # == Adding your own range formats to to_formatted_s
19
+ # You can add your own formats to the Range::RANGE_FORMATS hash.
20
+ # Use the format name as the hash key and a Proc instance.
21
+ #
22
+ # # config/initializers/range_formats.rb
23
+ # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
24
+ def to_formatted_s(format = :default)
25
+ if formatter = RANGE_FORMATS[format]
26
+ formatter.call(first, last)
27
+ else
28
+ to_default_s
29
+ end
30
+ end
31
+
32
+ alias_method :to_default_s, :to_s
33
+ alias_method :to_s, :to_formatted_s
34
+ end
@@ -0,0 +1,21 @@
1
+ module CoreExt
2
+ module EachTimeWithZone #:nodoc:
3
+ def each(&block)
4
+ ensure_iteration_allowed
5
+ super
6
+ end
7
+
8
+ def step(n = 1, &block)
9
+ ensure_iteration_allowed
10
+ super
11
+ end
12
+
13
+ private
14
+
15
+ def ensure_iteration_allowed
16
+ raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time)
17
+ end
18
+ end
19
+ end
20
+
21
+ Range.prepend(CoreExt::EachTimeWithZone)
@@ -0,0 +1,23 @@
1
+ module CoreExt
2
+ module IncludeWithRange #:nodoc:
3
+ # Extends the default Range#include? to support range comparisons.
4
+ # (1..5).include?(1..5) # => true
5
+ # (1..5).include?(2..3) # => true
6
+ # (1..5).include?(2..6) # => false
7
+ #
8
+ # The native Range#include? behavior is untouched.
9
+ # ('a'..'f').include?('c') # => true
10
+ # (5..9).include?(11) # => false
11
+ def include?(value)
12
+ if value.is_a?(::Range)
13
+ # 1...10 includes 1..9 but it does not include 1..10.
14
+ operator = exclude_end? && !value.exclude_end? ? :< : :<=
15
+ super(value.first) && value.last.send(operator, last)
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Range.prepend(CoreExt::IncludeWithRange)
@@ -0,0 +1,8 @@
1
+ class Range
2
+ # Compare two ranges and see if they overlap each other
3
+ # (1..5).overlaps?(4..6) # => true
4
+ # (1..5).overlaps?(7..9) # => false
5
+ def overlaps?(other)
6
+ cover?(other.first) || other.cover?(first)
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ require 'core_ext/range/conversions'
2
+ require 'core_ext/range/include_range'
3
+ require 'core_ext/range/overlaps'
4
+ require 'core_ext/range/each'
@@ -0,0 +1,5 @@
1
+ class Regexp #:nodoc:
2
+ def multiline?
3
+ options & MULTILINE == MULTILINE
4
+ end
5
+ end
@@ -0,0 +1,119 @@
1
+ require 'core_ext/concern'
2
+ require 'core_ext/class/attribute'
3
+ require 'core_ext/string/inflections'
4
+ require 'core_ext/array/extract_options'
5
+
6
+ module CoreExt
7
+ # Rescuable module adds support for easier exception handling.
8
+ module Rescuable
9
+ extend Concern
10
+
11
+ included do
12
+ class_attribute :rescue_handlers
13
+ self.rescue_handlers = []
14
+ end
15
+
16
+ module ClassMethods
17
+ # Rescue exceptions raised in controller actions.
18
+ #
19
+ # <tt>rescue_from</tt> receives a series of exception classes or class
20
+ # names, and a trailing <tt>:with</tt> option with the name of a method
21
+ # or a Proc object to be called to handle them. Alternatively a block can
22
+ # be given.
23
+ #
24
+ # Handlers that take one argument will be called with the exception, so
25
+ # that the exception can be inspected when dealing with it.
26
+ #
27
+ # Handlers are inherited. They are searched from right to left, from
28
+ # bottom to top, and up the hierarchy. The handler of the first class for
29
+ # which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
30
+ # any.
31
+ #
32
+ # class ApplicationController < ActionController::Base
33
+ # rescue_from User::NotAuthorized, with: :deny_access # self defined exception
34
+ # rescue_from ActiveRecord::RecordInvalid, with: :show_errors
35
+ #
36
+ # rescue_from 'MyAppError::Base' do |exception|
37
+ # render xml: exception, status: 500
38
+ # end
39
+ #
40
+ # protected
41
+ # def deny_access
42
+ # ...
43
+ # end
44
+ #
45
+ # def show_errors(exception)
46
+ # exception.record.new_record? ? ...
47
+ # end
48
+ # end
49
+ #
50
+ # Exceptions raised inside exception handlers are not propagated up.
51
+ def rescue_from(*klasses, &block)
52
+ options = klasses.extract_options!
53
+
54
+ unless options.has_key?(:with)
55
+ if block_given?
56
+ options[:with] = block
57
+ else
58
+ raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
59
+ end
60
+ end
61
+
62
+ klasses.each do |klass|
63
+ key = if klass.is_a?(Module) && klass.respond_to?(:===)
64
+ klass.name
65
+ elsif klass.is_a?(String)
66
+ klass
67
+ else
68
+ raise ArgumentError, "#{klass} is neither an Exception nor a String"
69
+ end
70
+
71
+ # Put the new handler at the end because the list is read in reverse.
72
+ self.rescue_handlers += [[key, options[:with]]]
73
+ end
74
+ end
75
+ end
76
+
77
+ # Tries to rescue the exception by looking up and calling a registered handler.
78
+ def rescue_with_handler(exception)
79
+ if handler = handler_for_rescue(exception)
80
+ handler.arity != 0 ? handler.call(exception) : handler.call
81
+ true # don't rely on the return value of the handler
82
+ end
83
+ end
84
+
85
+ def handler_for_rescue(exception)
86
+ # We go from right to left because pairs are pushed onto rescue_handlers
87
+ # as rescue_from declarations are found.
88
+ _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler|
89
+ # The purpose of allowing strings in rescue_from is to support the
90
+ # declaration of handler associations for exception classes whose
91
+ # definition is yet unknown.
92
+ #
93
+ # Since this loop needs the constants it would be inconsistent to
94
+ # assume they should exist at this point. An early raised exception
95
+ # could trigger some other handler and the array could include
96
+ # precisely a string whose corresponding constant has not yet been
97
+ # seen. This is why we are tolerant to unknown constants.
98
+ #
99
+ # Note that this tolerance only matters if the exception was given as
100
+ # a string, otherwise a NameError will be raised by the interpreter
101
+ # itself when rescue_from CONSTANT is executed.
102
+ klass = self.class.const_get(klass_name) rescue nil
103
+ klass ||= (klass_name.constantize rescue nil)
104
+ klass === exception if klass
105
+ end
106
+
107
+ case rescuer
108
+ when Symbol
109
+ method(rescuer)
110
+ when Proc
111
+ if rescuer.arity == 0
112
+ Proc.new { instance_exec(&rescuer) }
113
+ else
114
+ Proc.new { |_exception| instance_exec(_exception, &rescuer) }
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end