quo 1.0.0.beta2 → 2.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 +4 -4
- data/.devcontainer/Dockerfile +17 -0
- data/.devcontainer/compose.yml +10 -0
- data/.devcontainer/devcontainer.json +12 -0
- data/Appraisals +4 -12
- data/CHANGELOG.md +112 -1
- data/CLAUDE.md +19 -0
- data/Gemfile +7 -1
- data/LICENSE.txt +1 -1
- data/README.md +496 -203
- data/Rakefile +66 -6
- data/UPGRADING.md +216 -0
- data/badges/coverage_badge_total.svg +35 -0
- data/badges/rubycritic_badge_score.svg +35 -0
- data/claude-skill/README.md +100 -0
- data/claude-skill/SKILL.md +442 -0
- data/claude-skill/references/API_REFERENCE.md +462 -0
- data/claude-skill/references/COMPOSITION.md +396 -0
- data/claude-skill/references/PAGINATION.md +396 -0
- data/claude-skill/references/QUERY_TYPES.md +297 -0
- data/claude-skill/references/TRANSFORMERS.md +282 -0
- data/context/01-core-architecture.md +247 -0
- data/context/02-query-types-implementation.md +355 -0
- data/context/03-composition-transformation.md +441 -0
- data/context/04-pagination-results.md +485 -0
- data/context/05-testing-configuration.md +491 -0
- data/context/06-advanced-patterns-examples.md +153 -0
- data/gemfiles/rails_8.0.gemfile +10 -5
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/lib/generators/quo/install/USAGE +21 -0
- data/lib/generators/quo/install/install_generator.rb +63 -0
- data/lib/quo/collection_backed_query.rb +21 -15
- data/lib/quo/collection_results.rb +1 -0
- data/lib/quo/composed_collection_backed_query.rb +42 -0
- data/lib/quo/composed_instance.rb +144 -0
- data/lib/quo/composed_query.rb +43 -178
- data/lib/quo/composed_relation_backed_query.rb +42 -0
- data/lib/quo/composing/base_strategy.rb +22 -0
- data/lib/quo/composing/class_strategy.rb +86 -0
- data/lib/quo/composing/class_strategy_registry.rb +31 -0
- data/lib/quo/composing/query_classes_strategy.rb +38 -0
- data/lib/quo/composing.rb +81 -0
- data/lib/quo/engine.rb +1 -0
- data/lib/quo/minitest/helpers.rb +14 -24
- data/lib/quo/preloadable.rb +1 -0
- data/lib/quo/query.rb +22 -5
- data/lib/quo/relation_backed_query.rb +24 -18
- data/lib/quo/relation_backed_query_specification.rb +44 -25
- data/lib/quo/relation_results.rb +1 -0
- data/lib/quo/results.rb +31 -2
- data/lib/quo/rspec/helpers.rb +15 -26
- data/lib/quo/testing/collection_backed_fake.rb +1 -0
- data/lib/quo/testing/fake_helpers.rb +30 -0
- data/lib/quo/testing/relation_backed_fake.rb +1 -0
- data/lib/quo/version.rb +1 -1
- data/lib/quo/wrapped_collection_backed_query.rb +21 -0
- data/lib/quo/wrapped_relation_backed_query.rb +21 -0
- data/lib/quo.rb +8 -0
- data/quo.png +0 -0
- data/sig/generated/quo/collection_backed_query.rbs +10 -4
- data/sig/generated/quo/collection_results.rbs +1 -0
- data/sig/generated/quo/composed_collection_backed_query.rbs +25 -0
- data/sig/generated/quo/composed_instance.rbs +61 -0
- data/sig/generated/quo/composed_query.rbs +23 -56
- data/sig/generated/quo/composed_relation_backed_query.rbs +25 -0
- data/sig/generated/quo/composing/base_strategy.rbs +16 -0
- data/sig/generated/quo/composing/class_strategy.rbs +38 -0
- data/sig/generated/quo/composing/class_strategy_registry.rbs +16 -0
- data/sig/generated/quo/composing/query_classes_strategy.rbs +24 -0
- data/sig/generated/quo/composing.rbs +40 -0
- data/sig/generated/quo/engine.rbs +1 -0
- data/sig/generated/quo/minitest/helpers.rbs +12 -0
- data/sig/generated/quo/preloadable.rbs +1 -0
- data/sig/generated/quo/query.rbs +15 -4
- data/sig/generated/quo/relation_backed_query.rbs +15 -5
- data/sig/generated/quo/relation_backed_query_specification.rbs +47 -39
- data/sig/generated/quo/relation_results.rbs +1 -0
- data/sig/generated/quo/results.rbs +11 -0
- data/sig/generated/quo/rspec/helpers.rbs +12 -0
- data/sig/generated/quo/testing/collection_backed_fake.rbs +1 -0
- data/sig/generated/quo/testing/fake_helpers.rbs +14 -0
- data/sig/generated/quo/testing/relation_backed_fake.rbs +1 -0
- data/sig/generated/quo/wrapped_collection_backed_query.rbs +13 -0
- data/sig/generated/quo/wrapped_relation_backed_query.rbs +13 -0
- data/sig/generated/quo.rbs +1 -0
- data/website/.gitignore +6 -0
- data/website/.nojekyll +0 -0
- data/website/404.html +26 -0
- data/website/Gemfile +24 -0
- data/website/_config.yml +50 -0
- data/website/_data/navigation.yml +8 -0
- data/website/_data/sidebar.yml +2 -0
- data/website/_data/social_links.yml +3 -0
- data/website/_docs/api.md +261 -0
- data/website/_docs/get-started.md +289 -0
- data/website/assets/quo.png +0 -0
- data/website/index.md +141 -0
- metadata +70 -13
- data/gemfiles/rails_7.0.gemfile +0 -15
- data/gemfiles/rails_7.1.gemfile +0 -15
- data/gemfiles/rails_7.2.gemfile +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5075f8c25c83bed882ac8bdcf8d4d8134ba9855c86daa719c00bc55bc4e1c2f0
|
|
4
|
+
data.tar.gz: 0f1ddf08726dab47fe471241a577891444f2a50007587c65889e189d3cfb48b7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a9ccd8cefa74d620d371c0b4a6981acce0335e9122c3552c6273804ad4bd9081868b98da0497214bf93fae921e1394a692ccd87f0369c7666a38a3cae3bc36d
|
|
7
|
+
data.tar.gz: efafd7e1e2749187cc1cb1788ab59bb80d119f1476b4d0d3172abf2e08f17beaae5f254a620571b1b23a87e0af36d1776a0f584e89a83fdc435da31de501a05b
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version or gemspec
|
|
2
|
+
ARG RUBY_VERSION=3.4.2
|
|
3
|
+
FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
|
|
4
|
+
|
|
5
|
+
USER root
|
|
6
|
+
|
|
7
|
+
# Install pkg-config and SQLite development libraries
|
|
8
|
+
RUN apt-get update -qq && \
|
|
9
|
+
apt-get install -y pkg-config libsqlite3-dev && \
|
|
10
|
+
apt-get clean && \
|
|
11
|
+
rm -rf /var/lib/apt/lists/*
|
|
12
|
+
|
|
13
|
+
USER vscode
|
|
14
|
+
|
|
15
|
+
# Ensure binding is always 0.0.0.0
|
|
16
|
+
# Binds the server to all IP addresses of the container, so it can be accessed from outside the container.
|
|
17
|
+
ENV BINDING="0.0.0.0"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Quo Gem Development",
|
|
3
|
+
"dockerComposeFile": "compose.yml",
|
|
4
|
+
"service": "quo-dev-env",
|
|
5
|
+
"containerEnv": {
|
|
6
|
+
"RAILS_ENV": "development"
|
|
7
|
+
},
|
|
8
|
+
"forwardPorts": [3000],
|
|
9
|
+
"postCreateCommand": "bundle install && bundle exec appraisal install",
|
|
10
|
+
"postStartCommand": "bundle exec appraisal rake test",
|
|
11
|
+
"remoteUser": "vscode"
|
|
12
|
+
}
|
data/Appraisals
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
appraise "rails-
|
|
2
|
-
gem "rails", "~>
|
|
3
|
-
end
|
|
4
|
-
|
|
5
|
-
appraise "rails-7.1" do
|
|
6
|
-
gem "rails", "~> 7.1"
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
appraise "rails-7.2" do
|
|
10
|
-
gem "rails", "~> 7.2"
|
|
1
|
+
appraise "rails-8.0" do
|
|
2
|
+
gem "rails", "~> 8.0.0"
|
|
11
3
|
end
|
|
12
4
|
|
|
13
|
-
appraise "rails-8.
|
|
14
|
-
gem "rails", "~> 8.0"
|
|
5
|
+
appraise "rails-8.1" do
|
|
6
|
+
gem "rails", "~> 8.1.0"
|
|
15
7
|
end
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,117 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [2.0.0] - 2026-05-12
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Minimum Rails 8.0; minimum Literal 1.9.
|
|
8
|
+
- Instance composition (`q1 + q2`) returns a value (`Quo::ComposedRelationBackedQuery` / `Quo::ComposedCollectionBackedQuery`), not an anonymous class. No more per-call class allocation.
|
|
9
|
+
- Each operand keeps its own constructor-time props; merge is the AND of each side's filter (1.x's prop fan-out is gone).
|
|
10
|
+
- Pagination inherits as a coupled `(page, page_size)` pair from whichever operand is paginated (right wins).
|
|
11
|
+
- `wrap` is type-strict at definition time. `Quo::RelationBackedQuery.wrap` requires an `ActiveRecord::Relation` or `Quo::RelationBackedQuery`; `Quo::CollectionBackedQuery.wrap` requires `Enumerable` or `Quo::CollectionBackedQuery`. Block forms still defer.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `Quo::RelationBackedQuery.from(relation)` and `Quo::CollectionBackedQuery.from(enumerable)` value-form constructors. Zero class allocation per call.
|
|
16
|
+
- `copy(prop: value)` on a composed instance fans the override across every operand declaring the prop, recursing into composed operands.
|
|
17
|
+
- Composed and wrapped value-form classes inherit from `Quo.relation_backed_query_base_class` / `Quo.collection_backed_query_base_class`.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- #5 — `Quo::RelationBackedQuerySpecification#joins` / `#left_outer_joins` accept multiple tables.
|
|
22
|
+
- #6 — `_specification` survives `Query#copy` and merge paths.
|
|
23
|
+
- #7 — `Quo::Results` Enumerable delegation returns transformed items for filter-style methods (`select`, `reject`, `find`, `sort_by`, `partition`, …) where the block already saw transformed items.
|
|
24
|
+
- #8 — `wrap` rejects the wrong type at definition time with a message pointing at the matching constructor.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- Unused per-call `Quo::Composing::InstanceStrategyRegistry` + the four instance-strategy classes.
|
|
29
|
+
|
|
30
|
+
## [1.0.1] - 2026-05-12
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Pin the `literal` dependency to `~> 1.6.0` in the gemspec. Quo's
|
|
35
|
+
internals reference `Literal::Null` (renamed to `Literal::Undefined`
|
|
36
|
+
in Literal 1.7) and `Literal::Types::ConstraintType.new` (signature
|
|
37
|
+
changed in Literal 1.7). Users whose lockfile resolved Literal to
|
|
38
|
+
1.7+ saw `NameError: uninitialized constant Literal::Null` or
|
|
39
|
+
`ArgumentError` at runtime / in CI. The 1.x line is now firmly on
|
|
40
|
+
Literal 1.6.x; a broader range targeting newer Literal versions is
|
|
41
|
+
planned for 2.0.
|
|
42
|
+
- Defensive handling of the sentinel rename in `Quo::Query` so the
|
|
43
|
+
`LITERAL_UNSET` constant resolves to whichever name exists at load
|
|
44
|
+
time. This is belt-and-braces on top of the pin.
|
|
45
|
+
|
|
46
|
+
## [1.0.0] - 2026-05-06
|
|
47
|
+
|
|
48
|
+
First stable release. Folds in the perf work and tooling shipped in beta3.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **Claude Code skill bundled with the gem.** A skill at `claude-skill/`
|
|
53
|
+
teaches Claude Code how to build composable, type-safe query objects
|
|
54
|
+
with Quo. The skill's `SKILL.md` covers the most important Quo concept
|
|
55
|
+
to get right — class-vs-instance composition, when to use each, and the
|
|
56
|
+
performance trade-offs involved. Reference files in
|
|
57
|
+
`claude-skill/references/` go deep on query types, composition, pagination,
|
|
58
|
+
transformers, and the API surface.
|
|
59
|
+
- **Rails install generator.** `bin/rails generate quo:install` copies the
|
|
60
|
+
bundled skill into the host app's `.claude/skills/quo/` directory.
|
|
61
|
+
Re-running with `--force` refreshes the skill after a Quo upgrade.
|
|
62
|
+
Optional `--with-claude-md` opt-in appends a short pointer to the
|
|
63
|
+
project's top-level `CLAUDE.md`. The fragment is idempotent — safe to
|
|
64
|
+
re-run.
|
|
65
|
+
|
|
66
|
+
## [1.0.0.beta3] - 2026-05-06
|
|
67
|
+
|
|
68
|
+
### Performance
|
|
69
|
+
|
|
70
|
+
Composition is materially cheaper. Constructing and resolving a composed query
|
|
71
|
+
no longer reallocates per-call infrastructure that the Ruby/Literal class
|
|
72
|
+
hierarchy already provides for free. On a representative composition graph
|
|
73
|
+
(four-level tree, 16 page-renders), measured improvements over `1.0.0.beta2`:
|
|
74
|
+
|
|
75
|
+
- ~46% lower wall-clock time
|
|
76
|
+
- ~51% less time spent in GC
|
|
77
|
+
- ~38% fewer total allocations
|
|
78
|
+
|
|
79
|
+
No public API changes. The wins come from five internal fixes:
|
|
80
|
+
|
|
81
|
+
- `Quo::Composing` now reuses singleton class- and instance-strategy
|
|
82
|
+
registries instead of allocating fresh ones on every `composer` /
|
|
83
|
+
`merge_instances` call.
|
|
84
|
+
- The class-level helpers on composed query classes (`_composing_joins`,
|
|
85
|
+
`_left_query`, `inspect`, `quo_operand_desc`, etc.) live on a new
|
|
86
|
+
`Quo::ComposedQuery::ClassMethods` module that is `extend`ed via the
|
|
87
|
+
`included` hook, rather than being re-defined inside the per-class
|
|
88
|
+
`Class.new { class << self; ... end }` block.
|
|
89
|
+
- `Quo::Composing::ClassStrategy#collect_properties` now skips properties
|
|
90
|
+
that the composed class's chosen superclass already declares — Literal
|
|
91
|
+
inherits these automatically, and re-registering them on every anonymous
|
|
92
|
+
class did a full `Literal::Property` allocation, schema dup, and
|
|
93
|
+
`module_eval` for no behavioural gain. This is the dominant win.
|
|
94
|
+
- `Quo::RelationBackedQuery#respond_to_missing?` now reuses the memoized
|
|
95
|
+
`RelationBackedQuerySpecification.blank` singleton instead of allocating
|
|
96
|
+
a fresh specification on every probe (ActiveRecord's delegation chain
|
|
97
|
+
hits `respond_to?` heavily).
|
|
98
|
+
- `Quo::ComposedQuery#left` / `#right` now pass `_specification:` to the
|
|
99
|
+
child constructor directly. Previously they always allocated the child
|
|
100
|
+
via `.new`, then allocated a second copy via `.with_specification(...)`
|
|
101
|
+
(which calls `copy(...)`), even when the specification was `nil`.
|
|
102
|
+
|
|
103
|
+
## [1.0.0.beta2] - 2025-04-01
|
|
104
|
+
|
|
105
|
+
### Breaking Changes
|
|
106
|
+
|
|
107
|
+
- `Quo::ComposedQuery.composer` is now `Quo::Composing.composer`
|
|
108
|
+
- `Quo::ComposedQuery.merge_instances` is now `Quo::Composing.merge_instances`
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- Fixed issue with handling of query specifications in the query composer
|
|
113
|
+
|
|
114
|
+
## [1.0.0.beta1] - 2025-04-01
|
|
5
115
|
|
|
6
116
|
### Breaking Changes
|
|
7
117
|
|
|
@@ -25,6 +135,7 @@ Nearly everything has had changes. Porting will require some effort.
|
|
|
25
135
|
### Added
|
|
26
136
|
|
|
27
137
|
- Helpers `stub_query` and `mock_query` for Minitest
|
|
138
|
+
- Support for Rails 8
|
|
28
139
|
|
|
29
140
|
## [0.5.0] - 2022-12-23
|
|
30
141
|
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Quo Development Guide
|
|
2
|
+
|
|
3
|
+
## Build & Test Commands
|
|
4
|
+
- Run all tests: `bundle exec rake test`
|
|
5
|
+
- Run a single test: `bundle exec ruby -Ilib:test test/path/to/test_file.rb -n test_method_name`
|
|
6
|
+
- Run tests across Rails versions: `bundle exec appraisal rake test`
|
|
7
|
+
- Type checking: `bundle exec steep check`
|
|
8
|
+
- Lint code: `bundle exec standardrb`
|
|
9
|
+
- Fix lint issues: `bundle exec standardrb --fix`
|
|
10
|
+
|
|
11
|
+
## Code Style Guidelines
|
|
12
|
+
- **Frozen String Literals**: Include `# frozen_string_literal: true` at the top of every file
|
|
13
|
+
- **Types**: Use RBS for type annotations with `# rbs_inline: enabled` and `@rbs` annotations
|
|
14
|
+
- **Naming**: Use snake_case for methods/variables, CamelCase for classes, and SCREAMING_CASE for constants
|
|
15
|
+
- **Error Handling**: Raise specific errors with clear messages
|
|
16
|
+
- **Indentation**: 2 spaces (default Standard Ruby style)
|
|
17
|
+
- **Testing**: Use Minitest for tests
|
|
18
|
+
- **Framework**: Built on Literal gem - use Literal::Struct and Literal::Types
|
|
19
|
+
- **Documentation**: Document public methods with comments
|
data/Gemfile
CHANGED
|
@@ -5,7 +5,7 @@ source "https://rubygems.org"
|
|
|
5
5
|
# Specify your gem's dependencies in quo.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
-
gem "rails", "~>
|
|
8
|
+
gem "rails", "~> 8"
|
|
9
9
|
|
|
10
10
|
group :development, :test do
|
|
11
11
|
gem "sqlite3"
|
|
@@ -14,9 +14,15 @@ group :development, :test do
|
|
|
14
14
|
|
|
15
15
|
gem "minitest", "~> 5.0"
|
|
16
16
|
|
|
17
|
+
gem "simplecov", require: false
|
|
18
|
+
|
|
17
19
|
gem "standard", require: false
|
|
18
20
|
|
|
19
21
|
gem "steep", require: false
|
|
20
22
|
|
|
21
23
|
gem "rbs-inline", "~> 0.11.0", require: false
|
|
24
|
+
|
|
25
|
+
gem "simplecov-small-badge", require: false
|
|
26
|
+
gem "rubycritic-small-badge", require: false
|
|
27
|
+
gem "repo-small-badge"
|
|
22
28
|
end
|
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022-
|
|
3
|
+
Copyright (c) 2022-2025 Stephen Ierodiaconou
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|