simple_ext 0.1.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.
- checksums.yaml +7 -0
- data/lib/simple_ext/array/access.rb +122 -0
- data/lib/simple_ext/array.rb +3 -0
- data/lib/simple_ext/digest/uuid.rb +53 -0
- data/lib/simple_ext/digest.rb +3 -0
- data/lib/simple_ext/file/atomic.rb +70 -0
- data/lib/simple_ext/file.rb +3 -0
- data/lib/simple_ext/hash/except.rb +22 -0
- data/lib/simple_ext/hash/keys.rb +143 -0
- data/lib/simple_ext/hash/merge.rb +56 -0
- data/lib/simple_ext/hash/slice.rb +26 -0
- data/lib/simple_ext/hash/values.rb +46 -0
- data/lib/simple_ext/hash.rb +7 -0
- data/lib/simple_ext/numeric/bytes.rb +66 -0
- data/lib/simple_ext/numeric.rb +3 -0
- data/lib/simple_ext/object/blank.rb +154 -0
- data/lib/simple_ext/object/inclusion.rb +29 -0
- data/lib/simple_ext/object/instance_variables.rb +30 -0
- data/lib/simple_ext/object/to_query.rb +89 -0
- data/lib/simple_ext/object/try.rb +127 -0
- data/lib/simple_ext/object.rb +7 -0
- data/lib/simple_ext.rb +5 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5f972070633ea700b9c69fc42ed84258ed68bbfe3fb95df8284c62dc3342db42
|
4
|
+
data.tar.gz: 7e19bc443f5f7ddc6658aaf20936ccfce95e108e4e6498e489d68ec1f7da9973
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8778c40b09e43907f4da9cbfb7473a2d6add060632025925a77553daf61eb58249fe3f4afea0238d59ca0a29596207284d2388682c7fc70a7092d108247cfaa4
|
7
|
+
data.tar.gz: 8723bbf6360f6637382acae47b95d82bb1b7f6dddfcf8e0605670c449960ac0c36edb60621ed8d480c3fdbc441106cecb77655126328d8b4e58e72d385ea9f65
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# Removes and returns the elements for which the block returns a true value.
|
5
|
+
# If no block is given, an Enumerator is returned instead.
|
6
|
+
#
|
7
|
+
# numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
8
|
+
# odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
|
9
|
+
# numbers # => [0, 2, 4, 6, 8]
|
10
|
+
def extract!
|
11
|
+
return to_enum(:extract!) { size } unless block_given?
|
12
|
+
|
13
|
+
extracted_elements = []
|
14
|
+
|
15
|
+
reject! do |element|
|
16
|
+
extracted_elements << element if yield(element)
|
17
|
+
end
|
18
|
+
|
19
|
+
extracted_elements
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the tail of the array from +position+.
|
23
|
+
#
|
24
|
+
# %w( a b c d ).from(0) # => ["a", "b", "c", "d"]
|
25
|
+
# %w( a b c d ).from(2) # => ["c", "d"]
|
26
|
+
# %w( a b c d ).from(10) # => []
|
27
|
+
# %w().from(0) # => []
|
28
|
+
# %w( a b c d ).from(-2) # => ["c", "d"]
|
29
|
+
# %w( a b c ).from(-10) # => []
|
30
|
+
def from(position)
|
31
|
+
self[position, length] || []
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the beginning of the array up to +position+.
|
35
|
+
#
|
36
|
+
# %w( a b c d ).to(0) # => ["a"]
|
37
|
+
# %w( a b c d ).to(2) # => ["a", "b", "c"]
|
38
|
+
# %w( a b c d ).to(10) # => ["a", "b", "c", "d"]
|
39
|
+
# %w().to(0) # => []
|
40
|
+
# %w( a b c d ).to(-2) # => ["a", "b", "c"]
|
41
|
+
# %w( a b c ).to(-10) # => []
|
42
|
+
def to(position)
|
43
|
+
if position >= 0
|
44
|
+
take position + 1
|
45
|
+
else
|
46
|
+
self[0..position]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a new array that includes the passed elements.
|
51
|
+
#
|
52
|
+
# [ 1, 2, 3 ].including(4, 5) # => [ 1, 2, 3, 4, 5 ]
|
53
|
+
# [ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ]
|
54
|
+
def including(*elements)
|
55
|
+
self + elements.flatten(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns a copy of the Array excluding the specified elements.
|
59
|
+
#
|
60
|
+
# ["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]
|
61
|
+
# [ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ]) # => [ [ 0, 1 ] ]
|
62
|
+
#
|
63
|
+
# Note: This is an optimization of <tt>Enumerable#excluding</tt> that uses <tt>Array#-</tt>
|
64
|
+
# instead of <tt>Array#reject</tt> for performance reasons.
|
65
|
+
def excluding(*elements)
|
66
|
+
self - elements.flatten(1)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Alias for #excluding.
|
70
|
+
def without(*elements)
|
71
|
+
excluding(*elements)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Equal to <tt>self[1]</tt>.
|
75
|
+
#
|
76
|
+
# %w( a b c d e ).second # => "b"
|
77
|
+
def second
|
78
|
+
self[1]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Equal to <tt>self[2]</tt>.
|
82
|
+
#
|
83
|
+
# %w( a b c d e ).third # => "c"
|
84
|
+
def third
|
85
|
+
self[2]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Equal to <tt>self[3]</tt>.
|
89
|
+
#
|
90
|
+
# %w( a b c d e ).fourth # => "d"
|
91
|
+
def fourth
|
92
|
+
self[3]
|
93
|
+
end
|
94
|
+
|
95
|
+
# Equal to <tt>self[4]</tt>.
|
96
|
+
#
|
97
|
+
# %w( a b c d e ).fifth # => "e"
|
98
|
+
def fifth
|
99
|
+
self[4]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
|
103
|
+
#
|
104
|
+
# (1..42).to_a.forty_two # => 42
|
105
|
+
def forty_two
|
106
|
+
self[41]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Equal to <tt>self[-3]</tt>.
|
110
|
+
#
|
111
|
+
# %w( a b c d e ).third_to_last # => "c"
|
112
|
+
def third_to_last
|
113
|
+
self[-3]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Equal to <tt>self[-2]</tt>.
|
117
|
+
#
|
118
|
+
# %w( a b c d e ).second_to_last # => "d"
|
119
|
+
def second_to_last
|
120
|
+
self[-2]
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Digest
|
6
|
+
module UUID
|
7
|
+
DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
|
8
|
+
URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
|
9
|
+
OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
|
10
|
+
X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
|
11
|
+
|
12
|
+
# Generates a v5 non-random UUID (Universally Unique IDentifier).
|
13
|
+
#
|
14
|
+
# Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
|
15
|
+
# uuid_from_hash always generates the same UUID for a given name and namespace combination.
|
16
|
+
#
|
17
|
+
# See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
|
18
|
+
def self.uuid_from_hash(hash_class, uuid_namespace, name)
|
19
|
+
if hash_class == Digest::MD5
|
20
|
+
version = 3
|
21
|
+
elsif hash_class == Digest::SHA1
|
22
|
+
version = 5
|
23
|
+
else
|
24
|
+
raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
|
25
|
+
end
|
26
|
+
|
27
|
+
hash = hash_class.new
|
28
|
+
hash.update(uuid_namespace)
|
29
|
+
hash.update(name)
|
30
|
+
|
31
|
+
ary = hash.digest.unpack("NnnnnN")
|
32
|
+
ary[2] = (ary[2] & 0x0FFF) | (version << 12)
|
33
|
+
ary[3] = (ary[3] & 0x3FFF) | 0x8000
|
34
|
+
|
35
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convenience method for uuid_from_hash using Digest::MD5.
|
39
|
+
def self.uuid_v3(uuid_namespace, name)
|
40
|
+
uuid_from_hash(Digest::MD5, uuid_namespace, name)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Convenience method for uuid_from_hash using Digest::SHA1.
|
44
|
+
def self.uuid_v5(uuid_namespace, name)
|
45
|
+
uuid_from_hash(Digest::SHA1, uuid_namespace, name)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Convenience method for SecureRandom.uuid.
|
49
|
+
def self.uuid_v4
|
50
|
+
SecureRandom.uuid
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
class File
|
6
|
+
# Write to a file atomically. Useful for situations where you don't
|
7
|
+
# want other processes or threads to see half-written files.
|
8
|
+
#
|
9
|
+
# File.atomic_write('important.file') do |file|
|
10
|
+
# file.write('hello')
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# This method needs to create a temporary file. By default it will create it
|
14
|
+
# in the same directory as the destination file. If you don't like this
|
15
|
+
# behavior you can provide a different directory but it must be on the
|
16
|
+
# same physical filesystem as the file you're trying to write.
|
17
|
+
#
|
18
|
+
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
|
19
|
+
# file.write('hello')
|
20
|
+
# end
|
21
|
+
def self.atomic_write(file_name, temp_dir = dirname(file_name))
|
22
|
+
require "tempfile" unless defined?(Tempfile)
|
23
|
+
|
24
|
+
Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
|
25
|
+
temp_file.binmode
|
26
|
+
return_val = yield temp_file
|
27
|
+
temp_file.close
|
28
|
+
|
29
|
+
old_stat = if exist?(file_name)
|
30
|
+
# Get original file permissions
|
31
|
+
stat(file_name)
|
32
|
+
else
|
33
|
+
# If not possible, probe which are the default permissions in the
|
34
|
+
# destination directory.
|
35
|
+
probe_stat_in(dirname(file_name))
|
36
|
+
end
|
37
|
+
|
38
|
+
if old_stat
|
39
|
+
# Set correct permissions on new file
|
40
|
+
begin
|
41
|
+
chown(old_stat.uid, old_stat.gid, temp_file.path)
|
42
|
+
# This operation will affect filesystem ACL's
|
43
|
+
chmod(old_stat.mode, temp_file.path)
|
44
|
+
rescue Errno::EPERM, Errno::EACCES
|
45
|
+
# Changing file ownership failed, moving on.
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Overwrite original file with temp file
|
50
|
+
rename(temp_file.path, file_name)
|
51
|
+
return_val
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Private utility method.
|
56
|
+
def self.probe_stat_in(dir) #:nodoc:
|
57
|
+
basename = [
|
58
|
+
".permissions_check",
|
59
|
+
Thread.current.object_id,
|
60
|
+
Process.pid,
|
61
|
+
rand(1000000)
|
62
|
+
].join(".")
|
63
|
+
|
64
|
+
file_name = join(dir, basename)
|
65
|
+
FileUtils.touch(file_name)
|
66
|
+
stat(file_name)
|
67
|
+
ensure
|
68
|
+
FileUtils.rm_f(file_name) if file_name
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Hash
|
2
|
+
# Returns a hash that includes everything except given keys.
|
3
|
+
# hash = { a: true, b: false, c: nil }
|
4
|
+
# hash.except(:c) # => { a: true, b: false }
|
5
|
+
# hash.except(:a, :b) # => { c: nil }
|
6
|
+
# hash # => { a: true, b: false, c: nil }
|
7
|
+
#
|
8
|
+
# This is useful for limiting a set of parameters to everything but a few known toggles:
|
9
|
+
# @person.update(params[:person].except(:admin))
|
10
|
+
def except(*keys)
|
11
|
+
slice(*self.keys - keys)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Removes the given keys from hash and returns it.
|
15
|
+
# hash = { a: true, b: false, c: nil }
|
16
|
+
# hash.except!(:c) # => { a: true, b: false }
|
17
|
+
# hash # => { a: true, b: false }
|
18
|
+
def except!(*keys)
|
19
|
+
keys.each { |key| delete(key) }
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a new hash with all keys converted to strings.
|
5
|
+
#
|
6
|
+
# hash = { name: 'Rob', age: '28' }
|
7
|
+
#
|
8
|
+
# hash.stringify_keys
|
9
|
+
# # => {"name"=>"Rob", "age"=>"28"}
|
10
|
+
def stringify_keys
|
11
|
+
transform_keys(&:to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Destructively converts all keys to strings. Same as
|
15
|
+
# +stringify_keys+, but modifies +self+.
|
16
|
+
def stringify_keys!
|
17
|
+
transform_keys!(&:to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
21
|
+
# they respond to +to_sym+.
|
22
|
+
#
|
23
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
24
|
+
#
|
25
|
+
# hash.symbolize_keys
|
26
|
+
# # => {:name=>"Rob", :age=>"28"}
|
27
|
+
def symbolize_keys
|
28
|
+
transform_keys { |key| key.to_sym rescue key }
|
29
|
+
end
|
30
|
+
alias_method :to_options, :symbolize_keys
|
31
|
+
|
32
|
+
# Destructively converts all keys to symbols, as long as they respond
|
33
|
+
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
34
|
+
def symbolize_keys!
|
35
|
+
transform_keys! { |key| key.to_sym rescue key }
|
36
|
+
end
|
37
|
+
alias_method :to_options!, :symbolize_keys!
|
38
|
+
|
39
|
+
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
|
40
|
+
# +ArgumentError+ on a mismatch.
|
41
|
+
#
|
42
|
+
# Note that keys are treated differently than HashWithIndifferentAccess,
|
43
|
+
# meaning that string and symbol keys will not match.
|
44
|
+
#
|
45
|
+
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
|
46
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
|
47
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
48
|
+
def assert_valid_keys(*valid_keys)
|
49
|
+
valid_keys.flatten!
|
50
|
+
each_key do |k|
|
51
|
+
unless valid_keys.include?(k)
|
52
|
+
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns a new hash with all keys converted by the block operation.
|
58
|
+
# This includes the keys from the root hash and from all
|
59
|
+
# nested hashes and arrays.
|
60
|
+
#
|
61
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
62
|
+
#
|
63
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
64
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
65
|
+
def deep_transform_keys(&block)
|
66
|
+
_deep_transform_keys_in_object(self, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Destructively converts all keys by using the block operation.
|
70
|
+
# This includes the keys from the root hash and from all
|
71
|
+
# nested hashes and arrays.
|
72
|
+
def deep_transform_keys!(&block)
|
73
|
+
_deep_transform_keys_in_object!(self, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns a new hash with all keys converted to strings.
|
77
|
+
# This includes the keys from the root hash and from all
|
78
|
+
# nested hashes and arrays.
|
79
|
+
#
|
80
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
81
|
+
#
|
82
|
+
# hash.deep_stringify_keys
|
83
|
+
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
|
84
|
+
def deep_stringify_keys
|
85
|
+
deep_transform_keys(&:to_s)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Destructively converts all keys to strings.
|
89
|
+
# This includes the keys from the root hash and from all
|
90
|
+
# nested hashes and arrays.
|
91
|
+
def deep_stringify_keys!
|
92
|
+
deep_transform_keys!(&:to_s)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
96
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
97
|
+
# and from all nested hashes and arrays.
|
98
|
+
#
|
99
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
100
|
+
#
|
101
|
+
# hash.deep_symbolize_keys
|
102
|
+
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
103
|
+
def deep_symbolize_keys
|
104
|
+
deep_transform_keys { |key| key.to_sym rescue key }
|
105
|
+
end
|
106
|
+
|
107
|
+
# Destructively converts all keys to symbols, as long as they respond
|
108
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
109
|
+
# nested hashes and arrays.
|
110
|
+
def deep_symbolize_keys!
|
111
|
+
deep_transform_keys! { |key| key.to_sym rescue key }
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
# support methods for deep transforming nested hashes and arrays
|
116
|
+
def _deep_transform_keys_in_object(object, &block)
|
117
|
+
case object
|
118
|
+
when Hash
|
119
|
+
object.each_with_object({}) do |(key, value), result|
|
120
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
121
|
+
end
|
122
|
+
when Array
|
123
|
+
object.map { |e| _deep_transform_keys_in_object(e, &block) }
|
124
|
+
else
|
125
|
+
object
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def _deep_transform_keys_in_object!(object, &block)
|
130
|
+
case object
|
131
|
+
when Hash
|
132
|
+
object.keys.each do |key|
|
133
|
+
value = object.delete(key)
|
134
|
+
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
|
135
|
+
end
|
136
|
+
object
|
137
|
+
when Array
|
138
|
+
object.map! { |e| _deep_transform_keys_in_object!(e, &block) }
|
139
|
+
else
|
140
|
+
object
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
|
+
#
|
6
|
+
# h1 = { a: true, b: { c: [1, 2, 3] } }
|
7
|
+
# h2 = { a: false, b: { x: [3, 4, 5] } }
|
8
|
+
#
|
9
|
+
# h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
|
10
|
+
#
|
11
|
+
# Like with Hash#merge in the standard library, a block can be provided
|
12
|
+
# to merge values:
|
13
|
+
#
|
14
|
+
# h1 = { a: 100, b: 200, c: { c1: 100 } }
|
15
|
+
# h2 = { b: 250, c: { c1: 200 } }
|
16
|
+
# h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
17
|
+
# # => { a: 100, b: 450, c: { c1: 300 } }
|
18
|
+
def deep_merge(other_hash, &block)
|
19
|
+
dup.deep_merge!(other_hash, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Same as +deep_merge+, but modifies +self+.
|
23
|
+
def deep_merge!(other_hash, &block)
|
24
|
+
merge!(other_hash) do |key, this_val, other_val|
|
25
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
26
|
+
this_val.deep_merge(other_val, &block)
|
27
|
+
elsif block_given?
|
28
|
+
block.call(key, this_val, other_val)
|
29
|
+
else
|
30
|
+
other_val
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Merges the caller into +other_hash+. For example,
|
36
|
+
#
|
37
|
+
# options = options.reverse_merge(size: 25, velocity: 10)
|
38
|
+
#
|
39
|
+
# is equivalent to
|
40
|
+
#
|
41
|
+
# options = { size: 25, velocity: 10 }.merge(options)
|
42
|
+
#
|
43
|
+
# This is particularly useful for initializing an options hash
|
44
|
+
# with default values.
|
45
|
+
def reverse_merge(other_hash)
|
46
|
+
other_hash.merge(self)
|
47
|
+
end
|
48
|
+
alias_method :with_defaults, :reverse_merge
|
49
|
+
|
50
|
+
# Destructive +reverse_merge+.
|
51
|
+
def reverse_merge!(other_hash)
|
52
|
+
replace(reverse_merge(other_hash))
|
53
|
+
end
|
54
|
+
alias_method :reverse_update, :reverse_merge!
|
55
|
+
alias_method :with_defaults!, :reverse_merge!
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Replaces the hash with only the given keys.
|
5
|
+
# Returns a hash containing the removed key/value pairs.
|
6
|
+
#
|
7
|
+
# hash = { a: 1, b: 2, c: 3, d: 4 }
|
8
|
+
# hash.slice!(:a, :b) # => {:c=>3, :d=>4}
|
9
|
+
# hash # => {:a=>1, :b=>2}
|
10
|
+
def slice!(*keys)
|
11
|
+
omit = slice(*self.keys - keys)
|
12
|
+
hash = slice(*keys)
|
13
|
+
hash.default = default
|
14
|
+
hash.default_proc = default_proc if default_proc
|
15
|
+
replace(hash)
|
16
|
+
omit
|
17
|
+
end
|
18
|
+
|
19
|
+
# Removes and returns the key/value pairs matching the given keys.
|
20
|
+
#
|
21
|
+
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
|
22
|
+
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
|
23
|
+
def extract!(*keys)
|
24
|
+
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Returns a new hash with all values converted by the block operation.
|
5
|
+
# This includes the values from the root hash and from all
|
6
|
+
# nested hashes and arrays.
|
7
|
+
#
|
8
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
9
|
+
#
|
10
|
+
# hash.deep_transform_values{ |value| value.to_s.upcase }
|
11
|
+
# # => {person: {name: "ROB", age: "28"}}
|
12
|
+
def deep_transform_values(&block)
|
13
|
+
_deep_transform_values_in_object(self, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Destructively converts all values by using the block operation.
|
17
|
+
# This includes the values from the root hash and from all
|
18
|
+
# nested hashes and arrays.
|
19
|
+
def deep_transform_values!(&block)
|
20
|
+
_deep_transform_values_in_object!(self, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
# support methods for deep transforming nested hashes and arrays
|
25
|
+
def _deep_transform_values_in_object(object, &block)
|
26
|
+
case object
|
27
|
+
when Hash
|
28
|
+
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
|
29
|
+
when Array
|
30
|
+
object.map { |e| _deep_transform_values_in_object(e, &block) }
|
31
|
+
else
|
32
|
+
yield(object)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def _deep_transform_values_in_object!(object, &block)
|
37
|
+
case object
|
38
|
+
when Hash
|
39
|
+
object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
|
40
|
+
when Array
|
41
|
+
object.map! { |e| _deep_transform_values_in_object!(e, &block) }
|
42
|
+
else
|
43
|
+
yield(object)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Numeric
|
4
|
+
KILOBYTE = 1024
|
5
|
+
MEGABYTE = KILOBYTE * 1024
|
6
|
+
GIGABYTE = MEGABYTE * 1024
|
7
|
+
TERABYTE = GIGABYTE * 1024
|
8
|
+
PETABYTE = TERABYTE * 1024
|
9
|
+
EXABYTE = PETABYTE * 1024
|
10
|
+
|
11
|
+
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
|
12
|
+
#
|
13
|
+
# 2.bytes # => 2
|
14
|
+
def bytes
|
15
|
+
self
|
16
|
+
end
|
17
|
+
alias :byte :bytes
|
18
|
+
|
19
|
+
# Returns the number of bytes equivalent to the kilobytes provided.
|
20
|
+
#
|
21
|
+
# 2.kilobytes # => 2048
|
22
|
+
def kilobytes
|
23
|
+
self * KILOBYTE
|
24
|
+
end
|
25
|
+
alias :kilobyte :kilobytes
|
26
|
+
|
27
|
+
# Returns the number of bytes equivalent to the megabytes provided.
|
28
|
+
#
|
29
|
+
# 2.megabytes # => 2_097_152
|
30
|
+
def megabytes
|
31
|
+
self * MEGABYTE
|
32
|
+
end
|
33
|
+
alias :megabyte :megabytes
|
34
|
+
|
35
|
+
# Returns the number of bytes equivalent to the gigabytes provided.
|
36
|
+
#
|
37
|
+
# 2.gigabytes # => 2_147_483_648
|
38
|
+
def gigabytes
|
39
|
+
self * GIGABYTE
|
40
|
+
end
|
41
|
+
alias :gigabyte :gigabytes
|
42
|
+
|
43
|
+
# Returns the number of bytes equivalent to the terabytes provided.
|
44
|
+
#
|
45
|
+
# 2.terabytes # => 2_199_023_255_552
|
46
|
+
def terabytes
|
47
|
+
self * TERABYTE
|
48
|
+
end
|
49
|
+
alias :terabyte :terabytes
|
50
|
+
|
51
|
+
# Returns the number of bytes equivalent to the petabytes provided.
|
52
|
+
#
|
53
|
+
# 2.petabytes # => 2_251_799_813_685_248
|
54
|
+
def petabytes
|
55
|
+
self * PETABYTE
|
56
|
+
end
|
57
|
+
alias :petabyte :petabytes
|
58
|
+
|
59
|
+
# Returns the number of bytes equivalent to the exabytes provided.
|
60
|
+
#
|
61
|
+
# 2.exabytes # => 2_305_843_009_213_693_952
|
62
|
+
def exabytes
|
63
|
+
self * EXABYTE
|
64
|
+
end
|
65
|
+
alias :exabyte :exabytes
|
66
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'concurrent/map'
|
3
|
+
|
4
|
+
class Object
|
5
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
6
|
+
# For example, +nil+, '', ' ', [], {}, and +false+ are all blank.
|
7
|
+
#
|
8
|
+
# This simplifies
|
9
|
+
#
|
10
|
+
# !address || address.empty?
|
11
|
+
#
|
12
|
+
# to
|
13
|
+
#
|
14
|
+
# address.blank?
|
15
|
+
#
|
16
|
+
# @return [true, false]
|
17
|
+
def blank?
|
18
|
+
respond_to?(:empty?) ? !!empty? : !self
|
19
|
+
end
|
20
|
+
|
21
|
+
# An object is present if it's not blank.
|
22
|
+
#
|
23
|
+
# @return [true, false]
|
24
|
+
def present?
|
25
|
+
!blank?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the receiver if it's present otherwise returns +nil+.
|
29
|
+
# <tt>object.presence</tt> is equivalent to
|
30
|
+
#
|
31
|
+
# object.present? ? object : nil
|
32
|
+
#
|
33
|
+
# For example, something like
|
34
|
+
#
|
35
|
+
# state = params[:state] if params[:state].present?
|
36
|
+
# country = params[:country] if params[:country].present?
|
37
|
+
# region = state || country || 'US'
|
38
|
+
#
|
39
|
+
# becomes
|
40
|
+
#
|
41
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
42
|
+
#
|
43
|
+
# @return [Object]
|
44
|
+
def presence
|
45
|
+
self if present?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class NilClass
|
50
|
+
# +nil+ is blank:
|
51
|
+
#
|
52
|
+
# nil.blank? # => true
|
53
|
+
#
|
54
|
+
# @return [true]
|
55
|
+
def blank?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class FalseClass
|
61
|
+
# +false+ is blank:
|
62
|
+
#
|
63
|
+
# false.blank? # => true
|
64
|
+
#
|
65
|
+
# @return [true]
|
66
|
+
def blank?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class TrueClass
|
72
|
+
# +true+ is not blank:
|
73
|
+
#
|
74
|
+
# true.blank? # => false
|
75
|
+
#
|
76
|
+
# @return [false]
|
77
|
+
def blank?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Array
|
83
|
+
# An array is blank if it's empty:
|
84
|
+
#
|
85
|
+
# [].blank? # => true
|
86
|
+
# [1,2,3].blank? # => false
|
87
|
+
#
|
88
|
+
# @return [true, false]
|
89
|
+
alias_method :blank?, :empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
class Hash
|
93
|
+
# A hash is blank if it's empty:
|
94
|
+
#
|
95
|
+
# {}.blank? # => true
|
96
|
+
# { key: 'value' }.blank? # => false
|
97
|
+
#
|
98
|
+
# @return [true, false]
|
99
|
+
alias_method :blank?, :empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
class String
|
103
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
104
|
+
ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
|
105
|
+
h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
|
106
|
+
end
|
107
|
+
|
108
|
+
# A string is blank if it's empty or contains whitespaces only:
|
109
|
+
#
|
110
|
+
# ''.blank? # => true
|
111
|
+
# ' '.blank? # => true
|
112
|
+
# "\t\n\r".blank? # => true
|
113
|
+
# ' blah '.blank? # => false
|
114
|
+
#
|
115
|
+
# Unicode whitespace is supported:
|
116
|
+
#
|
117
|
+
# "\u00a0".blank? # => true
|
118
|
+
#
|
119
|
+
# @return [true, false]
|
120
|
+
def blank?
|
121
|
+
# The regexp that matches blank strings is expensive. For the case of empty
|
122
|
+
# strings we can speed up this method (~3.5x) with an empty? call. The
|
123
|
+
# penalty for the rest of strings is marginal.
|
124
|
+
empty? ||
|
125
|
+
begin
|
126
|
+
BLANK_RE.match?(self)
|
127
|
+
rescue Encoding::CompatibilityError
|
128
|
+
ENCODED_BLANKS[self.encoding].match?(self)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Numeric #:nodoc:
|
134
|
+
# No number is blank:
|
135
|
+
#
|
136
|
+
# 1.blank? # => false
|
137
|
+
# 0.blank? # => false
|
138
|
+
#
|
139
|
+
# @return [false]
|
140
|
+
def blank?
|
141
|
+
false
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Time #:nodoc:
|
146
|
+
# No Time is blank:
|
147
|
+
#
|
148
|
+
# Time.now.blank? # => false
|
149
|
+
#
|
150
|
+
# @return [false]
|
151
|
+
def blank?
|
152
|
+
false
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# Returns true if this object is included in the argument. Argument must be
|
5
|
+
# any object which responds to +#include?+. Usage:
|
6
|
+
#
|
7
|
+
# characters = ["Konata", "Kagami", "Tsukasa"]
|
8
|
+
# "Konata".in?(characters) # => true
|
9
|
+
#
|
10
|
+
# This will throw an +ArgumentError+ if the argument doesn't respond
|
11
|
+
# to +#include?+.
|
12
|
+
def in?(another_object)
|
13
|
+
another_object.include?(self)
|
14
|
+
rescue NoMethodError
|
15
|
+
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the receiver if it's included in the argument otherwise returns +nil+.
|
19
|
+
# Argument must be any object which responds to +#include?+. Usage:
|
20
|
+
#
|
21
|
+
# params[:bucket_type].presence_in %w( project calendar )
|
22
|
+
#
|
23
|
+
# This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+.
|
24
|
+
#
|
25
|
+
# @return [Object]
|
26
|
+
def presence_in(another_object)
|
27
|
+
in?(another_object) ? self : nil
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Object
|
4
|
+
# Returns a hash with string keys that maps instance variable names without "@" to their
|
5
|
+
# corresponding values.
|
6
|
+
#
|
7
|
+
# class C
|
8
|
+
# def initialize(x, y)
|
9
|
+
# @x, @y = x, y
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
|
14
|
+
def instance_values
|
15
|
+
Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns an array of instance variable names as strings including "@".
|
19
|
+
#
|
20
|
+
# class C
|
21
|
+
# def initialize(x, y)
|
22
|
+
# @x, @y = x, y
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
|
27
|
+
def instance_variable_names
|
28
|
+
instance_variables.map(&:to_s)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
class Object
|
6
|
+
# Alias of <tt>to_s</tt>.
|
7
|
+
def to_param
|
8
|
+
to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
# Converts an object into a string suitable for use as a URL query string,
|
12
|
+
# using the given <tt>key</tt> as the param name.
|
13
|
+
def to_query(key)
|
14
|
+
"#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class NilClass
|
19
|
+
# Returns +self+.
|
20
|
+
def to_param
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TrueClass
|
26
|
+
# Returns +self+.
|
27
|
+
def to_param
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class FalseClass
|
33
|
+
# Returns +self+.
|
34
|
+
def to_param
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Array
|
40
|
+
# Calls <tt>to_param</tt> on all its elements and joins the result with
|
41
|
+
# slashes. This is used by <tt>url_for</tt> in Action Pack.
|
42
|
+
def to_param
|
43
|
+
collect(&:to_param).join "/"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Converts an array into a string suitable for use as a URL query string,
|
47
|
+
# using the given +key+ as the param name.
|
48
|
+
#
|
49
|
+
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
|
50
|
+
def to_query(key)
|
51
|
+
prefix = "#{key}[]"
|
52
|
+
|
53
|
+
if empty?
|
54
|
+
nil.to_query(prefix)
|
55
|
+
else
|
56
|
+
collect { |value| value.to_query(prefix) }.join "&"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Hash
|
62
|
+
# Returns a string representation of the receiver suitable for use as a URL
|
63
|
+
# query string:
|
64
|
+
#
|
65
|
+
# {name: 'David', nationality: 'Danish'}.to_query
|
66
|
+
# # => "name=David&nationality=Danish"
|
67
|
+
#
|
68
|
+
# An optional namespace can be passed to enclose key names:
|
69
|
+
#
|
70
|
+
# {name: 'David', nationality: 'Danish'}.to_query('user')
|
71
|
+
# # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
|
72
|
+
#
|
73
|
+
# The string pairs "key=value" that conform the query string
|
74
|
+
# are sorted lexicographically in ascending order.
|
75
|
+
#
|
76
|
+
# This method is also aliased as +to_param+.
|
77
|
+
def to_query(namespace = nil)
|
78
|
+
query = collect do |key, value|
|
79
|
+
unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
|
80
|
+
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
81
|
+
end
|
82
|
+
end.compact
|
83
|
+
|
84
|
+
query.sort! unless namespace.to_s.include?("[]")
|
85
|
+
query.join("&")
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :to_param, :to_query
|
89
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Object
|
4
|
+
##
|
5
|
+
# :method: try
|
6
|
+
#
|
7
|
+
# :call-seq:
|
8
|
+
# try(*a, &b)
|
9
|
+
#
|
10
|
+
# Invokes the public method whose name goes as first argument just like
|
11
|
+
# +public_send+ does, except that if the receiver does not respond to it the
|
12
|
+
# call returns +nil+ rather than raising an exception.
|
13
|
+
#
|
14
|
+
# This method is defined to be able to write
|
15
|
+
#
|
16
|
+
# @person.try(:name)
|
17
|
+
#
|
18
|
+
# instead of
|
19
|
+
#
|
20
|
+
# @person.name if @person
|
21
|
+
#
|
22
|
+
# +try+ calls can be chained:
|
23
|
+
#
|
24
|
+
# @person.try(:spouse).try(:name)
|
25
|
+
#
|
26
|
+
# instead of
|
27
|
+
#
|
28
|
+
# @person.spouse.name if @person && @person.spouse
|
29
|
+
#
|
30
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
31
|
+
#
|
32
|
+
# @person.try(:non_existing_method) # => nil
|
33
|
+
#
|
34
|
+
# instead of
|
35
|
+
#
|
36
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
|
37
|
+
#
|
38
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
39
|
+
# to the method:
|
40
|
+
#
|
41
|
+
# nil.try(:to_i) # => nil, rather than 0
|
42
|
+
#
|
43
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
44
|
+
#
|
45
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
46
|
+
# ...
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# The number of arguments in the signature must match. If the object responds
|
50
|
+
# to the method the call is attempted and +ArgumentError+ is still raised
|
51
|
+
# in case of argument mismatch.
|
52
|
+
#
|
53
|
+
# If +try+ is called without arguments it yields the receiver to a given
|
54
|
+
# block unless it is +nil+:
|
55
|
+
#
|
56
|
+
# @person.try do |p|
|
57
|
+
# ...
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# You can also call try with a block without accepting an argument, and the block
|
61
|
+
# will be instance_eval'ed instead:
|
62
|
+
#
|
63
|
+
# @person.try { upcase.truncate(50) }
|
64
|
+
#
|
65
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
66
|
+
# with instances of classes that do not have +Object+ among their ancestors,
|
67
|
+
# like direct subclasses of +BasicObject+.
|
68
|
+
|
69
|
+
##
|
70
|
+
# :method: try!
|
71
|
+
#
|
72
|
+
# :call-seq:
|
73
|
+
# try!(*a, &b)
|
74
|
+
#
|
75
|
+
# Same as #try, but raises a +NoMethodError+ exception if the receiver is
|
76
|
+
# not +nil+ and does not implement the tried method.
|
77
|
+
#
|
78
|
+
# "a".try!(:upcase) # => "A"
|
79
|
+
# nil.try!(:upcase) # => nil
|
80
|
+
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
|
81
|
+
def try(method_name = nil, *args, &b)
|
82
|
+
if method_name.nil? && block_given?
|
83
|
+
if b.arity == 0
|
84
|
+
instance_eval(&b)
|
85
|
+
else
|
86
|
+
yield self
|
87
|
+
end
|
88
|
+
elsif respond_to?(method_name)
|
89
|
+
public_send(method_name, *args, &b)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def try!(method_name = nil, *args, &b)
|
94
|
+
if method_name.nil? && block_given?
|
95
|
+
if b.arity == 0
|
96
|
+
instance_eval(&b)
|
97
|
+
else
|
98
|
+
yield self
|
99
|
+
end
|
100
|
+
else
|
101
|
+
public_send(method_name, *args, &b)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class NilClass
|
107
|
+
# Calling +try+ on +nil+ always returns +nil+.
|
108
|
+
# It becomes especially helpful when navigating through associations that may return +nil+.
|
109
|
+
#
|
110
|
+
# nil.try(:name) # => nil
|
111
|
+
#
|
112
|
+
# Without +try+
|
113
|
+
# @person && @person.children.any? && @person.children.first.name
|
114
|
+
#
|
115
|
+
# With +try+
|
116
|
+
# @person.try(:children).try(:first).try(:name)
|
117
|
+
def try(method_name = nil, *args)
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
# Calling +try!+ on +nil+ always returns +nil+.
|
122
|
+
#
|
123
|
+
# nil.try!(:name) # => nil
|
124
|
+
def try!(method_name = nil, *args)
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
end
|
data/lib/simple_ext.rb
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_ext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tushar Hawaldar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-02-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Core ruby extentions extracted from Rails ActiveSupport
|
14
|
+
email:
|
15
|
+
- kumartushar1111@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/simple_ext.rb
|
21
|
+
- lib/simple_ext/array.rb
|
22
|
+
- lib/simple_ext/array/access.rb
|
23
|
+
- lib/simple_ext/digest.rb
|
24
|
+
- lib/simple_ext/digest/uuid.rb
|
25
|
+
- lib/simple_ext/file.rb
|
26
|
+
- lib/simple_ext/file/atomic.rb
|
27
|
+
- lib/simple_ext/hash.rb
|
28
|
+
- lib/simple_ext/hash/except.rb
|
29
|
+
- lib/simple_ext/hash/keys.rb
|
30
|
+
- lib/simple_ext/hash/merge.rb
|
31
|
+
- lib/simple_ext/hash/slice.rb
|
32
|
+
- lib/simple_ext/hash/values.rb
|
33
|
+
- lib/simple_ext/numeric.rb
|
34
|
+
- lib/simple_ext/numeric/bytes.rb
|
35
|
+
- lib/simple_ext/object.rb
|
36
|
+
- lib/simple_ext/object/blank.rb
|
37
|
+
- lib/simple_ext/object/inclusion.rb
|
38
|
+
- lib/simple_ext/object/instance_variables.rb
|
39
|
+
- lib/simple_ext/object/to_query.rb
|
40
|
+
- lib/simple_ext/object/try.rb
|
41
|
+
homepage: https://github.com/kumartushar/simple_ext.git
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.1.2
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Simple Ruby Extentions
|
64
|
+
test_files: []
|