diff --git a/CHANGELOG.md b/CHANGELOG.md index 73eef84..fff1736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### 0.3.0 / 2026-03-19 +* Hash-like Collection methods return Collection objects (#37) + ### 0.2.2 / 2026-03-02 * Ensure that cloned/duped objects get independent collection instances diff --git a/lib/compliance_engine/collection.rb b/lib/compliance_engine/collection.rb index 4d46411..f4d6250 100644 --- a/lib/compliance_engine/collection.rb +++ b/lib/compliance_engine/collection.rb @@ -67,6 +67,13 @@ def keys to_h.keys end + # Returns the values of the collection + # + # @return [Array] the values of the collection + def values + to_h.values + end + # Return a single value from the collection # # @param key [String] the key of the value to return @@ -78,22 +85,34 @@ def [](key) # Iterates over the collection # # @param block [Proc] the block to execute + # @return [self, Enumerator] def each(&block) + return to_enum(:each) unless block + to_h.each(&block) + self end # Iterates over values in the collection # # @param block [Proc] the block to execute + # @return [self, Enumerator] def each_value(&block) + return to_enum(:each_value) unless block + to_h.each_value(&block) + self end # Iterates over keys in the collection # # @param block [Proc] the block to execute + # @return [self, Enumerator] def each_key(&block) + return to_enum(:each_key) unless block + to_h.each_key(&block) + self end # Return true if any of the values in the collection match the block @@ -115,17 +134,35 @@ def all?(&block) # Select values in the collection # # @param block [Proc] the block to execute - # @return [Hash] the filtered hash + # @return [ComplianceEngine::Collection, Enumerator] the filtered collection or an Enumerator when no block is given def select(&block) - to_h.select(&block) + return to_enum(:select) unless block_given? + + result = dup + result.collection = result.to_h.select(&block) + result end + alias filter select + # Filter out values in the collection # # @param block [Proc] the block to execute - # @return [Hash] the filtered hash + # @return [ComplianceEngine::Collection, Enumerator] the filtered collection or an Enumerator when no block is given def reject(&block) - to_h.reject(&block) + return to_enum(:reject) unless block_given? + + result = dup + result.collection = result.to_h.reject(&block) + result + end + + # Transform values in the collection + # + # @param block [Proc] the block to execute + # @return [Hash, Enumerator] a hash with transformed values, or an Enumerator when no block is given + def transform_values(&block) + to_h.transform_values(&block) end private diff --git a/lib/compliance_engine/version.rb b/lib/compliance_engine/version.rb index d90256d..119606a 100644 --- a/lib/compliance_engine/version.rb +++ b/lib/compliance_engine/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ComplianceEngine - VERSION = '0.2.2' + VERSION = '0.3.0' # Handle supported compliance data versions class Version diff --git a/metadata.json b/metadata.json index 406f72e..6c7f6b5 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "simp-compliance_engine", - "version": "0.2.2", + "version": "0.3.0", "author": "Sicura", "summary": "Hiera backend for Sicura Compliance Engine data", "license": "Apache-2.0", diff --git a/spec/classes/compliance_engine/ces_spec.rb b/spec/classes/compliance_engine/ces_spec.rb index 0988062..fd4b879 100644 --- a/spec/classes/compliance_engine/ces_spec.rb +++ b/spec/classes/compliance_engine/ces_spec.rb @@ -12,6 +12,70 @@ expect(ces).to be_instance_of(described_class) end + # --------------------------------------------------------------------------- + # Hash-like methods returning Collections (issue #37) + # --------------------------------------------------------------------------- + describe 'Hash-like methods that return Collections' do + subject(:ces) { described_class.new(ComplianceEngine::Data.new(ComplianceEngine::DataLoader.new(compliance_data))) } + + let(:compliance_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'ce_one' => { 'title' => 'CE One' }, + 'ce_two' => { 'title' => 'CE Two' }, + 'ce_three' => { 'title' => 'CE Three' }, + }, + } + end + + describe '#select' do + it 'returns a Collection of the same type' do + result = ces.select { |k, _| k == 'ce_one' } + expect(result).to be_instance_of(described_class) + end + + it 'contains only the selected keys' do + result = ces.select { |k, _| k == 'ce_one' } + expect(result.keys).to eq(['ce_one']) + end + + it 'does not modify the original collection' do + ces.select { |k, _| k == 'ce_one' } + expect(ces.keys).to contain_exactly('ce_one', 'ce_two', 'ce_three') + end + end + + describe '#reject' do + it 'returns a Collection of the same type' do + result = ces.reject { |k, _| k == 'ce_one' } + expect(result).to be_instance_of(described_class) + end + + it 'excludes the rejected keys' do + result = ces.reject { |k, _| k == 'ce_one' } + expect(result.keys).to contain_exactly('ce_two', 'ce_three') + end + + it 'does not modify the original collection' do + ces.reject { |k, _| k == 'ce_one' } + expect(ces.keys).to contain_exactly('ce_one', 'ce_two', 'ce_three') + end + end + + describe '#transform_values' do + it 'returns a Hash' do + result = ces.transform_values(&:title) + expect(result).to be_instance_of(Hash) + end + + it 'maps each component to its transformed value' do + result = ces.transform_values(&:title) + expect(result).to eq('ce_one' => 'CE One', 'ce_two' => 'CE Two', 'ce_three' => 'CE Three') + end + end + end + # --------------------------------------------------------------------------- # clone/dup isolation (Collection behavior) #