activerecord-hackery / ransack

Object-based searching.
https://activerecord-hackery.github.io/ransack/
MIT License
5.64k stars 789 forks source link

Search on serialized attribute seems broken with Rails 5.2 #932

Open jmvallet opened 6 years ago

jmvallet commented 6 years ago

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'"
paneq commented 6 years ago

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
luizcarvalho commented 5 years ago

Nicely @paneq =D

scarroll32 commented 3 years ago

Is this closable @jmvallet ?

vanboom commented 3 years ago

:+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.

vanboom commented 3 years ago

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?

scarroll32 commented 3 years ago

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.