Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gem "activesupport", github: "rails/rails", branch: branch
gem "activemodel", github: "rails/rails", branch: branch
gem "activejob", github: "rails/rails", branch: branch
gem "activerecord", github: "rails/rails", branch: branch
gem "rack"
gem "sqlite3", branch == "7-0-stable" ? "~> 1.4" : nil

gem "rubocop"
Expand All @@ -18,6 +19,7 @@ gem "rubocop-performance"
gem "rubocop-rails"
gem "rubocop-rails-omakase"

gem "minitest", "< 6"
gem "minitest-bisect"

gemspec
Expand Down
27 changes: 27 additions & 0 deletions lib/active_resource/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,34 @@ def all(*args)

# This is an alias for all. You can pass in all the same
# arguments to this method as you can to <tt>all</tt> and <tt>find(:all)</tt>
#
# #where accepts conditions in one of several formats. In the examples below, the resulting
# URL is given as an illustration.
#
# === \String
#
# A string is passed as URL query parameters.
#
# Person.where("name=Matz")
# # https://api.people.com/people.json?name=Matz
#
# === \Hash
#
# #where will also accept a hash condition, in which the keys are fields and the values
# are values to be searched for.
#
# Fields can be symbols or strings. Values can be single values, arrays, or ranges.
#
# Person.where(name: "Matz")
# # https://api.people.com/people.json?name=Matz
#
# Person.where(person: { name: "Matz" })
# # https://api.people.com/people.json?person[name]=Matz
#
# Article.where(tags: ["Ruby", "Rails"])
# # https://api.people.com/people.json?tags[]=Ruby&tags[]=Rails
def where(clauses = {})
clauses = query_format.decode(clauses) if clauses.is_a?(String)
clauses = sanitize_forbidden_attributes(clauses)
raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
all(params: clauses)
Expand Down
15 changes: 13 additions & 2 deletions lib/active_resource/formats/url_encoded_format.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
# frozen_string_literal: true

require "active_support/core_ext/array/wrap"

module ActiveResource
module Formats
module UrlEncodedFormat
extend self

attr_accessor :query_parser # :nodoc:

# URL encode the parameters Hash
def encode(params, options = nil)
params.to_query
end

# URL decode the query string
def decode(query, remove_root = true)
query = query.delete_prefix("?")

if query_parser == :rack
Rack::Utils.parse_nested_query(query)
else
URI.decode_www_form(query).to_h
end
end
end
end
end
4 changes: 4 additions & 0 deletions lib/active_resource/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,9 @@ class Railtie < Rails::Railtie
teardown { ActiveResource::HttpMock.reset! }
end
end

config.after_initialize do
Formats::UrlEncodedFormat.query_parser ||= :rack if defined?(Rack::Utils)
end
end
end
1 change: 1 addition & 0 deletions lib/active_resource/where_clause.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def initialize(resource_class, options = {})
end

def where(clauses = {})
clauses = @resource_class.query_format.decode(clauses) if clauses.is_a?(::String)
all(params: clauses)
end

Expand Down
27 changes: 27 additions & 0 deletions test/cases/finder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,33 @@ def test_where_clause_with_permitted_params
assert_kind_of StreetAddress, addresses.first
end

def test_where_clause_string
query = URI.encode_www_form([ [ "id", "1" ] ])
ActiveResource::HttpMock.respond_to { |m| m.get "/people.json?" + query, {}, @people_david }
people = Person.where(query)
assert_equal 1, people.size
assert_kind_of Person, people.first
assert_equal "David", people.first.name
end

def test_where_clause_string_chained
ActiveResource::HttpMock.respond_to { |m| m.get "/people.json?a=1&b=2&c=3&id=2", {}, @people_david }
people = Person.where("id=2").where(a: 1).where("b=2").where(c: 3)
assert_equal [ "David" ], people.map(&:name)
end

def test_where_clause_string_with_multiple_params
previous_query_parser = ActiveResource::Formats::UrlEncodedFormat.query_parser
ActiveResource::Formats::UrlEncodedFormat.query_parser = :rack

query = URI.encode_www_form([ [ "id[]", "1" ], [ "id[]", "2" ] ])
ActiveResource::HttpMock.respond_to { |m| m.get "/people.json?" + query, {}, @people }
people = Person.where(query)
assert_equal [ "Matz", "David" ], people.map(&:name)
ensure
ActiveResource::Formats::UrlEncodedFormat.query_parser = previous_query_parser
end

def test_where_with_clause_in
ActiveResource::HttpMock.respond_to { |m| m.get "/people.json?id%5B%5D=2", {}, @people_david }
people = Person.where(id: [ 2 ])
Expand Down
33 changes: 33 additions & 0 deletions test/cases/formats/url_encoded_format_test.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "abstract_unit"
require "rack/utils"

class UrlEncodedFormatTest < ActiveSupport::TestCase
test "#encode transforms a Hash into an application/x-www-form-urlencoded query string" do
Expand All @@ -10,4 +11,36 @@ class UrlEncodedFormatTest < ActiveSupport::TestCase

assert_equal "a=1&b=2&c%5B%5D=3&c%5B%5D=4", encoded
end

test "#encode transforms a nested Hash into an application/x-www-form-urlencoded query string" do
params = { "person" => { "name" => "Matz" } }

encoded = ActiveResource::Formats::UrlEncodedFormat.encode(params)

assert_equal "person%5Bname%5D=Matz", encoded
end

test "#decode transforms an application/x-www-form-urlencoded query string into a Hash" do
decoded = ActiveResource::Formats::UrlEncodedFormat.decode("a=1")

assert_equal({ "a" => "1" }, decoded)
end

test "#decode ignores a ?-prefix" do
decoded = ActiveResource::Formats::UrlEncodedFormat.decode("?a=1")

assert_equal({ "a" => "1" }, decoded)
end

test "#decode transforms an application/x-www-form-urlencoded query string with multiple params into a Hash" do
previous_query_parser = ActiveResource::Formats::UrlEncodedFormat.query_parser
ActiveResource::Formats::UrlEncodedFormat.query_parser = :rack
query = URI.encode_www_form([ [ "a[]", "1" ], [ "a[]", "2" ] ])

decoded = ActiveResource::Formats::UrlEncodedFormat.decode(query)

assert_equal({ "a" => [ "1", "2" ] }, decoded)
ensure
ActiveResource::Formats::UrlEncodedFormat.query_parser = previous_query_parser
end
end