Skip to content

Restore force_reload argument on ActiveRecord associations

License

Notifications You must be signed in to change notification settings

ilabsolutions/rails-force-reload

 
 

Repository files navigation

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.

About

Restore force_reload argument on ActiveRecord associations

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 71.8%
  • Shell 28.2%