darthjee-core_ext 2.0.0 → 3.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 +5 -5
- data/.circleci/config.yml +61 -10
- data/.github/copilot-instructions.md +99 -0
- data/.github/core_ext-usage.md +324 -0
- data/.rubocop.yml +22 -4
- data/.rubocop_todo.yml +28 -8
- data/Dockerfile +19 -3
- data/Gemfile +20 -0
- data/Makefile +7 -0
- data/README.md +9 -6
- data/Rakefile +3 -0
- data/config/check_specs.yml +20 -0
- data/config/yardstick.yml +8 -36
- data/core_ext.gemspec +3 -13
- data/core_ext.jpg +0 -0
- data/docker-compose.yml +9 -0
- data/lib/darthjee/core_ext/array/hash_builder.rb +14 -1
- data/lib/darthjee/core_ext/array.rb +5 -2
- data/lib/darthjee/core_ext/class.rb +1 -0
- data/lib/darthjee/core_ext/enumerable.rb +6 -5
- data/lib/darthjee/core_ext/hash/cameliazable.rb +13 -13
- data/lib/darthjee/core_ext/hash/chain_fetcher.rb +23 -2
- data/lib/darthjee/core_ext/hash/changeable.rb +4 -4
- data/lib/darthjee/core_ext/hash/deep_hash_constructor/setter.rb +12 -1
- data/lib/darthjee/core_ext/hash/deep_hash_constructor.rb +9 -3
- data/lib/darthjee/core_ext/hash/key_changeable.rb +15 -15
- data/lib/darthjee/core_ext/hash/key_changer.rb +21 -11
- data/lib/darthjee/core_ext/hash/keys_sorter.rb +13 -0
- data/lib/darthjee/core_ext/hash/squasher.rb +8 -2
- data/lib/darthjee/core_ext/hash/transformable.rb +2 -1
- data/lib/darthjee/core_ext/hash/value_changer.rb +18 -0
- data/lib/darthjee/core_ext/hash.rb +2 -2
- data/lib/darthjee/core_ext/numeric.rb +3 -2
- data/lib/darthjee/core_ext/object.rb +0 -2
- data/lib/darthjee/core_ext/time.rb +1 -0
- data/lib/darthjee/core_ext/version.rb +1 -1
- data/lib/darthjee/core_ext.rb +1 -0
- data/lib/darthjee.rb +1 -0
- data/spec/integration/readme/class_spec.rb +3 -3
- data/spec/integration/readme/hash_spec.rb +10 -1
- data/spec/integration/yard/darthjee/core_ext/array_spec.rb +1 -1
- data/spec/integration/yard/darthjee/core_ext/class/default_value_spec.rb +10 -8
- data/spec/integration/yard/darthjee/core_ext/hash/deep_hash_constructor_spec.rb +10 -10
- data/spec/integration/yard/darthjee/core_ext/hash/squasher_spec.rb +3 -3
- data/spec/integration/yard/darthjee/core_ext/hash/transformable_spec.rb +3 -3
- data/spec/integration/yard/darthjee/core_ext/hash/transposeable_spec.rb +1 -1
- data/spec/integration/yard/darthjee/core_ext/hash/value_changer_spec.rb +1 -1
- data/spec/lib/array_spec.rb +2 -1
- data/spec/lib/darthjee/core_ext/hash/value_changer_spec.rb +1 -1
- data/spec/lib/hash_spec.rb +1 -7
- data/spec/lib/symbol_spec.rb +2 -2
- data/spec/spec_helper.rb +9 -2
- data/spec/support/models/client.rb +2 -1
- data/spec/support/models/dummy_iterator.rb +4 -4
- data/spec/support/models/hash/value_changer/dummy.rb +1 -0
- data/spec/support/shared_examples/array/array_random.rb +2 -3
- data/spec/support/shared_examples/hash/chain_hash_keys_changer.rb +1 -1
- data/spec/support/shared_examples/hash/hash_keys_changer.rb +3 -3
- data/spec/support/shared_examples/hash/map_to_hash.rb +1 -1
- data/spec/support/shared_examples/hash/remap.rb +4 -4
- data/spec/support/shared_examples/hash/value_changer.rb +2 -2
- metadata +15 -223
- data/mech.jpg +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: df2eab1682af7c7411711127f5ed298a7084c802b5f13b9b2f0e809040488edf
|
|
4
|
+
data.tar.gz: 4bc429c6fbd6a6f920c6ea8657260a975ef475db5115919ee4f46927c1a49b1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e85edf7601b5ba6196329fc472d15f6f67ef6df19bcf42addbea475fc0f8759000109d5d84a5888d2a5073d788648848404815c104ec15ebea0d6fc211a8f035
|
|
7
|
+
data.tar.gz: d726c8c5cf025ddf824fd9449392d496015f4ff83adec37531746446d0cdee7dfd07fff3381aa2baca50dad986097baca57a3151cc6ba76b26675d90220cdaf0
|
data/.circleci/config.yml
CHANGED
|
@@ -1,31 +1,82 @@
|
|
|
1
1
|
version: 2
|
|
2
|
+
workflows:
|
|
3
|
+
version: 2
|
|
4
|
+
test-and-build:
|
|
5
|
+
jobs:
|
|
6
|
+
- test:
|
|
7
|
+
filters:
|
|
8
|
+
tags:
|
|
9
|
+
only: /.*/
|
|
10
|
+
- checks:
|
|
11
|
+
filters:
|
|
12
|
+
tags:
|
|
13
|
+
only: /.*/
|
|
14
|
+
- build-and-release:
|
|
15
|
+
requires: [test, checks]
|
|
16
|
+
filters:
|
|
17
|
+
tags:
|
|
18
|
+
only: /\d+\.\d+\.\d+/
|
|
19
|
+
branches:
|
|
20
|
+
only:
|
|
21
|
+
- main
|
|
2
22
|
jobs:
|
|
3
|
-
|
|
23
|
+
test:
|
|
4
24
|
docker:
|
|
5
|
-
- image: darthjee/
|
|
25
|
+
- image: darthjee/circleci_ruby_331:1.1.1
|
|
26
|
+
environment:
|
|
27
|
+
PROJECT: core_ext
|
|
6
28
|
steps:
|
|
7
29
|
- checkout
|
|
8
|
-
- run:
|
|
9
|
-
name: Prepare Coverage Test Report
|
|
10
|
-
command: cc-test-reporter before-build
|
|
11
30
|
- run:
|
|
12
31
|
name: Bundle Install
|
|
13
32
|
command: bundle install
|
|
14
33
|
- run:
|
|
15
34
|
name: RSpec
|
|
16
35
|
command: bundle exec rspec
|
|
36
|
+
- run:
|
|
37
|
+
name: Upload coverage to Codacy
|
|
38
|
+
command: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage/lcov/project.lcov
|
|
39
|
+
checks:
|
|
40
|
+
docker:
|
|
41
|
+
- image: darthjee/circleci_ruby_331:1.1.1
|
|
42
|
+
environment:
|
|
43
|
+
PROJECT: core_ext
|
|
44
|
+
steps:
|
|
45
|
+
- checkout
|
|
46
|
+
- run:
|
|
47
|
+
name: Bundle Install
|
|
48
|
+
command: bundle install
|
|
17
49
|
- run:
|
|
18
50
|
name: Rubocop
|
|
19
51
|
command: rubocop
|
|
20
|
-
- run:
|
|
21
|
-
name: Coverage Test Report
|
|
22
|
-
command: cc-test-reporter after-build --exit-code $?
|
|
23
52
|
- run:
|
|
24
53
|
name: Yardstick coverage check
|
|
25
54
|
command: bundle exec rake verify_measurements
|
|
26
55
|
- run:
|
|
27
56
|
name: Check version documentation
|
|
28
|
-
command:
|
|
57
|
+
command: PROJECT=darthjee-core_ext check_readme.sh
|
|
29
58
|
- run:
|
|
30
59
|
name: Rubycritcs check
|
|
31
|
-
command:
|
|
60
|
+
command: rubycritic.sh
|
|
61
|
+
- run:
|
|
62
|
+
name: Check unit tests
|
|
63
|
+
command: check_specs
|
|
64
|
+
build-and-release:
|
|
65
|
+
docker:
|
|
66
|
+
- image: darthjee/circleci_ruby_331:1.1.1
|
|
67
|
+
environment:
|
|
68
|
+
PROJECT: core_ext
|
|
69
|
+
steps:
|
|
70
|
+
- checkout
|
|
71
|
+
- run:
|
|
72
|
+
name: Bundle Install
|
|
73
|
+
command: bundle install
|
|
74
|
+
- run:
|
|
75
|
+
name: Signin
|
|
76
|
+
command: build_gem.sh signin
|
|
77
|
+
- run:
|
|
78
|
+
name: Build Gem
|
|
79
|
+
command: PROJECT=darthjee-core_ext build_gem.sh build
|
|
80
|
+
- run:
|
|
81
|
+
name: Push Gem
|
|
82
|
+
command: PROJECT=darthjee-core_ext build_gem.sh push
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# GitHub Copilot Instructions for `darthjee/core_ext`
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
`core_ext` is a Ruby gem that **adds methods to Ruby's standard objects** (core extensions). It extends built-in classes such as `Array`, `Hash`, `Symbol`, `Enumerable`, `Date`, `Object`, `Numeric`, and others with additional utility methods, while maintaining compatibility and predictable behavior.
|
|
6
|
+
|
|
7
|
+
- Gem name: `darthjee-core_ext`
|
|
8
|
+
- YARD docs: [rubydoc.info/gems/darthjee-core_ext](https://www.rubydoc.info/gems/darthjee-core_ext)
|
|
9
|
+
- Extensions live under `lib/darthjee/core_ext/`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Language
|
|
14
|
+
|
|
15
|
+
- All code, comments, commit messages, pull request titles, PR descriptions, review comments, and documentation **must be written in English**.
|
|
16
|
+
- No exceptions: even if the author's primary language is not English, all contributions to this repository must use English.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Testing
|
|
21
|
+
|
|
22
|
+
- **Every new feature or bug fix must include tests.**
|
|
23
|
+
- Tests are written using RSpec and live under `spec/`.
|
|
24
|
+
- Follow the existing test structure: unit tests mirror the `lib/` directory layout under `spec/lib/`.
|
|
25
|
+
- If a file does not have a corresponding spec file and **intentionally** has no tests (e.g., it is a pure re-export or namespace file), it must be listed in `config/check_specs.yml` under the `ignore:` key.
|
|
26
|
+
- Do **not** add files to `config/check_specs.yml` as a shortcut to avoid writing tests. Only files that genuinely cannot or should not have direct tests belong there.
|
|
27
|
+
- Run the full test suite before opening a PR: `bundle exec rspec`.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
- All public methods must be documented using **YARD** syntax.
|
|
34
|
+
- Use `@param`, `@return`, `@example`, and `@raise` tags where applicable.
|
|
35
|
+
- Include at least one `@example` block for methods that demonstrate non-trivial behavior.
|
|
36
|
+
- Keep documentation close to the code (inline in the source file).
|
|
37
|
+
- Example:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# Converts the hash keys to camel case.
|
|
41
|
+
#
|
|
42
|
+
# @param [Symbol] type the case type (:lower or :upper)
|
|
43
|
+
# @return [Hash] a new hash with camel-cased keys
|
|
44
|
+
# @example
|
|
45
|
+
# { my_key: 1 }.camelize_keys #=> { myKey: 1 }
|
|
46
|
+
def camelize_keys(type = :lower)
|
|
47
|
+
# ...
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Design Principles
|
|
54
|
+
|
|
55
|
+
### Single Responsibility
|
|
56
|
+
|
|
57
|
+
- Classes and methods should have **one reason to change** (Single Responsibility Principle).
|
|
58
|
+
- Prefer small, focused methods. If a method is doing more than one thing, extract the extra responsibility into a helper method or a collaborator class.
|
|
59
|
+
- Follow the approach described by **Sandi Metz** in *99 Bottles of OOP*:
|
|
60
|
+
- Prefer simple, obvious code over clever code.
|
|
61
|
+
- Let the tests drive refactoring: make it work, make it right, make it fast.
|
|
62
|
+
- Use small objects that collaborate through messages.
|
|
63
|
+
- Avoid premature abstraction; introduce it only when duplication demands it.
|
|
64
|
+
|
|
65
|
+
### Law of Demeter
|
|
66
|
+
|
|
67
|
+
- Avoid **Law of Demeter violations**: do not chain method calls across multiple object boundaries (e.g., `a.b.c.d`).
|
|
68
|
+
- Instead, delegate behavior closer to the data: expose a method on the intermediate object, or use a helper that encapsulates the traversal.
|
|
69
|
+
- Code that violates the Law of Demeter increases coupling and makes refactoring harder.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# Avoid
|
|
73
|
+
user.address.city.upcase
|
|
74
|
+
|
|
75
|
+
# Prefer
|
|
76
|
+
user.city_upcased
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### General Style
|
|
80
|
+
|
|
81
|
+
- Prefer composition over inheritance.
|
|
82
|
+
- Keep class hierarchies shallow.
|
|
83
|
+
- Avoid monkey-patching outside of the intended extension pattern of this gem.
|
|
84
|
+
- New core extensions must be clearly scoped to a specific Ruby class and added in the appropriate file under `lib/darthjee/core_ext/`.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Pull Request Guidelines
|
|
89
|
+
|
|
90
|
+
- PR titles and descriptions must be in **English**.
|
|
91
|
+
- Every PR should include:
|
|
92
|
+
- A clear description of what changed and why.
|
|
93
|
+
- References to any related issues.
|
|
94
|
+
- Confirmation that tests pass locally.
|
|
95
|
+
- Reviewers should check that:
|
|
96
|
+
- New code is covered by tests.
|
|
97
|
+
- Documentation follows YARD conventions.
|
|
98
|
+
- Design principles (single responsibility, Demeter) are respected.
|
|
99
|
+
- No file was added to `config/check_specs.yml` without a valid justification.
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Using `darthjee-core_ext` in Your Project
|
|
2
|
+
|
|
3
|
+
This file is intended to be copied into the `.github/` folder of other projects.
|
|
4
|
+
It provides GitHub Copilot (and developers) with a clear, actionable guide on how
|
|
5
|
+
to use the [`darthjee-core_ext`](https://github.com/darthjee/core_ext) Ruby gem.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What Is `core_ext`?
|
|
10
|
+
|
|
11
|
+
`darthjee-core_ext` is a Ruby gem that extends Ruby's built-in (core) classes with
|
|
12
|
+
additional utility methods. It follows the convention of "core extensions" — monkey-patching
|
|
13
|
+
standard objects such as `Array`, `Hash`, `Symbol`, `Enumerable`, `Date`, `Object`,
|
|
14
|
+
`Numeric`, and `Math` with useful helpers, while maintaining predictable behavior.
|
|
15
|
+
|
|
16
|
+
- **Gem name**: `darthjee-core_ext`
|
|
17
|
+
- **Source**: <https://github.com/darthjee/core_ext>
|
|
18
|
+
- **YARD docs**: <https://www.rubydoc.info/gems/darthjee-core_ext>
|
|
19
|
+
- **Current stable release**: 3.0.0
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Adding the Gem to Your Project
|
|
24
|
+
|
|
25
|
+
### Via RubyGems (recommended)
|
|
26
|
+
|
|
27
|
+
Add to your `Gemfile`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem 'darthjee-core_ext'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or pin a specific version for reproducible builds:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
gem 'darthjee-core_ext', '~> 3.0'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Via GitHub (source)
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
gem 'darthjee-core_ext',
|
|
43
|
+
github: 'darthjee/core_ext',
|
|
44
|
+
branch: 'main'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installing
|
|
50
|
+
|
|
51
|
+
After adding the gem to your `Gemfile`, run:
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
bundle install
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or install it system-wide:
|
|
58
|
+
|
|
59
|
+
```shell
|
|
60
|
+
gem install darthjee-core_ext
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Loading the Gem
|
|
66
|
+
|
|
67
|
+
When using Bundler (Rails, typical Ruby apps), the gem is loaded automatically via
|
|
68
|
+
`Bundler.require`. No explicit `require` is needed.
|
|
69
|
+
|
|
70
|
+
In plain Ruby scripts or when Bundler auto-require is disabled, add:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
require 'darthjee/core_ext'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Extensions Provided
|
|
79
|
+
|
|
80
|
+
### `Hash`
|
|
81
|
+
|
|
82
|
+
| Method | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `#append_to_keys` | Appends a string to every key |
|
|
85
|
+
| `#prepend_to_keys` | Prepends a string to every key |
|
|
86
|
+
| `#camelize_keys` / `#camelize_keys!` | Converts keys to CamelCase |
|
|
87
|
+
| `#lower_camelize_keys` / `#lower_camelize_keys!` | Converts keys to lowerCamelCase |
|
|
88
|
+
| `#underscore_keys` / `#underscore_keys!` | Converts keys to snake_case |
|
|
89
|
+
| `#change_keys` / `#change_keys!` | Transforms keys with a block |
|
|
90
|
+
| `#chain_change_keys` / `#chain_change_keys!` | Transforms keys by chaining method calls |
|
|
91
|
+
| `#change_values` / `#change_values!` | Transforms values with a block (optionally recursive) |
|
|
92
|
+
| `#chain_fetch` | Fetches nested keys in a chain |
|
|
93
|
+
| `#exclusive_merge` / `#exclusive_merge!` | Merges only existing keys |
|
|
94
|
+
| `#remap_keys` / `#remap_keys!` | Renames keys based on a mapping hash |
|
|
95
|
+
| `#sort_keys` / `#sort_keys!` | Sorts the hash by its keys |
|
|
96
|
+
| `#squash` / `#squash!` | Flattens a deep hash into a single-level hash |
|
|
97
|
+
| `#to_deep_hash` / `#to_deep_hash!` | Expands a squashed hash back into a deep hash |
|
|
98
|
+
| `#map_to_hash` | Maps values keeping original keys |
|
|
99
|
+
| `#transpose` / `#transpose!` | Swaps keys with values |
|
|
100
|
+
|
|
101
|
+
### `Array`
|
|
102
|
+
|
|
103
|
+
| Method | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `#as_hash` | Zips the array with a keys array into a Hash |
|
|
106
|
+
| `#average` | Returns the arithmetic average of the elements |
|
|
107
|
+
| `#chain_map` | Applies `.map` in a chain of method calls |
|
|
108
|
+
| `#mapk` | Maps by fetching nested keys from hashes inside the array |
|
|
109
|
+
| `#procedural_join` | Joins elements with a dynamically computed separator |
|
|
110
|
+
| `#random` | Returns a random element |
|
|
111
|
+
| `#random!` | Removes and returns a random element |
|
|
112
|
+
|
|
113
|
+
### `Symbol`
|
|
114
|
+
|
|
115
|
+
| Method | Description |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `#camelize` | Camelizes the symbol (`:my_sym` → `:MySym`) |
|
|
118
|
+
| `#underscore` | Underscores a camelized symbol (`:MySym` → `:my_sym`) |
|
|
119
|
+
|
|
120
|
+
### `Enumerable` (available on `Array`, `Hash`, and any `Enumerable`)
|
|
121
|
+
|
|
122
|
+
| Method | Description |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `#clean` | Returns a copy with empty values removed |
|
|
125
|
+
| `#clean!` | Removes empty values in place (recursive) |
|
|
126
|
+
| `#map_and_find` | Maps and stops at the first truthy result |
|
|
127
|
+
| `#map_and_select` | Maps and returns only truthy results |
|
|
128
|
+
| `#map_to_hash` | Maps values, using original elements as keys |
|
|
129
|
+
|
|
130
|
+
### `Object` (available on every Ruby object)
|
|
131
|
+
|
|
132
|
+
| Method | Description |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `#is_any?` | Returns `true` if the object is an instance of any of the given classes |
|
|
135
|
+
| `#trueful?` | Returns `true` only when the object is not `nil` (unlike `#present?`, `[]` and `{}` are trueful) |
|
|
136
|
+
|
|
137
|
+
### `Date`
|
|
138
|
+
|
|
139
|
+
| Method | Description |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `#days_between` | Returns the absolute number of days between two dates |
|
|
142
|
+
|
|
143
|
+
### `Math`
|
|
144
|
+
|
|
145
|
+
| Method | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `.average` | Calculates the (optionally weighted) average of values |
|
|
148
|
+
|
|
149
|
+
### `Class`
|
|
150
|
+
|
|
151
|
+
| Method | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `.default_value` | Adds a reader that returns the same default instance every time |
|
|
154
|
+
| `.default_values` | Adds multiple readers sharing the same default instance |
|
|
155
|
+
| `.default_reader` | Adds a reader that returns a default value only when the instance variable was never set |
|
|
156
|
+
| `.default_readers` | Adds multiple default readers sharing the same default value |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Code Examples
|
|
161
|
+
|
|
162
|
+
### Hash key transformations
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# Converting API responses from camelCase to snake_case
|
|
166
|
+
response = { 'userId' => 1, 'firstName' => 'Alice' }
|
|
167
|
+
response.underscore_keys # => { 'user_id' => 1, 'first_name' => 'Alice' }
|
|
168
|
+
|
|
169
|
+
# Preparing a payload for a camelCase API
|
|
170
|
+
params = { user_id: 1, first_name: 'Alice' }
|
|
171
|
+
params.lower_camelize_keys
|
|
172
|
+
# => { userId: 1, firstName: 'Alice' }
|
|
173
|
+
|
|
174
|
+
# Equivalent long form with explicit option
|
|
175
|
+
params.camelize_keys(uppercase_first_letter: false)
|
|
176
|
+
# => { userId: 1, firstName: 'Alice' }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Fetching nested values safely
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
config = { database: { primary: { host: 'localhost' } } }
|
|
183
|
+
|
|
184
|
+
config.chain_fetch(:database, :primary, :host) # => 'localhost'
|
|
185
|
+
config.chain_fetch(:database, :replica, :host) { |key, _rest| "default-#{key}" }
|
|
186
|
+
# => 'default-replica'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Flattening and restoring deep hashes
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
deep = { a: { b: [1, 2] } }
|
|
193
|
+
flat = deep.squash # => { 'a.b[0]' => 1, 'a.b[1]' => 2 }
|
|
194
|
+
flat.to_deep_hash # => { 'a' => { 'b' => [1, 2] } }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Exclusive merge (update only existing keys)
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
defaults = { timeout: 30, retries: 3 }
|
|
201
|
+
overrides = { retries: 5, unknown_key: 'ignored' }
|
|
202
|
+
|
|
203
|
+
defaults.exclusive_merge(overrides) # => { timeout: 30, retries: 5 }
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Array utilities
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
# Zip an array with keys into a Hash
|
|
210
|
+
values = [10, 20, 30]
|
|
211
|
+
values.as_hash(%i[x y z]) # => { x: 10, y: 20, z: 30 }
|
|
212
|
+
|
|
213
|
+
# Chain map calls
|
|
214
|
+
[:hello, :world].chain_map(:to_s, :upcase) # => ['HELLO', 'WORLD']
|
|
215
|
+
|
|
216
|
+
# Fetch nested keys from an array of hashes
|
|
217
|
+
records = [{ user: { id: 1 } }, { user: { id: 2 } }]
|
|
218
|
+
records.mapk(:user, :id) # => [1, 2]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Symbol utilities
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
:my_method_name.camelize # => :MyMethodName
|
|
225
|
+
:my_method_name.camelize(:lower) # => :myMethodName
|
|
226
|
+
:MyMethodName.underscore # => :my_method_name
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Enumerable cleaning
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
data = { name: 'Alice', nickname: nil, tags: [], meta: {} }
|
|
233
|
+
data.clean # => { name: 'Alice' }
|
|
234
|
+
|
|
235
|
+
mixed = [1, nil, '', [], {}, 'hello']
|
|
236
|
+
mixed.clean # => [1, 'hello']
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Object helpers
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
value = 42
|
|
243
|
+
value.is_any?(String, Symbol) # => false
|
|
244
|
+
value.is_any?(String, Symbol, Integer) # => true
|
|
245
|
+
|
|
246
|
+
nil.trueful? # => false
|
|
247
|
+
[].trueful? # => true (unlike blank?/present?)
|
|
248
|
+
''.trueful? # => true
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Class default readers
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
class Report
|
|
255
|
+
attr_writer :title
|
|
256
|
+
default_reader :title, 'Untitled Report'
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
r = Report.new
|
|
260
|
+
r.title # => 'Untitled Report'
|
|
261
|
+
r.title = 'Q1'
|
|
262
|
+
r.title # => 'Q1'
|
|
263
|
+
r.title = nil
|
|
264
|
+
r.title # => nil (nil is respected; differs from default_value)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Math weighted average
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# Simple average
|
|
271
|
+
Math.average([10, 20, 30]) # => 20.0
|
|
272
|
+
|
|
273
|
+
# Weighted average (value => weight)
|
|
274
|
+
Math.average(10 => 1, 20 => 2, 30 => 1) # => 20.0
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Best Practices and Caveats
|
|
280
|
+
|
|
281
|
+
### Monkey-patching awareness
|
|
282
|
+
|
|
283
|
+
`core_ext` extends Ruby's built-in classes globally. This means:
|
|
284
|
+
|
|
285
|
+
- All objects of the extended classes gain the new methods **throughout your entire application**, including third-party gems.
|
|
286
|
+
- **Name collision risk**: if another gem or your application already defines a method with the same name on the same class, the last `require`/`load` wins. Review the method list above before adoption and check for conflicts.
|
|
287
|
+
- In libraries (gems) intended for wide reuse, prefer not requiring `core_ext` at the top level unless you own all consumers.
|
|
288
|
+
|
|
289
|
+
### Recursive options
|
|
290
|
+
|
|
291
|
+
Several `Hash` methods (`change_keys`, `camelize_keys`, `underscore_keys`, `change_values`, `clean!`) operate recursively by default, descending into nested arrays and hashes. Pass `recursive: false` when you only need shallow transformation to avoid unintended side-effects on nested structures.
|
|
292
|
+
|
|
293
|
+
### Bang (`!`) methods vs. non-bang methods
|
|
294
|
+
|
|
295
|
+
Methods ending with `!` mutate the receiver **in place**. Use them when you are sure you do not need the original structure. Use the non-bang variants to return a transformed copy.
|
|
296
|
+
|
|
297
|
+
### Versioning
|
|
298
|
+
|
|
299
|
+
Pin to a minor version to avoid unexpected breaking changes:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
gem 'darthjee-core_ext', '~> 3.0'
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Check the [CHANGELOG / releases page](https://github.com/darthjee/core_ext/releases) before upgrading.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Running the Test Suite / Contributing
|
|
310
|
+
|
|
311
|
+
If you are contributing to `core_ext` itself or want to verify its behavior locally:
|
|
312
|
+
|
|
313
|
+
```shell
|
|
314
|
+
# Install dependencies
|
|
315
|
+
bundle install
|
|
316
|
+
|
|
317
|
+
# Run the full test suite
|
|
318
|
+
bundle exec rspec
|
|
319
|
+
|
|
320
|
+
# Run a single spec file
|
|
321
|
+
bundle exec rspec spec/lib/darthjee/core_ext/hash_spec.rb
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Tests live under `spec/` and mirror the structure of `lib/`. Every public method must have a corresponding spec. See the [README](https://github.com/darthjee/core_ext/blob/main/README.md) and the repository's Copilot instructions (`.github/copilot-instructions.md`) for more details.
|
data/.rubocop.yml
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rspec
|
|
3
|
+
- rubocop-rake
|
|
2
4
|
inherit_from: .rubocop_todo.yml
|
|
3
5
|
|
|
4
6
|
AllCops:
|
|
5
|
-
TargetRubyVersion:
|
|
7
|
+
TargetRubyVersion: 3.3
|
|
8
|
+
NewCops: enable
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
RSpec/MultipleExpectations:
|
|
8
11
|
Exclude:
|
|
9
|
-
- '
|
|
12
|
+
- 'spec/integration/**/*_spec.rb'
|
|
10
13
|
|
|
11
14
|
Metrics/BlockLength:
|
|
12
15
|
Exclude:
|
|
@@ -18,3 +21,18 @@ RSpec/AlignLeftLetBrace:
|
|
|
18
21
|
|
|
19
22
|
RSpec/NestedGroups:
|
|
20
23
|
Max: 5
|
|
24
|
+
|
|
25
|
+
RSpec/IndexedLet:
|
|
26
|
+
Enabled: false
|
|
27
|
+
|
|
28
|
+
RSpec/SpecFilePathFormat:
|
|
29
|
+
Exclude:
|
|
30
|
+
- 'spec/integration/**/*_spec.rb'
|
|
31
|
+
|
|
32
|
+
Style/HashEachMethods:
|
|
33
|
+
Exclude:
|
|
34
|
+
- 'lib/darthjee/core_ext/hash/deep_hash_constructor.rb'
|
|
35
|
+
- 'lib/darthjee/core_ext/hash/squasher.rb'
|
|
36
|
+
|
|
37
|
+
Gemspec/RequireMFA:
|
|
38
|
+
Enabled: false
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on
|
|
3
|
+
# on 2026-03-11 13:23:23 UTC using RuboCop version 1.85.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
|
-
# Offense count:
|
|
10
|
-
# Configuration parameters:
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
# Offense count: 1
|
|
10
|
+
# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
|
|
11
|
+
# NamePrefix: is_, has_, have_, does_
|
|
12
|
+
# ForbiddenPrefixes: is_, has_, have_, does_
|
|
13
|
+
# AllowedMethods: is_a?
|
|
14
|
+
# MethodDefinitionMacros: define_method, define_singleton_method
|
|
15
|
+
Naming/PredicatePrefix:
|
|
16
|
+
Exclude:
|
|
17
|
+
- 'spec/**/*'
|
|
18
|
+
- 'lib/darthjee/core_ext/object.rb'
|
|
13
19
|
|
|
14
|
-
# Offense count:
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
# Offense count: 4
|
|
21
|
+
# Configuration parameters: AllowSubject.
|
|
22
|
+
RSpec/MultipleMemoizedHelpers:
|
|
23
|
+
Max: 6
|
|
24
|
+
|
|
25
|
+
# Offense count: 8
|
|
26
|
+
# Configuration parameters: AllowedClasses.
|
|
27
|
+
Style/OneClassPerFile:
|
|
28
|
+
Exclude:
|
|
29
|
+
- 'lib/darthjee/core_ext/array.rb'
|
|
30
|
+
- 'lib/darthjee/core_ext/class.rb'
|
|
31
|
+
- 'lib/darthjee/core_ext/date.rb'
|
|
32
|
+
- 'lib/darthjee/core_ext/hash.rb'
|
|
33
|
+
- 'lib/darthjee/core_ext/math.rb'
|
|
34
|
+
- 'lib/darthjee/core_ext/numeric.rb'
|
|
35
|
+
- 'lib/darthjee/core_ext/object.rb'
|
|
36
|
+
- 'lib/darthjee/core_ext/symbol.rb'
|
data/Dockerfile
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
FROM darthjee/
|
|
1
|
+
FROM darthjee/scripts:0.7.0 as scripts
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
COPY --chown=app ./ /home/app/app/
|
|
3
|
+
FROM darthjee/ruby_331:1.1.1 as base
|
|
5
4
|
|
|
5
|
+
COPY --chown=app:app ./ /home/app/app/
|
|
6
|
+
|
|
7
|
+
######################################
|
|
8
|
+
|
|
9
|
+
FROM base as builder
|
|
10
|
+
|
|
11
|
+
COPY --chown=app:app --from=scripts /home/scripts/builder/bundle_builder.sh /usr/local/sbin/bundle_builder.sh
|
|
12
|
+
|
|
13
|
+
ENV HOME_DIR /home/app
|
|
14
|
+
RUN bundle_builder.sh
|
|
15
|
+
|
|
16
|
+
#######################
|
|
17
|
+
#FINAL IMAGE
|
|
18
|
+
FROM base
|
|
19
|
+
|
|
20
|
+
COPY --chown=app:app --from=builder /home/app/bundle/ /usr/local/bundle/
|
|
6
21
|
RUN bundle install
|
|
22
|
+
|
data/Gemfile
CHANGED
|
@@ -4,3 +4,23 @@ source 'https://rubygems.org'
|
|
|
4
4
|
|
|
5
5
|
# Specify your gem's dependencies in credential_builder.gemspec
|
|
6
6
|
gemspec
|
|
7
|
+
|
|
8
|
+
gem 'bundler', '>= 2.5.13'
|
|
9
|
+
gem 'pry', '0.14.2'
|
|
10
|
+
gem 'pry-nav', '1.0.0'
|
|
11
|
+
gem 'rake', '13.2.1'
|
|
12
|
+
gem 'reek', '6.5.0'
|
|
13
|
+
gem 'rspec', '3.13.2'
|
|
14
|
+
gem 'rspec-core', '3.13.6'
|
|
15
|
+
gem 'rspec-expectations', '3.13.5'
|
|
16
|
+
gem 'rspec-mocks', '3.13.8'
|
|
17
|
+
gem 'rspec-support', '3.13.7'
|
|
18
|
+
gem 'rubocop', '1.85.1'
|
|
19
|
+
gem 'rubocop-rake', '0.7.1'
|
|
20
|
+
gem 'rubocop-rspec', '3.9.0'
|
|
21
|
+
gem 'rubycritic', '5.0.0'
|
|
22
|
+
gem 'simplecov', '0.22.0'
|
|
23
|
+
gem 'simplecov-html', '0.13.2'
|
|
24
|
+
gem 'simplecov-lcov', '0.9.0'
|
|
25
|
+
gem 'yard', '0.9.38'
|
|
26
|
+
gem 'yardstick', '0.9.9'
|