hash_of 1.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abd0405b2537b883cf128551077589f57e7222a0c768fc831c0a34c87d1f4c59
4
- data.tar.gz: c7c16a9e52d80c23e5e139a415732ca11f21cbe1ec82f3566126fd7e4ae269e6
3
+ metadata.gz: 1031246240479bb3f4c2256f542bb1530e34878371458c255615133eb14c3d25
4
+ data.tar.gz: 9589f482f2dc1d8e29c7b80b264ddac410b965654d55fbcffcd1e08c0d3961ea
5
5
  SHA512:
6
- metadata.gz: 02daed3abcb45213c7f7750ea9117b7962f628f7bcd367591b2485735c5e069da49a9a56301957d8972587a683d4d841dd3b6dae0681ef116611bf5755cee3a4
7
- data.tar.gz: 540208b6ee76af2253f98adb7248bd1dadf797a30085b896ca9e3c73e79264073c743d0a8de7b34ee3359acff35afe9a5d8fa91853d2d00c2f7e8e0cb3bb286a
6
+ metadata.gz: 0053fc60c174a1fef807f5c8d6ef6976842961419a39fa4059d2aaaed23e45da5831bd1a95687c3d798e852aadb0ba54e4bfbe47898d9dd2cdaf5a28af64853b
7
+ data.tar.gz: d9301de5ec5afd829a3020922bce0a01d7544ea4b1775fbddc1d41f3235db65ef76275130a2839d15875f2a690b3443374ba9889ceadaa50924443d9daf95152
data/.rubocop.yml CHANGED
@@ -1,11 +1,11 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rake
3
3
  - rubocop-rspec
4
4
  - rubocop-performance
5
5
 
6
6
  AllCops:
7
7
  NewCops: enable
8
- TargetRubyVersion: 3.0
8
+ TargetRubyVersion: 3.3
9
9
 
10
10
  Style/StringLiterals:
11
11
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 1.1.0 (February 27, 2026)
2
+
3
+ - Raise `ArgumentError` for invalid type arguments (e.g., `Hash.of(:string)`)
4
+ - Raise `ArgumentError` when passing `recursive: true` with `:array`
5
+ - Drop Ruby < 3.3 support; now tested on Ruby 3.3, 3.4, and 4.0
6
+ - Update all dependencies (rubocop 1.85, rubocop-rspec 3.x, etc.)
7
+
1
8
  # 1.0.1 (February 12, 2024)
2
9
 
3
10
  - Simpler internal approach for creating the recursive hash that avoids a new class
data/CLAUDE.md ADDED
@@ -0,0 +1,28 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
7
+ hash_of is a Ruby gem that extends `Hash` with a `.of()` class method for creating auto-populating hashes (arrays, hashes, or recursive hashes). It targets Ruby 3.0+.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ bundle exec rake # Default: runs tests + rubocop
13
+ bundle exec rake spec # Run RSpec tests only
14
+ bundle exec rubocop # Run linting only
15
+ bundle exec rspec spec/hash_of_spec.rb # Run a single test file
16
+ bundle exec rake coverage # Generate coverage report and open in browser
17
+ ```
18
+
19
+ ## Architecture
20
+
21
+ The entire gem is a single module in `lib/hash_of.rb`. It defines three lambdas (`OF_ARRAY_LAMBDA`, `OF_HASH_LAMBDA`, `OF_HASH_RECURSIVE_LAMBDA`) used as `default_proc` values and a single `of(type, recursive: false)` method. The module is extended onto `Hash` directly via `Hash.extend(HashOf)`.
22
+
23
+ All tests live in `spec/hash_of_spec.rb`. Coverage is tracked with SimpleCov.
24
+
25
+ ## Style
26
+
27
+ - RuboCop enforced: double-quoted strings, 120 char max line length
28
+ - CI tests against Ruby 3.0, 3.1, 3.2, 3.3
data/README.md CHANGED
@@ -1,24 +1,69 @@
1
1
  # Hash.of
2
2
 
3
- Mostly syntactic sugar to quickly and tersely create a hash of arrays or hashes, and optionally, recursive hashes.
3
+ Syntactic sugar to tersely create a hash of arrays or hashes, and optionally, recursive hashes.
4
4
 
5
- If you find yourself doing `Hash.new { |hash, key] hash[key] = {} }` a lot you can make this more concise with `Hash.of(:hash)`.
5
+ While clear, creating a hash of hashes via `Hash.new { |hash, key| hash[key] = {} }` is verbose. If this pattern is common in your codebase you can express it more concisely as `Hash.of(:hash)`. This can help readability when iterating with `#reduce` or `#each_with_object` to create efficient lookup tables.
6
6
 
7
- It helps readability when iterating with `#reduce` or `#each_with_object` to create efficient lookup tables. E.g.:
7
+ ## Example
8
+
9
+ We have a bunch of records containing the winners of the Academy Awards. We'd like to provide a simple interface for people to look up winners by year and category like the following:
10
+
11
+ ```ruby
12
+ winners_by_year_by_category[2024][:best_picture]
13
+ # => "Oppenheimer"
14
+ ```
15
+
16
+ Let's say we have `AcademyAwardResult` objects which have the properties `year`, `category`, and `winner`. They may look something like:
17
+
18
+ ```ruby
19
+ [
20
+ #<AcademyAwardResult year=2024, category=:best_picture, winner="Oppenheimer">,
21
+ #<AcademyAwardResult year=2024, category=:best_actor, winner="Cillian Murphy">,
22
+
23
+ #<AcademyAwardResult year=2023, category=:best_picture, winner="Everything Everywhere All at Once">,
24
+
25
+ #<AcademyAwardResult year=2022, category=:best_picture, winner="CODA">
26
+ ]
27
+ ```
28
+
29
+ The following turns this array of records into a lookup table:
8
30
 
9
31
  ```ruby
10
- # This is a contrived example for illustrative purposes only as Rails' `Enumerable#index_by` is a better tool for the specific case being depicted.
11
- a_buncha_records.each_with_object(Hash.new { |hash, key| hash[key] = {} }) do |record, cache|
12
- cache[record.id] = record
32
+ award_results.each_with_object(Hash.new { |hash, key| hash[key] = {} }) do |result, lookup|
33
+ lookup[result.year][result.category] = result.winner
13
34
  end
35
+ ```
36
+
37
+ `Hash.of` enables the terser:
14
38
 
15
- # vs.
16
- a_buncha_records.each_with_object(Hash.of(:hash)) do |record, cache|
17
- cache[record.id] = record
39
+ ```ruby
40
+ award_results.each_with_object(Hash.of(:hash)) do |result, lookup|
41
+ lookup[result.year][result.category] = result.winner
18
42
  end
19
43
 
20
- # or go nuts and inline it
21
- a_buncha_records.each_with_object(Hash.of(:hash)) { |record, cache| cache[record.id] = record }
44
+ # or the incredibly terse inline
45
+
46
+ award_results.each_with_object(Hash.of(:hash)) { _2[_1.year][_1.category] = _1.winner }
47
+ ```
48
+
49
+ Both result in the following object that powers the specified interface:
50
+
51
+ ```ruby
52
+ {
53
+ 2024 => {
54
+ :best_picture => "Oppenheimer",
55
+ :best_actor => "Cillian Murphy",
56
+
57
+ },
58
+ 2023 => {
59
+ :best_picture => "Everything Everywhere All at Once",
60
+
61
+ },
62
+ 2022 => {
63
+ :best_picture => "CODA",
64
+
65
+ }
66
+ }
22
67
  ```
23
68
 
24
69
  ## Installation
@@ -27,7 +72,7 @@ Install the gem and add to the application's Gemfile by executing:
27
72
 
28
73
  $ bundle add hash_of
29
74
 
30
- Alternatively add to your Gemfile via
75
+ Alternatively add to your `Gemfile` via
31
76
 
32
77
  gem "hash_of", "~> 1.0"
33
78
 
@@ -35,10 +80,9 @@ If bundler is not being used to manage dependencies, install the gem by executin
35
80
 
36
81
  $ gem install hash_of
37
82
 
38
-
39
83
  ## Usage
40
84
 
41
- Adds the `Hash.of` method providing the following uses
85
+ `Hash.of` provides the following uses
42
86
 
43
87
  ### 1. `Hash.of(:array)`
44
88
 
@@ -90,12 +134,12 @@ recursive_hash_of_hashes
90
134
 
91
135
  ## Development
92
136
 
93
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and Rubocop. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
137
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake` to run the tests and RuboCop. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
94
138
 
95
139
  ## Contributing
96
140
 
97
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hash_of.
141
+ Bug reports and pull requests are welcome on GitHub at https://github.com/agrberg/hash_of.
98
142
 
99
143
  ## License
100
144
 
101
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
145
+ The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
data/Rakefile CHANGED
@@ -10,3 +10,9 @@ require "rubocop/rake_task"
10
10
  RuboCop::RakeTask.new
11
11
 
12
12
  task default: %i[spec rubocop]
13
+
14
+ desc "Generate code coverage with simplecov"
15
+ task :coverage do
16
+ `COVERAGE=true rspec`
17
+ `open coverage/index.html`
18
+ end
data/hash_of.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/hash_of/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "hash_of"
7
+ spec.version = HashOf::VERSION
8
+ spec.authors = ["Aaron Rosenberg"]
9
+ spec.email = ["aarongrosenberg@gmail.com"]
10
+
11
+ spec.summary = "Syntactic sugar to create hashes of hashes or arrays and ability to make them recursive."
12
+ spec.homepage = "https://github.com/agrberg/hash_of"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.3.0"
15
+
16
+ spec.metadata["bug_tracker_uri"] = "https://github.com/agrberg/hash_of/issues"
17
+ spec.metadata["changelog_uri"] = "https://github.com/agrberg/hash_of/blob/main/CHANGELOG.md"
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["rubygems_mfa_required"] = "true"
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (File.expand_path(f) == __FILE__) ||
27
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
28
+ end
29
+ end
30
+ spec.require_paths = ["lib"]
31
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HashOf
4
- VERSION = "1.0.1"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/hash_of.rb CHANGED
@@ -2,13 +2,20 @@
2
2
 
3
3
  require_relative "hash_of/version"
4
4
 
5
- # Module to contain the constants, subclass, and class method Hash will extend
5
+ # Module to contain the constants and class method Hash will extend
6
6
  module HashOf
7
7
  OF_ARRAY_LAMBDA = ->(hash, key) { hash[key] = [] }
8
8
  OF_HASH_LAMBDA = ->(hash, key) { hash[key] = {} }
9
9
  OF_HASH_RECURSIVE_LAMBDA = ->(hash, key) { hash[key] = Hash.new(&hash.default_proc) }
10
10
 
11
+ VALID_TYPES = %i[array hash].freeze
12
+
11
13
  def of(type, recursive: false)
14
+ unless VALID_TYPES.include?(type)
15
+ raise ArgumentError, "Invalid type: #{type.inspect}. Valid types are: #{VALID_TYPES.join(", ")}"
16
+ end
17
+ raise ArgumentError, "recursive is only supported with type: :hash" if recursive && type != :hash
18
+
12
19
  case type
13
20
  when :array
14
21
  Hash.new(&OF_ARRAY_LAMBDA)
data/sig/hash_of.rbs CHANGED
@@ -1,13 +1,10 @@
1
- # Module to contain the constants, subclass, and class method Hash will extend
1
+ # Module to contain the constants and class method Hash will extend
2
2
  module HashOf
3
- OF_ARRAY_LAMBDA: Lambda
4
- OF_HASH_LAMBDA: Lambda
3
+ OF_ARRAY_LAMBDA: ^(Hash, untyped) -> Array[untyped]
4
+ OF_HASH_LAMBDA: ^(Hash, untyped) -> Hash[untyped, untyped]
5
+ OF_HASH_RECURSIVE_LAMBDA: ^(Hash, untyped) -> Hash[untyped, untyped]
6
+ VALID_TYPES: Array[Symbol]
5
7
  VERSION: String
6
8
 
7
- # Produce new Hashes with their default proc set to create hashes recursively
8
- class OfHash < Hash
9
- def initialize: () -> void
10
- end
11
-
12
- def of: (Symbol type, ?recursive: bool) -> Hash
9
+ def of: (Symbol type, ?recursive: bool) -> Hash[untyped, untyped]
13
10
  end
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hash_of
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Rosenberg
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2024-02-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email:
15
13
  - aarongrosenberg@gmail.com
16
14
  executables: []
@@ -20,9 +18,11 @@ files:
20
18
  - ".rspec"
21
19
  - ".rubocop.yml"
22
20
  - CHANGELOG.md
21
+ - CLAUDE.md
23
22
  - LICENSE.txt
24
23
  - README.md
25
24
  - Rakefile
25
+ - hash_of.gemspec
26
26
  - lib/hash_of.rb
27
27
  - lib/hash_of/version.rb
28
28
  - sig/hash_of.rbs
@@ -30,11 +30,11 @@ homepage: https://github.com/agrberg/hash_of
30
30
  licenses:
31
31
  - MIT
32
32
  metadata:
33
+ bug_tracker_uri: https://github.com/agrberg/hash_of/issues
33
34
  changelog_uri: https://github.com/agrberg/hash_of/blob/main/CHANGELOG.md
34
35
  homepage_uri: https://github.com/agrberg/hash_of
35
36
  rubygems_mfa_required: 'true'
36
37
  source_code_uri: https://github.com/agrberg/hash_of
37
- post_install_message:
38
38
  rdoc_options: []
39
39
  require_paths:
40
40
  - lib
@@ -42,15 +42,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: 3.0.0
45
+ version: 3.3.0
46
46
  required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
50
  version: '0'
51
51
  requirements: []
52
- rubygems_version: 3.5.5
53
- signing_key:
52
+ rubygems_version: 4.0.3
54
53
  specification_version: 4
55
54
  summary: Syntactic sugar to create hashes of hashes or arrays and ability to make
56
55
  them recursive.