[Unreleased]

✨ 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 objects

    class 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] loads weekly_sales.sql or weekly_sales.sql.erb.
  • πŸ”— Nested result queries via with_select β€” chain transformations using :_ placeholder to reference the previous result ruby 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 need ruby 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_cte causing parse errors
  • πŸ”§ Fix binds being reset when no placeholders found
  • ⚑ select_one now uses LIMIT 1 for better performance

πŸ“š Documentation

  • πŸ“– Revised README with cleaner intro and examples
  • 🏠 Added example Rails app in examples/demo

[0.4.0] - 2025-12-15

features