By default, calling `inspect` on a record will yield a formatted string including just the `id`.
```ruby
Post.first.inspect #=> "#<Post id: 1>"
```
The attributes to be included in the output of `inspect` can be configured with
`ActiveRecord::Core#attributes_for_inspect`.
```ruby
Post.attributes_for_inspect = [:id, :title]
Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
```
With the `attributes_for_inspect` set to `:all`, `inspect` will list all the record's attributes.
```ruby
Post.attributes_for_inspect = :all
Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
```
I discovered this while working on https://github.com/rails/rails/pull/47800
The bug is quite subtle.
If you call `Model.all.delete(id)`, `all` is an `ActiveRecord::Relation`
which does not respond to `delete`, so it delegates to `Model.delete`
and generate that method in the `GeneratedRelationMethods` module.
The problem however, is that `CollectionProxy` does define `delete`,
so after that initial call, the `Model::ActiveRecord_CollectionProxy`
subclass now has its `delete` method overridden, and now delegate
to the model.
Here I chose to keep that method generation caching, but I'm honestly
not convinced it's really needed. I question how much of a hotspot
these methods are, and we're busting method caches and generating
a lot of code to save on a minor `method_missing` call.
I think we should just remove that caching.
In our use case - we have a base model that has a default scope that we
want enabled for all queries, ex:
```ruby
class Developer < ApplicationRecord
default_scope -> { where(firm_id: Current.firm_id) }, all_queries:
true
end
```
We're also leveraging a module that will add a default scope to only
find soft-deleted records.
```ruby
module SoftDeletable
extend ActiveSupport::Concern
included do
default_scope { where(deleted_at: nil) }
end
```
Through this, we've found that when using default scopes in combination,
*specifically in the use case where the _non_ all queries scope is
declared first*, that we would get an error when calling `.update`:
```ruby
class Developer < ApplicationRecord
include SoftDeletable
default_scope -> { where(firm_id: Current.firm_id) }, all_queries:
true
```
```ruby
Current.firm_id = 5
developer = Developer.new(name: "Steve")
developer.update(name: "Stephen")
NoMethodError: undefined method `where' for nil:NilClass
```
In digging into the code, this was due to there not being an `else` case
for the `inject` used to combine `default_scopes` together (`inject`
uses the return value of the block as the memoizer).
Without the `else` case, if the block returned `nil`, `nil` was passed
to the evaluation of the next `default_scope`.
This commit adds the `else`, and also makes a minor adjustment in
variable naming (`default_scope` to `combined_scope`) in an effort to
add a little more readability, as we're iterating over an array of
default scopes, but what we're building is _the_ default scope to be
used in the query, etc.
Co-authored-by: Will Cosgrove <will@cosgrove.email>
Fixes: #41453Closes: #41461
Previously when a model had strict loading set to true and then had a
relation set strict_loading to false the false wasn't considered when
deciding whether to raise/warn about strict loading.
Code example:
```ruby
class Dog < ActiveRecord::Base
self.strict_loading_by_default = true
has_many :treats, strict_loading: false
end
```
In the example, `dog.treats` would still raise even though
`strict_loading` was set to false. This is a bug effecting more than
Active Storage which is why I made this PR superceeding #41461. We need
to fix this for all applications since the behavior is a little
surprising. I took the test from ##41461 and the code suggestion from #41453
with some additions.
Co-authored-by: Radamés Roriz <radamesroriz@gmail.com>
#40779 will be caused if (1) having nested where condition for through
association, and (2) through scope has references values, and (3) the
model has no association which is the same name with table name.
In that case, the join root will be found by the table name but the join
root isn't related with association reflection.
This changes to return the table klass from join dependency tree, it
works regardless of whether the join root or join association children.
Fixes#40779.
We don't want strict loading to throw an error on validations since
those need to load the record in order to validate it.
This change check the owners's `validation_context`. If it's `nil` then
we know we're not currently validating an object in create/update. If
it's set then we're validating and want to skip raising for strict
loading.
Fixes: #40767
This change allows for applications to optionally run a `default_scope`
on `update` and `delete` queries. Default scopes already ran on select
and insert queries.
Applications can now run a set default scope on all queries for a model
by setting a `all_queries` option:
```ruby
class Article < ApplicationRecord
default_scope -> { where(blog_id: 1) }, all_queries: true
end
```
Using the default scope in this way is useful for applications that need
to query by more than the primary key by default. An example of this
would be in an application using a sharding strategy like Vitess like.
For Rails sharding, we route connections first and then query the
database. However, Vitess and other solutions use a parameter in the
query to figure out how to route the queries. By extending
`default_scope` to apply to all queries we can allow applications to
optionally apply additional constraints to all queries. Note that this
only works with `where` queries as it does not make sense to select a
record by primary key with an order. With this change we're allowing
apps to select with a primary key and an additional key.
To make this change dynamic for routing queries in a tenant sharding
strategy applications can use the `Current` API or parameters in a
request to route queries:
```ruby
class Article < ApplicationRecord
default_scope -> { where(blog_id: Current.blog.id) }, all_queries: true
end
```
In order to achieve this I created a new object when default scopes are
created. This allows us to store both the scope itself and whether we
should run this on all queries. I chose not to implement an `on:` option
that takes an array of actions because there is no simple or clear way
to turn off the default scope for create/select. It also doesn't really
make sense to only have a default scope for delete queries. The decision
to use `all_queries` here allows for the implementation to be more
flexible than it was without creating a mess in an application.
For now, timestamp magic columns are only allowed for real physical
columns, it is not a matter for newly created app, but it is harder to
get the usefulness for legacy databases.
The reason that doesn't work is some low-level API does not care about
attribute aliases. I think that uses low-level API without attribute
alias resolution for timestamp attributes is not intended (e.g.
`updated_at_before_type_cast` works, but `has_attribute?("updated_at")`
and `_read_attribute("updated_at")` doesn't work).
I've addressed all missing attribute alias resolution for timestamp
attributes to work that consistently.
Fixes#37554.
Add `#strict_loading` to any record to prevent lazy loading of associations.
`strict_loading` will cascade down from the parent record to all the
associations to help you catch any places where you may want to use
`preload` instead of lazy loading. This is useful for preventing N+1's.
Co-authored-by: Aaron Patterson <aaron.patterson@gmail.com>
`inverse_of` on through associations was accidently removed/caused to
stop working in commit f8d2899 which was part of a refactoring on
`ThroughReflection`.
To fix we moved `inverse_of` and `check_validity_of_inverse!` to the
`AbstractReflection` so it's available to the `ThroughReflection`
without having to dup any methods. We then need to delegate `inverse_name`
method in `ThroughReflection`. `inverse_name` can't be moved to
`AbstractReflection` without moving methods that set the instance
variable `@automatic_inverse_of`.
This adds a test that ensures that `inverse_of` on a `ThroughReflection`
returns the correct class name, and the correct record for the inverse
relationship.
Fixes#21692
Since after 87d1aba3c `dependent: :destroy` callbacks on has_one
assocations run *after* destroy, it is possible that a nullification is
attempted on an already destroyed target:
class Car < ActiveRecord::Base
has_one :engine, dependent: :nullify
end
class Engine < ActiveRecord::Base
belongs_to :car, dependent: :destroy
end
> car = Car.create!
> engine = Engine.create!(car: car)
> engine.destroy! # => ActiveRecord::ActiveRecordError: cannot update a
> destroyed record
In the above case, `engine.destroy!` deletes `engine` and *then* triggers the
deletion of `car`, which in turn triggers a nullification of `engine.car_id`.
However, `engine` is already destroyed at that point.
Fixes#21223.
with dynamic conditions.
Fixes#16128
This bug was introduced in c35e438620
so it's present from 4.1.2-rc1 and after.
c35e438620
merges any relation scopes passed as proc objects to the relation,
but does *not* take into account the arity of the lambda.
To reproduce: https://gist.github.com/Agis-/5f1f0d664d2cd08dfb9b
HABTM should fall back to using the normal CollectionAssociation's size calculation if the collection is not cached or loaded.
This addresses both #14913 and #14914 for master.
in a default_scope.
`Model.joins(...).where(condition_on_joined_table).update_all` /
`delete_all` worked, but the same operation implemented with a
default_scope generated a SQL error because ActiveRecord ignored the
join but implemented the where condition anyways.
This reverts commit 14fc8b34521f8354a17e50cd11fa3f809e423592.
Reason: we need to discuss a better path from this removal.
Conflicts:
activerecord/lib/active_record/reflection.rb
activerecord/test/cases/base_test.rb
activerecord/test/models/developer.rb
Since composed_of was removed in 051747449e7afc817c599e4135bc629d4de064eb,
these tests were working "by mistake", due to the matching "address"
string in the error message, but with a different error message than the
expected multiparameter assignment error.
Since "address" is not an attribute from Customer anymore, the error was
"undefined method klass for nil", where nil was supposed to be the
column object.
This feature adds a lot of complication to ActiveRecord for dubious
value. Let's talk about what it does currently:
class Customer < ActiveRecord::Base
composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
end
Instead, you can do something like this:
def balance
@balance ||= Money.new(value, currency)
end
def balance=(balance)
self[:value] = balance.value
self[:currency] = balance.currency
@balance = balance
end
Since that's fairly easy code to write, and doesn't need anything
extra from the framework, if you use composed_of today, you'll
have to add accessors/mutators like that.
Closes#1436Closes#2084Closes#3807