In a multiple database application, associations can't join across
databases. When set, this option tells Rails to make 2 or more queries
rather than using joins for associations.
Set the option on a has many through association:
```ruby
class Dog
has_many :treats, through: :humans, disable_joins: true
has_many :humans
end
```
Then instead of generating join SQL, two queries are used for `@dog.treats`:
```
SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ? [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?) [["human_id", 1], ["human_id", 2], ["human_id", 3]]
```
This code is extracted from a gem we use internally at GitHub which
means the implementation here is used in production daily and isn't
experimental.
I often get the question "why can't Rails do this automatically" so I
figured I'd include the answer in the commit. Rails can't do this
automatically because associations are lazily loaded. `dog.treats` needs
to load `Dog`, then `Human` and then `Treats`. When `dog.treats` is
called Rails pre-generates the SQL that will be run and puts that
information into a reflection object. Because the SQL parts are pre-generated,
as soon as `dog.treats` is loaded it's too late to skip a join. The join
is already available on the object and that join is what's run to load
`treats` from `dog` through `humans`. I think the only way to avoid setting
an option on the association is to rewrite how and when the SQL is
generated for associations which is a large undertaking. Basically the
way that Active Record associations are designed, it is currently
impossible to have Rails figure out to not join (loading the association
will cause the join to occur, and that join will raise an error if the
models don't live in the same db).
The original implementation was written by me and Aaron. Lee helped port
over tests, and I refactored the extraction to better match Rails style.
Co-authored-by: Lee Quarella <leequarella@gmail.com>
Co-authored-by: Aaron Patterson <aaron@rubyonrails.org>
Ultimately it's exactly the same semantic that was implemented
using `superclass`, except it avoid recursion and benefit from
method cache, so much faster the longer the inheritance chain.
The use of an alias_method chain is unfortunate, but I don't
see any alternative to cast the provided value to string.
When saving a record, autosave adds callbacks to save its' associations.
Since the associations can have similar callbacks for the inverse,
endless loops could occur.
To prevent these endless loops, the callbacks for `has_many` and
`belongs_to` are defined as methods that only execute once.
This is implemented in the `define_non_cyclic_method` method.
However, this wasn't used for the `has_one` callbacks.
While `has_one` association callbacks didn't result in endless loops,
they could execute multiple times.
For example for a bidirectional `has_one` with autosave enabled,
the `save_has_one_association` gets called twice:
class Pirate < ActiveRecord::Base
has_one :ship, autosave: true
def save_has_one_association(reflection)
@count ||= 0
@count += 1 if reflection.name == :ship
super
end
end
class Ship < ActiveRecord::Base
belongs_to :pirate, autosave: true
end
pirate = Pirate.new(catchphrase: "Aye")
pirate.build_ship(name: "Nights Dirty Lightning")
pirate.save!
# this returns 2 instead of 1.
assert_equal 1, pirate.instance_variable_get(:@count)
This commit changes `has_one` autosave callbacks to be non-cyclic as
well. By doing this the autosave callback are made more consistent for
all 3 cases: `has_many`, `has_one` and `belongs_to`.
The return type was changed in the PR #41704 after addition of mode
option. The current documentation is misleading since
documentation puropose strict_loading! would return boolean whereas
it returns the current mode set.
I can across this issue while debugging issue: #41827 and thought
this should be brought to the attention.
PR fixes the issue and would always return boolean based on
strict_loading is enabled or disabled.
```
user.strict_loading! # => true
user.strict_loading!(false) # => false
user.strict_loading!(mode: :n_plus_one_only) # => true
```
Calling save on a record with cyclic autosave callbacks, can call other
callbacks and hooks multiple times. This can lead to unexpected
behaviour.
For example `save` gets called twice on Post in the following example.
This results in `changes_applied` getting called twice.
class Post < ApplicationRecord
belongs_to :postable, polymorphic: true, inverse_of: :post
end
class Message < ApplicationRecord
has_one :post, as: :postable
end
post = Post.create!(postable: Message.new(subject: "Hello, world!"))
# the following would return false when true is expected
post.id_previously_changed?
`save` gets called twice because Post autosaves Message, which
autosaves Post again.
Instead of calling `changes_applied` everytime `save` is called,
we can skip it if it has already been called once in the current saving
cycle. This requires us to track the `@_saving` state of a record.
if `@_saving` is true we know we the record is being saved.
To track if a method has already been called we reuse the
@_already_called hash that is already used for this purpose.
Commit a1a5d37749964b1e1a23914ef13da327403e34cb tried to fix doubles
saves in autosaved associations. However, it didn't work properly for
non-nullable foreign_keys and had to be reverted.
To prevent this in the future, add a regression test.
The previous: config option was being skipped because it was checking
the existence of a reader method instead of the accessor itself.
This also adds a test for the .configure option that was missing.
Before when calling `preventing_writes?` in the connection the code
would raise a `NoMethodError` when calling `current_preventing_writes`
in a String.
This problem only happen when `establish_connection` is called on the
`ConnectionHandler` and not in the model itself.
This also removes the conditional from the `PoolConfig`.