Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Question】 If pass arguments "1" value to the scope method for ransackable_scopes, ArgumentError (wrong number of arguments (given 0, expected 1)): #1232

Open
tayagi-aim opened this issue Jun 18, 2021 · 6 comments

Comments

@tayagi-aim
Copy link

tayagi-aim commented Jun 18, 2021

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?

@tom-lord
Copy link

What's the full error trace? Only showing the top line of the error with no context makes this difficult to understand.

@tayagi-aim
Copy link
Author

@tom-lord

Hi, Thanks for reply.

Full Trace

I did attach full trace log for rails.

@tayagi-aim
Copy link
Author

@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.

def add_scope(key, args)

    def add_scope(key, args)
      sanitized_args = if Ransack.options[:sanitize_scope_args] && !@context.ransackable_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

def chain_scope(scope, args)

    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] && !@context.ransackable_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"?

@synion
Copy link

synion commented Sep 9, 2021

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.

@synion
Copy link

synion commented Sep 9, 2021

Solved in #924,
just add

def self.ransackable_scopes_skip_sanitize_args
  [:scope_name]
end

to the model.

@f-g-p
Copy link

f-g-p commented Apr 1, 2022

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

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

No branches or pull requests

4 participants