everythingrb 0.6.1 → 0.8.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 +4 -4
- data/CHANGELOG.md +65 -1
- data/README.md +469 -46
- data/lib/everythingrb/all.rb +8 -0
- data/lib/everythingrb/array.rb +5 -21
- data/lib/everythingrb/boolean.rb +33 -0
- data/lib/everythingrb/data.rb +7 -0
- data/lib/everythingrb/date.rb +31 -0
- data/lib/everythingrb/enumerable.rb +4 -2
- data/lib/everythingrb/extensions/quotable.rb +82 -0
- data/lib/everythingrb/extensions.rb +3 -0
- data/lib/everythingrb/hash.rb +247 -24
- data/lib/everythingrb/kernel.rb +35 -0
- data/lib/everythingrb/nil.rb +15 -0
- data/lib/everythingrb/numeric.rb +18 -0
- data/lib/everythingrb/ostruct.rb +11 -1
- data/lib/everythingrb/prelude.rb +1 -0
- data/lib/everythingrb/range.rb +20 -0
- data/lib/everythingrb/regexp.rb +19 -0
- data/lib/everythingrb/string.rb +42 -9
- data/lib/everythingrb/struct.rb +7 -0
- data/lib/everythingrb/symbol.rb +3 -3
- data/lib/everythingrb/time.rb +19 -0
- data/lib/everythingrb/version.rb +1 -1
- metadata +12 -2
data/lib/everythingrb/array.rb
CHANGED
@@ -6,7 +6,6 @@
|
|
6
6
|
# Provides:
|
7
7
|
# - #join_map: Combine filter_map and join operations in one step
|
8
8
|
# - #key_map, #dig_map: Extract values from arrays of hashes
|
9
|
-
# - #deep_freeze: Recursively freeze array and contents
|
10
9
|
# - #compact_prefix, #compact_suffix, #trim_nils: Clean up array boundaries
|
11
10
|
# - ActiveSupport integrations: #trim_blanks and more when ActiveSupport is loaded
|
12
11
|
#
|
@@ -16,6 +15,8 @@
|
|
16
15
|
# [{name: "Alice"}, {name: "Bob"}].key_map(:name) # => ["Alice", "Bob"]
|
17
16
|
#
|
18
17
|
class Array
|
18
|
+
include Everythingrb::InspectQuotable
|
19
|
+
|
19
20
|
#
|
20
21
|
# Combines filter_map and join operations
|
21
22
|
#
|
@@ -73,34 +74,17 @@ class Array
|
|
73
74
|
# @return [Array] Array of nested values
|
74
75
|
#
|
75
76
|
# @example
|
76
|
-
# [
|
77
|
+
# users = [
|
77
78
|
# {user: {profile: {name: "Alice"}}},
|
78
79
|
# {user: {profile: {name: "Bob"}}}
|
79
|
-
# ]
|
80
|
+
# ]
|
81
|
+
# users.dig_map(:user, :profile, :name)
|
80
82
|
# # => ["Alice", "Bob"]
|
81
83
|
#
|
82
84
|
def dig_map(*keys)
|
83
85
|
map { |v| v.dig(*keys) }
|
84
86
|
end
|
85
87
|
|
86
|
-
#
|
87
|
-
# Recursively freezes self and all of its contents
|
88
|
-
#
|
89
|
-
# @return [self] Returns the frozen array
|
90
|
-
#
|
91
|
-
# @example Freeze an array with nested structures
|
92
|
-
# ["hello", { name: "Alice" }, [1, 2, 3]].deep_freeze
|
93
|
-
# # => All elements and nested structures are now frozen
|
94
|
-
#
|
95
|
-
# @note CAUTION: Be careful when freezing collections that contain class objects
|
96
|
-
# or singleton instances - this will freeze those classes/objects globally!
|
97
|
-
# Only use deep_freeze on pure data structures you want to make immutable.
|
98
|
-
#
|
99
|
-
def deep_freeze
|
100
|
-
each { |v| v.respond_to?(:deep_freeze) ? v.deep_freeze : v.freeze }
|
101
|
-
freeze
|
102
|
-
end
|
103
|
-
|
104
88
|
#
|
105
89
|
# Removes nil values from the beginning of an array
|
106
90
|
#
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core TrueClass
|
5
|
+
#
|
6
|
+
# Provides:
|
7
|
+
# - #in_quotes, #with_quotes: Wrap boolean values in quotes
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "everythingrb/boolean"
|
11
|
+
#
|
12
|
+
# true.in_quotes # => "\"true\""
|
13
|
+
# false.in_quotes # => "\"false\""
|
14
|
+
#
|
15
|
+
class TrueClass
|
16
|
+
include Everythingrb::InspectQuotable
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Extensions to Ruby's core FalseClass
|
21
|
+
#
|
22
|
+
# Provides:
|
23
|
+
# - #in_quotes, #with_quotes: Wrap boolean values in quotes
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# require "everythingrb/boolean"
|
27
|
+
#
|
28
|
+
# true.in_quotes # => "\"true\""
|
29
|
+
# false.in_quotes # => "\"false\""
|
30
|
+
#
|
31
|
+
class FalseClass
|
32
|
+
include Everythingrb::InspectQuotable
|
33
|
+
end
|
data/lib/everythingrb/data.rb
CHANGED
@@ -5,11 +5,18 @@
|
|
5
5
|
#
|
6
6
|
# Provides:
|
7
7
|
# - #to_deep_h: Recursively convert to hash with all nested objects
|
8
|
+
# - #in_quotes, #with_quotes: Wrap object in quotes
|
8
9
|
#
|
9
10
|
class Data
|
11
|
+
include Everythingrb::InspectQuotable
|
12
|
+
|
10
13
|
#
|
11
14
|
# Recursively converts the Data object and all nested objects to hashes
|
12
15
|
#
|
16
|
+
# This method traverses the entire Data structure, converting not just
|
17
|
+
# the top-level Data object but also nested Data objects, Structs, OpenStructs,
|
18
|
+
# and any other objects that implement `to_h`.
|
19
|
+
#
|
13
20
|
# @return [Hash] A deeply converted hash of the Data object
|
14
21
|
#
|
15
22
|
# @example
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Date class
|
5
|
+
#
|
6
|
+
# Provides:
|
7
|
+
# - #in_quotes, #with_quotes: Wrap date representations in quotes
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "everythingrb/date"
|
11
|
+
#
|
12
|
+
# Date.today.in_quotes
|
13
|
+
#
|
14
|
+
class Date
|
15
|
+
include Everythingrb::StringQuotable
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Extensions to Ruby's core DateTime class
|
20
|
+
#
|
21
|
+
# Provides:
|
22
|
+
# - #in_quotes, #with_quotes: Wrap time representations in quotes
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# require "everythingrb/date"
|
26
|
+
#
|
27
|
+
# DateTime.now.in_quotes
|
28
|
+
#
|
29
|
+
class DateTime < Date
|
30
|
+
include Everythingrb::StringQuotable
|
31
|
+
end
|
@@ -4,8 +4,8 @@
|
|
4
4
|
# Extensions to Ruby's core Enumerable module
|
5
5
|
#
|
6
6
|
# Provides:
|
7
|
-
# - #join_map: Combine filter_map and join operations
|
8
|
-
# - #group_by_key: Group elements by
|
7
|
+
# - #join_map: Combine filter_map and join operations into one step
|
8
|
+
# - #group_by_key: Group elements by key or nested keys, simplifying collection organization
|
9
9
|
#
|
10
10
|
# @example
|
11
11
|
# require "everythingrb/enumerable"
|
@@ -24,6 +24,7 @@ module Enumerable
|
|
24
24
|
# @yieldparam index [Integer] The index of the current element (only if with_index: true)
|
25
25
|
#
|
26
26
|
# @return [String] Joined string of filtered and transformed elements
|
27
|
+
# @return [Enumerator] If no block is given
|
27
28
|
#
|
28
29
|
# @example Without index
|
29
30
|
# [1, 2, nil, 3].join_map(" ") { |n| n&.to_s if n&.odd? }
|
@@ -57,6 +58,7 @@ module Enumerable
|
|
57
58
|
# @yieldreturn [Object] The transformed value to use as the group key
|
58
59
|
#
|
59
60
|
# @return [Hash] A hash where keys are the grouped values and values are arrays of elements
|
61
|
+
# @return [Enumerator] If no block is given
|
60
62
|
#
|
61
63
|
# @example Group by a single key
|
62
64
|
# users = [
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions for making objects quotable with double quotes
|
5
|
+
#
|
6
|
+
# These modules add the ability to quote objects by wrapping
|
7
|
+
# their string representations in double quotes. This is useful for
|
8
|
+
# generating error messages, debug output, or formatted logs where
|
9
|
+
# you want to clearly distinguish between different value types.
|
10
|
+
#
|
11
|
+
# @example Basic usage with different types
|
12
|
+
# 1.in_quotes # => "\"1\""
|
13
|
+
# nil.in_quotes # => "\"nil\""
|
14
|
+
# "hello".in_quotes # => "\"\\\"hello\\\"\""
|
15
|
+
# [1, 2, 3].in_quotes # => "\"[1, 2, 3]\""
|
16
|
+
#
|
17
|
+
# @example Using with collections
|
18
|
+
# values = [1, nil, "hello"]
|
19
|
+
# values.map(&:in_quotes) # => ["\"1\"", "\"nil\"", "\"\\\"hello\\\"\""]
|
20
|
+
#
|
21
|
+
# @example Error messages
|
22
|
+
# expected = 42
|
23
|
+
# actual = nil
|
24
|
+
# raise "Expected #{expected.in_quotes} but got #{actual.in_quotes}"
|
25
|
+
# # => "Expected \"42\" but got \"nil\""
|
26
|
+
#
|
27
|
+
module Everythingrb
|
28
|
+
#
|
29
|
+
# Adds quotable functionality using inspect representation
|
30
|
+
#
|
31
|
+
# Provides methods for wrapping an object's inspection
|
32
|
+
# representation in double quotes.
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# [1, 2, 3].in_quotes # => "\"[1, 2, 3]\""
|
36
|
+
#
|
37
|
+
module InspectQuotable
|
38
|
+
#
|
39
|
+
# Adds quotable functionality using inspect representation
|
40
|
+
#
|
41
|
+
# Provides methods for wrapping an object's inspection
|
42
|
+
# representation in double quotes.
|
43
|
+
#
|
44
|
+
# @return [String] The object's inspect representation surrounded by double quotes
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# [1, 2, 3].in_quotes # => "\"[1, 2, 3]\""
|
48
|
+
# {a: 1}.in_quotes # => "\"{:a=>1}\""
|
49
|
+
#
|
50
|
+
def in_quotes
|
51
|
+
%("#{inspect}")
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :with_quotes, :in_quotes
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Adds quotable functionality using to_s representation
|
59
|
+
#
|
60
|
+
# Provides methods for wrapping an object's string
|
61
|
+
# representation in double quotes.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# Time.now.in_quotes # => "\"2025-05-03 12:34:56 -0400\""
|
65
|
+
#
|
66
|
+
module StringQuotable
|
67
|
+
#
|
68
|
+
# Returns the object's string representation wrapped in double quotes
|
69
|
+
#
|
70
|
+
# @return [String] The object's to_s representation surrounded by double quotes
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# Date.today.in_quotes # => "\"2025-05-03\""
|
74
|
+
# Time.now.in_quotes # => "\"2025-05-03 12:34:56 -0400\""
|
75
|
+
#
|
76
|
+
def in_quotes
|
77
|
+
%("#{self}")
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :with_quotes, :in_quotes
|
81
|
+
end
|
82
|
+
end
|
data/lib/everythingrb/hash.rb
CHANGED
@@ -6,12 +6,14 @@
|
|
6
6
|
# Provides:
|
7
7
|
# - #to_struct, #to_ostruct, #to_istruct: Convert hashes to different structures
|
8
8
|
# - #join_map: Combine filter_map and join operations
|
9
|
-
# - #deep_freeze: Recursively freeze hash and contents
|
10
9
|
# - #transform_values.with_key: Transform values with access to keys
|
11
10
|
# - #transform, #transform!: Transform keys and values
|
12
11
|
# - #value_where, #values_where: Find values based on conditions
|
13
12
|
# - #rename_key, #rename_keys: Rename hash keys while preserving order
|
14
13
|
# - ::new_nested_hash: Create automatically nesting hashes
|
14
|
+
# - #merge_if, #merge_if!: Conditionally merge based on key-value pairs
|
15
|
+
# - #merge_if_values, #merge_if_values!: Conditionally merge based on values
|
16
|
+
# - #merge_compact, #merge_compact!: Merge only non-nil values
|
15
17
|
#
|
16
18
|
# @example
|
17
19
|
# require "everythingrb/hash"
|
@@ -19,6 +21,8 @@
|
|
19
21
|
# config.server.port # => 443
|
20
22
|
#
|
21
23
|
class Hash
|
24
|
+
include Everythingrb::InspectQuotable
|
25
|
+
|
22
26
|
#
|
23
27
|
# A minimal empty struct for Ruby 3.2+ compatibility
|
24
28
|
#
|
@@ -46,6 +50,10 @@ class Hash
|
|
46
50
|
#
|
47
51
|
# @return [Hash] A hash that creates nested hashes for missing keys
|
48
52
|
#
|
53
|
+
# @note This implementation is not thread-safe for concurrent modifications of deeply
|
54
|
+
# nested structures. If you need thread safety, consider using a mutex when modifying
|
55
|
+
# the deeper levels of the hash.
|
56
|
+
#
|
49
57
|
# @example Unlimited nesting (default behavior)
|
50
58
|
# users = Hash.new_nested_hash
|
51
59
|
# users[:john][:role] = "admin" # No need to initialize users[:john] first
|
@@ -113,7 +121,7 @@ class Hash
|
|
113
121
|
# and calls #to_h on any object that responds to it. Useful for
|
114
122
|
# normalizing nested data structures and parsing nested JSON.
|
115
123
|
#
|
116
|
-
# @return [Hash] A deeply converted hash with all nested objects
|
124
|
+
# @return [Hash] A deeply converted hash with all nested objects
|
117
125
|
#
|
118
126
|
# @example Converting nested Data objects
|
119
127
|
# user = { name: "Alice", metadata: Data.define(:source).new(source: "API") }
|
@@ -233,28 +241,10 @@ class Hash
|
|
233
241
|
OpenStruct.new(**transform_values { |value| recurse.call(value) })
|
234
242
|
end
|
235
243
|
|
236
|
-
#
|
237
|
-
# Recursively freezes self and all of its values
|
238
|
-
#
|
239
|
-
# @return [self] Returns the frozen hash
|
240
|
-
#
|
241
|
-
# @example
|
242
|
-
# { user: { name: "Alice", roles: ["admin"] } }.deep_freeze
|
243
|
-
# # => Hash and all nested structures are now frozen
|
244
|
-
#
|
245
|
-
# @note CAUTION: Be careful when freezing collections that contain class objects
|
246
|
-
# or singleton instances - this will freeze those classes/objects globally!
|
247
|
-
# Only use deep_freeze on pure data structures you want to make immutable.
|
248
|
-
#
|
249
|
-
def deep_freeze
|
250
|
-
each_value { |v| v.respond_to?(:deep_freeze) ? v.deep_freeze : v.freeze }
|
251
|
-
freeze
|
252
|
-
end
|
253
|
-
|
254
|
-
# Allows calling original method. See below
|
244
|
+
# @!visibility private
|
255
245
|
alias_method :og_transform_values, :transform_values
|
256
246
|
|
257
|
-
#
|
247
|
+
# @!visibility private
|
258
248
|
alias_method :og_transform_values!, :transform_values!
|
259
249
|
|
260
250
|
#
|
@@ -333,10 +323,10 @@ class Hash
|
|
333
323
|
|
334
324
|
# ActiveSupport integrations
|
335
325
|
if defined?(ActiveSupport)
|
336
|
-
#
|
326
|
+
# @!visibility private
|
337
327
|
alias_method :og_deep_transform_values, :deep_transform_values
|
338
328
|
|
339
|
-
#
|
329
|
+
# @!visibility private
|
340
330
|
alias_method :og_deep_transform_values!, :deep_transform_values!
|
341
331
|
|
342
332
|
#
|
@@ -663,4 +653,237 @@ class Hash
|
|
663
653
|
self[new_key] = delete(old_key)
|
664
654
|
self
|
665
655
|
end
|
656
|
+
|
657
|
+
#
|
658
|
+
# Selects hash entries based only on their values
|
659
|
+
#
|
660
|
+
# @yield [value] Block that determines whether to include the entry
|
661
|
+
# @yieldparam value [Object] The current value
|
662
|
+
# @yieldreturn [Boolean] Whether to include this entry
|
663
|
+
#
|
664
|
+
# @return [Hash] A new hash including only entries where the block returned truthy
|
665
|
+
# @return [Enumerator] If no block is given
|
666
|
+
#
|
667
|
+
# @example Filter to include only present values (with ActiveSupport)
|
668
|
+
# {name: "Alice", bio: nil, role: ""}.select_values(&:present?)
|
669
|
+
# # => {name: "Alice"}
|
670
|
+
#
|
671
|
+
# @example Filter using more complex logic
|
672
|
+
# {id: 1, count: 0, items: [1, 2, 3]}.select_values { |v| v.is_a?(Array) || v > 0 }
|
673
|
+
# # => {id: 1, items: [1, 2, 3]}
|
674
|
+
#
|
675
|
+
def select_values(&block)
|
676
|
+
return to_enum(:select_values) if block.nil?
|
677
|
+
|
678
|
+
select { |_k, v| block.call(v) }
|
679
|
+
end
|
680
|
+
|
681
|
+
alias_method :filter_values, :select_values
|
682
|
+
|
683
|
+
#
|
684
|
+
# Selects hash entries based only on their values, modifying the hash in place
|
685
|
+
#
|
686
|
+
# @yield [value] Block that determines whether to keep the entry
|
687
|
+
# @yieldparam value [Object] The current value
|
688
|
+
# @yieldreturn [Boolean] Whether to keep this entry
|
689
|
+
#
|
690
|
+
# @return [self, nil] The modified hash, or nil if no changes were made
|
691
|
+
# @return [Enumerator] If no block is given
|
692
|
+
#
|
693
|
+
# @example Remove entries with empty values (with ActiveSupport)
|
694
|
+
# hash = {name: "Alice", bio: nil, role: ""}
|
695
|
+
# hash.select_values!(&:present?)
|
696
|
+
# # => {name: "Alice"}
|
697
|
+
# # hash is now {name: "Alice"}
|
698
|
+
#
|
699
|
+
def select_values!(&block)
|
700
|
+
return to_enum(:select_values!) if block.nil?
|
701
|
+
|
702
|
+
select! { |_k, v| block.call(v) }
|
703
|
+
end
|
704
|
+
|
705
|
+
alias_method :filter_values!, :select_values!
|
706
|
+
|
707
|
+
#
|
708
|
+
# Rejects hash entries based only on their values
|
709
|
+
#
|
710
|
+
# @yield [value] Block that determines whether to exclude the entry
|
711
|
+
# @yieldparam value [Object] The current value
|
712
|
+
# @yieldreturn [Boolean] Whether to exclude this entry
|
713
|
+
#
|
714
|
+
# @return [Hash] A new hash excluding entries where the block returned truthy
|
715
|
+
# @return [Enumerator] If no block is given
|
716
|
+
#
|
717
|
+
# @example Remove blank values (with ActiveSupport)
|
718
|
+
# {name: "Alice", bio: nil, role: ""}.reject_values(&:blank?)
|
719
|
+
# # => {name: "Alice"}
|
720
|
+
#
|
721
|
+
# @example Remove specific types of values
|
722
|
+
# {id: 1, count: 0, items: [1, 2, 3]}.reject_values { |v| v.is_a?(Integer) && v == 0 }
|
723
|
+
# # => {id: 1, items: [1, 2, 3]}
|
724
|
+
#
|
725
|
+
def reject_values(&block)
|
726
|
+
return to_enum(:reject_values) if block.nil?
|
727
|
+
|
728
|
+
reject { |_k, v| block.call(v) }
|
729
|
+
end
|
730
|
+
|
731
|
+
#
|
732
|
+
# Rejects hash entries based only on their values, modifying the hash in place
|
733
|
+
#
|
734
|
+
# @yield [value] Block that determines whether to remove the entry
|
735
|
+
# @yieldparam value [Object] The current value
|
736
|
+
# @yieldreturn [Boolean] Whether to remove this entry
|
737
|
+
#
|
738
|
+
# @return [self, nil] The modified hash, or nil if no changes were made
|
739
|
+
# @return [Enumerator] If no block is given
|
740
|
+
#
|
741
|
+
# @example Remove blank values in place (with ActiveSupport)
|
742
|
+
# hash = {name: "Alice", bio: nil, role: ""}
|
743
|
+
# hash.reject_values!(&:blank?)
|
744
|
+
# # => {name: "Alice"}
|
745
|
+
# # hash is now {name: "Alice"}
|
746
|
+
#
|
747
|
+
def reject_values!(&block)
|
748
|
+
return to_enum(:reject_values!) if block.nil?
|
749
|
+
|
750
|
+
reject! { |_k, v| block.call(v) }
|
751
|
+
end
|
752
|
+
|
753
|
+
#
|
754
|
+
# Conditionally merges key-value pairs from another hash based on a block
|
755
|
+
#
|
756
|
+
# @param other [Hash] The hash to merge from
|
757
|
+
#
|
758
|
+
# @yield [key, value] Block that determines whether to include each key-value pair
|
759
|
+
# @yieldparam key [Object] The key from the other hash
|
760
|
+
# @yieldparam value [Object] The value from the other hash
|
761
|
+
# @yieldreturn [Boolean] Whether to include this key-value pair
|
762
|
+
#
|
763
|
+
# @return [Hash] A new hash with conditionally merged key-value pairs
|
764
|
+
#
|
765
|
+
# @example Merge only even-numbered keys
|
766
|
+
# {a: 1, b: 2}.merge_if(c: 3, d: 4) { |key, _| key.to_s.ord.even? }
|
767
|
+
# # => {a: 1, b: 2, d: 4}
|
768
|
+
#
|
769
|
+
# @example Merge only positive values
|
770
|
+
# {a: 1, b: 2}.merge_if(c: 3, d: -4) { |_, value| value > 0 }
|
771
|
+
# # => {a: 1, b: 2, c: 3}
|
772
|
+
#
|
773
|
+
def merge_if(other = {}, &block)
|
774
|
+
other = other.select(&block) unless block.nil?
|
775
|
+
|
776
|
+
merge(other)
|
777
|
+
end
|
778
|
+
|
779
|
+
#
|
780
|
+
# Conditionally merges key-value pairs from another hash in place
|
781
|
+
#
|
782
|
+
# @param other [Hash] The hash to merge from
|
783
|
+
#
|
784
|
+
# @yield [key, value] Block that determines whether to include each key-value pair
|
785
|
+
# @yieldparam key [Object] The key from the other hash
|
786
|
+
# @yieldparam value [Object] The value from the other hash
|
787
|
+
# @yieldreturn [Boolean] Whether to include this key-value pair
|
788
|
+
#
|
789
|
+
# @return [self] The modified hash
|
790
|
+
#
|
791
|
+
# @example Merge only even-numbered keys in place
|
792
|
+
# hash = {a: 1, b: 2}
|
793
|
+
# hash.merge_if!(c: 3, d: 4) { |key, _| key.to_s.ord.even? }
|
794
|
+
# # => {a: 1, b: 2, d: 4}
|
795
|
+
#
|
796
|
+
def merge_if!(other = {}, &block)
|
797
|
+
other = other.select(&block) unless block.nil?
|
798
|
+
|
799
|
+
merge!(other)
|
800
|
+
end
|
801
|
+
|
802
|
+
#
|
803
|
+
# Conditionally merges key-value pairs based only on values
|
804
|
+
#
|
805
|
+
# @param other [Hash] The hash to merge from
|
806
|
+
#
|
807
|
+
# @yield [value] Block that determines whether to include each value
|
808
|
+
# @yieldparam value [Object] The value from the other hash
|
809
|
+
# @yieldreturn [Boolean] Whether to include this value
|
810
|
+
#
|
811
|
+
# @return [Hash] A new hash with conditionally merged values
|
812
|
+
#
|
813
|
+
# @example Merge only string values
|
814
|
+
# {a: 1, b: "old"}.merge_if_values(c: "new", d: 2) { |v| v.is_a?(String) }
|
815
|
+
# # => {a: 1, b: "old", c: "new"}
|
816
|
+
#
|
817
|
+
def merge_if_values(other = {}, &block)
|
818
|
+
merge_if(other) { |k, v| block.call(v) }
|
819
|
+
end
|
820
|
+
|
821
|
+
#
|
822
|
+
# Conditionally merges key-value pairs based only on values, in place
|
823
|
+
#
|
824
|
+
# @param other [Hash] The hash to merge from
|
825
|
+
#
|
826
|
+
# @yield [value] Block that determines whether to include each value
|
827
|
+
# @yieldparam value [Object] The value from the other hash
|
828
|
+
# @yieldreturn [Boolean] Whether to include this value
|
829
|
+
#
|
830
|
+
# @return [self] The modified hash
|
831
|
+
#
|
832
|
+
# @example Merge only numeric values in place
|
833
|
+
# hash = {a: 1, b: "text"}
|
834
|
+
# hash.merge_if_values!(c: "ignore", d: 2) { |v| v.is_a?(Numeric) }
|
835
|
+
# # => {a: 1, b: "text", d: 2}
|
836
|
+
#
|
837
|
+
def merge_if_values!(other = {}, &block)
|
838
|
+
merge_if!(other) { |k, v| block.call(v) }
|
839
|
+
end
|
840
|
+
|
841
|
+
#
|
842
|
+
# Merges only non-nil values from another hash
|
843
|
+
#
|
844
|
+
# This is a convenience method for the common pattern of merging
|
845
|
+
# only values that are not nil.
|
846
|
+
#
|
847
|
+
# @param other [Hash] The hash to merge from
|
848
|
+
#
|
849
|
+
# @return [Hash] A new hash with non-nil values merged
|
850
|
+
#
|
851
|
+
# @example Merge only non-nil values (common when building parameters)
|
852
|
+
# user_id = 42
|
853
|
+
# email = nil
|
854
|
+
# name = "Alice"
|
855
|
+
#
|
856
|
+
# {}.merge_compact(
|
857
|
+
# id: user_id,
|
858
|
+
# email: email,
|
859
|
+
# name: name
|
860
|
+
# )
|
861
|
+
# # => {id: 42, name: "Alice"}
|
862
|
+
#
|
863
|
+
def merge_compact(other = {})
|
864
|
+
merge_if_values(other, &:itself)
|
865
|
+
end
|
866
|
+
|
867
|
+
#
|
868
|
+
# Merges only non-nil values from another hash, in place
|
869
|
+
#
|
870
|
+
# This is a convenience method for the common pattern of merging
|
871
|
+
# only values that are not nil.
|
872
|
+
#
|
873
|
+
# @param other [Hash] The hash to merge from
|
874
|
+
#
|
875
|
+
# @return [self] The modified hash
|
876
|
+
#
|
877
|
+
# @example Merge only non-nil values in place
|
878
|
+
# params = {format: "json"}
|
879
|
+
# params.merge_compact!(
|
880
|
+
# page: 1,
|
881
|
+
# per_page: nil,
|
882
|
+
# sort: "created_at"
|
883
|
+
# )
|
884
|
+
# # => {format: "json", page: 1, sort: "created_at"}
|
885
|
+
#
|
886
|
+
def merge_compact!(other = {})
|
887
|
+
merge_if_values!(other, &:itself)
|
888
|
+
end
|
666
889
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Kernel module
|
5
|
+
#
|
6
|
+
# Provides:
|
7
|
+
# - #morph: A more intuitive alias for Kernel#then (yield_self)
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "everythingrb/kernel"
|
11
|
+
#
|
12
|
+
# # Instead of:
|
13
|
+
# config.fetch(:key).then { |v| process(v) }
|
14
|
+
#
|
15
|
+
# # More expressive with morph:
|
16
|
+
# config.fetch(:key).morph { |v| process(v) }
|
17
|
+
#
|
18
|
+
module Kernel
|
19
|
+
#
|
20
|
+
# Transforms the receiver by passing it to the given block and returning the block's result
|
21
|
+
#
|
22
|
+
# This method is an alias for `then` (and `yield_self`) that more clearly communicates
|
23
|
+
# the transformation intent.
|
24
|
+
#
|
25
|
+
# @yield [receiver] Block that transforms the receiver
|
26
|
+
# @yieldparam receiver [Object] The receiver object
|
27
|
+
# @yieldreturn [Object] The transformed result
|
28
|
+
#
|
29
|
+
# @return [Object] The result of the block
|
30
|
+
#
|
31
|
+
# @example Convert a semantic version to a string
|
32
|
+
# version.to_sem_version.morph { |v| "#{v.major}.#{v.minor}" }
|
33
|
+
#
|
34
|
+
alias_method :morph, :then
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's NilClass
|
5
|
+
#
|
6
|
+
# Provides:
|
7
|
+
# - #in_quotes, #with_quotes: Wrap nil's string representation in quotes
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "everythingrb/nil"
|
11
|
+
# nil.in_quotes # => "\"nil\""
|
12
|
+
#
|
13
|
+
class NilClass
|
14
|
+
include Everythingrb::InspectQuotable
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Extensions to Ruby's core Numeric class and subclasses
|
5
|
+
#
|
6
|
+
# Provides:
|
7
|
+
# - #in_quotes, #with_quotes: Wrap numeric values in quotes
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# require "everythingrb/numeric"
|
11
|
+
#
|
12
|
+
# 42.in_quotes # => "\"42\""
|
13
|
+
# 3.14.in_quotes # => "\"3.14\""
|
14
|
+
# (1+2i).in_quotes # => "\"1+2i\""
|
15
|
+
#
|
16
|
+
class Numeric
|
17
|
+
include Everythingrb::InspectQuotable
|
18
|
+
end
|
data/lib/everythingrb/ostruct.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
# - #join_map: Combine filter_map and join operations
|
9
9
|
# - #blank?, #present?: ActiveSupport integrations when available
|
10
10
|
# - #to_deep_h: Recursively convert to hash with all nested objects
|
11
|
+
# - #in_quotes, #with_quotes: Wrap struct in quotes
|
11
12
|
#
|
12
13
|
# @example
|
13
14
|
# require "everythingrb/ostruct"
|
@@ -16,6 +17,8 @@
|
|
16
17
|
# person.map { |k, v| "#{k}: #{v}" } # => ["name: Alice", "age: 30"]
|
17
18
|
#
|
18
19
|
class OpenStruct
|
20
|
+
include Everythingrb::InspectQuotable
|
21
|
+
|
19
22
|
# ActiveSupport integrations
|
20
23
|
if defined?(ActiveSupport)
|
21
24
|
#
|
@@ -23,6 +26,8 @@ class OpenStruct
|
|
23
26
|
#
|
24
27
|
# @return [Boolean] true if the OpenStruct has no attributes
|
25
28
|
#
|
29
|
+
# @note Only available when ActiveSupport is loaded
|
30
|
+
#
|
26
31
|
def blank?
|
27
32
|
@table.blank?
|
28
33
|
end
|
@@ -30,7 +35,9 @@ class OpenStruct
|
|
30
35
|
#
|
31
36
|
# Checks if the OpenStruct has any attributes
|
32
37
|
#
|
33
|
-
# @return [Boolean] true if the OpenStruct has attributes
|
38
|
+
# @return [Boolean] true if the OpenStruct has any attributes
|
39
|
+
#
|
40
|
+
# @note Only available when ActiveSupport is loaded
|
34
41
|
#
|
35
42
|
def present?
|
36
43
|
@table.present?
|
@@ -114,6 +121,9 @@ class OpenStruct
|
|
114
121
|
#
|
115
122
|
# Recursively converts the OpenStruct and all nested objects to hashes
|
116
123
|
#
|
124
|
+
# This method will convert the OpenStruct and all nested OpenStructs,
|
125
|
+
# Structs, Data objects, and other convertible objects to plain hashes.
|
126
|
+
#
|
117
127
|
# @return [Hash] A deeply converted hash of the OpenStruct
|
118
128
|
#
|
119
129
|
# @example
|