No commit activity in last 3 years
No release in over 3 years
Starting in v5.0, Rails removed the `force_reload` option from ActiveRecord association readers. This gem adds that functionality back in.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

< 6.0, >= 5.0
 Project Readme

Rails force_reload

Gem Version Coverage Status Build Status

Starting in v5.0, Rails removed the force_reload option from ActiveRecord association readers. This gem adds that functionality back in.

# Collection association (has_many)

@user.posts(true)
# or @user.posts.reload

# Singular association (has_one)

@user.profile(true)
# or @user.reload.profile
# or @user.reload_profile
# See "Background" below for detail on the syntactical difference between these

Installation

Gemfile

gem 'rails-force-reload'

We recommend you insert this immediately after the gem 'rails' line, so that it's loaded in all appropriate Bundler groups.

Command line

gem install 'rails-force-reload'

Compatibility

  • Ruby 2.2.2 or greater (tested against 2.2, 2.3, 2.4, and 2.5 lines)
  • Rails 5.0 or greater (tested against 5.0, 5.1 and 5.2 lines)

Tests are borrowed nearly verbatim from Rails source code.

TL;DR

In Rails 4.2, you could forcibly reload a model's association from the DB by passing a boolean true to the association caller. If we have a User model, with a has_many :posts association, then user.posts(true) would force Rails to pull all Post records from DB, even if they had previously been looked up and cached. This was useful when a script would make DB changes after an association was loaded.

The boolean option was removed in 5.0, and users were directed to the reload method to achieve the same result. There's technically two methods -- under CollectionProxy and Persistence), both of which already existed in the 4.x line.

The decision to deprecate the @parent.association(true) syntax was intended to simplify the API (one way to reload associations), and better honor the Principle of Least Surprise ("what's this true mean here?"). See the original Groups thread and initial pull request for further context.

We agree with the spirit of the decision's intent, but it comes with tradeoffs the we feel warrant the effort of maintaining the original functionality.

Background

"Reload-only" syntax limits readability, specifically when conditionally reloading a given association. GitHub user @heaven offered a succinct example (in the commit removing the functionality, no less)

That was pretty much useful #association(self.persisted?).

It was:

record.tags(record.persisted?).map(&:name)

It becomes:

(record.persisted? ? record.tags.reload : record.tags).map(&:name)

The first example is clearly more succinct and readily absorbed.

Reload syntax also breaks expectations when dealing with a singular association (Foo.has_one :bar) versus a collection association (Foo.has_many :bars).

The reload call comes after a collection...

@user.posts.reload

but before a singular

@user.reload.profile

In addition, the behavior on a singular association is not a one-for-one match with the force_reload syntax. Since we are reloading the parent, we also throw away any unsaved changes and existing existing caches. To compensate, a new reload_<association> dynamic method was introduced. Our above example would be rewritten as such:

@user.reload_profile

This is the recommended way to handle singular associations, given the side effects of reloading the parent, however both methods will technically work.

DHH expressed satisfaction with divergent handling to has_one vs has_many associations. We (politely!) disagree with this assessment.

Our totally subjective opinion
  • The "reload before/after" syntax, coupled with the introduction of a dynamic magic method to recover lost functionality would seem to be a net loss.
  • The universal consistency of @parent.association(true) also better supports Least Surprise theory.
  • Looking at the meta, SingularAssociation and CollectionAssociation behave a little differently, but are grouped and handled collectively, both within the Rails core and Rails-powered applications. The close relationship warrants maintaining parity where possible.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run bundle exec rake install.

We use the appraisal gem to help us generate the individual Gemfiles for each ActiveRecord version and to run the tests locally against each generated Gemfile. The bundle exec rake appraisal test command actually runs our test suite against all Rails versions in our Appraisal file. If you want to run the tests for a specific Rails version, use rake -T for a list.

$ bundle exec appraisal activerecord52 rake test

We provide a Vagrant box to spin up the entire gem in a clean environment. Doing so will avoid the need to prefix all commands with bundle exec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/battlebrisket/rails-force-reload.

License

The gem is available as open source under the terms of the MIT License.