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:
- Creating the associated record
- Updating the associated record
- 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
- Use when needed: Only use
touchwhen you need to track parent updates - Consider performance: Be aware of the performance impact
- Use specific timestamps: Use custom timestamps when appropriate
- Combine with callbacks: Use with
after_touchfor 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.