Skip to content

Commit

Permalink
[WIP] Implement Cuprum::Collections::Query#scope.
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepingkingstudios committed Jan 19, 2024
1 parent 376387c commit 8f65567
Show file tree
Hide file tree
Showing 24 changed files with 401 additions and 862 deletions.
46 changes: 19 additions & 27 deletions lib/cuprum/collections/basic/query.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
# frozen_string_literal: true

require 'cuprum/collections/basic'
require 'cuprum/collections/basic/query_builder'
require 'cuprum/collections/basic/scopes/null_scope'
require 'cuprum/collections/query'

module Cuprum::Collections::Basic
# Concrete implementation of a Query for an in-memory collection.
class Query < Cuprum::Collections::Query
include Enumerable

# @param data [Array<Hash>] The current data in the collection. Should be an
# Array of Hashes, each of which represents one item in the collection.
def initialize(data)
super()

@data = data
@filters = []
@limit = nil
@offset = nil
@order = {}
@data = data
end

# Iterates through the collection, yielding each item matching the query.
Expand Down Expand Up @@ -51,7 +45,7 @@ def initialize(data)
def each(...)
return enum_for(:each, ...) unless block_given?

filtered_data.each(...)
scoped_data.each(...)
end

# Checks for the presence of collection items matching the query.
Expand All @@ -62,9 +56,9 @@ def each(...)
#
# @return [Boolean] true if any items match the query; otherwise false.
def exists?
data.any? do |item|
@filters.all? { |filter| filter.call(item) }
end
return data.any? unless scope

data.any? { |item| scope.match?(item: item) }
end

# Returns an array containing each collection item matching the query.
Expand All @@ -90,17 +84,13 @@ def exists?
# @see #order
# @see #where
def to_a
filtered_data
scoped_data
end

protected

def query_builder
Cuprum::Collections::Basic::QueryBuilder.new(self)
end

def reset!
@filtered_data = nil
@scoped_data = nil

self
end
Expand All @@ -117,12 +107,6 @@ def with_filters(filters)

attr_reader :filters

def apply_filters(data)
data.select do |item|
@filters.all? { |filter| filter.call(item) }
end
end

def apply_limit_offset(data)
return data[@offset...(@offset + @limit)] || [] if @limit && @offset
return data[0...@limit] if @limit
Expand All @@ -148,10 +132,18 @@ def apply_order(data)
end
end

def filtered_data
@filtered_data ||=
def apply_scope(data)
scope ? scope.call(data: data) : data
end

def default_scope
Cuprum::Collections::Basic::Scopes::NullScope.new
end

def scoped_data
@scoped_data ||=
data
.then { |ary| apply_filters(ary) }
.then { |ary| apply_scope(ary) }
.then { |ary| apply_order(ary) }
.then { |ary| apply_limit_offset(ary) }
.map(&:dup)
Expand Down
66 changes: 35 additions & 31 deletions lib/cuprum/collections/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,21 @@

require 'cuprum/collections'
require 'cuprum/collections/queries/ordering'
require 'cuprum/collections/scopes/null_scope'

module Cuprum::Collections
# Abstract base class for collection Query implementations.
class Query
include Enumerable

UNDEFINED = Object.new.freeze
private_constant :UNDEFINED

def initialize
@criteria = []
end

# Returns a normalized representation of the query criteria.
#
# The query criteria define which data from the collection matches the
# query. Specifically, an item in the collection matches the query if and
# only if it matches each criterion. If the query has no criteria, then it
# will match all items in the collection.
#
# Each criterion is represented as an Array with three elements:
# - The name of the property or column to select by.
# - The operation to filter, such as :eq (an equality operation).
# - The expected value.
#
# For example, a query that selects all items whose :series property is
# equal to 'The Lord of the Rings' would have the following criterion:
# `[:series, :eq, 'The Lord of the Rings']`.
#
# @return [Array<Array>] the query criteria.
#
# @see #where
def criteria
@criteria.dup
@limit = nil
@offset = nil
@order = {}
@scope = nil
end

# Sets or returns the maximum number of items returned by the query.
Expand Down Expand Up @@ -151,6 +134,21 @@ def reset
dup.reset!
end

# Returns the current scope for the query.
#
# Composition methods should not be called on the scope directly, as they
# will not change the scope object bound to the query. Call the
# corresponding methods on the query itself, i.e. call query.where() instead
# of query.scope.where().
#
# @return [Cuprum::Collections::Scopes::Base] the current scope for the
# query.
def scope
@scope ||= default_scope
end

# @todo: Rewrite
#
# Returns a copy of the query with the specified filters.
#
# The given parameters are used to construct query criteria, which define
Expand Down Expand Up @@ -187,13 +185,9 @@ def reset
# values should be either the literal value for that attribute or a
# method call for a valid operation defined for the query.
#
# @see #criteria
def where(filter = nil, strategy: nil, &block)
filter ||= block

return dup if filter.nil? && strategy.nil?

query_builder.call(strategy: strategy, where: filter)
# @see #scope
def where(...)
dup.with_scope(scope.where(...)).reset!
end

protected
Expand Down Expand Up @@ -228,8 +222,18 @@ def with_order(order)
self
end

def with_scope(scope)
@scope = scope

self
end

private

def default_scope
Cuprum::Collections::Scopes::NullScope.new
end

def validate_limit(count)
return if count.is_a?(Integer) && !count.negative?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,9 @@ module WithBasicCommandContextsContract
Cuprum::Collections::Basic::Query.new(mapped_data)
end
let(:scope) do
Cuprum::Collections::Basic::Query
.new(mapped_data).where(scope_filter)
next query.where(scope_filter) unless scope_filter.is_a?(Proc)

query.where(&scope_filter)
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,13 +406,15 @@ def tools
end
end

it { expect(query.criteria).to be == [] }

it { expect(query.limit).to be nil }

it { expect(query.offset).to be nil }

it { expect(query.order).to be == default_order }

it { expect(query.scope).to be_a Cuprum::Collections::Scopes::Base }

it { expect(query.scope.type).to be :null }
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ module ShouldBeAFindMatchingCommandContract
it { expect(result.value[collection_name]).to be == expected_data }
end

let(:filter) { defined?(super()) ? super() : nil }
let(:options) do
opts = {}

Expand Down
Loading

0 comments on commit 8f65567

Please sign in to comment.