Ransack: Search on serialized attribute seems broken with Rails 5.2

Created on 11 Jul 2018  路  6Comments  路  Source: activerecord-hackery/ransack

Hi,

I use ransack in order to be able to search on serialized attributes of models. This feature seems to be broken with Rails 5.2 and Ransack 1.8.8 while I have no problem with rails 4.2 and Ransack 1.8.2.

This repository reproduces the problem: https://github.com/jmvallet/ransack-search-with-serialize-attr-bug

Thank you for your help on this topic.

Post model

class Post < ApplicationRecord
  serialize :content
end

Post fixtures

one:
  content: ["hello", "world"]

two:
  content: ["hello"]

Model test

test "Ransack search on serialized attribute" do
  assert_equal 1, Post.where("content like ?", "%world%").count
  assert_equal 1, Post.ransack(content_cont: "world").result(distinct: true).count
end

Failure:

PostTest#test_Ransack_search_on_serialized_attribute
Expected: 1
  Actual: 0

In rails console:

p Post.ransack(content_cont: "world").result.to_sql
"SELECT \"posts\".* FROM \"posts\" WHERE \"posts\".\"content\" LIKE '--- \"%world%\"\n'"

Most helpful comment

My workaround for serialized list of integers:

class Package < ApplicationRecord
  serialize :ids

  ransacker :ids_raw, type: :string do
    Arel.sql('packages.ids')
  end
Ransack.configure do |config|
  config.add_predicate 'yaml_array_cont',
    arel_predicate: 'matches',
    formatter: proc { |v| "%- #{v.strip}\n%" },
    validator: proc { |v| v.present? },
    type: :string
end
  def test_can_find_package_ids_in_yaml_serialized_column
    package = Package::ProductFormatEbook.create!(
      ids: [10, 103, 1034],
    )
    assert_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "10").result, package)
    assert_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "103").result, package)
    assert_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "1034").result, package)

    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "1").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "11").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "102").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "1033").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "10345").result, package)
  end

All 6 comments

My workaround for serialized list of integers:

class Package < ApplicationRecord
  serialize :ids

  ransacker :ids_raw, type: :string do
    Arel.sql('packages.ids')
  end
Ransack.configure do |config|
  config.add_predicate 'yaml_array_cont',
    arel_predicate: 'matches',
    formatter: proc { |v| "%- #{v.strip}\n%" },
    validator: proc { |v| v.present? },
    type: :string
end
  def test_can_find_package_ids_in_yaml_serialized_column
    package = Package::ProductFormatEbook.create!(
      ids: [10, 103, 1034],
    )
    assert_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "10").result, package)
    assert_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "103").result, package)
    assert_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "1034").result, package)

    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "1").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "11").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "102").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "1033").result, package)
    assert_not_includes(Package.where(id: package).ransack(ids_raw_yaml_array_cont: "10345").result, package)
  end

Nicely @paneq =D

Is this closable @jmvallet ?

:+1: Same issue here after moving to Rails 5.2 - Ransack is attempting to treat the serialized array (which is simply text in the database) as an array column where previously it simply searched the plain text.

This appears to be a change in Arel. For example, consider a User model with a text column holding a serialized array of permissions.

pred = User.arel_table[:permission]
User.where(pred.matches("%admin%"))
ActiveRecord::SerializationTypeMismatch (can't dump 'access': was supposed to be a Array, but was a String. -- "%admin%"

Arel should query the text column as plain text -- effectively querying the YAML for a text match. I am still digging - any advice is appreciated. Possibly this is an Arel issue?

This code above worked fine on Rails 4.1.8, but fails on 5.2 and I think is the root why Ransack fails to search on the serialized attribute.

In rails/rails#41787 we received this reply:

Thank you for the issue. Arel is not public API of Rails and should not be used by applications. Also Rails 5.2 is not supported to bug fixes anymore.

Are you aware that this is the Rails position regarding Arel? Seems that will have a big impact on this gem going forward?

Hi @vanboom that is true that Arel is not a public API, but many applications use it. The key aspect is that there is no protection from breaking changes when a Rails version changes.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mkalmykov picture mkalmykov  路  3Comments

mbajur picture mbajur  路  5Comments

timoschilling picture timoschilling  路  5Comments

seanfcarroll picture seanfcarroll  路  4Comments

zenati picture zenati  路  4Comments