【Question】 If pass arguments "1" value to the scope method for ransackable_scopes, ArgumentError (wrong number of arguments (given 0, expected 1)):
Version
- ransack: 2.4.2
- Rails: 5.2.6
- Ruby: 2.6.7
Source code
Model
app/model/user.rb
class User < ApplicationRecord
has_one :invitation, dependent: :destroy
has_many :invitees, through: :invitation
has_many :invitees_invitation_acceptances, through: :invitation, source: :invitation_acceptances
has_many :invitees_invitation_players, -> { where(invitation_achievement_id: INVITATION_PLAYER) }, through: :invitees_invitation_acceptances, source: :invitation_awards
has_one :invitation_acceptance, foreign_key: :link_auth_id, primary_key: :link_auth_id, dependent: :destroy
has_one :inviter, through: :invitation_acceptance
has_many :invitation_awards, through: :invitation_acceptance
scope :invitation_player_gteq, ->(value) { having("COUNT(invitation_awards.id) >= ?", value) }
scope :invitation_player_lt, ->(value) { having("COUNT(invitation_awards.id) < ?", value) }
private
class << self
def ransackable_scopes(auth_object = nil)
[:invitation_player_gteq, :invitation_player_lt, :invitation_complete_gteq, :invitation_complete_lt]
end
end
end
Controller
class UsersController < ApplicationController
def invites
user = if invite_search_params[:s]&.include?("invitation_complete_count") || !invite_search_params["invitation_complete_gteq"]&.empty? || !invite_search_params["invitation_complete_lt"]&.empty?
User.left_joins(:invitees_invitation_completes).group(:id)
else
User.left_joins(:invitees_invitation_players).group(:id)
end
@q = user.ransack(invite_search_params)
@users = @q.result.page(params[:page])
end
private
def invite_search_params
params.fetch(:q, {}).permit(
:uid_eq,
:identifier_cont,
:invitation_player_gteq,
:invitation_player_lt,
:invitation_complete_gteq,
:invitation_complete_lt,
:s,
)
end
end
If not "1" arguments
debug
[2] pry(#<*****>)> params
=> <***** {"utf8"=>"✓", "q"=>{ "invitation_player_gteq"=>"2"}, "commit"=>"検索", "controller"=>"admin/users", "action"=>"invites"} permitted: false>
80: scope :invitation_player_gteq, ->(value) { binding.pry
=> 81: having("COUNT(invitation_awards.id) >= ?", value) }
[1] pry(#<*****>)> value
=> "2"
But If "1" arguments
[2] pry(#<*****>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "q"=>{ "invitation_player_gteq"=>"1"}, "commit"=>"検索", "controller"=>"admin/users", "action"=>"invites"} permitted: false>
scope :invitation_player_gteq, ->(value) { binding.pry
having("COUNT(invitation_awards.id) >= ?", value) }
ArgumentError (wrong number of arguments (given 0, expected 1)):
Why If pass arguments "1" value to the scope method for ransackable_scopes, Argument Error?
What's the full error trace? Only showing the top line of the error with no context makes this difficult to understand.
@tom-lord
Hi, Thanks for reply.

I did attach full trace log for rails.
@tom-lord
Hi. I had reading source code for ransack.
Therefore, I did notice that "1" character did convert from "1" to "true" for sanitize scope method.
https://github.com/activerecord-hackery/ransack/blob/16ce9110cbaee6682aca6e06ccb9d66a0c7a46c3/lib/ransack/search.rb#L128
def add_scope(key, args)
sanitized_args = if Ransack.options[:sanitize_scope_args] && [email protected]_scope_skip_sanitize_args?(key, @context.object)
sanitized_scope_args(args)
else
args
end
if @context.scope_arity(key) == 1
@scope_args[key] = args.is_a?(Array) ? args[0] : args
else
@scope_args[key] = args.is_a?(Array) ? sanitized_args : args
end
@context.chain_scope(key, sanitized_args)
end
def sanitized_scope_args(args)
if args.is_a?(Array)
args = args.map(&method(:sanitized_scope_args))
end
if Constants::TRUE_VALUES.include? args
true
elsif Constants::FALSE_VALUES.include? args
false
else
args
end
end
https://github.com/activerecord-hackery/ransack/blob/16ce9110cbaee6682aca6e06ccb9d66a0c7a46c3/lib/ransack/context.rb#L47
def chain_scope(scope, args)
return unless @klass.method(scope) && args != false
@object = if scope_arity(scope) < 1 && args == true
@object.public_send(scope)
else
@object.public_send(scope, *args)
end
end
Debug Result
130: def add_scope(key, args)
131: binding.pry
=> 132: sanitized_args = if Ransack.options[:sanitize_scope_args] && [email protected]_scope_skip_sanitize_args?(key, @context.object)
133: sanitized_scope_args(args)
134: else
135: args
136: end
137:
138: if @context.scope_arity(key) == 1
139: @scope_args[key] = args.is_a?(Array) ? args[0] : args
140: else
141: @scope_args[key] = args.is_a?(Array) ? sanitized_args : args
142: end
143: @context.chain_scope(key, sanitized_args)
144: end
[1] pry(#<Ransack::Search>)> args
=> "1"
47: def chain_scope(scope, args)
48: binding.pry
=> 49: return unless @klass.method(scope) && args != false
50: @object = if scope_arity(scope) < 1 && args == true
51: @object.public_send(scope)
52: else
53: @object.public_send(scope, *args)
54: end
55: end
[1] pry(#<Ransack::Adapters::ActiveRecord::Context>)> args
=> true
47: def chain_scope(scope, args)
48: binding.pry
49: return unless @klass.method(scope) && args != false
50: @object = if scope_arity(scope) < 1 && args == true
=> 51: @object.public_send(scope)
52: else
53: @object.public_send(scope, *args)
54: end
55: end
ArgumentError (wrong number of arguments (given 0, expected 1)):
By this sanitize scope method, For what reason "1" character convert from "1" to "true"?
Is there any other solution, better than adding a default value to the scope eg. ?
scope :scope_name, ->(string = '1') { sql }
Even more, if you pass '0' to the ransack scope this scope isn't called at all.
Solved in https://github.com/activerecord-hackery/ransack/issues/924, just add
def self.ransackable_scopes_skip_sanitize_args
[:scope_name]
end
to the model.
Hi,
I noticed I have the same issue with the letter t, and not just with 1
and when I replace ransackable_scopes with ransackable_scopes_skip_sanitize_args, then my ransack start behaving wrongly.
Adding my repro app to help:
versions
ransack: 3.0.0 rails: ~> 7.0.0 ruby: 3.0.0p0
model
class User < ApplicationRecord
scope :by_first_or_last_name, lambda { |name|
where('lower(first_name) LIKE :prefix', prefix: "#{name.downcase}%")
.or(where('lower(last_name) LIKE :prefix', prefix: "#{name.downcase}%"))
}
def self.ransackable_scopes(_auth_object = nil)
[:by_first_or_last_name]
end
end
controller
class UsersController < ApplicationController
def index
users = User.ransack(by_first_or_last_name: params[:by_first_or_last_name]).result
render json: users
end
end
test
class UsersControllerTest < ActionDispatch::IntegrationTest
test 'should get index' do
User.create!(first_name: 'John', last_name: 'Doe')
User.create!(first_name: 'Joe', last_name: 'Dohn')
get '/users'
assert_response :success
response_body = JSON.parse(response.body)
assert_equal(response_body.size, 2)
end
test 'should get index with filter' do
User.create!(first_name: 'John', last_name: 'Doe')
User.create!(first_name: 'Joe', last_name: 'Dohn')
get '/users?by_first_or_last_name=John'
assert_response :success
response_body = JSON.parse(response.body)
# new issue here:
assert_equal(response_body.size, 1) # fails with ransackable_scopes_skip_sanitize_args (returns both users instead of the only matching one)
end
test 'should get index with filter - fails with letter t o_O' do # my initial issue
User.create!(first_name: 'John', last_name: 'Doe')
User.create!(first_name: 'Joe', last_name: 'Dohn')
assert_raise do
get '/users?by_first_or_last_name=t'
end
end
end