0

I'm querying a Searchkick endpoint where I want to find all results where a field is nil.

The query in the console

Product.search('*', where: { category_id: nil })

correctly returns results.

When passed from the client nil, null and all others are interpreted as strings so my query becomes

Product.search('*', where: { category_id: 'nil' })

Is there a rails way or a specific encoding to use for query strings to allow it to be correctly parsed as nil without diving into the params hash and searching for strings?

1
  • @engineersmnky the title implies that it's send as a query string and not JSON encoded.
    – max
    CommentedOct 19, 2024 at 11:41

3 Answers 3

0

To handle this scenario, you need to manipulate the query parameters on the server side to convert the string 'nil' (or 'null') back into a proper nil value before passing it to the Searchkick query. Here are a few approaches:

def search category_id = params[:category_id] category_id = nil if category_id.blank? || category_id == '_nil' products = Product.search('*', where: { category_id: category_id }) render json: products end 
1
  • This works, just trying to avoid it as not only do I need to check category_id but any other nil value which involves traversing all the params. Thank you
    – Romuloux
    CommentedOct 17, 2024 at 15:50
0

The FormData format doesn't actually have a (working) concept of nothing as it's not typed and the values are just strings.

It's kind of implied that with formdata the client should not serialize incomplete inputs (like unchecked checkboxes).

The closest you get is an empty string which can be sent by just omitting the RVAL.

irb(main):001:0> Rack::Utils.parse_nested_query("foo=&bar=2&baz=3") => {"foo"=>"", "bar"=>"2", "baz"=>"3"} 

While it's trivial to convert the empty strings into nils:

irb(main):007:0> params = ActionController::Parameters.new(Rack::Utils.parse_nested_query("foo=&bar=2&baz=3")) => #<ActionController::Parameters {"foo"=>"", "bar"=>"2", "baz"=>"3"} permitted: false> irb(main):008:0> params.transform_values { |v| v.empty? ? nil : v } => #<ActionController::Parameters {"foo"=>nil, "bar"=>"2", "baz"=>"3"} permitted: false> 

YMMV as there are a lot of potential bugs that can occur if you equate "not filled in" with explicitly nothing.

A better idea is to invent a token for nothing (Rails doesn't have one) or send a POST request with a JSON payload instead of using the query string. Or you can embrace the Form Object pattern instead of just forwarding whatever the internet happens to throw at your controller into the model.

class SearchForm include ActiveModel::Model include ActiveModel::Attributes attribute :foo def foo=(val) super(val.empty? ? nil : v) end end 
    0

    The challenge you’re facing arises because when data is passed from the client (e.g., via query parameters in an HTTP request), nil and null are converted to strings like "nil" or "null" instead of being interpreted as Ruby's nil. Here's how you can properly handle this scenario in your Rails application:

    Solution 1: Parameter Conversion in Controller

    One way to solve this issue is by converting "nil" or "null" values back to nil in the Rails controller. You could define a helper method to process the parameters.

    Example

    # app/controllers/products_controller.rb class ProductsController < ApplicationController def index category_id = convert_to_nil(params[:category_id]) @products = Product.search('*', where: { category_id: category_id }) render json: @products end private def convert_to_nil(value) value.in?(['nil', 'null', '']) ? nil : value end end 

    Solution 2: Use a Custom JavaScript Value

    You can avoid sending "nil" or "null" from the frontend by sending an explicit placeholder value (e.g., "__nil__") to signal the need for nil in the backend.

    JavaScript Code

    const query = { category_id: selectedCategory || "__nil__" }; fetch(`/products?${new URLSearchParams(query)}`); 

    Controller Adjustment

    def index category_id = params[:category_id] == '__nil__' ? nil : params[:category_id] @products = Product.search('*', where: { category_id: category_id }) render json: @products end 

    Solution 3: Use presence or blank? Check

    If the presence or absence of the parameter is enough to decide the query logic, you could use Rails’ presence method:

    category_id = params[:category_id].presence @products = Product.search('*', where: { category_id: category_id }) 

    Solution 4: Encode nil Using Query Parameter Standards (Optional)

    If you are using JSON in the body of requests instead of query parameters, you can directly pass null in the JSON payload.

    fetch('/products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ category_id: null }), }); 

    Controller Example

    def index category_id = params[:category_id] @products = Product.search('*', where: { category_id: category_id }) render json: @products end 

    These solutions help ensure that nil is correctly interpreted without requiring excessive manipulation of the params hash in every request.

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.