Skip to content

Instantly share code, notes, and snippets.

@fadhlirahim
Last active January 31, 2024 09:16
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save fadhlirahim/683cff8c99018a1d886e to your computer and use it in GitHub Desktop.
Save fadhlirahim/683cff8c99018a1d886e to your computer and use it in GitHub Desktop.
Awesome simple solution for Rails ActiveRecord dirty tracking associations
# Credit Brandon Weiss of http://anti-pattern.com/dirty-associations-with-activerecord
# app/models/dirty_associations.rb
module DirtyAssociations
attr_accessor :dirty
attr_accessor :_record_changes
def make_dirty(record)
self.dirty = true
self._record_changes = record
end
def changed?
dirty || super
end
end
# app/models/lolrus.rb
class Lolrus
include DirtyAssociations
has_many :buckets,
:after_add => :make_dirty,
:after_remove => :make_dirty
end
# Usage
lol = Lolrus.create!
lol.buckets << Bucket.new
lol.changed?
# => true
lol._record_changes
# => #<Bucket# ... >
lol._record_changes.changed?
# => true
lol._record_changes.changes?
# for example ...
# => {[attr] => [old_value, new_value]}
@sas1ni69
Copy link

sas1ni69 commented Mar 9, 2018

Just passing by.. 👋 😄

@yourivdlans
Copy link

yourivdlans commented Apr 17, 2019

Neat!

When adding or removing multiple records only the last one would be present in _record_changes.

You could rewrite made_dirty to get all changed records like so:

def make_dirty(record)
  self.dirty = true
  self._record_changes ||= []
  self._record_changes << record
end

Also, reload doesn't reset the _record_changes. You can overload the reload method like so:

def reload(*)
  super.tap do
    self._record_changes = nil
  end
end

@a-poliusov
Copy link

@yourivdlans hi. thanks for you solution. He does not mentioned any reload methods. Can you elaborate why we need reload?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment