kenn / standby

Read from standby databases for ActiveRecord
MIT License
87 stars 28 forks source link

ActiveRecord relation inverse_of broken #34

Closed EscoCreeks closed 2 years ago

EscoCreeks commented 3 years ago

Hello, I know that this repository is not maintained anymore but I still prefer to warn other developers of the finding we went through.

Whenever we use the standby gem, the file lib/standby/active_record/relation.rb will break the inverse_of your ActiveRecord models.

Let's say you have those two models:

class Note < ApplicationRecord
  belongs_to :musician
end
class Musician < ApplicationRecord
  has_many :notes
end

If you do something like this:

  musician = Notes.last.musician
  musician.name
  #=> 'Bar'
  musician.attributes = { name: 'Foo' }
  musician.notes.last.musician.name
  #=> 'Bar' # Here, we expect it to be 'Foo'

The in-memory change of your model is not reflected anymore through the inverse_of. You will actually see a call done to the DB to fetch again the musician coming from your last note.

When we comment the lib/standby/active_record/relation.rb file, it works again.

Cheers,

dsinn commented 3 years ago

I just ran into this problem as well on Rails 5; ActiveRecord::Relation#exec_queries can take a block as of rails/rails@caa178c178468f7adcc5f4c597280e6362d9e175 and the monkey patch in this gem's relation.rb doesn't support the change. If anybody wants to retain the standby_target feature that the patch provided, I believe we could instead update relation.rb to use Module#prepend:

module RelationWithStandbyTarget
  def exec_queries
    if standby_target
      Standby.on_standby(standby_target) { super }
    else
      super
    end
  end
end

module ActiveRecord
  class Relation
    attr_accessor :standby_target

    # Supports queries like User.on_standby.count
    alias_method :calculate_without_standby, :calculate

    def calculate(*args)
      if standby_target
        Standby.on_standby(standby_target) { calculate_without_standby(*args) }
      else
        calculate_without_standby(*args)
      end
    end
  end
end

ActiveRecord::Relation.prepend(RelationWithStandbyTarget)