Skip to content

Conversation

@seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Dec 20, 2025

The problem

When a resource queries an "index"-like endpoint and receives a response
payload that relies on URL-based pagination, it can be tedious to
deconstruct the next page's URL and transform it into a subsequent
.where call.

For example, consider a Post payload like:

{
  "posts": { },
  "next_page": "https://api.blog.com/posts?page=2"
}

In order to query the URL in the next_page property, the collection
parser must extract a { "page" => "2" } Hash and forward it to a
resource_class.where call:

def initialize(parsed = {})
  @elements = parsed["posts"]
  @next_page_uri = URI.parse(parsed["next_page"])
end

def next_page_params
  query_string = @next_page_uri.query

  URI.decode_www_form(query_string).to_h
end

def next_page
  resource_class.where(next_page_params)
end

collection.next_page_params # => { "page" => "2" }
collection.next_page        # => GET https://api.blog.com/posts?page=2

The process becomes complicated when there are other parameters
(including "array"-like keys):

{
  "posts": { },
  "next_page": "https://api.blog.com/posts?tags[]=Ruby&tags[]=Rails&page=2"
}

In this scenario, the Array created by URI.decode_www_form will only
retain both tags[]-keyed values, but the Array#to_h call will
flatten that Array into a Hash that only contains the last key. In this
case, tags[]=Ruby will be omitted, and the resulting Hash would be { "tags[]" => "Rails", "page" => "2" }.

The proposal

Active Record's .where method supports both String and Array
arguments. In the context of Active Record, String and Array
arguments are in support of the underlying SQL queries to be executed.

In the context of Active Resource, the underlying format for a "query"
is an HTTP-compliant query that's encoded as an
application/x-www-form-urlencoded string.

This commit proposes adding support to Base.where to accept String
arguments.

This support would simplify the scenario above:

def initialize(parsed = {})
  @elements = parsed["posts"]
  @next_page_uri = URI.parse(parsed["next_page"])
end

def next_page
  resource_class.where(@next_page_uri.query)
end

collection.next_page # => GET https://api.blog.com/posts?page=2

When Active Resource is loaded alongside a Rails or Rack application,
rely on Rack::Utils.parse_nested_query to decode key-value pairs.
Otherwise, rely on URI.decode_www_form and Array#to_h.

@seanpdoyle seanpdoyle force-pushed the where-string-and-array-support branch 3 times, most recently from 0ba7104 to 4c211b0 Compare December 20, 2025 17:12
@seanpdoyle
Copy link
Contributor Author

seanpdoyle commented Dec 20, 2025

While this is passing, it expands the surface area of the dependencies.

The favorable part of that trade-off is that the package gains more sophisticated query string parsing when it's available through Rack).

However, I wonder if the goals of the use case described in the PR could be achieved by expanding .where and .all to forward along String arguments as a find(:all, from: …) argument, rather than parsing them into Hash instances.

I'll explore that in a separate PR.

@seanpdoyle seanpdoyle force-pushed the where-string-and-array-support branch 5 times, most recently from 9b07df4 to 76e38b4 Compare December 21, 2025 02:05
The problem
---

When a resource queries an "index"-like endpoint and receives a response
payload that relies on URL-based pagination, it can be tedious to
deconstruct the next page's URL and transform it into a subsequent
`.where` call.

For example, consider a `Post` payload like:

```json
{
  "posts": { … },
  "next_page": "https://api.blog.com/posts?page=2"
}
```

In order to query the URL in the `next_page` property, the collection
parser must extract a `{ "page" => "2" }` Hash and forward it to a
`resource_class.where` call:

```ruby
def initialize(parsed = {})
  @elements = parsed["posts"]
  @next_page_uri = URI.parse(parsed["next_page"])
end

def next_page_params
  query_string = @next_page_uri.query

  URI.decode_www_form(query_string).to_h
end

def next_page
  resource_class.where(next_page_params)
end

collection.next_page_params # => { "page" => "2" }
collection.next_page        # => GET https://api.blog.com/posts?page=2
```

The process becomes complicated when there are other parameters
(including "array"-like keys):

```json
{
  "posts": { … },
  "next_page": "https://api.blog.com/posts?tags[]=Ruby&tags[]=Rails&page=2"
}
```

In this scenario, the Array created by [URI.decode_www_form][] will only
retain both `tags[]`-keyed values, but the [Array#to_h][] call will
flatten that Array into a Hash that only contains the last key. In this
case, `tags[]=Ruby` will be omitted, and the resulting Hash would be `{
"tags[]" => "Rails", "page" => "2" }`.

The proposal
---

Active Record's `.where` method supports both [String][] and [Array][]
arguments. In the context of Active Record, `String` and `Array`
arguments are in support of the underlying SQL queries to be executed.

In the context of Active Resource, the underlying format for a "query"
is an HTTP-compliant query that's encoded as an
[application/x-www-form-urlencoded][] string.

This commit proposes adding support to `Base.where` to accept String
arguments.

This support would simplify the scenario above:

```ruby
def initialize(parsed = {})
  @elements = parsed["posts"]
  @next_page_uri = URI.parse(parsed["next_page"])
end

def next_page
  resource_class.where(@next_page_uri.query)
end

collection.next_page # => GET https://api.blog.com/posts?page=2
```

When Active Resource is loaded alongside a Rails or Rack application,
rely on [Rack::Utils.parse_nested_query][] to decode key-value pairs.
Otherwise, rely on [URI.decode_www_form][] and [Array#to_h][].

[URI.decode_www_form]: https://docs.ruby-lang.org/en/master/URI.html#method-c-decode_www_form
[String]: https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where-label-String
[Array]: https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where-label-Array
[application/x-www-form-urlencoded]: https://url.spec.whatwg.org/#application/x-www-form-urlencoded
[Rack::Utils.parse_nested_query]: https://www.rubydoc.info/gems/rack/Rack/Utils#parse_nested_query-class_method
[Array#to_h]: https://docs.ruby-lang.org/en/master/Array.html#method-i-to_h
@seanpdoyle seanpdoyle force-pushed the where-string-and-array-support branch from 76e38b4 to d9fa989 Compare December 21, 2025 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant