Skip to content

Conversation

@seanpdoyle
Copy link
Contributor

Follow-up to #421

Problem

Not all HTTP APIs support snake_case query parameters. Active Resource's built-in finders (like .find(:all, params: { … }), .all(params: { … }), where(…)) do not support transforming keys prior to their encoding.

If an API expects camelCase keys (like ?firstName=Matz rather than ?first_name=Matz), Active Resource's does not provide a method or hook to override. The already defined Base.query_string method used by the current implementation is private. If a consumer were to override it (despite depending on or overriding private methods being discouraged), they would need to be responsible for transforming the Hash and encoding it to a String.

Proposal

This commit proposed the introduction of the
ActiveResource::Formats::UrlEncodedFormat. It's modeled after the XmlFormat and JsonFormat, and defines an encode method with the prior behavior (a call to Hash#to_query). Along with the new class, this commit also introduce a new .query_format class attribute (with getter and setter methods), modeled after the .format class attribute.

Consumers can provide their own implementation with or without depending on the ActiveResource::Formats::UrlEncodedFormat. For example, callers can camelCase keys:

module CamelcaseUrlEncodedFormat
  extend self, ActiveResource::Formats::UrlEncodedFormat

  def encode(params, options = nil)
    params = params.deep_transform { |key| key.to_s.camelcase(:lower) }

    super
  end
end

class Person < ActiveResource::Base
  self.site = "https://example.com"
  self.query_format = CamelcaseUrlEncodedFormat
end

Person.where(first_name: "Sean")
 # => GET https://example.com/people.json?firstName=Sean

The URL encoding only applies to query parameters. Prefix options remain snake_case:

class Person < ActiveResource::Base
  self.site = "https://example.com"
  self.prefix = "/teams/:team_id"
  self.query_format = CamelcaseUrlEncodedFormat
end

Person.where(team_id: 1, first_name: "Sean")
 # => GET https://example.com/teams/1/people.json?firstName=Sean

This commit also includes changes to the README.md example code's query parameters to demonstrate that keys are snake_case by default.

Follow-up to [rails#421][]

Problem
---

Not all HTTP APIs support `snake_case` query parameters. Active
Resource's built-in finders (like `.find(:all, params: { … })`,
`.all(params: { … })`, `where(…)`) do not support transforming keys
prior to their encoding.

If an API expects camelCase keys (like `?firstName=Matz` rather than
`?first_name=Matz`), Active Resource's does not provide a method or hook
to override. The already defined `Base.query_string` method used by the
current implementation is `private`. If a consumer were to override it
(despite depending on or overriding `private` methods being
discouraged), they would need to be responsible for transforming the
`Hash` *and* encoding it to a `String`.

Proposal
---

This commit proposed the introduction of the
`ActiveResource::Formats::UrlEncodedFormat`. It's modeled after the
`XmlFormat` and `JsonFormat`, and defines an `encode` method with the
prior behavior (a call to [Hash#to_query][]). Along with the new class,
this commit also introduce a new `.query_format` class attribute (with
getter and setter methods), modeled after the `.format` class attribute.

Consumers can provide their own implementation with or without depending
on the `ActiveResource::Formats::UrlEncodedFormat`. For example, callers
can camelCase keys:

```ruby
module CamelcaseUrlEncodedFormat
  extend self, ActiveResource::Formats::UrlEncodedFormat

  def encode(params, options = nil)
    params = params.deep_transform { |key| key.to_s.camelcase(:lower) }

    super
  end
end

class Person < ActiveResource::Base
  self.site = "https://example.com"
  self.query_format = CamelcaseUrlEncodedFormat
end

Person.where(first_name: "Sean")
 # => GET https://example.com/people.json?firstName=Sean
```

The URL encoding only applies to query parameters. Prefix options remain
snake_case:

```ruby
class Person < ActiveResource::Base
  self.site = "https://example.com"
  self.prefix = "/teams/:team_id"
  self.query_format = CamelcaseUrlEncodedFormat
end

Person.where(team_id: 1, first_name: "Sean")
 # => GET https://example.com/teams/1/people.json?firstName=Sean
```

This commit also includes changes to the `README.md` example code's
query parameters to demonstrate that keys are `snake_case` by default.

[rails#421]: rails#421
[Hash#to_query]: https://api.rubyonrails.org/classes/Hash.html#method-i-to_query
@rafaelfranca rafaelfranca merged commit 62b941e into rails:main Oct 22, 2025
19 checks passed
@seanpdoyle seanpdoyle deleted the query-format branch October 22, 2025 23:13
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.

2 participants