cuprum-collections 0.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 +7 -0
- data/CHANGELOG.md +59 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +25 -0
- data/LICENSE +22 -0
- data/README.md +950 -0
- data/lib/cuprum/collections/base.rb +11 -0
- data/lib/cuprum/collections/basic/collection.rb +135 -0
- data/lib/cuprum/collections/basic/command.rb +112 -0
- data/lib/cuprum/collections/basic/commands/assign_one.rb +54 -0
- data/lib/cuprum/collections/basic/commands/build_one.rb +45 -0
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +48 -0
- data/lib/cuprum/collections/basic/commands/find_many.rb +65 -0
- data/lib/cuprum/collections/basic/commands/find_matching.rb +126 -0
- data/lib/cuprum/collections/basic/commands/find_one.rb +49 -0
- data/lib/cuprum/collections/basic/commands/insert_one.rb +50 -0
- data/lib/cuprum/collections/basic/commands/update_one.rb +52 -0
- data/lib/cuprum/collections/basic/commands/validate_one.rb +69 -0
- data/lib/cuprum/collections/basic/commands.rb +18 -0
- data/lib/cuprum/collections/basic/query.rb +160 -0
- data/lib/cuprum/collections/basic/query_builder.rb +69 -0
- data/lib/cuprum/collections/basic/rspec/command_contract.rb +392 -0
- data/lib/cuprum/collections/basic/rspec.rb +8 -0
- data/lib/cuprum/collections/basic.rb +22 -0
- data/lib/cuprum/collections/command.rb +26 -0
- data/lib/cuprum/collections/commands/abstract_find_many.rb +77 -0
- data/lib/cuprum/collections/commands/abstract_find_matching.rb +64 -0
- data/lib/cuprum/collections/commands/abstract_find_one.rb +44 -0
- data/lib/cuprum/collections/commands.rb +8 -0
- data/lib/cuprum/collections/constraints/attribute_name.rb +22 -0
- data/lib/cuprum/collections/constraints/order/attributes_array.rb +26 -0
- data/lib/cuprum/collections/constraints/order/attributes_hash.rb +27 -0
- data/lib/cuprum/collections/constraints/order/complex_ordering.rb +46 -0
- data/lib/cuprum/collections/constraints/order/sort_direction.rb +32 -0
- data/lib/cuprum/collections/constraints/order.rb +8 -0
- data/lib/cuprum/collections/constraints/ordering.rb +114 -0
- data/lib/cuprum/collections/constraints/query_hash.rb +25 -0
- data/lib/cuprum/collections/constraints.rb +8 -0
- data/lib/cuprum/collections/errors/already_exists.rb +86 -0
- data/lib/cuprum/collections/errors/extra_attributes.rb +66 -0
- data/lib/cuprum/collections/errors/failed_validation.rb +66 -0
- data/lib/cuprum/collections/errors/invalid_parameters.rb +50 -0
- data/lib/cuprum/collections/errors/invalid_query.rb +55 -0
- data/lib/cuprum/collections/errors/missing_default_contract.rb +49 -0
- data/lib/cuprum/collections/errors/not_found.rb +81 -0
- data/lib/cuprum/collections/errors/unknown_operator.rb +71 -0
- data/lib/cuprum/collections/errors.rb +8 -0
- data/lib/cuprum/collections/queries/ordering.rb +74 -0
- data/lib/cuprum/collections/queries/parse.rb +22 -0
- data/lib/cuprum/collections/queries/parse_block.rb +206 -0
- data/lib/cuprum/collections/queries/parse_strategy.rb +91 -0
- data/lib/cuprum/collections/queries.rb +25 -0
- data/lib/cuprum/collections/query.rb +247 -0
- data/lib/cuprum/collections/query_builder.rb +61 -0
- data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +168 -0
- data/lib/cuprum/collections/rspec/build_one_command_contract.rb +93 -0
- data/lib/cuprum/collections/rspec/collection_contract.rb +153 -0
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +106 -0
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +327 -0
- data/lib/cuprum/collections/rspec/find_matching_command_contract.rb +194 -0
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +154 -0
- data/lib/cuprum/collections/rspec/fixtures.rb +89 -0
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +83 -0
- data/lib/cuprum/collections/rspec/query_builder_contract.rb +92 -0
- data/lib/cuprum/collections/rspec/query_contract.rb +650 -0
- data/lib/cuprum/collections/rspec/querying_contract.rb +298 -0
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +79 -0
- data/lib/cuprum/collections/rspec/validate_one_command_contract.rb +96 -0
- data/lib/cuprum/collections/rspec.rb +8 -0
- data/lib/cuprum/collections/version.rb +59 -0
- data/lib/cuprum/collections.rb +26 -0
- metadata +219 -0
| @@ -0,0 +1,392 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/collections/basic/rspec'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cuprum::Collections::Basic::RSpec
         | 
| 6 | 
            +
              # Contract validating the behavior of a basic command implementation.
         | 
| 7 | 
            +
              COMMAND_CONTRACT = lambda do
         | 
| 8 | 
            +
                describe '.subclass' do
         | 
| 9 | 
            +
                  let(:subclass) { described_class.subclass }
         | 
| 10 | 
            +
                  let(:constructor_options) do
         | 
| 11 | 
            +
                    {
         | 
| 12 | 
            +
                      collection_name: 'books',
         | 
| 13 | 
            +
                      data:            data,
         | 
| 14 | 
            +
                      optional_key:    'optional value'
         | 
| 15 | 
            +
                    }
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  it 'should define the class method' do
         | 
| 19 | 
            +
                    expect(described_class)
         | 
| 20 | 
            +
                      .to respond_to(:subclass)
         | 
| 21 | 
            +
                      .with(0).arguments
         | 
| 22 | 
            +
                      .and_any_keywords
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  it { expect(subclass).to be_a Class }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  it { expect(subclass).to be < described_class }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  it 'should define the constructor' do
         | 
| 30 | 
            +
                    expect(subclass)
         | 
| 31 | 
            +
                      .to respond_to(:new)
         | 
| 32 | 
            +
                      .with(0).arguments
         | 
| 33 | 
            +
                      .and_any_keywords
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  it 'should return the collection name' do
         | 
| 37 | 
            +
                    expect(subclass.new(**constructor_options).collection_name)
         | 
| 38 | 
            +
                      .to be collection_name
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  it 'should return the data' do
         | 
| 42 | 
            +
                    expect(subclass.new(**constructor_options).data)
         | 
| 43 | 
            +
                      .to be data
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  it 'should return the options' do
         | 
| 47 | 
            +
                    expect(subclass.new(**constructor_options).options)
         | 
| 48 | 
            +
                      .to be == { optional_key: 'optional value' }
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  describe 'with options' do
         | 
| 52 | 
            +
                    let(:default_options) do
         | 
| 53 | 
            +
                      {
         | 
| 54 | 
            +
                        collection_name: 'books',
         | 
| 55 | 
            +
                        custom_key:      'custom value'
         | 
| 56 | 
            +
                      }
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                    let(:constructor_options) do
         | 
| 59 | 
            +
                      {
         | 
| 60 | 
            +
                        data:         data,
         | 
| 61 | 
            +
                        optional_key: 'optional value'
         | 
| 62 | 
            +
                      }
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
                    let(:subclass) { described_class.subclass(**default_options) }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    it { expect(subclass).to be_a Class }
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    it { expect(subclass).to be < described_class }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    it 'should define the constructor' do
         | 
| 71 | 
            +
                      expect(subclass)
         | 
| 72 | 
            +
                        .to respond_to(:new)
         | 
| 73 | 
            +
                        .with(0).arguments
         | 
| 74 | 
            +
                        .and_any_keywords
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    it 'should return the collection name' do
         | 
| 78 | 
            +
                      expect(subclass.new(**constructor_options).collection_name)
         | 
| 79 | 
            +
                        .to be collection_name
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    it 'should return the data' do
         | 
| 83 | 
            +
                      expect(subclass.new(**constructor_options).data)
         | 
| 84 | 
            +
                        .to be data
         | 
| 85 | 
            +
                    end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    it 'should return the options' do
         | 
| 88 | 
            +
                      expect(subclass.new(**constructor_options).options)
         | 
| 89 | 
            +
                        .to be == {
         | 
| 90 | 
            +
                          custom_key:   'custom value',
         | 
| 91 | 
            +
                          optional_key: 'optional value'
         | 
| 92 | 
            +
                        }
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                describe '#collection_name' do
         | 
| 98 | 
            +
                  include_examples 'should have reader',
         | 
| 99 | 
            +
                    :collection_name,
         | 
| 100 | 
            +
                    -> { collection_name }
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  context 'when initialized with collection_name: symbol' do
         | 
| 103 | 
            +
                    let(:collection_name) { :books }
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    it { expect(command.collection_name).to be == collection_name.to_s }
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                describe '#data' do
         | 
| 110 | 
            +
                  include_examples 'should define reader', :data, -> { data }
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                describe '#default_contract' do
         | 
| 114 | 
            +
                  include_examples 'should define reader', :default_contract, nil
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  context 'when initialized with a default contract' do
         | 
| 117 | 
            +
                    let(:default_contract) { Stannum::Contract.new }
         | 
| 118 | 
            +
                    let(:constructor_options) do
         | 
| 119 | 
            +
                      super().merge(default_contract: default_contract)
         | 
| 120 | 
            +
                    end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    it { expect(command.default_contract).to be default_contract }
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                describe '#member_name' do
         | 
| 127 | 
            +
                  def tools
         | 
| 128 | 
            +
                    SleepingKingStudios::Tools::Toolbelt.instance
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  include_examples 'should have reader',
         | 
| 132 | 
            +
                    :member_name,
         | 
| 133 | 
            +
                    -> { tools.str.singularize(collection_name) }
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  context 'when initialized with collection_name: value' do
         | 
| 136 | 
            +
                    let(:collection_name) { :books }
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                    it 'should return the singular collection name' do
         | 
| 139 | 
            +
                      expect(command.member_name)
         | 
| 140 | 
            +
                        .to be == tools.str.singularize(collection_name.to_s)
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  context 'when initialized with member_name: string' do
         | 
| 145 | 
            +
                    let(:member_name)         { 'tome' }
         | 
| 146 | 
            +
                    let(:constructor_options) { super().merge(member_name: member_name) }
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    it 'should return the singular collection name' do
         | 
| 149 | 
            +
                      expect(command.member_name).to be member_name
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  context 'when initialized with member_name: symbol' do
         | 
| 154 | 
            +
                    let(:member_name)         { :tome }
         | 
| 155 | 
            +
                    let(:constructor_options) { super().merge(member_name: member_name) }
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    it 'should return the singular collection name' do
         | 
| 158 | 
            +
                      expect(command.member_name).to be == member_name.to_s
         | 
| 159 | 
            +
                    end
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                describe '#options' do
         | 
| 164 | 
            +
                  let(:expected_options) do
         | 
| 165 | 
            +
                    defined?(super()) ? super() : constructor_options
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  include_examples 'should define reader',
         | 
| 169 | 
            +
                    :options,
         | 
| 170 | 
            +
                    -> { be == expected_options }
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  context 'when initialized with options' do
         | 
| 173 | 
            +
                    let(:constructor_options) { super().merge({ key: 'value' }) }
         | 
| 174 | 
            +
                    let(:expected_options)    { super().merge({ key: 'value' }) }
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    it { expect(command.options).to be == expected_options }
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                describe '#primary_key_name' do
         | 
| 181 | 
            +
                  include_examples 'should define reader', :primary_key_name, :id
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                  context 'when initialized with a primary key name' do
         | 
| 184 | 
            +
                    let(:primary_key_name) { :uuid }
         | 
| 185 | 
            +
                    let(:constructor_options) do
         | 
| 186 | 
            +
                      super().merge({ primary_key_name: primary_key_name })
         | 
| 187 | 
            +
                    end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    it { expect(command.primary_key_name).to be == primary_key_name }
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                describe '#primary_key_type' do
         | 
| 194 | 
            +
                  include_examples 'should define reader', :primary_key_type, Integer
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                  context 'when initialized with a primary key type' do
         | 
| 197 | 
            +
                    let(:primary_key_type) { String }
         | 
| 198 | 
            +
                    let(:constructor_options) do
         | 
| 199 | 
            +
                      super().merge({ primary_key_type: primary_key_type })
         | 
| 200 | 
            +
                    end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                    it { expect(command.primary_key_type).to be == primary_key_type }
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                describe '#validate_primary_key' do
         | 
| 207 | 
            +
                  let(:primary_key_type) { Integer }
         | 
| 208 | 
            +
                  let(:expected_error) do
         | 
| 209 | 
            +
                    type     = primary_key_type
         | 
| 210 | 
            +
                    contract = Stannum::Contracts::ParametersContract.new do
         | 
| 211 | 
            +
                      keyword :primary_key, type
         | 
| 212 | 
            +
                    end
         | 
| 213 | 
            +
                    errors = contract.errors_for(
         | 
| 214 | 
            +
                      {
         | 
| 215 | 
            +
                        arguments: [],
         | 
| 216 | 
            +
                        block:     nil,
         | 
| 217 | 
            +
                        keywords:  { primary_key: nil }
         | 
| 218 | 
            +
                      }
         | 
| 219 | 
            +
                    )
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                    Cuprum::Collections::Errors::InvalidParameters.new(
         | 
| 222 | 
            +
                      command: command,
         | 
| 223 | 
            +
                      errors:  errors
         | 
| 224 | 
            +
                    )
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  it 'should define the private method' do
         | 
| 228 | 
            +
                    expect(command)
         | 
| 229 | 
            +
                      .to respond_to(:validate_primary_key, true)
         | 
| 230 | 
            +
                      .with(1).argument
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  describe 'with nil' do
         | 
| 234 | 
            +
                    it 'should return a failing result' do
         | 
| 235 | 
            +
                      expect(command.send(:validate_primary_key, nil))
         | 
| 236 | 
            +
                        .to be_a_failing_result
         | 
| 237 | 
            +
                        .with_error(expected_error)
         | 
| 238 | 
            +
                    end
         | 
| 239 | 
            +
                  end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                  describe 'with an Object' do
         | 
| 242 | 
            +
                    it 'should return a failing result' do
         | 
| 243 | 
            +
                      expect(command.send(:validate_primary_key, Object.new.freeze))
         | 
| 244 | 
            +
                        .to be_a_failing_result
         | 
| 245 | 
            +
                        .with_error(expected_error)
         | 
| 246 | 
            +
                    end
         | 
| 247 | 
            +
                  end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
                  describe 'with a String' do
         | 
| 250 | 
            +
                    it 'should return a failing result' do
         | 
| 251 | 
            +
                      expect(command.send(:validate_primary_key, '12345'))
         | 
| 252 | 
            +
                        .to be_a_failing_result
         | 
| 253 | 
            +
                        .with_error(expected_error)
         | 
| 254 | 
            +
                    end
         | 
| 255 | 
            +
                  end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                  describe 'with an Integer' do
         | 
| 258 | 
            +
                    it 'should not return a result' do
         | 
| 259 | 
            +
                      expect(command.send(:validate_primary_key, 12_345)).not_to be_a_result
         | 
| 260 | 
            +
                    end
         | 
| 261 | 
            +
                  end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                  context 'when initialized with a primary key type' do
         | 
| 264 | 
            +
                    let(:primary_key_type) { String }
         | 
| 265 | 
            +
                    let(:constructor_options) do
         | 
| 266 | 
            +
                      super().merge({ primary_key_type: primary_key_type })
         | 
| 267 | 
            +
                    end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    describe 'with an Integer' do
         | 
| 270 | 
            +
                      it 'should return a failing result' do
         | 
| 271 | 
            +
                        expect(command.send(:validate_primary_key, 12_345))
         | 
| 272 | 
            +
                          .to be_a_failing_result
         | 
| 273 | 
            +
                          .with_error(expected_error)
         | 
| 274 | 
            +
                      end
         | 
| 275 | 
            +
                    end
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                    describe 'with a String' do
         | 
| 278 | 
            +
                      it 'should not return a result' do
         | 
| 279 | 
            +
                        expect(command.send(:validate_primary_key, '12345'))
         | 
| 280 | 
            +
                          .not_to be_a_result
         | 
| 281 | 
            +
                      end
         | 
| 282 | 
            +
                    end
         | 
| 283 | 
            +
                  end
         | 
| 284 | 
            +
                end
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                describe '#validate_primary_keys' do
         | 
| 287 | 
            +
                  let(:primary_keys)     { nil }
         | 
| 288 | 
            +
                  let(:primary_key_type) { Integer }
         | 
| 289 | 
            +
                  let(:expected_error) do
         | 
| 290 | 
            +
                    type     = primary_key_type
         | 
| 291 | 
            +
                    contract = Stannum::Contracts::ParametersContract.new do
         | 
| 292 | 
            +
                      keyword :primary_keys,
         | 
| 293 | 
            +
                        Stannum::Constraints::Types::ArrayType.new(item_type: type)
         | 
| 294 | 
            +
                    end
         | 
| 295 | 
            +
                    errors = contract.errors_for(
         | 
| 296 | 
            +
                      {
         | 
| 297 | 
            +
                        arguments: [],
         | 
| 298 | 
            +
                        block:     nil,
         | 
| 299 | 
            +
                        keywords:  { primary_keys: primary_keys }
         | 
| 300 | 
            +
                      }
         | 
| 301 | 
            +
                    )
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                    Cuprum::Collections::Errors::InvalidParameters.new(
         | 
| 304 | 
            +
                      command: command,
         | 
| 305 | 
            +
                      errors:  errors
         | 
| 306 | 
            +
                    )
         | 
| 307 | 
            +
                  end
         | 
| 308 | 
            +
             | 
| 309 | 
            +
                  it 'should define the private method' do
         | 
| 310 | 
            +
                    expect(command)
         | 
| 311 | 
            +
                      .to respond_to(:validate_primary_keys, true)
         | 
| 312 | 
            +
                      .with(1).argument
         | 
| 313 | 
            +
                  end
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                  describe 'with nil' do
         | 
| 316 | 
            +
                    it 'should return a failing result' do
         | 
| 317 | 
            +
                      expect(command.send(:validate_primary_keys, nil))
         | 
| 318 | 
            +
                        .to be_a_failing_result
         | 
| 319 | 
            +
                        .with_error(expected_error)
         | 
| 320 | 
            +
                    end
         | 
| 321 | 
            +
                  end
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                  describe 'with an Object' do
         | 
| 324 | 
            +
                    it 'should return a failing result' do
         | 
| 325 | 
            +
                      expect(command.send(:validate_primary_keys, Object.new.freeze))
         | 
| 326 | 
            +
                        .to be_a_failing_result
         | 
| 327 | 
            +
                        .with_error(expected_error)
         | 
| 328 | 
            +
                    end
         | 
| 329 | 
            +
                  end
         | 
| 330 | 
            +
             | 
| 331 | 
            +
                  describe 'with a String' do
         | 
| 332 | 
            +
                    it 'should return a failing result' do
         | 
| 333 | 
            +
                      expect(command.send(:validate_primary_keys, '12345'))
         | 
| 334 | 
            +
                        .to be_a_failing_result
         | 
| 335 | 
            +
                        .with_error(expected_error)
         | 
| 336 | 
            +
                    end
         | 
| 337 | 
            +
                  end
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                  describe 'with an Integer' do
         | 
| 340 | 
            +
                    it 'should return a failing result' do
         | 
| 341 | 
            +
                      expect(command.send(:validate_primary_keys, 12_345))
         | 
| 342 | 
            +
                        .to be_a_failing_result
         | 
| 343 | 
            +
                        .with_error(expected_error)
         | 
| 344 | 
            +
                    end
         | 
| 345 | 
            +
                  end
         | 
| 346 | 
            +
             | 
| 347 | 
            +
                  describe 'with an empty Array' do
         | 
| 348 | 
            +
                    it 'should not return a result' do
         | 
| 349 | 
            +
                      expect(command.send(:validate_primary_keys, []))
         | 
| 350 | 
            +
                        .not_to be_a_result
         | 
| 351 | 
            +
                    end
         | 
| 352 | 
            +
                  end
         | 
| 353 | 
            +
             | 
| 354 | 
            +
                  describe 'with an Array with nil values' do
         | 
| 355 | 
            +
                    let(:primary_keys) { Array.new(3, nil) }
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                    it 'should return a failing result' do
         | 
| 358 | 
            +
                      expect(command.send(:validate_primary_keys, primary_keys))
         | 
| 359 | 
            +
                        .to be_a_failing_result
         | 
| 360 | 
            +
                        .with_error(expected_error)
         | 
| 361 | 
            +
                    end
         | 
| 362 | 
            +
                  end
         | 
| 363 | 
            +
             | 
| 364 | 
            +
                  describe 'with an Array with Object values' do
         | 
| 365 | 
            +
                    let(:primary_keys) { Array.new(3) { Object.new.freeze } }
         | 
| 366 | 
            +
             | 
| 367 | 
            +
                    it 'should return a failing result' do
         | 
| 368 | 
            +
                      expect(command.send(:validate_primary_keys, primary_keys))
         | 
| 369 | 
            +
                        .to be_a_failing_result
         | 
| 370 | 
            +
                        .with_error(expected_error)
         | 
| 371 | 
            +
                    end
         | 
| 372 | 
            +
                  end
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                  describe 'with an Array with String values' do
         | 
| 375 | 
            +
                    let(:primary_keys) { %w[ichi ni san] }
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                    it 'should return a failing result' do
         | 
| 378 | 
            +
                      expect(command.send(:validate_primary_keys, primary_keys))
         | 
| 379 | 
            +
                        .to be_a_failing_result
         | 
| 380 | 
            +
                        .with_error(expected_error)
         | 
| 381 | 
            +
                    end
         | 
| 382 | 
            +
                  end
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                  describe 'with an Array with Integer values' do
         | 
| 385 | 
            +
                    it 'should not return a result' do
         | 
| 386 | 
            +
                      expect(command.send(:validate_primary_keys, [0, 1, 2]))
         | 
| 387 | 
            +
                        .not_to be_a_result
         | 
| 388 | 
            +
                    end
         | 
| 389 | 
            +
                  end
         | 
| 390 | 
            +
                end
         | 
| 391 | 
            +
              end
         | 
| 392 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/collections'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Cuprum::Collections
         | 
| 6 | 
            +
              # The Basic collection is an example, in-memory collection implementation.
         | 
| 7 | 
            +
              module Basic
         | 
| 8 | 
            +
                # Returns a new instance of Basic::Collection.
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # @param options [Hash] Constructor options for the collection.
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                # @see Cuprum::Collections::Basic::Collection#initialize.
         | 
| 13 | 
            +
                def self.new(**options)
         | 
| 14 | 
            +
                  Cuprum::Collections::Basic::Collection.new(**options)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                autoload :Collection, 'cuprum/collections/basic/collection'
         | 
| 18 | 
            +
                autoload :Command,    'cuprum/collections/basic/command'
         | 
| 19 | 
            +
                autoload :Commands,   'cuprum/collections/basic/commands'
         | 
| 20 | 
            +
                autoload :Query,      'cuprum/collections/basic/query'
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'stannum/parameter_validation'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'cuprum/collections'
         | 
| 6 | 
            +
            require 'cuprum/collections/errors/invalid_parameters'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Cuprum::Collections
         | 
| 9 | 
            +
              # Abstract base class for Cuprum::Collection commands.
         | 
| 10 | 
            +
              class Command < Cuprum::Command
         | 
| 11 | 
            +
                extend  Stannum::ParameterValidation
         | 
| 12 | 
            +
                include Stannum::ParameterValidation
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def handle_invalid_parameters(errors:, method_name:)
         | 
| 17 | 
            +
                  return super unless method_name == :call
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  error = Cuprum::Collections::Errors::InvalidParameters.new(
         | 
| 20 | 
            +
                    command: self,
         | 
| 21 | 
            +
                    errors:  errors
         | 
| 22 | 
            +
                  )
         | 
| 23 | 
            +
                  failure(error)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/collections/commands'
         | 
| 4 | 
            +
            require 'cuprum/collections/errors/not_found'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Cuprum::Collections::Commands
         | 
| 7 | 
            +
              # Abstract implementation of the FindMany command.
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              # Subclasses must define the #build_query method, which returns an empty
         | 
| 10 | 
            +
              # Query instance for that collection.
         | 
| 11 | 
            +
              module AbstractFindMany
         | 
| 12 | 
            +
                private
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def apply_query(primary_keys:, scope:)
         | 
| 15 | 
            +
                  key = primary_key_name
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  (scope || build_query).where { { key => one_of(primary_keys) } }
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def handle_missing_items(allow_partial:, items:, primary_keys:)
         | 
| 21 | 
            +
                  found, missing = match_items(items: items, primary_keys: primary_keys)
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  return found if missing.empty?
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  return found if allow_partial && !found.empty?
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  error = Cuprum::Collections::Errors::NotFound.new(
         | 
| 28 | 
            +
                    collection_name:    collection_name,
         | 
| 29 | 
            +
                    primary_key_name:   primary_key_name,
         | 
| 30 | 
            +
                    primary_key_values: missing
         | 
| 31 | 
            +
                  )
         | 
| 32 | 
            +
                  Cuprum::Result.new(error: error)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def items_with_primary_keys(items:)
         | 
| 36 | 
            +
                  # :nocov:
         | 
| 37 | 
            +
                  items.map { |item| [item.send(primary_key_name), item] }.to_h
         | 
| 38 | 
            +
                  # :nocov:
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def match_items(items:, primary_keys:)
         | 
| 42 | 
            +
                  items   = items_with_primary_keys(items: items)
         | 
| 43 | 
            +
                  found   = []
         | 
| 44 | 
            +
                  missing = []
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  primary_keys.each do |key|
         | 
| 47 | 
            +
                    item = items[key]
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    item.nil? ? (missing << key) : (found << item)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  [found, missing]
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def process(
         | 
| 56 | 
            +
                  primary_keys:,
         | 
| 57 | 
            +
                  allow_partial: false,
         | 
| 58 | 
            +
                  envelope:      false,
         | 
| 59 | 
            +
                  scope:         nil
         | 
| 60 | 
            +
                )
         | 
| 61 | 
            +
                  query = apply_query(primary_keys: primary_keys, scope: scope)
         | 
| 62 | 
            +
                  items = step do
         | 
| 63 | 
            +
                    handle_missing_items(
         | 
| 64 | 
            +
                      allow_partial: allow_partial,
         | 
| 65 | 
            +
                      items:         query.to_a,
         | 
| 66 | 
            +
                      primary_keys:  primary_keys
         | 
| 67 | 
            +
                    )
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  envelope ? wrap_items(items) : items
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def wrap_items(items)
         | 
| 74 | 
            +
                  { collection_name => items }
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/collections/commands'
         | 
| 4 | 
            +
            require 'cuprum/collections/queries/parse'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Cuprum::Collections::Commands
         | 
| 7 | 
            +
              # Abstract implementation of the FindMatching command.
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              # Subclasses must define the #build_query method, which returns an empty
         | 
| 10 | 
            +
              # Query instance for that collection.
         | 
| 11 | 
            +
              module AbstractFindMatching
         | 
| 12 | 
            +
                private
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def apply_query(criteria:, limit:, offset:, order:, scope:)
         | 
| 15 | 
            +
                  query = scope || build_query
         | 
| 16 | 
            +
                  query = query.limit(limit)   if limit
         | 
| 17 | 
            +
                  query = query.offset(offset) if offset
         | 
| 18 | 
            +
                  query = query.order(order)   if order
         | 
| 19 | 
            +
                  query = query.where(criteria, strategy: :unsafe) unless criteria.empty?
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  success(query)
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def parse_criteria(strategy:, where:, &block)
         | 
| 25 | 
            +
                  return [] if strategy.nil? && where.nil? && !block_given?
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  Cuprum::Collections::Queries::Parse.new.call(
         | 
| 28 | 
            +
                    strategy: strategy,
         | 
| 29 | 
            +
                    where:    where || block
         | 
| 30 | 
            +
                  )
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def process( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
         | 
| 34 | 
            +
                  envelope: false,
         | 
| 35 | 
            +
                  limit:    nil,
         | 
| 36 | 
            +
                  offset:   nil,
         | 
| 37 | 
            +
                  order:    nil,
         | 
| 38 | 
            +
                  scope:    nil,
         | 
| 39 | 
            +
                  strategy: nil,
         | 
| 40 | 
            +
                  where:    nil,
         | 
| 41 | 
            +
                  &block
         | 
| 42 | 
            +
                )
         | 
| 43 | 
            +
                  criteria = step do
         | 
| 44 | 
            +
                    parse_criteria(strategy: strategy, where: where, &block)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  query = step do
         | 
| 48 | 
            +
                    apply_query(
         | 
| 49 | 
            +
                      criteria: criteria,
         | 
| 50 | 
            +
                      limit:    limit,
         | 
| 51 | 
            +
                      offset:   offset,
         | 
| 52 | 
            +
                      order:    order,
         | 
| 53 | 
            +
                      scope:    scope
         | 
| 54 | 
            +
                    )
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  envelope ? wrap_query(query) : query.each
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def wrap_query(query)
         | 
| 61 | 
            +
                  { collection_name => query.to_a }
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/collections/commands'
         | 
| 4 | 
            +
            require 'cuprum/collections/errors/not_found'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Cuprum::Collections::Commands
         | 
| 7 | 
            +
              # Abstract implementation of the FindOne command.
         | 
| 8 | 
            +
              #
         | 
| 9 | 
            +
              # Subclasses must define the #build_query method, which returns an empty
         | 
| 10 | 
            +
              # Query instance for that collection.
         | 
| 11 | 
            +
              module AbstractFindOne
         | 
| 12 | 
            +
                private
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def apply_query(primary_key:, scope:)
         | 
| 15 | 
            +
                  key = primary_key_name
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  (scope || build_query).where { { key => equals(primary_key) } }.limit(1)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def handle_missing_item(item:, primary_key:)
         | 
| 21 | 
            +
                  return if item
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  error = Cuprum::Collections::Errors::NotFound.new(
         | 
| 24 | 
            +
                    collection_name:    collection_name,
         | 
| 25 | 
            +
                    primary_key_name:   primary_key_name,
         | 
| 26 | 
            +
                    primary_key_values: [primary_key]
         | 
| 27 | 
            +
                  )
         | 
| 28 | 
            +
                  Cuprum::Result.new(error: error)
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def process(envelope:, primary_key:, scope:)
         | 
| 32 | 
            +
                  query = apply_query(primary_key: primary_key, scope: scope)
         | 
| 33 | 
            +
                  item  = query.to_a.first
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  step { handle_missing_item(item: item, primary_key: primary_key) }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  envelope ? wrap_item(item) : item
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def wrap_item(item)
         | 
| 41 | 
            +
                  { member_name => item }
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'stannum/constraints/hashes/indifferent_key'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'cuprum/collections/constraints'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Cuprum::Collections::Constraints
         | 
| 8 | 
            +
              # Asserts that the object is a non-empty String or Symbol.
         | 
| 9 | 
            +
              class AttributeName < Stannum::Constraints::Hashes::IndifferentKey
         | 
| 10 | 
            +
                # The :type of the error generated for a matching object.
         | 
| 11 | 
            +
                NEGATED_TYPE = 'cuprum.collections.constraints.is_valid_attribute_name'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # The :type of the error generated for a non-matching object.
         | 
| 14 | 
            +
                TYPE = 'cuprum.collections.constraints.is_not_valid_attribute_name'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # @return [Cuprum::Collections::Constraints::AttributeName] a cached
         | 
| 17 | 
            +
                #   instance of the constraint with default options.
         | 
| 18 | 
            +
                def self.instance
         | 
| 19 | 
            +
                  @instance ||= new
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'cuprum/collections/constraints/attribute_name'
         | 
| 4 | 
            +
            require 'cuprum/collections/constraints/order'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require 'stannum/constraints/types/array_type'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Cuprum::Collections::Constraints::Order
         | 
| 9 | 
            +
              # Asserts that the object is an Array of attribute names.
         | 
| 10 | 
            +
              class AttributesArray < Stannum::Constraints::Types::ArrayType
         | 
| 11 | 
            +
                # @return [Cuprum::Collections::Constraints::Order::AttributesArray] a
         | 
| 12 | 
            +
                #   cached instance of the constraint with default options.
         | 
| 13 | 
            +
                def self.instance
         | 
| 14 | 
            +
                  @instance ||= new
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                # @param options [Hash<Symbol, Object>] Configuration options for the
         | 
| 18 | 
            +
                #   constraint. Defaults to an empty Hash.
         | 
| 19 | 
            +
                def initialize(**options)
         | 
| 20 | 
            +
                  super(
         | 
| 21 | 
            +
                    item_type: Cuprum::Collections::Constraints::AttributeName.instance,
         | 
| 22 | 
            +
                    **options
         | 
| 23 | 
            +
                  )
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         |