Skip to content

Commit

Permalink
First pass at migrating RBS sigs to inline RBS
Browse files Browse the repository at this point in the history
  • Loading branch information
stevegeek committed Sep 18, 2024
1 parent 3524a24 commit 3479104
Show file tree
Hide file tree
Showing 38 changed files with 693 additions and 494 deletions.
3 changes: 3 additions & 0 deletions .standard.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# For available configuration options, see:
# https://github.com/testdouble/standard
ruby_version: 3.1
ignore:
- "**/*":
- "Layout/LeadingCommentSpace"
6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ group :development, :test do

gem "minitest", "~> 5.0"

gem "standard"
gem "standard", require: false

gem "steep"
gem "steep", require: false

gem "rbs-inline", "~> 0.8.0", require: false
end
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ The core implementation provides the following functionality:
* provides a number of utility methods that operate on the underlying collection (eg `exists?`)
* provides a `+` (`compose`) method which merges two query object instances (see section below for details!)
* can specify a mapping or transform method to `transform` to perform on results
* in development outputs the callstack that led to the execution of the query
* acts as a callable which executes the underlying query with `.first`
* can return an `Enumerable` of results

Expand Down
12 changes: 3 additions & 9 deletions lib/quo.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# frozen_string_literal: true

# rbs_inline: enabled

require_relative "quo/version"
require "quo/engine"

module Quo
extend ActiveSupport::Autoload

autoload :Callstack, "quo/utilities/callstack"
autoload :Compose, "quo/utilities/compose"
autoload :Sanitize, "quo/utilities/sanitize"
autoload :Wrap, "quo/utilities/wrap"

autoload :Query
autoload :Results
autoload :ComposedQuery
Expand All @@ -19,13 +16,10 @@ module Quo
autoload :WrappedQuery

mattr_accessor :base_query_class, default: "Quo::Query"
mattr_accessor :formatted_query_log, default: true
mattr_accessor :query_show_callstack_size, default: 10
mattr_accessor :logger, default: nil
mattr_accessor :max_page_size, default: 200
mattr_accessor :default_page_size, default: 20

def self.base_query_class
def self.base_query_class #: Quo::Query
@@base_query_class.constantize
end
end
113 changes: 72 additions & 41 deletions lib/quo/composed_query.rb
Original file line number Diff line number Diff line change
@@ -1,86 +1,100 @@
# frozen_string_literal: true

# rbs_inline: enabled

module Quo
# @rbs inherits Quo::Query
class ComposedQuery < Quo.base_query_class
class << self
# Combine two Query classes into a new composed query class
def compose(left_query_class, right_query_class, joins: nil)
props = {}
props.merge!(left_query_class.literal_properties.properties_index) if left_query_class < Quo::Query
props.merge!(right_query_class.literal_properties.properties_index) if right_query_class < Quo::Query

klass = Class.new(self) do
class << self
attr_reader :_composing_joins, :_left_query, :_right_query
end

props.each do |name, property|
prop name, property.type, property.kind, reader: property.reader, writer: property.writer, default: property.default, shadow_check: false
end
# Combine two Query classes into a new composed query class
# Combine two query-like or composeable entities:
# These can be Quo::Query, Quo::MergedQuery, Quo::EagerQuery and ActiveRecord::Relations.
# See the `README.md` docs for more details.
# @rbs left_query_class: singleton(Quo::Query | ::ActiveRecord::Relation)
# @rbs right_query_class: singleton(Quo::Query | ::ActiveRecord::Relation)
# @rbs joins: untyped
# @rbs return: singleton(Quo::ComposedQuery)
def self.compose(left_query_class, right_query_class, joins: nil)
props = {}
props.merge!(left_query_class.literal_properties.properties_index) if left_query_class < Quo::Query
props.merge!(right_query_class.literal_properties.properties_index) if right_query_class < Quo::Query

klass = Class.new(self) do
class << self
attr_reader :_composing_joins, :_left_query, :_right_query
end
klass.instance_variable_set(:@_composing_joins, joins)
klass.instance_variable_set(:@_left_query, left_query_class)
klass.instance_variable_set(:@_right_query, right_query_class)
# klass.set_temporary_name = "quo::ComposedQuery" # Ruby 3.3+
klass
end

# We can also merge instance of prepared queries
def merge_instances(left_instance, right_instance, joins: nil)
raise ArgumentError, "Cannot merge, left has incompatible type #{left_instance.class}" unless left_instance.is_a?(Quo::Query) || left_instance.is_a?(::ActiveRecord::Relation)
raise ArgumentError, "Cannot merge, right has incompatible type #{right_instance.class}" unless right_instance.is_a?(Quo::Query) || right_instance.is_a?(::ActiveRecord::Relation)
return compose(left_instance.class, right_instance, joins: joins).new(**left_instance.to_h) if left_instance.is_a?(Quo::Query) && right_instance.is_a?(::ActiveRecord::Relation)
return compose(left_instance, right_instance.class, joins: joins).new(**right_instance.to_h) if right_instance.is_a?(Quo::Query) && left_instance.is_a?(::ActiveRecord::Relation)
return compose(left_instance.class, right_instance.class, joins: joins).new(**left_instance.to_h.merge(right_instance.to_h)) if left_instance.is_a?(Quo::Query) && right_instance.is_a?(Quo::Query)
compose(left_instance, right_instance, joins: joins).new # Both are relations
end

def inspect
"Quo::ComposedQuery[#{operand_desc(_left_query)}, #{operand_desc(_right_query)}]"
props.each do |name, property|
prop name, property.type, property.kind, reader: property.reader, writer: property.writer, default: property.default, shadow_check: false
end
end
klass.instance_variable_set(:@_composing_joins, joins)
klass.instance_variable_set(:@_left_query, left_query_class)
klass.instance_variable_set(:@_right_query, right_query_class)
# klass.set_temporary_name = "quo::ComposedQuery" # Ruby 3.3+
klass
end

private
# We can also merge instance of prepared queries
# @rbs left_instance: Quo::Query | ::ActiveRecord::Relation
# @rbs right_instance: Quo::Query | ::ActiveRecord::Relation
# @rbs joins: untyped
# @rbs return: Quo::ComposedQuery
def self.merge_instances(left_instance, right_instance, joins: nil)
raise ArgumentError, "Cannot merge, left has incompatible type #{left_instance.class}" unless left_instance.is_a?(Quo::Query) || left_instance.is_a?(::ActiveRecord::Relation)
raise ArgumentError, "Cannot merge, right has incompatible type #{right_instance.class}" unless right_instance.is_a?(Quo::Query) || right_instance.is_a?(::ActiveRecord::Relation)
return compose(left_instance.class, right_instance, joins: joins).new(**left_instance.to_h) if left_instance.is_a?(Quo::Query) && right_instance.is_a?(::ActiveRecord::Relation)
return compose(left_instance, right_instance.class, joins: joins).new(**right_instance.to_h) if right_instance.is_a?(Quo::Query) && left_instance.is_a?(::ActiveRecord::Relation)
return compose(left_instance.class, right_instance.class, joins: joins).new(**left_instance.to_h.merge(right_instance.to_h)) if left_instance.is_a?(Quo::Query) && right_instance.is_a?(Quo::Query)
compose(left_instance, right_instance, joins: joins).new # Both are relations
end

def operand_desc(operand)
if operand < Quo::ComposedQuery
operand.inspect
else
operand.name || "(anonymous)"
end
end
# @rbs override
def self.inspect
left = _left_query
left_desc = (left < Quo::ComposedQuery) ? left.inspect : (left.name || "(anonymous)")
right = _right_query
right_desc = (right < Quo::ComposedQuery) ? right.inspect : (right.name || "(anonymous)")
"Quo::ComposedQuery[#{left_desc}, #{right_desc}]"
end

# @rbs override
def query
merge_left_and_right(left, right, _composing_joins)
end

# @rbs return: Quo::Query | ::ActiveRecord::Relation
def left
return _left_query if relation?(_left_query)
_left_query.new(**child_options(_left_query))
end

# @rbs return: Quo::Query | ::ActiveRecord::Relation
def right
return _right_query if relation?(_right_query)
_right_query.new(**child_options(_right_query))
end

delegate :_composing_joins, :_left_query, :_right_query, to: :class

# @rbs override
def inspect
"Quo::ComposedQuery[#{operand_desc(left)}, #{operand_desc(right)}]"
end

private

# @rbs return: Hash[Symbol, untyped]
def child_options(query_class)
names = property_names(query_class)
to_h.slice(*names)
end

# @rbs return: Array[Symbol]
def property_names(query_class)
query_class.literal_properties.properties_index.keys
end

# @rbs return: ActiveRecord::Relation | Object & Enumerable[untyped]
def merge_left_and_right(left, right, joins)
left_rel = unwrap_relation(left)
right_rel = unwrap_relation(right)
Expand All @@ -98,30 +112,47 @@ def merge_left_and_right(left, right, joins)
end
end

# @rbs left_rel: ActiveRecord::Relation
# @rbs joins: untyped
# @rbs return: ActiveRecord::Relation
def apply_joins(left_rel, joins)
joins.present? ? left_rel.joins(joins) : left_rel
end

# @rbs rel: untyped
# @rbs return: bool
def relation?(rel)
rel.is_a?(::ActiveRecord::Relation)
end

# @rbs left: untyped
# @rbs right: untyped
# @rbs return: bool
def both_relations?(left, right)
relation?(left) && relation?(right)
end

# @rbs left: untyped
# @rbs right: untyped
# @rbs return: bool
def left_relation_right_enumerable?(left, right)
relation?(left) && !relation?(right)
end

# @rbs left: untyped
# @rbs right: untyped
# @rbs return: bool
def left_enumerable_right_relation?(left, right)
!relation?(left) && relation?(right)
end

# @rbs override
def unwrap_relation(query)
query.is_a?(Quo::Query) ? query.unwrap_unpaginated : query
end

# @rbs operand: Quo::ComposedQuery | Quo::Query | ::ActiveRecord::Relation
# @rbs return: String
def operand_desc(operand)
if operand.is_a? Quo::ComposedQuery
operand.inspect
Expand Down
19 changes: 14 additions & 5 deletions lib/quo/eager_query.rb
Original file line number Diff line number Diff line change
@@ -1,51 +1,60 @@
# frozen_string_literal: true

# rbs_inline: enabled

module Quo
# @rbs inherits Quo::Query
class EagerQuery < Quo.base_query_class
# Optionally return the `total_count` option if it has been set.
# This is useful when the total count is known and not equal to size
# of wrapped collection.

# @rbs override
def count
options[:total_count] || underlying_query.count
end

# @rbs override
def page_count
query_with_logging.count
configured_query.count
end

# Is this query object paged? (when no total count)
# @rbs override
def paged?
options[:total_count].nil? && page_index.present?
end

# @rbs return: Object & Enumerable[untyped]
def collection
raise NotImplementedError, "EagerQuery objects must define a 'collection' method"
end

# @rbs return: Object & Enumerable[untyped]
def query
records = collection
preload_includes(records) if options[:includes]
records
end

# @rbs override
def relation?
false
end

# @rbs override
def eager?
true
end

private

# @rbs override
def underlying_query
unwrap_relation(query)
end

def unwrap_relation(query)
query.is_a?(Quo::Query) ? query.unwrap : query
end

# @rbs (untyped records, ?untyped? preload) -> untyped
def preload_includes(records, preload = nil)
::ActiveRecord::Associations::Preloader.new(
records: records,
Expand Down
2 changes: 2 additions & 0 deletions lib/quo/engine.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# rbs_inline: enabled

module Quo
class Engine < ::Rails::Engine
isolate_namespace Quo
Expand Down
5 changes: 5 additions & 0 deletions lib/quo/loaded_query.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# frozen_string_literal: true

# rbs_inline: enabled

module Quo
class LoadedQuery < Quo::EagerQuery
# @rbs data: untyped, props: Symbol => untyped, block: () -> untyped
# @rbs return: Quo::LoadedQuery
def self.wrap(data = nil, props: {}, &block)
klass = Class.new(self)
if block
Expand All @@ -15,6 +19,7 @@ def self.wrap(data = nil, props: {}, &block)
klass
end

# @rbs override
def loaded?
true
end
Expand Down
Loading

0 comments on commit 3479104

Please sign in to comment.