Rails 7 adds change tracking methods for belongs_to associations


Sometimes minor updates add the most joy to users! A recent Rails update introduced the ability to monitor for changes in belongs_to associations. This brings in a welcome change for developers who like to conform to “the Rails way”.

Change tracking is one of the convenient methods that Rails supports out of the box. When using ActiveRecord, we can query for database changes. This is useful when building triggers and events. Let’s look at an example!

Imagine a database structure that has posts and categories.

class Post
  belongs_to :category
end

class Category
  has_many :posts
end

Now let’s say that we want to record activity when the posts’ title gets changed. We could write something that looks like this,

class Post
  belongs_to :category

  before_update :record_activity

  private
  def record_activity
    if self.title_changed?
      # record an activity
    end
  end
end

The title_changed? allows us to monitor any changes to the value of the title attribute.

Before

Now, let’s try to do the same thing when a posts’ category changes.

class Post
  belongs_to :category

  before_update :record_activity

  private
  def record_activity
    if self.category_id_changed?
      # record an activity
    end
  end
end

This is an eyesore! We have an association called category, but we can’t monitor for its change. Rather, we have to resort to a rather crude way of monitoring for category_id changes. This isn’t very “the Rails way”.

After

Fortunately, Rails 7 now introduces a way to do this. With this PR, we can now do this.

class Post
  belongs_to :category

  before_update :record_activity

  private
  def record_activity
    if self.category_changed?
      # record an activity
    end
  end
end

The association_changed? method (assuming an association named :association) returns true if a different associated object has been assigned and, the foreign key will be updated in the next save!

We also get access to this method, association_previously_changed? which returns true if the previous save updated the association to reference a different associated object.

  post.category # => #<Post category_id: 123, title: "Welcome to Rails!">
  post.category_previously_changed? # => false

  post.category = Category.second # => #<Post category_id: 456, title: "Welcome to Rails!">
  post.save!

  post.category_previously_changed? # => true