[Unreleased]
0.8.0
Releasedate: 14-1-2026
Rubygems: https://rubygems.org/gems/appquery/versions/0.8.0
💥 Breaking Changes
- ⚠️ RSpec helpers refactored
Query under test is expected to be a class,select_*are no longer separate helpers:ruby expect(described_query.first).to \ include("id" => be_a(Integer), ...) expect(described_query.entries).to include(a_hash_including("item_code" => "123456"))
✨ Features
- 📤
copy_to— efficient PostgreSQL COPY export to CSV/text/binary ```ruby # Return as string csv = AppQuery[:users].copy_to
# Write to file AppQuery[:users].copy_to(dest: "export.csv")
# Stream to IO (e.g., Rails response) query.copy_to(dest: response.stream)
- 🎯 **`cte(:name)`** — focus a query on a specific CTE for testing or inspection
```ruby
query = AppQuery("WITH active AS (...), admins AS (...) SELECT ...")
query.cte(:active).entries # select from the active CTE
query.cte(:admins).count # count rows in admins CTE
🗃️
AppQuery.table(:name)— quick query from a tableAppQuery.table(:products).count AppQuery.table(:users).take(5)🔢
take(n)/take_last(n)— fetch first or last n rowsquery.take(5) # first 5 rows query.take_last(5) # last 5 rows⏮️
last— fetch the last row (counterpart tofirst)query.last # => {"id" => 42, "name" => "Zoe"}📋
column_names— get column names without fetching rowsquery.column_names # => ["id", "name", "email"]🦄
unique:keyword forQ#column— return distinct valuesquery.column(:status, unique: true) # => ["active", "pending"]🏗️ Overhauled generators — moved to
AppQuery::namespacerails g app_query:example # annotated example query rails g app_query:query Products rails g query Products # hidden alias rails g query --help # details
0.7.0
Releasedate: 8-1-2026
Rubygems: https://rubygems.org/gems/appquery/versions/0.7.0
💥 Breaking Changes
- ⛔ drop Ruby 3.2 support
Ruby 3.2 will be EOL in 2 months but is already no longer working for Rails >v8.1.
✨ Features
- 🗒️ Paginatable: unpaginated
Override any setting for pagination:ruby query = ArticlesQuery.build => #<RecentQuery:0x000000016ed7ef78 @page=1, @per_page=10, ...> query.unpaginated.count #=> 699Also: when@page.nil?then paginate erb-helper renders nothing:ruby # articles_query.erb.sql # before # skip pagination when we need the total count <%= @page && paginate(page:, per_page:) -%> # after <%= paginate(page:, per_page:) -%> - 🌗 darkmode for API docs
🐛 Fixes
- 🔧 Fix literal strings containing parentheses breaking CTE-parsing.
0.6.0
Releasedate: 2-1-2026
Rubygems: https://rubygems.org/gems/appquery/versions/0.6.0
✨ Features
- 🏗️
AppQuery::BaseQuery— structured query objects with explicit parameter declaration ```ruby class ArticlesQuery < AppQuery::BaseQuery bind :author_id bind :status, default: nil var :order_by, default: "created_at DESC" cast published_at: :datetime end
ArticlesQuery.new(author_id: 1).entries ArticlesQuery.new(author_id: 1, status: "draft").first
Benefits over `AppQuery[:my_query]`:
- Explicit `bind` and `var` declarations with defaults
- Unknown parameter validation (catches typos)
- Self-documenting: `ArticlesQuery.binds`, `ArticlesQuery.vars`
- Middleware support via concerns
- 📄 **`AppQuery::Paginatable`** — pagination middleware (Kaminari-compatible)
```ruby
class ApplicationQuery < AppQuery::BaseQuery
include AppQuery::Paginatable
per_page 25
end
# With count (full pagination)
articles = ArticlesQuery.new.paginate(page: 1).entries
articles.total_pages # => 5
# Without count (large datasets, uses limit+1 trick)
articles = ArticlesQuery.new.paginate(page: 1, without_count: true).entries
articles.next_page # => 2 or nil
🗺️
AppQuery::Mappable— map results to Ruby objectsclass ArticlesQuery < ApplicationQuery include AppQuery::Mappable class Item < Data.define(:title, :url, :published_on) end end
articles = ArticlesQuery.new.entries articles.first.title # => "Hello World" articles.first.class # => ArticlesQuery::Item
# Skip mapping ArticlesQuery.new.raw.entries.first # => => "Hello", ...
- 🔄 **`Result#transform!`** — transform result records in-place
```ruby
result = AppQuery[:users].select_all
result.transform! { |row| row.merge("full_name" => "#{row['first']} #{row['last']}") }
- Add
any?,none?- efficient ways to see if there's any results for a query. - 🎯 Cast type shorthands — use symbols instead of explicit type classes
ruby query.select_all(cast: {"published_on" => :date}) # instead of query.select_all(cast: {"published_on" => ActiveRecord::Type::Date.new})Supports all ActiveRecord types including adapter-specific ones (:uuid,:jsonb, etc.). - 🔑 Indifferent access — for rows and cast keys ```ruby row = query.select_one row["name"] # works row[:name] # also works
# cast keys can be symbols too query.select_all(cast: :date)
## [0.5.0] - 2025-12-21
### 💥 Breaking Changes
- 🔄 **`select:` keyword argument removed** — use positional argument instead
```ruby
# before
query.select_all(select: "SELECT * FROM :_")
# after
query.select_all("SELECT * FROM :_")
✨ Features
- 🍾 Add paginate ERB-helper
ruby SELECT * FROM articles <%= paginate(page: 1, per_page: 15) %> # SELECT * FROM articles LIMIT 15 OFFSET 0 - 🧰 Resolve query without extension
AppQuery[:weekly_sales]loadsweekly_sales.sqlorweekly_sales.sql.erb. - 🔗 Nested result queries via
with_select— chain transformations using:_placeholder to reference the previous resultruby active_users = AppQuery("SELECT * FROM users").with_select("SELECT * FROM :_ WHERE active") active_users.count("SELECT * FROM :_ WHERE admin") - 🚀 New methods:
#column,#ids,#count,#entries— efficient shortcuts that only fetch what you needruby query.column(:email) # SELECT email only query.ids # SELECT id only query.count # SELECT COUNT(*) only query.entries # shorthand for select_all.entries
🐛 Fixes
- 🔧 Fix leading whitespace in
prepend_ctecausing parse errors - 🔧 Fix binds being reset when no placeholders found
- ⚡
select_onenow usesLIMIT 1for better performance
📚 Documentation
- 📖 Revised README with cleaner intro and examples
- 🏠 Added example Rails app in
examples/demo
[0.4.0] - 2025-12-15
features
- add insert, update and delete
- API docs at eval.github.io/appquery
- add ERB-helpers values, bind and quote .
- enabled trusted publishing to rubygems.org