Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## Unreleased

### Added
- Added support for nested search fields using dot notation in Payrix search query builder (e.g. `merchant.entity.login.division`)

### Changed
- Increased Payrix API timeout to 60 seconds to improve reliability for slower requests

### Breaking Changes

- Change API for setting request environment.
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
payrix (1.0.0)
payrix (1.1.0)
faraday (~> 2.0.1)
faraday-follow_redirects

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ Here is a list of all simple operators available.
- `Payrix::Search.greater`
- `Payrix::Search.less`

> Note: Field names can be provided as either symbols (`:status`) or dot-notation strings (`'merchant.entity.login.division'`) when querying nested attributes.

The interface of all operators follows `.operator(:field, value)`.

Use compound operators to make combinations of simple and other compound operators.
Expand Down
2 changes: 1 addition & 1 deletion lib/payrix/http/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module Http
class Request # rubocop:disable Style/Documentation - Legacy file, which will be removed eventually
include Singleton

def send_http(method, base_url, endpoint, data = {}, headers = {}, timeout = 30)
def send_http(method, base_url, endpoint, data = {}, headers = {}, timeout = 60)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not approve of this. LOL.

conn = Faraday.new(url: base_url) do |connection|
connection.response :follow_redirects, limit: 3

Expand Down
35 changes: 29 additions & 6 deletions lib/payrix/request_options/search/atom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,31 @@ def initialize(field, operator, value)
def construct(prefix = '')
raise ArgumentError, 'Prefix parameter must be a string' unless prefix.is_a?(String)

field = Payrix::Util.camel_case(@field.to_s)
formatted_field = rendered_field(prefix)

if prefix == ''
"#{field}[#{@operator}]=#{@value}"
if prefix.empty?
"#{formatted_field}[#{@operator}]=#{@value}"
elsif formatted_field.start_with?('[')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this branch for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the dot notation field will be formatted to something like [merchant][entity][login][division][equals] when it formatted to something start with [ then we do not want to add an outter [].

"#{prefix}#{formatted_field}[#{@operator}]=#{@value}"
else
"#{prefix}[#{field}][#{@operator}]=#{@value}"
"#{prefix}[#{formatted_field}][#{@operator}]=#{@value}"
end
Comment thread
jazziining marked this conversation as resolved.
end

private

def validate_field
return if @field.is_a?(Symbol)
return if @field.is_a?(String) && @field != ''
raise ArgumentError, 'Field parameter must be a symbol or a non-empty string' unless valid_string_field?
raise ArgumentError, 'Field parameter must not contain empty dot notation segments' if empty_dot_segment?
end

def valid_string_field?
@field.is_a?(String) && @field != ''
end

raise ArgumentError, 'Field parameter must be a symbol or a non-empty string'
def empty_dot_segment?
@field.include?('.') && @field.split('.').any?(&:empty?)
end

def validate_operator
Expand All @@ -52,6 +61,20 @@ def validate_value

raise ArgumentError, 'Value parameter must be a non-empty string'
end

def rendered_field(prefix)
return Payrix::Util.camel_case(@field.to_s) unless @field.is_a?(String) && @field.include?('.')

dot_field(prefix.empty?)
end

def dot_field(unprefixed)
parts = @field.split('.').map { |part| Payrix::Util.camel_case(part) }

return parts.map { |part| "[#{part}]" }.join unless unprefixed

[parts.first, *parts.drop(1).map { |part| "[#{part}]" }].join
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original implementation always wrapped the key with an [], which could lead to double-wrapping when the field was already partially formatted.

The goal of this change is to treat string inputs using dot notation (e.g. "merchant.entity.login.division") as a structured field path. These strings are parsed into an array of segments and then consistently formatted into the expected bracketed query structure.

This ensures:

  • Symbol inputs remain supported for simple fields
  • Dot notation strings are correctly parsed into nested fields
  • Do not unintentionally double-wrapped nested fields

end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/payrix/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Payrix
VERSION = '1.0.0'
VERSION = '1.1.0'
end
27 changes: 27 additions & 0 deletions spec/lib/payrix/request_options/search/atom_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@
end
end

context 'when the field is dot notation with empty segments at the beginning' do
it 'raises ArgumentError' do
expect { described_class.new('.field', :operator, 'value') }.to raise_error(ArgumentError)
end
end

context 'when the field is dot notation with empty segments at middle' do
it 'raises ArgumentError' do
expect { described_class.new('a..field', :operator, 'value') }.to raise_error(ArgumentError)
end
end

context 'when the operator is nil' do
it 'raises ArgumentError' do
expect { described_class.new(:field, nil, 'value') }.to raise_error(ArgumentError)
Expand Down Expand Up @@ -251,5 +263,20 @@
expect { atom.construct({}) }.to raise_error(ArgumentError)
end
end

context 'when the field is a dotted string' do
it 'returns a valid nested search argument' do
atom = described_class.new('a.field', :operator, 'value')

expect(atom.construct).to eq('a[field][operator]=value')
end
end

context 'when the field is a dotted string and the prefix is a non-empty string' do
it 'returns a valid nested search argument with a prefix' do
atom = described_class.new('a.field', :operator, 'value')
expect(atom.construct('prefix')).to eq('prefix[a][field][operator]=value')
end
end
end
end
Loading