core_ext 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/lib/core_ext/array/access.rb +76 -0
- data/lib/core_ext/array/conversions.rb +211 -0
- data/lib/core_ext/array/extract_options.rb +29 -0
- data/lib/core_ext/array/grouping.rb +116 -0
- data/lib/core_ext/array/inquiry.rb +17 -0
- data/lib/core_ext/array/prepend_and_append.rb +7 -0
- data/lib/core_ext/array/wrap.rb +46 -0
- data/lib/core_ext/array.rb +7 -0
- data/lib/core_ext/array_inquirer.rb +44 -0
- data/lib/core_ext/benchmark.rb +14 -0
- data/lib/core_ext/benchmarkable.rb +49 -0
- data/lib/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/core_ext/big_decimal.rb +1 -0
- data/lib/core_ext/builder.rb +6 -0
- data/lib/core_ext/callbacks.rb +770 -0
- data/lib/core_ext/class/attribute.rb +128 -0
- data/lib/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/core_ext/class/subclasses.rb +42 -0
- data/lib/core_ext/class.rb +2 -0
- data/lib/core_ext/concern.rb +142 -0
- data/lib/core_ext/configurable.rb +148 -0
- data/lib/core_ext/date/acts_like.rb +8 -0
- data/lib/core_ext/date/blank.rb +12 -0
- data/lib/core_ext/date/calculations.rb +143 -0
- data/lib/core_ext/date/conversions.rb +93 -0
- data/lib/core_ext/date/zones.rb +6 -0
- data/lib/core_ext/date.rb +5 -0
- data/lib/core_ext/date_and_time/calculations.rb +328 -0
- data/lib/core_ext/date_and_time/zones.rb +40 -0
- data/lib/core_ext/date_time/acts_like.rb +14 -0
- data/lib/core_ext/date_time/blank.rb +12 -0
- data/lib/core_ext/date_time/calculations.rb +177 -0
- data/lib/core_ext/date_time/conversions.rb +104 -0
- data/lib/core_ext/date_time/zones.rb +6 -0
- data/lib/core_ext/date_time.rb +5 -0
- data/lib/core_ext/deprecation/behaviors.rb +86 -0
- data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
- data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
- data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
- data/lib/core_ext/deprecation/reporting.rb +105 -0
- data/lib/core_ext/deprecation.rb +43 -0
- data/lib/core_ext/digest/uuid.rb +51 -0
- data/lib/core_ext/duration.rb +157 -0
- data/lib/core_ext/enumerable.rb +106 -0
- data/lib/core_ext/file/atomic.rb +68 -0
- data/lib/core_ext/file.rb +1 -0
- data/lib/core_ext/hash/compact.rb +20 -0
- data/lib/core_ext/hash/conversions.rb +261 -0
- data/lib/core_ext/hash/deep_merge.rb +38 -0
- data/lib/core_ext/hash/except.rb +22 -0
- data/lib/core_ext/hash/indifferent_access.rb +23 -0
- data/lib/core_ext/hash/keys.rb +170 -0
- data/lib/core_ext/hash/reverse_merge.rb +22 -0
- data/lib/core_ext/hash/slice.rb +48 -0
- data/lib/core_ext/hash/transform_values.rb +29 -0
- data/lib/core_ext/hash.rb +9 -0
- data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
- data/lib/core_ext/inflections.rb +70 -0
- data/lib/core_ext/inflector/inflections.rb +244 -0
- data/lib/core_ext/inflector/methods.rb +381 -0
- data/lib/core_ext/inflector/transliterate.rb +112 -0
- data/lib/core_ext/inflector.rb +7 -0
- data/lib/core_ext/integer/inflections.rb +29 -0
- data/lib/core_ext/integer/multiple.rb +10 -0
- data/lib/core_ext/integer/time.rb +29 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/json/decoding.rb +67 -0
- data/lib/core_ext/json/encoding.rb +127 -0
- data/lib/core_ext/json.rb +2 -0
- data/lib/core_ext/kernel/agnostics.rb +11 -0
- data/lib/core_ext/kernel/concern.rb +10 -0
- data/lib/core_ext/kernel/reporting.rb +41 -0
- data/lib/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/core_ext/kernel.rb +4 -0
- data/lib/core_ext/load_error.rb +30 -0
- data/lib/core_ext/logger.rb +57 -0
- data/lib/core_ext/logger_silence.rb +24 -0
- data/lib/core_ext/marshal.rb +19 -0
- data/lib/core_ext/module/aliasing.rb +74 -0
- data/lib/core_ext/module/anonymous.rb +28 -0
- data/lib/core_ext/module/attr_internal.rb +36 -0
- data/lib/core_ext/module/attribute_accessors.rb +212 -0
- data/lib/core_ext/module/concerning.rb +135 -0
- data/lib/core_ext/module/delegation.rb +218 -0
- data/lib/core_ext/module/deprecation.rb +23 -0
- data/lib/core_ext/module/introspection.rb +62 -0
- data/lib/core_ext/module/method_transplanting.rb +3 -0
- data/lib/core_ext/module/qualified_const.rb +52 -0
- data/lib/core_ext/module/reachable.rb +8 -0
- data/lib/core_ext/module/remove_method.rb +35 -0
- data/lib/core_ext/module.rb +11 -0
- data/lib/core_ext/multibyte/chars.rb +231 -0
- data/lib/core_ext/multibyte/unicode.rb +388 -0
- data/lib/core_ext/multibyte.rb +21 -0
- data/lib/core_ext/name_error.rb +31 -0
- data/lib/core_ext/numeric/bytes.rb +64 -0
- data/lib/core_ext/numeric/conversions.rb +132 -0
- data/lib/core_ext/numeric/inquiry.rb +26 -0
- data/lib/core_ext/numeric/time.rb +74 -0
- data/lib/core_ext/numeric.rb +4 -0
- data/lib/core_ext/object/acts_like.rb +10 -0
- data/lib/core_ext/object/blank.rb +140 -0
- data/lib/core_ext/object/conversions.rb +4 -0
- data/lib/core_ext/object/deep_dup.rb +53 -0
- data/lib/core_ext/object/duplicable.rb +98 -0
- data/lib/core_ext/object/inclusion.rb +27 -0
- data/lib/core_ext/object/instance_variables.rb +28 -0
- data/lib/core_ext/object/json.rb +199 -0
- data/lib/core_ext/object/to_param.rb +1 -0
- data/lib/core_ext/object/to_query.rb +84 -0
- data/lib/core_ext/object/try.rb +146 -0
- data/lib/core_ext/object/with_options.rb +69 -0
- data/lib/core_ext/object.rb +14 -0
- data/lib/core_ext/option_merger.rb +25 -0
- data/lib/core_ext/ordered_hash.rb +48 -0
- data/lib/core_ext/ordered_options.rb +81 -0
- data/lib/core_ext/range/conversions.rb +34 -0
- data/lib/core_ext/range/each.rb +21 -0
- data/lib/core_ext/range/include_range.rb +23 -0
- data/lib/core_ext/range/overlaps.rb +8 -0
- data/lib/core_ext/range.rb +4 -0
- data/lib/core_ext/regexp.rb +5 -0
- data/lib/core_ext/rescuable.rb +119 -0
- data/lib/core_ext/securerandom.rb +23 -0
- data/lib/core_ext/security_utils.rb +20 -0
- data/lib/core_ext/string/access.rb +104 -0
- data/lib/core_ext/string/behavior.rb +6 -0
- data/lib/core_ext/string/conversions.rb +56 -0
- data/lib/core_ext/string/exclude.rb +11 -0
- data/lib/core_ext/string/filters.rb +102 -0
- data/lib/core_ext/string/indent.rb +43 -0
- data/lib/core_ext/string/inflections.rb +235 -0
- data/lib/core_ext/string/inquiry.rb +13 -0
- data/lib/core_ext/string/multibyte.rb +53 -0
- data/lib/core_ext/string/output_safety.rb +261 -0
- data/lib/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/core_ext/string/strip.rb +23 -0
- data/lib/core_ext/string/zones.rb +14 -0
- data/lib/core_ext/string.rb +13 -0
- data/lib/core_ext/string_inquirer.rb +26 -0
- data/lib/core_ext/tagged_logging.rb +78 -0
- data/lib/core_ext/test_case.rb +88 -0
- data/lib/core_ext/testing/assertions.rb +99 -0
- data/lib/core_ext/testing/autorun.rb +12 -0
- data/lib/core_ext/testing/composite_filter.rb +54 -0
- data/lib/core_ext/testing/constant_lookup.rb +50 -0
- data/lib/core_ext/testing/declarative.rb +26 -0
- data/lib/core_ext/testing/deprecation.rb +36 -0
- data/lib/core_ext/testing/file_fixtures.rb +34 -0
- data/lib/core_ext/testing/isolation.rb +115 -0
- data/lib/core_ext/testing/method_call_assertions.rb +41 -0
- data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
- data/lib/core_ext/testing/stream.rb +42 -0
- data/lib/core_ext/testing/tagged_logging.rb +25 -0
- data/lib/core_ext/testing/time_helpers.rb +134 -0
- data/lib/core_ext/time/acts_like.rb +8 -0
- data/lib/core_ext/time/calculations.rb +284 -0
- data/lib/core_ext/time/conversions.rb +66 -0
- data/lib/core_ext/time/zones.rb +95 -0
- data/lib/core_ext/time.rb +20 -0
- data/lib/core_ext/time_with_zone.rb +503 -0
- data/lib/core_ext/time_zone.rb +464 -0
- data/lib/core_ext/uri.rb +25 -0
- data/lib/core_ext/version.rb +3 -0
- data/lib/core_ext/xml_mini/jdom.rb +181 -0
- data/lib/core_ext/xml_mini/libxml.rb +79 -0
- data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
- data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
- data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
- data/lib/core_ext/xml_mini/rexml.rb +130 -0
- data/lib/core_ext/xml_mini.rb +194 -0
- data/lib/core_ext.rb +3 -0
- 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,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
|