activerecord-hackery / ransack

Object-based searching.

Home Page:https://activerecord-hackery.github.io/ransack/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ransack generates redundant left join statements

itsalongstory opened this issue · comments

test_ransack.rb

require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'
  gem 'activerecord', '~> 7.0', '>= 7.0.6', require: "active_record"
  gem 'sqlite3', '~> 1.6', '>= 1.6.3'
  gem 'ransack', '~> 4.0'
  gem 'minitest', '~> 5.19', require: "minitest/autorun"
end

ActiveRecord::Base.establish_connection(
  adapter:  "sqlite3",
  database: "./test_ransack_search"
)

ActiveRecord::Schema.define do
  drop_table(:authors, if_exists: true)
  drop_table(:articles, if_exists: true)
  drop_table(:comments, if_exists: true)

  create_table :authors do |t|
    t.string :name
  end

  create_table :articles do |t|
    t.belongs_to :author
    t.string :title
  end

  create_table :comments do |t|
    t.belongs_to :article
    t.string :content
  end
end

class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  def self.ransackable_attributes(auth_object = nil)
    authorizable_ransackable_attributes
  end

  def self.ransackable_associations(auth_object = nil)
    authorizable_ransackable_associations
  end
end

class Author < ApplicationRecord
  has_many :articles
end

class Article < ApplicationRecord
  has_many :comments
  belongs_to :author
end

class Comment < ApplicationRecord
  belongs_to :article
end

class MyTest < Minitest::Test
  # success
  def test_1
    query_params = { article_title_eq: 'abc' }
    assert_equal "SELECT \"comments\".* FROM \"comments\" INNER JOIN \"articles\" ON \"articles\".\"id\" = \"comments\".\"article_id\" WHERE \"articles\".\"title\" = 'abc'", Comment.joins(:article).ransack(query_params).result.to_sql
  end

  # failure
  def test_2
    query_params = { article_author_name_eq: 'abc' }
    assert_equal "SELECT \"comments\".* FROM \"comments\" INNER JOIN \"articles\" ON \"articles\".\"id\" = \"comments\".\"article_id\" INNER JOIN \"authors\" ON \"authors\".\"id\" = \"articles\".\"author_id\" WHERE \"authors\".\"name\" = 'abc'", Comment.joins(article: :author).ransack(query_params).result.to_sql
  end
end
deploy@fms-test:~$ ruby test_ransack.rb 
Fetching gem metadata from https://gems.ruby-china.com/........
Resolving dependencies...
Using concurrent-ruby 1.2.2
Using minitest 5.19.0
Using i18n 1.14.1
Using bundler 2.4.16
Using tzinfo 2.0.6
Using sqlite3 1.6.3 (x86_64-linux)
Using activesupport 7.0.6
Using activemodel 7.0.6
Using activerecord 7.0.6
Using ransack 4.0.0
-- drop_table(:authors, {:if_exists=>true})
   -> 0.0503s
-- drop_table(:articles, {:if_exists=>true})
   -> 0.0358s
-- drop_table(:comments, {:if_exists=>true})
   -> 0.0358s
-- create_table(:authors)
   -> 0.0359s
-- create_table(:articles)
   -> 0.0719s
-- create_table(:comments)
   -> 0.0717s
Run options: --seed 62497

# Running:

.F

Finished in 0.060084s, 33.2867 runs/s, 33.2867 assertions/s.

  1) Failure:
MyTest#test_2 [test_ransack.rb:71]:
--- expected
+++ actual
@@ -1 +1 @@
-"SELECT \"comments\".* FROM \"comments\" INNER JOIN \"articles\" ON \"articles\".\"id\" = \"comments\".\"article_id\" INNER JOIN \"authors\" ON \"authors\".\"id\" = \"articles\".\"author_id\" WHERE \"authors\".\"name\" = 'abc'"
+"SELECT \"comments\".* FROM \"comments\" INNER JOIN \"articles\" ON \"articles\".\"id\" = \"comments\".\"article_id\" INNER JOIN \"authors\" ON \"authors\".\"id\" = \"articles\".\"author_id\" LEFT OUTER JOIN \"authors\" \"authors_articles\" ON \"authors_articles\".\"id\" = \"articles\".\"author_id\" WHERE \"authors\".\"name\" = 'abc'"


2 runs, 2 assertions, 1 failures, 0 errors, 0 skips

In test_2, ransack generate sql like this:

SELECT
    "comments".*
FROM
    "comments"
    INNER JOIN "articles" ON "articles"."id" = "comments"."article_id"
    INNER JOIN "authors" ON "authors"."id" = "articles"."author_id"
    LEFT OUTER JOIN "authors" "authors_articles" ON "authors_articles"."id" = "articles"."author_id" -- ?????
WHERE
    "authors"."name" = 'abc'

The LEFT OUTER JOIN ... is redundant, isn't it?

What we need is just like below:

SELECT
    "comments".*
FROM
    "comments"
    INNER JOIN "articles" ON "articles"."id" = "comments"."article_id"
    INNER JOIN "authors" ON "authors"."id" = "articles"."author_id"
WHERE
    "authors"."name" = 'abc'

I've noticed the same thing in my application, it brokes some of my SQL calculation. Did you find any solution to this? @itsalongstory

I've noticed the same thing in my application, it brokes some of my SQL calculation. Did you find any solution to this? @itsalongstory

Not yet.