Back to Blog

Rails 7 adds ActiveRecord::Relation#structurally_compatible?

on September 15, 2021
This blog is part of our Rails 7 series.

ActiveRecord is one of the most powerful features in Rails. With ActiveRecord we can easily query and handle database objects without writing any SQL.

By using ActiveRecord Query Interface, we can perform various query operations like Joins, Group, Find, Order. We can also chain relations with where, and, or, not but for and or or the two chaining relations must be structurally compatible.

For any two relations to be Structurally Compatible they must be scoping the same model, and they must differ only by the where clause when no group clause has been defined. If a group clause is present then the relations must differ by the having clause. Also, Neither relation may use a limit, offset, or distinct method.

Previously for and or or query methods, we needed to make sure that the two relations are structurally compatible otherwise ActiveRecord would raise an error.

However, Rails 7 has added ActiveRecord::Relation#structurally_compatible? which provides a method to easily tell if two relations are structurally compatible. We can use this method before we run and or or query methods on any two relations.

Let's assume we have two models Blog and Post with the following relations

1# app/models/blog.rb
2class Blog < ApplicationRecord
3  has_many :posts
4end
1# app/models/post.rb
2class Post < ApplicationRecord
3  belongs_to :blog
4end

Before

If we run or query between incompatible relations we would get an ArgumentError.

1relation_1 = Blog.where(name: 'bigbinary blog')
2relation_2 = Blog.joins(:posts).where(posts: { user_id: current_user.id})
3
4begin
5  relation_1.or(relation_2)
6rescue ArgumentError
7  # Rescue ArgumentError
8end

Rails 7 onwards

We can check the structural compatibility of the two relations.

1relation_1 = Blog.where(name: 'bigbinary blog')
2relation_2 = Blog.where(user_id: current_user.id)
3
4if relation_1.structurally_compatible?(relation_2) # returns true
5  relation_1.or(relation_2)
6end

Check out this pull request for more details.