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'"
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.
Most helpful comment
My workaround for serialized list of integers: