Last active
February 21, 2017 20:07
-
-
Save nachokb/a343b3f98f4d5d0bdca3 to your computer and use it in GitHub Desktop.
ActiveRecord: #merge with joins and a has_many :through creates redundant INNER JOINs and postgresql does not like those
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
system 'rm Gemfile' if File.exist?('Gemfile') | |
File.write('Gemfile', <<-GEMFILE) | |
source 'https://rubygems.org' | |
gem 'activerecord', | |
'5.0.1' | |
# '4.2.0' | |
# '4.1.8' | |
# '4.0.12' | |
# '3.2.21' | |
gem 'sqlite3' | |
GEMFILE | |
system 'bundle install' | |
require 'byebug' | |
require 'bundler' | |
Bundler.setup(:default) | |
require 'active_record' | |
require 'minitest/autorun' | |
require 'logger' | |
# This connection will do for database-independent bug reports. | |
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') | |
ActiveRecord::Base.logger = Logger.new(STDOUT) | |
ActiveRecord::Schema.define do | |
create_table :companies do |t| | |
t.string :name | |
end | |
create_table :titles do |t| | |
t.string :name | |
t.boolean :obscene_salary | |
end | |
create_table :positions do |t| | |
t.belongs_to :company | |
t.belongs_to :title | |
t.string :location | |
end | |
create_table :jobs do |t| | |
t.belongs_to :position | |
t.date :started_at | |
t.string :full_name | |
end | |
end | |
class Company < ActiveRecord::Base | |
has_many :positions | |
has_many :titles, | |
through: :positions | |
end | |
# think of titles as being regulated, companies can't just create a title | |
class Title < ActiveRecord::Base | |
has_many :positions | |
has_many :companies, | |
through: :positions | |
# titles which had at least one position with at least one active job on a given date | |
scope :active_on, -> (date) { joins(:positions).uniq.merge(Position.active_on(date)) } | |
end | |
class Position < ActiveRecord::Base | |
belongs_to :title | |
belongs_to :company | |
has_many :jobs | |
# positions which had at least one active job on a given date | |
scope :active_on, -> (date) { joins(:jobs).uniq.merge(Job.active_on(date)) } | |
end | |
class Job < ActiveRecord::Base | |
belongs_to :position | |
# jobs which were active on a given date | |
scope :active_on, -> (date) { where('started_at <= ?', date) } | |
def active?(date) | |
started_at <= date | |
end | |
end | |
# class BugTest < Minitest::Test | |
class BugTest < MiniTest::Unit::TestCase | |
def setup | |
# One Company | |
@acme = Company.create name: 'Acme' | |
# Titles | |
@cto = Title.create name: 'CTO', obscene_salary: true | |
@clerk = Title.create name: 'Clerk', obscene_salary: true | |
# Positions | |
@kl_cto = Position.create title: @cto, company: @acme, location: "King's Landing" | |
@kl_clerk = Position.create title: @clerk, company: @acme, location: "King's Landing" | |
@ss_clerk = Position.create title: @clerk, company: @acme, location: "Sunspear" | |
# Jobs | |
@albert = Job.create position: @kl_cto, started_at: Date.civil(2015, 1, 1), full_name: 'Albert Doe' | |
@bruce = Job.create position: @kl_clerk, started_at: Date.civil(2015, 2, 1), full_name: 'Bruce Doe' | |
@carl = Job.create position: @ss_clerk, started_at: Date.civil(2015, 3, 1), full_name: 'Carl Doe' | |
end | |
def test_merge_with_joins | |
assert_equal 2, Title.active_on(Date.civil(2015, 3, 10)).count # this is sometimes 4 ¯\_(ツ)_/¯ | |
assert_equal 1, Title.active_on(Date.civil(2015, 1, 10)).count | |
end | |
def test_merge_with_joins_and_has_many | |
assert_equal 2, @acme.titles.active_on(Date.civil(2015, 3, 10)).count | |
assert_equal 1, @acme.titles.active_on(Date.civil(2015, 1, 10)).count | |
sql = @acme.titles.active_on(Date.civil(2015, 3, 10)).to_sql | |
puts sql | |
assert_equal 1, | |
sql.scan(/INNER JOIN .positions/).count, | |
'hmt and explicit joins duplicate the INNER JOIN on positions, which Postgres does not approve' | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Actually, a more appropriate description would be that postgresql does not like the reference to "positions" in the second join (
"positions"."id"
) when that is defined later. Query:This is what Postgresql says with this query: