squeel
squeel copied to clipboard
Join with where block and default is broken.
Squeel appears to be broken when defining an association with a where
clause of type String
, and a default_scope
on one of the models.
I'm using rails 4.1.9, though I've seen it on other versions > 4 as well. Using squeel 1.2.3 and postgres.
Models
class Vehicle < ActiveRecord::Base
# Specify a simple string based where clause on the association
has_many :vehicle_assignments, -> { where("vehicles.id = 1") }
end
class VehicleAssignment < ActiveRecord::Base
belongs_to :vehicle
# Add a simple default scope
default_scope lambda {
where(id: 1)
}
end
Code
Vehicle.joins(:vehicle_assignments)
Result
PG::SyntaxError: ERROR: syntax error at or near "="
LINE 1: ..."."id" AND "vehicle_assignments"."id" = 1, (vehicles.id = 1)
SQL
SELECT "vehicles".* FROM "vehicles" INNER JOIN "vehicle_assignments" ON
"vehicle_assignments"."vehicle_id" = "vehicles"."id" AND "vehicle_assignments"."id" = 1, (vehicles.id = 1)
As you can see, the last conditional is not being added to the SQL correctly. We expect AND (vehicles.id = 1)
but get , (vehicles.id = 1)
. My assumption is that this occurs because vehicles.id = 1
is being added as a simple simple string. If we change the syntax to "vehicles.id" => 1
then everything works as expected.
This only occurs when there is both a string based where
scope block in the association definition, and when the association's model has a default_scope
. The error occurs even when not using squeel syntax anywhere in the app.
I've traced the problem to the collapse_wheres
method, specifically in the final code block where String
based where clauses are explicitly handled. String based where clauses are added to arel.where
but they are never ANDed
with the other groups. I'll add a pull request with what I think is a correct solution, though I will mention that I am not deeply versed in arel.
Any input would be greatly appreciated.
I have been using squeel long time already. So I thought that it is Active Record issue. But after I made test for it I reconsidered it as it didn't occur if squeel wasn't required.
# Activate the gem you are reporting the issue against.
gem 'activerecord', '4.1.9'
gem 'minitest'
gem 'logger'
gem 'squeel', '1.2.2'
require 'active_record'
require 'minitest/autorun'
require 'logger'
require 'squeel'
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection({
adapter: 'mysql2',
database: 'kliendikaardid_test',
username: 'vagrant',
password: 'vagrant',
host: 'localhost'
})
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :trees, force: true do |t|
end
create_table :branches, force: true do |t|
t.belongs_to :tree
end
create_table :leafs, force: true do |t|
t.belongs_to :branch
t.boolean :broken
t.boolean :some_bool
end
end
module SomeModule
def default_scope_from_module
default_scope { where('leafs.broken IS NULL') }
end
end
ActiveRecord::Base.send :extend, SomeModule
class Tree < ActiveRecord::Base
has_many :branches
end
class Branch < ActiveRecord::Base
belongs_to :tree, inverse_of: :branches
has_many :leafs
end
class Leaf < ActiveRecord::Base
default_scope_from_module
default_scope { where('leafs.some_bool = 1') }
belongs_to :branch, inverse_of: :leafs
end
class BugTest < Minitest::Test
def test_default_scope_from_extend
tree = Tree.create!()
tree.branches.joins(:leafs).each do ||
# error already met before this step
end
end
end
If require 'squeel' is included the following exception will be thrown:
ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1) WHERE `branches`.`tree_id` = 1' at line 1: SELECT `branches`.* FROM `branches` INNER JOIN `leafs` ON `leafs`.`branch_id` = `branches`.`id` AND (leafs.broken IS NULL), (leafs.some_bool = 1) WHERE `branches`.`tree_id` = 1