Rails 6 adds touch option to has_one association

Rails 6 added the touch option to has_one associations, allowing you to automatically update the updated_at timestamp of the parent record when the associated record is modified.

Before Rails 6

Previously, touch was only available for belongs_to and has_many associations:

# Only worked for belongs_to
class Post < ApplicationRecord
  belongs_to :user, touch: true
end

# Only worked for has_many
class User < ApplicationRecord
  has_many :posts, touch: true
end

# Did NOT work for has_one
class User < ApplicationRecord
  has_one :profile, touch: true # This didn't work before Rails 6
end

Rails 6 Solution

Rails 6 extends touch support to has_one associations:

class User < ApplicationRecord
  has_one :profile, touch: true
end

class Profile < ApplicationRecord
  belongs_to :user
end

Now, when a profile is created, updated, or destroyed, the user's updated_at timestamp is automatically updated.

Basic Usage

Simple Touch

Touch the parent's updated_at:

class User < ApplicationRecord
  has_one :profile, touch: true
end

# When profile is updated
profile.update(name: 'New Name')
# User's updated_at is automatically updated

Touch Specific Attributes

Touch specific attributes instead of just updated_at:

class User < ApplicationRecord
  has_one :profile, touch: :profile_updated_at
end

# When profile is updated
profile.update(name: 'New Name')
# User's profile_updated_at is automatically updated

Real-World Examples

User Profile

class User < ApplicationRecord
  has_one :profile, touch: true
end

class Profile < ApplicationRecord
  belongs_to :user
end

# Updating profile touches user
user.profile.update(bio: 'New bio')
user.reload.updated_at # => Updated timestamp

Account Settings

class Account < ApplicationRecord
  has_one :settings, touch: true
end

class Settings < ApplicationRecord
  belongs_to :account
end

# Changing settings touches account
account.settings.update(theme: 'dark')
account.reload.updated_at # => Updated timestamp

Custom Timestamp

class User < ApplicationRecord
  has_one :preference, touch: :preferences_updated_at
end

class Preference < ApplicationRecord
  belongs_to :user
end

# Updating preference touches custom timestamp
user.preference.update(language: 'en')
user.reload.preferences_updated_at # => Updated timestamp

When Touch is Triggered

The touch option is triggered when:

  1. Creating the associated record
  2. Updating the associated record
  3. Destroying the associated record
# Create - touches parent
user.create_profile(name: 'John')

# Update - touches parent
user.profile.update(name: 'Jane')

# Destroy - touches parent
user.profile.destroy

Combining with Other Options

With Dependent Destroy

class User < ApplicationRecord
  has_one :profile, touch: true, dependent: :destroy
end

With Conditions

class User < ApplicationRecord
  has_one :profile, touch: true, dependent: :destroy
end

Performance Considerations

Avoiding Unnecessary Touches

If you're updating multiple associated records, consider batching:

# This touches user multiple times
profile.update(name: 'New Name')
profile.update(bio: 'New Bio')

# Better: Single touch
profile.update(name: 'New Name', bio: 'New Bio')

Using touch_later

For background processing:

# Touch immediately
user.profile.update(name: 'New Name')

# Touch asynchronously (Rails 5.2+)
user.touch_later

Comparison with Other Associations

belongs_to touch

# Child touches parent
class Profile < ApplicationRecord
  belongs_to :user, touch: true
end

has_many touch

# Any child touches parent
class User < ApplicationRecord
  has_many :posts, touch: true
end

has_one touch (Rails 6+)

# Associated record touches parent
class User < ApplicationRecord
  has_one :profile, touch: true
end

Use Cases

Cache Invalidation

Use touch to invalidate caches:

class User < ApplicationRecord
  has_one :profile, touch: true

  def cache_key
    "#{super}/#{updated_at.to_i}"
  end
end

Activity Tracking

Track when users update their profiles:

class User < ApplicationRecord
  has_one :profile, touch: :last_profile_update_at
end

Search Index Updates

Trigger search index updates:

class User < ApplicationRecord
  has_one :profile, touch: true

  after_touch :update_search_index

  private

  def update_search_index
    SearchIndexJob.perform_later(self)
  end
end

Best Practices

  1. Use when needed: Only use touch when you need to track parent updates
  2. Consider performance: Be aware of the performance impact
  3. Use specific timestamps: Use custom timestamps when appropriate
  4. Combine with callbacks: Use with after_touch for additional logic

Conclusion

Rails 6's addition of touch to has_one associations provides consistency across all association types. This feature makes it easier to keep parent records updated when their associated records change, which is useful for cache invalidation, activity tracking, and maintaining data consistency.

Rails 6 adds touch option to has_one association - Abhay Nikam