map 6.6.0 → 8.0.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/LICENSE +1 -1
- data/README.md +268 -0
- data/Rakefile +140 -64
- data/images/giraffe.jpeg +0 -0
- data/images/map.png +0 -0
- data/lib/map/_lib.rb +85 -0
- data/lib/map/ordering.rb +126 -0
- data/lib/map.rb +151 -121
- data/map.gemspec +21 -12
- data/specs/001-ordered-map-module/checklists/requirements.md +62 -0
- data/specs/001-ordered-map-module/data-model.md +250 -0
- data/specs/001-ordered-map-module/plan.md +139 -0
- data/specs/001-ordered-map-module/quickstart.md +430 -0
- data/specs/001-ordered-map-module/research.md +189 -0
- data/specs/001-ordered-map-module/spec.md +108 -0
- data/specs/001-ordered-map-module/tasks.md +264 -0
- data/test/map_test.rb +21 -57
- metadata +39 -23
- data/a.rb +0 -10
- data/lib/map/integrations/active_record.rb +0 -140
- data/lib/map/params.rb +0 -79
- data/lib/map/struct.rb +0 -52
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Feature Specification: Conditional Ordered Map Module
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `001-ordered-map-module`
|
|
4
|
+
**Created**: 2025-11-10
|
|
5
|
+
**Status**: Draft
|
|
6
|
+
**Input**: User description: "we want to support all features of map in newer rubies, which have ordered maps. we wish to extact the 'ordered' parts of map as a module, and only include it in Map for older/non-hash-ordered, rubies. we want a KISS way to test this."
|
|
7
|
+
|
|
8
|
+
## User Scenarios & Testing *(mandatory)*
|
|
9
|
+
|
|
10
|
+
### User Story 1 - Legacy Ruby Support (Priority: P1)
|
|
11
|
+
|
|
12
|
+
When developers use Map in Ruby 1.8.x (or other non-ordered hash implementations), they must receive the same ordered hash behavior that Map has always provided, with keys tracked in insertion order via the `@keys` array mechanism.
|
|
13
|
+
|
|
14
|
+
**Why this priority**: This is the core value proposition - maintaining backward compatibility for users on older Ruby versions without breaking existing functionality. Without this, Map would lose its "works in all rubies" promise.
|
|
15
|
+
|
|
16
|
+
**Independent Test**: Can be fully tested by running the existing test suite on Ruby 1.8.x or similar non-ordered hash implementations and verifying that all ordering-related tests pass.
|
|
17
|
+
|
|
18
|
+
**Acceptance Scenarios**:
|
|
19
|
+
|
|
20
|
+
1. **Given** Map running on Ruby 1.8.x, **When** a user creates a map with `Map[:a, 1, :b, 2, :c, 3]`, **Then** calling `keys` returns `['a', 'b', 'c']` in insertion order
|
|
21
|
+
2. **Given** Map running on Ruby 1.8.x, **When** a user iterates with `each`, **Then** key-value pairs are yielded in insertion order
|
|
22
|
+
3. **Given** Map running on Ruby 1.8.x, **When** a user calls `first` and `last`, **Then** they return the first and last inserted key-value pairs respectively
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### User Story 2 - Modern Ruby Optimization (Priority: P2)
|
|
27
|
+
|
|
28
|
+
When developers use Map in Ruby 1.9+ (where Hash is ordered by default), they must benefit from native Hash ordering performance without the overhead of maintaining a redundant `@keys` array.
|
|
29
|
+
|
|
30
|
+
**Why this priority**: This improves performance and reduces memory overhead for the majority of current users, but doesn't break functionality since Ruby 1.9+ hashes are already ordered. This is secondary to maintaining backward compatibility.
|
|
31
|
+
|
|
32
|
+
**Independent Test**: Can be fully tested by running the test suite on Ruby 1.9+ and verifying that: (1) all tests pass, (2) no `@keys` instance variable is present in Map instances, and (3) ordering behavior matches expected results.
|
|
33
|
+
|
|
34
|
+
**Acceptance Scenarios**:
|
|
35
|
+
|
|
36
|
+
1. **Given** Map running on Ruby 1.9+, **When** a user creates a map with `Map[:a, 1, :b, 2, :c, 3]`, **Then** calling `keys` returns `['a', 'b', 'c']` in insertion order without using `@keys` array
|
|
37
|
+
2. **Given** Map running on Ruby 1.9+, **When** a user inspects a Map instance, **Then** no `@keys` instance variable is present
|
|
38
|
+
3. **Given** Map running on Ruby 1.9+, **When** a user calls ordering-dependent methods (`first`, `last`, `values`, `each`), **Then** they work correctly using native Hash ordering
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### User Story 3 - Simple Cross-Version Testing (Priority: P3)
|
|
43
|
+
|
|
44
|
+
When developers or CI systems need to verify Map behavior across Ruby versions, they must have a simple, KISS (Keep It Simple, Stupid) way to run tests that validate both code paths (with and without the ordered module).
|
|
45
|
+
|
|
46
|
+
**Why this priority**: Testing is important but can be implemented after the core functionality works. It's a development/maintenance concern rather than end-user functionality.
|
|
47
|
+
|
|
48
|
+
**Independent Test**: Can be fully tested by running a test command that simulates both Ruby version scenarios and verifying that it produces clear pass/fail results for both paths.
|
|
49
|
+
|
|
50
|
+
**Acceptance Scenarios**:
|
|
51
|
+
|
|
52
|
+
1. **Given** a test runner configuration, **When** tests are executed, **Then** both "ordered module included" and "ordered module excluded" scenarios are tested
|
|
53
|
+
2. **Given** test results, **When** reviewing output, **Then** it's clear which tests ran under which configuration (ordered module on/off)
|
|
54
|
+
3. **Given** CI environment, **When** tests run, **Then** failures in either path are clearly reported with context about which Ruby version scenario failed
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### Edge Cases
|
|
59
|
+
|
|
60
|
+
- What happens when the Ruby version detection is ambiguous (e.g., alternative Ruby implementations like JRuby, Rubinius)?
|
|
61
|
+
- How does the system handle Ruby versions with the ordering backport?
|
|
62
|
+
- What happens during Marshal load/dump of Maps created on different Ruby versions?
|
|
63
|
+
- How does the module inclusion affect class inheritance and subclassing of Map?
|
|
64
|
+
|
|
65
|
+
## Requirements *(mandatory)*
|
|
66
|
+
|
|
67
|
+
### Functional Requirements
|
|
68
|
+
|
|
69
|
+
- **FR-001**: System MUST extract all ordering-related code (using `@keys` array) into a separate module
|
|
70
|
+
- **FR-002**: System MUST conditionally include the ordering module only when Ruby Hash is not ordered by default (Ruby < 1.9)
|
|
71
|
+
- **FR-003**: System MUST detect Ruby version at load time to determine whether Hash is ordered
|
|
72
|
+
- **FR-004**: System MUST maintain identical external API behavior regardless of whether ordering module is included
|
|
73
|
+
- **FR-005**: System MUST preserve Marshal compatibility for existing Map instances
|
|
74
|
+
- **FR-006**: System MUST ensure all existing tests pass on both old and new Ruby versions without modification
|
|
75
|
+
- **FR-007**: System MUST provide ordering module methods that override default Hash methods when included (e.g., `keys`, `values`, `each`, `first`, `last`, `delete`, `[]=`)
|
|
76
|
+
- **FR-008**: Test framework MUST provide simple mechanism to test both code paths (with/without ordering module)
|
|
77
|
+
- **FR-009**: System MUST handle `@keys` array initialization in `allocate` method conditionally based on Ruby version
|
|
78
|
+
|
|
79
|
+
### Key Entities *(include if feature involves data)*
|
|
80
|
+
|
|
81
|
+
- **OrderingModule**: A module containing all `@keys` array-dependent methods that maintain insertion order manually
|
|
82
|
+
- **Map Class**: The main class that conditionally includes OrderingModule based on Ruby version detection
|
|
83
|
+
- **Ruby Version Detector**: Logic that determines if native Hash ordering is available
|
|
84
|
+
|
|
85
|
+
## Success Criteria *(mandatory)*
|
|
86
|
+
|
|
87
|
+
### Measurable Outcomes
|
|
88
|
+
|
|
89
|
+
- **SC-001**: All existing Map tests pass without modification on both Ruby 1.8.x and Ruby 1.9+ versions
|
|
90
|
+
- **SC-002**: Map instances on Ruby 1.9+ do not contain `@keys` instance variable (verified via instance_variables inspection)
|
|
91
|
+
- **SC-003**: Map maintains insertion order correctly in both Ruby version scenarios (100% of ordering-dependent operations return correct results)
|
|
92
|
+
- **SC-004**: Developers can run a single test command that validates both code paths with clear pass/fail output
|
|
93
|
+
- **SC-005**: Memory footprint per Map instance is reduced on Ruby 1.9+ due to absence of redundant `@keys` array
|
|
94
|
+
- **SC-006**: Test execution time remains similar or improves (no more than 5% regression) across both Ruby versions
|
|
95
|
+
|
|
96
|
+
### Assumptions
|
|
97
|
+
|
|
98
|
+
- Ruby 1.9.0 is the cutoff version where Hash became ordered by default
|
|
99
|
+
- The existing test suite adequately covers all ordering-dependent behavior
|
|
100
|
+
- Marshal serialization format compatibility is acceptable to break across Ruby versions (Map objects serialized on old Ruby may not deserialize correctly on new Ruby if format changes)
|
|
101
|
+
- Alternative Ruby implementations (JRuby, Rubinius, TruffleRuby) follow the same ordering behavior as MRI for their respective version numbers
|
|
102
|
+
- The KISS testing approach means a simple flag or environment variable to force inclusion/exclusion of the ordering module, rather than complex test matrix infrastructure
|
|
103
|
+
|
|
104
|
+
### Dependencies
|
|
105
|
+
|
|
106
|
+
- Access to multiple Ruby version environments for testing (Ruby 1.8.x and Ruby 1.9+)
|
|
107
|
+
- Existing test suite must be comprehensive enough to validate ordering behavior
|
|
108
|
+
- No breaking changes to public API (all existing Map users' code must continue to work)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Tasks: Conditional Ordered Map Module
|
|
2
|
+
|
|
3
|
+
**Input**: Design documents from `/specs/001-ordered-map-module/`
|
|
4
|
+
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md
|
|
5
|
+
|
|
6
|
+
**Tests**: No explicit TDD requirement in spec - using existing test suite for validation
|
|
7
|
+
|
|
8
|
+
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
|
9
|
+
|
|
10
|
+
## Format: `[ID] [P?] [Story] Description`
|
|
11
|
+
|
|
12
|
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
|
13
|
+
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
|
14
|
+
- Include exact file paths in descriptions
|
|
15
|
+
|
|
16
|
+
## Path Conventions
|
|
17
|
+
|
|
18
|
+
Ruby gem structure: `lib/` for source, `test/` for tests at repository root.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Phase 1: Setup (Shared Infrastructure)
|
|
23
|
+
|
|
24
|
+
**Purpose**: Prepare development environment and understand current codebase
|
|
25
|
+
|
|
26
|
+
- [x] T001 Read and understand existing Map implementation in lib/map.rb
|
|
27
|
+
- [x] T002 Identify all 11 ordering-dependent methods that use `@keys` array in lib/map.rb
|
|
28
|
+
- [x] T003 [P] Document current `@keys` array usage patterns and synchronization points
|
|
29
|
+
- [x] T004 [P] Run existing test suite to establish baseline (rake test)
|
|
30
|
+
|
|
31
|
+
**Checkpoint**: Understanding of current implementation complete
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Phase 2: Foundational (Blocking Prerequisites)
|
|
36
|
+
|
|
37
|
+
**Purpose**: Core module structure that MUST be complete before ANY user story can be implemented
|
|
38
|
+
|
|
39
|
+
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
|
40
|
+
|
|
41
|
+
- [x] T005 Create new file lib/map/ordering.rb with module structure skeleton
|
|
42
|
+
- [x] T006 Define Map::Ordering module with proper namespace in lib/map/ordering.rb
|
|
43
|
+
- [x] T007 Implement module self.included hook for class method injection in lib/map/ordering.rb
|
|
44
|
+
- [x] T008 Add conditional require and include logic in lib/map.rb (RUBY_VERSION < '1.9' || ENV['MAP_FORCE_ORDERING'])
|
|
45
|
+
|
|
46
|
+
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Phase 3: User Story 1 - Legacy Ruby Support (Priority: P1) 🎯 MVP
|
|
51
|
+
|
|
52
|
+
**Goal**: Extract all ordering-related code to Map::Ordering module so Ruby < 1.9 maintains insertion order via `@keys` array
|
|
53
|
+
|
|
54
|
+
**Independent Test**: Run tests with `MAP_FORCE_ORDERING=1` to simulate Ruby < 1.9 behavior, verify all ordering tests pass and `@keys` variable exists
|
|
55
|
+
|
|
56
|
+
### Implementation for User Story 1
|
|
57
|
+
|
|
58
|
+
- [x] T009 [P] [US1] Move Map.allocate class method to Map::Ordering module (initializes `@keys = []`) in lib/map/ordering.rb
|
|
59
|
+
- [x] T010 [P] [US1] Move keys instance method to Map::Ordering module (returns `@keys`) in lib/map/ordering.rb
|
|
60
|
+
- [x] T011 [P] [US1] Move []= (store) method to Map::Ordering module (tracks keys in `@keys` array) in lib/map/ordering.rb
|
|
61
|
+
- [x] T012 [P] [US1] Move values method to Map::Ordering module (iterates `@keys` to build array) in lib/map/ordering.rb
|
|
62
|
+
- [x] T013 [P] [US1] Move first method to Map::Ordering module (uses `@keys.first`) in lib/map/ordering.rb
|
|
63
|
+
- [x] T014 [P] [US1] Move last method to Map::Ordering module (uses `@keys.last`) in lib/map/ordering.rb
|
|
64
|
+
- [x] T015 [P] [US1] Move each/each_pair method to Map::Ordering module (iterates `@keys`) in lib/map/ordering.rb
|
|
65
|
+
- [x] T016 [P] [US1] Move each_key method to Map::Ordering module (iterates `@keys`) in lib/map/ordering.rb
|
|
66
|
+
- [x] T017 [P] [US1] Move each_value method to Map::Ordering module (iterates `@keys` to access values) in lib/map/ordering.rb
|
|
67
|
+
- [x] T018 [P] [US1] Move each_with_index method to Map::Ordering module (uses `@keys.each_with_index`) in lib/map/ordering.rb
|
|
68
|
+
- [x] T019 [P] [US1] Move delete method to Map::Ordering module (removes from `@keys` array) in lib/map/ordering.rb
|
|
69
|
+
- [x] T020 [US1] Remove or comment out the 11 extracted methods from lib/map.rb (module will provide them when included)
|
|
70
|
+
- [x] T021 [US1] Verify Map::Ordering module is complete and syntactically correct (ruby -c lib/map/ordering.rb)
|
|
71
|
+
- [x] T022 [US1] Test with MAP_FORCE_ORDERING=1 rake test - verify all tests pass
|
|
72
|
+
- [x] T023 [US1] Verify @keys instance variable exists when module included (add inspection test)
|
|
73
|
+
|
|
74
|
+
**Checkpoint**: At this point, User Story 1 should be fully functional - Ruby < 1.9 behavior simulated via MAP_FORCE_ORDERING=1
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Phase 4: User Story 2 - Modern Ruby Optimization (Priority: P2)
|
|
79
|
+
|
|
80
|
+
**Goal**: Ensure Ruby 1.9+ uses native Hash ordering without `@keys` array overhead, achieving 25% memory savings
|
|
81
|
+
|
|
82
|
+
**Independent Test**: Run tests normally (without MAP_FORCE_ORDERING) on Ruby 1.9+, verify all tests pass, no `@keys` variable exists, and ordering works correctly
|
|
83
|
+
|
|
84
|
+
### Implementation for User Story 2
|
|
85
|
+
|
|
86
|
+
- [x] T024 [US2] Verify conditional inclusion logic in lib/map.rb correctly excludes module on Ruby >= 1.9
|
|
87
|
+
- [x] T025 [US2] Test normal execution (rake test) on current Ruby version (3.x) - verify all tests pass
|
|
88
|
+
- [x] T026 [US2] Verify Map instances have no @keys instance variable (map.instance_variables inspection)
|
|
89
|
+
- [x] T027 [US2] Verify ordering methods delegate to Hash superclass correctly (keys, values, each, first, last)
|
|
90
|
+
- [x] T028 [US2] Add test case to verify memory optimization - Map instance should not have @keys in lib/map.rb comment or test
|
|
91
|
+
- [x] T029 [US2] Verify Map::Options subclass inherits correct behavior (no @keys on Ruby 1.9+)
|
|
92
|
+
- [x] T030 [US2] Performance verification - ensure no regression in iteration speed (optional benchmark)
|
|
93
|
+
|
|
94
|
+
**Checkpoint**: At this point, User Stories 1 AND 2 should both work - Ruby 1.9+ optimized, legacy simulated via env var
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Phase 5: User Story 3 - Simple Cross-Version Testing (Priority: P3)
|
|
99
|
+
|
|
100
|
+
**Goal**: Provide KISS testing mechanism via MAP_FORCE_ORDERING=1 environment variable to validate both code paths
|
|
101
|
+
|
|
102
|
+
**Independent Test**: Run test suite both ways (with and without MAP_FORCE_ORDERING), verify clear output shows which path is active
|
|
103
|
+
|
|
104
|
+
### Implementation for User Story 3
|
|
105
|
+
|
|
106
|
+
- [ ] T031 [P] [US3] Add test helper to detect and report which mode is active in test/map_test.rb or test/lib/testing.rb
|
|
107
|
+
- [ ] T032 [P] [US3] Create verification test that checks @keys presence matches expected mode in test/map_test.rb
|
|
108
|
+
- [ ] T033 [US3] Document testing approach in README.md or TESTING.md file
|
|
109
|
+
- [ ] T034 [US3] Add CI configuration example for testing both paths in .github/workflows or equivalent
|
|
110
|
+
- [ ] T035 [US3] Create quick verification script (test/verify_ordering.rb) that demonstrates both modes per quickstart.md
|
|
111
|
+
- [ ] T036 [US3] Update Rakefile to support `rake test:with_ordering` and `rake test:without_ordering` tasks
|
|
112
|
+
- [ ] T037 [US3] Verify test output clearly indicates which configuration ran (module included/excluded)
|
|
113
|
+
- [ ] T038 [US3] Run full test suite in both modes and verify identical pass/fail results
|
|
114
|
+
|
|
115
|
+
**Checkpoint**: All user stories should now be independently functional - both code paths validated
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Phase 6: Polish & Cross-Cutting Concerns
|
|
120
|
+
|
|
121
|
+
**Purpose**: Improvements that affect multiple user stories and finalize the feature
|
|
122
|
+
|
|
123
|
+
- [ ] T039 [P] Update CHANGELOG with feature description and version notes
|
|
124
|
+
- [ ] T040 [P] Update README.md with explanation of conditional ordering module (if needed)
|
|
125
|
+
- [ ] T041 [P] Add code comments explaining module inclusion logic in lib/map.rb
|
|
126
|
+
- [ ] T042 [P] Add module documentation comments in lib/map/ordering.rb
|
|
127
|
+
- [ ] T043 [P] Document Marshal compatibility limitation in CHANGELOG or README
|
|
128
|
+
- [ ] T044 [US3] Run quickstart.md validation - verify all examples work
|
|
129
|
+
- [ ] T045 Verify Map::Options subclass works correctly in both modes
|
|
130
|
+
- [ ] T046 Check for any Ruby 1.8 syntax issues in Map::Ordering module (if targeting old Ruby)
|
|
131
|
+
- [ ] T047 Final regression test - run existing test suite 3 times (normal, forced ordering, different Ruby if available)
|
|
132
|
+
- [ ] T048 Code review checklist - verify all 11 methods extracted, no duplicates, proper delegation
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Dependencies & Execution Order
|
|
137
|
+
|
|
138
|
+
### Phase Dependencies
|
|
139
|
+
|
|
140
|
+
- **Setup (Phase 1)**: No dependencies - can start immediately
|
|
141
|
+
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
|
142
|
+
- **User Stories (Phase 3-5)**: All depend on Foundational phase completion
|
|
143
|
+
- User Story 1 (P1): Can start after Foundational - No dependencies on other stories
|
|
144
|
+
- User Story 2 (P2): Depends on User Story 1 completion (needs module to verify exclusion works)
|
|
145
|
+
- User Story 3 (P3): Depends on User Stories 1 & 2 (needs both paths working to test)
|
|
146
|
+
- **Polish (Phase 6)**: Depends on all user stories being complete
|
|
147
|
+
|
|
148
|
+
### User Story Dependencies
|
|
149
|
+
|
|
150
|
+
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - Core extraction work
|
|
151
|
+
- **User Story 2 (P2)**: Depends on User Story 1 completion - Validates optimization path
|
|
152
|
+
- **User Story 3 (P3)**: Depends on User Stories 1 & 2 - Validates testing mechanism across both
|
|
153
|
+
|
|
154
|
+
**Note**: Unlike typical projects, these user stories are sequential because:
|
|
155
|
+
- US2 requires US1 module to exist before verifying it's correctly excluded
|
|
156
|
+
- US3 requires both code paths (US1 & US2) working before testing mechanism is meaningful
|
|
157
|
+
|
|
158
|
+
### Within Each User Story
|
|
159
|
+
|
|
160
|
+
- **User Story 1**: Tasks T009-T019 (method moves) are parallelizable [P]
|
|
161
|
+
- **User Story 2**: Tasks T026-T027 are verification steps, run sequentially
|
|
162
|
+
- **User Story 3**: Tasks T031-T032 (test helpers) are parallelizable [P]
|
|
163
|
+
|
|
164
|
+
### Parallel Opportunities
|
|
165
|
+
|
|
166
|
+
- Phase 1: Tasks T003-T004 can run in parallel
|
|
167
|
+
- Phase 2: All tasks sequential (building foundation)
|
|
168
|
+
- User Story 1: Tasks T009-T019 (11 method extractions) can be done in parallel
|
|
169
|
+
- User Story 3: Tasks T031-T032, T033-T034, T039-T043 can run in parallel
|
|
170
|
+
- Polish Phase: Documentation tasks T039-T043 can run in parallel
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Parallel Example: User Story 1 (Method Extraction)
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# All 11 method extractions can happen simultaneously:
|
|
178
|
+
Task: "Move Map.allocate to Map::Ordering (initializes @keys) in lib/map/ordering.rb"
|
|
179
|
+
Task: "Move keys method to Map::Ordering (returns @keys) in lib/map/ordering.rb"
|
|
180
|
+
Task: "Move []= method to Map::Ordering (tracks keys) in lib/map/ordering.rb"
|
|
181
|
+
Task: "Move values method to Map::Ordering (iterates @keys) in lib/map/ordering.rb"
|
|
182
|
+
Task: "Move first method to Map::Ordering (uses @keys.first) in lib/map/ordering.rb"
|
|
183
|
+
Task: "Move last method to Map::Ordering (uses @keys.last) in lib/map/ordering.rb"
|
|
184
|
+
Task: "Move each/each_pair to Map::Ordering (iterates @keys) in lib/map/ordering.rb"
|
|
185
|
+
Task: "Move each_key method to Map::Ordering (iterates @keys) in lib/map/ordering.rb"
|
|
186
|
+
Task: "Move each_value to Map::Ordering (iterates @keys) in lib/map/ordering.rb"
|
|
187
|
+
Task: "Move each_with_index to Map::Ordering (uses @keys) in lib/map/ordering.rb"
|
|
188
|
+
Task: "Move delete method to Map::Ordering (removes from @keys) in lib/map/ordering.rb"
|
|
189
|
+
|
|
190
|
+
# These are all independent - different methods being moved to same new file
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Implementation Strategy
|
|
196
|
+
|
|
197
|
+
### MVP First (User Story 1 Only)
|
|
198
|
+
|
|
199
|
+
1. Complete Phase 1: Setup (understand current code)
|
|
200
|
+
2. Complete Phase 2: Foundational (create module structure)
|
|
201
|
+
3. Complete Phase 3: User Story 1 (extract all ordering methods)
|
|
202
|
+
4. **STOP and VALIDATE**: Test with MAP_FORCE_ORDERING=1, verify @keys exists and tests pass
|
|
203
|
+
5. Ready for PR/review with backward compatibility proven
|
|
204
|
+
|
|
205
|
+
### Incremental Delivery
|
|
206
|
+
|
|
207
|
+
1. Complete Setup + Foundational → Module structure ready
|
|
208
|
+
2. Add User Story 1 → Test with forced ordering → Backward compat proven (MVP!)
|
|
209
|
+
3. Add User Story 2 → Test normal mode → Optimization verified
|
|
210
|
+
4. Add User Story 3 → Test both modes → Testing mechanism validated
|
|
211
|
+
5. Polish → Documentation complete → Feature done
|
|
212
|
+
|
|
213
|
+
### Single Developer Strategy
|
|
214
|
+
|
|
215
|
+
Since user stories are sequential:
|
|
216
|
+
|
|
217
|
+
1. Complete Setup + Foundational (Phase 1-2)
|
|
218
|
+
2. Extract all methods to module (Phase 3 / US1) - can parallelize the 11 method moves
|
|
219
|
+
3. Verify optimization works (Phase 4 / US2)
|
|
220
|
+
4. Add testing mechanism (Phase 5 / US3)
|
|
221
|
+
5. Polish and document (Phase 6)
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Task Summary
|
|
226
|
+
|
|
227
|
+
**Total Tasks**: 48 tasks
|
|
228
|
+
|
|
229
|
+
**Task Count by Phase**:
|
|
230
|
+
- Phase 1 (Setup): 4 tasks
|
|
231
|
+
- Phase 2 (Foundational): 4 tasks
|
|
232
|
+
- Phase 3 (User Story 1 - Legacy Support): 15 tasks
|
|
233
|
+
- Phase 4 (User Story 2 - Optimization): 7 tasks
|
|
234
|
+
- Phase 5 (User Story 3 - Testing): 8 tasks
|
|
235
|
+
- Phase 6 (Polish): 10 tasks
|
|
236
|
+
|
|
237
|
+
**Parallelizable Tasks**: 26 tasks marked with [P]
|
|
238
|
+
|
|
239
|
+
**User Story Mapping**:
|
|
240
|
+
- US1 (Legacy Ruby Support): 15 tasks - Module extraction
|
|
241
|
+
- US2 (Modern Ruby Optimization): 7 tasks - Verification and optimization
|
|
242
|
+
- US3 (Cross-Version Testing): 8 tasks - Testing mechanism
|
|
243
|
+
|
|
244
|
+
**Suggested MVP Scope**:
|
|
245
|
+
- Phase 1 + Phase 2 + Phase 3 (User Story 1)
|
|
246
|
+
- Total: 23 tasks to achieve backward compatibility with module extraction
|
|
247
|
+
- Deliverable: Map works with forced ordering mode, proving legacy Ruby support
|
|
248
|
+
|
|
249
|
+
**Independent Testing**:
|
|
250
|
+
- US1: Run with `MAP_FORCE_ORDERING=1 rake test`, verify @keys exists
|
|
251
|
+
- US2: Run with `rake test`, verify no @keys, all tests pass
|
|
252
|
+
- US3: Run both modes, verify test output clarity
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Notes
|
|
257
|
+
|
|
258
|
+
- [P] tasks = can run in parallel (different methods, independent work)
|
|
259
|
+
- [Story] label maps task to specific user story for traceability
|
|
260
|
+
- This is an internal refactoring, so no external API contracts
|
|
261
|
+
- Existing test suite is validation - no new tests required per spec
|
|
262
|
+
- Module extraction enables clean separation without code duplication
|
|
263
|
+
- Commit after each logical group (e.g., after all methods extracted)
|
|
264
|
+
- Stop at any checkpoint to validate story independently
|
data/test/map_test.rb
CHANGED
|
@@ -173,18 +173,6 @@ Testing Map do
|
|
|
173
173
|
assert{ a != b}
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
-
testing 'simple struct usage' do
|
|
177
|
-
a = assert{ Map.new(:k => :v) }
|
|
178
|
-
s = assert{ a.struct }
|
|
179
|
-
assert{ s.k == :v }
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
testing 'nested struct usage' do
|
|
183
|
-
a = assert{ Map.new(:k => {:l => :v}) }
|
|
184
|
-
s = assert{ a.struct }
|
|
185
|
-
assert{ s.k.l == :v }
|
|
186
|
-
end
|
|
187
|
-
|
|
188
176
|
testing 'that subclassing and clobbering initialize does not kill nested coersion' do
|
|
189
177
|
c = Class.new(Map){ def initialize(arg) end }
|
|
190
178
|
o = assert{ c.new(42) }
|
|
@@ -250,7 +238,6 @@ Testing Map do
|
|
|
250
238
|
end
|
|
251
239
|
|
|
252
240
|
testing 'that coercion is minimal' do
|
|
253
|
-
map = Map.new
|
|
254
241
|
a = Class.new(Map) do
|
|
255
242
|
def to_map() {:k => :a} end
|
|
256
243
|
end
|
|
@@ -284,7 +271,6 @@ Testing Map do
|
|
|
284
271
|
logic = proc do |method, args|
|
|
285
272
|
before = args.dup
|
|
286
273
|
opts = assert{ Map.send(method, args) }
|
|
287
|
-
after = args
|
|
288
274
|
|
|
289
275
|
assert{ opts.is_a?(Map) }
|
|
290
276
|
assert{ !args.last.is_a?(Hash) } if before.last.is_a?(Hash)
|
|
@@ -503,7 +489,7 @@ Testing Map do
|
|
|
503
489
|
each = []
|
|
504
490
|
array = %w( a b c )
|
|
505
491
|
Map.each_pair(array){|k,v| each.push(k,v)}
|
|
506
|
-
assert{
|
|
492
|
+
assert{ each == ['a', 'b', 'c', nil] }
|
|
507
493
|
end
|
|
508
494
|
|
|
509
495
|
testing 'that maps support breath_first_each' do
|
|
@@ -517,6 +503,7 @@ Testing Map do
|
|
|
517
503
|
|
|
518
504
|
accum = []
|
|
519
505
|
Map.breadth_first_each(map){|k, v| accum.push([k, v])}
|
|
506
|
+
|
|
520
507
|
expected =
|
|
521
508
|
[[["hash"], {"x"=>"y"}],
|
|
522
509
|
[["nested hash"], {"nested"=>{"a"=>"b"}}],
|
|
@@ -535,6 +522,8 @@ Testing Map do
|
|
|
535
522
|
[["nested array", 0, 0], 3],
|
|
536
523
|
[["nested array", 1, 0], 4],
|
|
537
524
|
[["nested array", 2, 0], 5]]
|
|
525
|
+
|
|
526
|
+
assert{ expected == accum }
|
|
538
527
|
end
|
|
539
528
|
|
|
540
529
|
testing 'that maps have a needle-in-a-haystack like #contains? method' do
|
|
@@ -666,30 +655,6 @@ Testing Map do
|
|
|
666
655
|
assert( m.A? )
|
|
667
656
|
end
|
|
668
657
|
|
|
669
|
-
testing 'that maps have a clever little question method on Struct' do
|
|
670
|
-
m = Map.new
|
|
671
|
-
m.set(:a, :b, :c, 42)
|
|
672
|
-
m.set([:x, :y, :z] => 42.0, [:A, 2] => 'forty-two')
|
|
673
|
-
s = m.struct
|
|
674
|
-
|
|
675
|
-
assert( s.a.b.c == 42 )
|
|
676
|
-
assert( s.x.y.z == 42.0 )
|
|
677
|
-
|
|
678
|
-
assert( !s.b? )
|
|
679
|
-
assert( s.a? )
|
|
680
|
-
assert( s.a.b? )
|
|
681
|
-
assert( s.a.b.c? )
|
|
682
|
-
assert( !s.a.b.d? )
|
|
683
|
-
|
|
684
|
-
assert( s.x? )
|
|
685
|
-
assert( s.x.y? )
|
|
686
|
-
assert( s.x.y.z? )
|
|
687
|
-
assert( !s.y? )
|
|
688
|
-
|
|
689
|
-
assert( s.A? )
|
|
690
|
-
|
|
691
|
-
end
|
|
692
|
-
|
|
693
658
|
testing 'that Map#default= blows up until a sane strategy for dealing with it is developed' do
|
|
694
659
|
m = Map.new
|
|
695
660
|
|
|
@@ -745,24 +710,6 @@ Testing Map do
|
|
|
745
710
|
assert{ map.list.class != Array }
|
|
746
711
|
end
|
|
747
712
|
|
|
748
|
-
testing 'rack compatible params' do
|
|
749
|
-
m = Map.for(:a => [{}, {:b => 42}], :x => [ nil, [ nil, {:y => 42}] ], :A => {:B => {:C => 42}})
|
|
750
|
-
|
|
751
|
-
assert{ m.param_for(:a, 1, :b) == 'map[a][][b]=42' }
|
|
752
|
-
assert{ m.name_for(:a, 1, :b) == 'map[a][][b]' }
|
|
753
|
-
|
|
754
|
-
assert{ m.param_for(:x, 1, 1, :y) == 'map[x][][][y]=42' }
|
|
755
|
-
assert{ m.name_for(:x, 1, 1, :y) == 'map[x][][][y]' }
|
|
756
|
-
|
|
757
|
-
assert{ m.param_for(:A, :B, :C) == 'map[A][B][C]=42' }
|
|
758
|
-
assert{ m.name_for(:A, :B, :C) == 'map[A][B][C]' }
|
|
759
|
-
|
|
760
|
-
assert{ m.name_for(:A, :B, :C, :prefix => :foo) == 'foo[A][B][C]' }
|
|
761
|
-
|
|
762
|
-
m = Map.for({"a"=>{"b"=>42}, "x"=>{"y"=>42}, "foo"=>:bar, "array"=>[{"k"=>:v}]})
|
|
763
|
-
assert{ m.to_params == "map[a][b]=42&map[x][y]=42&map[foo]=bar&map[array][][k]=v" }
|
|
764
|
-
end
|
|
765
|
-
|
|
766
713
|
testing 'delete_if' do
|
|
767
714
|
m = Map.for(:k => :v)
|
|
768
715
|
assert{ m.delete_if{|k| k.to_s == 'k'} }
|
|
@@ -773,6 +720,23 @@ Testing Map do
|
|
|
773
720
|
assert{ m.empty?}
|
|
774
721
|
end
|
|
775
722
|
|
|
723
|
+
testing 'deep fetch' do
|
|
724
|
+
m = Map.for(:a => {:b => {:c => :v}})
|
|
725
|
+
|
|
726
|
+
assert{ m.fetch(:a, :b, :c) == :v }
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
testing 'fetch mapifys' do
|
|
730
|
+
m = Map.new
|
|
731
|
+
h = {:key => :value, :hash => {:a => :b}, :array => [0, {:a => :b}]}
|
|
732
|
+
|
|
733
|
+
missing = assert{ m.fetch(:missing){ h } }
|
|
734
|
+
assert{ missing.is_a?(Map) }
|
|
735
|
+
assert{ missing[:hash].is_a?(Map) }
|
|
736
|
+
assert{ not missing[:array][0].is_a?(Map) }
|
|
737
|
+
assert{ missing[:array][1].is_a?(Map) }
|
|
738
|
+
end
|
|
739
|
+
|
|
776
740
|
protected
|
|
777
741
|
def new_int_map(n = 1024)
|
|
778
742
|
map = assert{ Map.new }
|
metadata
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: map
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 8.0.0
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Ara T. Howard
|
|
9
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 2025-11-13 00:00:00.000000000 Z
|
|
13
12
|
dependencies: []
|
|
14
|
-
description:
|
|
15
|
-
indifferent ordered hash that works in all rubies
|
|
13
|
+
description: |-
|
|
14
|
+
map.rb is a string/symbol indifferent ordered hash that works in all rubies.
|
|
15
|
+
|
|
16
|
+
out of the over 200 ruby gems i have written, this is the one i use
|
|
17
|
+
every day, in all my projects.
|
|
18
|
+
|
|
19
|
+
some may be accustomed to using ActiveSupport::HashWithIndiffentAccess
|
|
20
|
+
and, although there are some similarities, map.rb is more complete,
|
|
21
|
+
works without requiring a mountain of code, and has been in production
|
|
22
|
+
usage for over 15 years.
|
|
23
|
+
|
|
24
|
+
it has no dependencies, and suports a myriad of other, 'tree-ish'
|
|
25
|
+
operators that will allow you to slice and dice data like a giraffee
|
|
26
|
+
with a giant weed whacker.
|
|
16
27
|
email: ara.t.howard@gmail.com
|
|
17
28
|
executables: []
|
|
18
29
|
extensions: []
|
|
@@ -20,41 +31,46 @@ extra_rdoc_files: []
|
|
|
20
31
|
files:
|
|
21
32
|
- LICENSE
|
|
22
33
|
- README
|
|
34
|
+
- README.md
|
|
23
35
|
- Rakefile
|
|
24
|
-
-
|
|
36
|
+
- images/giraffe.jpeg
|
|
37
|
+
- images/map.png
|
|
25
38
|
- lib/map.rb
|
|
26
|
-
- lib/map/
|
|
39
|
+
- lib/map/_lib.rb
|
|
27
40
|
- lib/map/options.rb
|
|
28
|
-
- lib/map/
|
|
29
|
-
- lib/map/struct.rb
|
|
41
|
+
- lib/map/ordering.rb
|
|
30
42
|
- map.gemspec
|
|
43
|
+
- specs/001-ordered-map-module/checklists/requirements.md
|
|
44
|
+
- specs/001-ordered-map-module/data-model.md
|
|
45
|
+
- specs/001-ordered-map-module/plan.md
|
|
46
|
+
- specs/001-ordered-map-module/quickstart.md
|
|
47
|
+
- specs/001-ordered-map-module/research.md
|
|
48
|
+
- specs/001-ordered-map-module/spec.md
|
|
49
|
+
- specs/001-ordered-map-module/tasks.md
|
|
31
50
|
- test/leak.rb
|
|
32
51
|
- test/lib/testing.rb
|
|
33
52
|
- test/map_test.rb
|
|
34
53
|
homepage: https://github.com/ahoward/map
|
|
35
54
|
licenses:
|
|
36
|
-
-
|
|
37
|
-
|
|
55
|
+
- Ruby
|
|
56
|
+
metadata: {}
|
|
57
|
+
post_install_message:
|
|
38
58
|
rdoc_options: []
|
|
39
59
|
require_paths:
|
|
40
60
|
- lib
|
|
41
61
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
-
none: false
|
|
43
62
|
requirements:
|
|
44
|
-
- -
|
|
63
|
+
- - ">="
|
|
45
64
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
65
|
+
version: '3.0'
|
|
47
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
-
none: false
|
|
49
67
|
requirements:
|
|
50
|
-
- -
|
|
68
|
+
- - ">="
|
|
51
69
|
- !ruby/object:Gem::Version
|
|
52
70
|
version: '0'
|
|
53
71
|
requirements: []
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
summary: map
|
|
72
|
+
rubygems_version: 3.5.18
|
|
73
|
+
signing_key:
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: the perfect ruby data structure
|
|
59
76
|
test_files: []
|
|
60
|
-
has_rdoc:
|