Commit Graph

981 Commits

Author SHA1 Message Date
Paarth Madan
3d820195a0 Support fixture associations for composite models
Most of the support here is in implementing how to correctly substitute
multiple values in place of one, for composite caes. In composite cases,
it's not sufficient to hash a label into a single integer value.
Instead, we build an API that accepts a single label, and a list of
columns that we'd like to map to. The algorithm used internally is very
similar to #identify, with an additional bit shift and modulo to cycle
the hash and ensure it doesn't exceed a max.
2023-04-14 18:12:18 -04:00
Paarth Madan
6afc035cbc Support deleting records from associations for CPK 2023-04-12 17:53:20 -04:00
eileencodes
885bd850e3
Ensure that ids_writer and ids_reader is working for CPK
This fixes `ids_writer` so that it can handle a composite primary key.
Using a CPK model associated with a non-CPK model was working correctly
(which I added a test for). Using a CPK model associated with another
CPK model was not working correctly. It now takes it into account to
write the correct ids.

While working on `ids_reader` I found that `pluck` is not working for
CPK because it's passing an array of attributes and that's not supported
by `disallow_raw_sql!`. I chose to call `flatten` in `pluck` but not
conditionally because this seems like it could be a problem elsewhere as
well. This fixes pluck by CPK overall and fixes a test in the
calculations test file.
2023-04-12 15:02:58 -04:00
Nikita Vasilevsky
5cbdfba670 Fix associations associated with a CPK model by id attribute
Given a model associated with a composite primary key by `id` attribute,
for example:
```ruby
Order.primary_key = [:shop_id, :id]
OrderAgreement.primary_key = :id

Order.has_many :order_agreements, primary_key: :id
```

Accessing the association should perform queries using
the `id` attribute value and not the `id` as Order's composite primary key.

```ruby
order = Order.last # => #<Order id: 1, shop_id: 2>

order.order_agreements.to_a
```
2023-04-12 16:02:24 +00:00
Aaron Harpole
4a4cc21371 fix infinite recursion with foreign_key
In 15369fd we introduced the ability to infer the foreign_key for a model using `inverse_of`.

In some association configurations, such as when there is a `has_one` that is the inverse of another `has_one` association, this inference causes infinite recursion.

This addresses that by adding a param to `Reflection#foreign_key` to indicate whether to infer from `inverse_of`, and in `#derive_foreign_key` we explicitly disable this behavior when calling `#foreign_key` on the inverse association.
2023-04-07 10:42:34 -07:00
Daniel Whitney
15369fd912
Infer foerign_key when inverse_of is present (#47797)
* Infer `foerign_key` when `inverse_of` is present

Automatically infer `foreign_key` on `has_one` and `has_many` associations when `inverse_of` is present.

When inverse of is present, rails has all the info it needs to figure out what the foreign_key on the associated model should be.  I can't imagine this breaking anything

* Update test models to remove redundant foreign_keys

* add changelog entry

* fix changelog grammar

Co-authored-by: Rafael Mendonça França <rafael@franca.dev>
2023-03-30 15:22:23 -04:00
Eileen M. Uchitelle
fc2b937d56
Merge pull request #47819 from Shopify/always-respect-explicitly-configured-query-constraints
Respect explicitly configured `query_constraints`
2023-03-30 11:59:05 -04:00
Nikita Vasilevsky
767cd52e76 Respect explicitly configured query_constraints 2023-03-30 15:15:46 +00:00
Jean Boussier
e4009c8d66 ActiveRecord::Delegation prevent overriding existing methods
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.
2023-03-30 11:52:53 +02:00
Jean Boussier
dee93277e3 Implement marshal_dump and marshal_load on ActiveRecord::Base
Fix: https://github.com/rails/rails/issues/47704
Superseed: https://github.com/rails/rails/pull/47722

While the instance variable ordering bug will be fixed in Ruby 3.2.2,
it's not great that we're depending on such brittle implementation detail.

Additionally, Marshalling Active Record instances is currently very inefficient,
the payload include lots of redundant data that shouldn't make it into the cache.

In this new format the serialized payload only contains basic Ruby core or stdlib objects,
reducing the risk of changes in the internal representation of Rails classes.
2023-03-28 16:46:14 +02:00
Nikita Vasilevsky
f29ff0f0a0 Fix precedence of primary_key: in associations with query_constraints 2023-03-23 23:01:06 +00:00
Eileen M. Uchitelle
9527e5e307
Merge pull request #47668 from Shopify/pm/destroy-async-composite-keys
Destroying associations asynchronously respect query constraints
2023-03-15 09:18:42 -04:00
Paarth Madan
a270108bf6 Destroy associations async respect query constraints
The codepaths related to destroying associations asynchronously now
consider when query constraints are present. In most cases, this means
interpreting the primary key as an array of columns, and identifying
associated records by a tuple of these columns, where previously this
would've been a single ID. In each of the callsites, we use branching.
This is done to maintain backwards compatibility and ensure the
signature of the destroy job remains stable: it has consumers outside of
Rails.
2023-03-14 19:09:09 -04:00
Jason Karns
1f61fd65ae
Delegated Type supports customizeable foreign_type column 2023-03-14 09:19:30 -04:00
fatkodima
fe97e26785 Optimize slow tests in activerecord 2023-03-13 14:07:28 +02:00
Nikita Vasilevsky
8b43c13e53 Use composite primary_key value as a fallback for query_constraints_list
Given a model with a composite primary key, the `query_constraints_list`
should equal the `primary_key` value to enable capabilities managed by
`query_constraints_list` such as `destroy`, `update`, `delete` and others.
2023-03-10 18:31:02 +00:00
Nikita Vasilevsky
fb127c0d42 Define ActiveRecord::Base#id API for composite primary key models
Given a model with a composite primary key:
```ruby
class Order < ActiveRecord::Base
  self.primary_key = [:shop_id, :id]
end
```

`ActiveRecord::Base#id` method will return an array of values for every
column of the primary key.

```ruby
order = Order.create!(shop_id: 1, id: 2)
order.id # => [1, 2]
```

The `id` column is accessible through the `read_attribute` method:

```ruby
order.read_attribute(:id) # => 2
```
2023-03-10 14:42:57 +00:00
Nikita Vasilevsky
f45663a30b Do not use query_constraints if association doesn't explicitly specifies it 2023-03-03 00:19:39 +00:00
Nikita Vasilevsky
2801ea64c3 Fix has_many through association assginments with custom scope and custom joining association name
`where_values_hash` method signature accepts `table_name` which is not
always the same as the association name.
So passing `through_association.reflection.name.to_s` as `table_name`
in `through_scope_attributes` wasn't accurate for every case.
This commit fixes the issue by passing the `table_name` taken from
`through_association.reflection.klass.table_name` instead.
2023-03-01 17:56:03 +00:00
Nikita Vasilevsky
c12a77da40 Fix assigning through records when using polymorphic has many through association 2023-02-28 17:56:56 +00:00
Jean Boussier
185f2d718d Allow to define the default column serializer
YAML has quite a bit of footguns, as such it's desirable
to be able to substitute it for something else or even
simply to force users to define a serializer explictly for
every serialized columns.
2023-02-22 19:32:28 +01:00
Nikita Vasilevsky
5e915f3879 Support has_many through associations with composite query_constraints
Given an association like:
```ruby
BlogPost.has_many :blog_post_tags,
  query_constraints: [:blog_id, :blog_post_id]
BlogPost.has_many :tags, through: :blog_post_tags
```

The `tags` association records can be queried
and the resulting JOIN query will include both `blog_id` and
`blog_post_id` in the ON clause.
2023-02-22 00:01:22 +00:00
eileencodes
f25fa3fabe
Refactor query constraints feature set
This PR refactors the query constraints feature to be more contained and
have less implicit behavior. Eventually we'll want `primary_key` to flow
through query constraints and return `["id"]` when we have a single
column and internally Rails will use an array for all `primary_key` and
`foreign_key` calls, falling back to query constraints. At the moment
however, this is a large change in Rails and one that could break
current expected behavior. In order to implenent the feature and not
break compatibility I think we should walk back the feature a little
bit. The changes are:

* Only return `query_constraints_list` if `query_constraints` was set by
the model, otherwise return nil.
* Re-implement `primary_key` calls where we only have a single ID
* Update the `foreign_key` option on associations to use a
`query_constraints` option instead so we don't need to check
`is_a?(Array)`.

These changes will ensure that the changes are contained to
`query_constraints` rather than having to make decisions about whether
we want to treat `primary_key` as an array. For now we will call
everything `query_constraints` which makes it easy to see the line when
we want an array vs single column. Later we can change the meaning of
`primary_key` if necessary.
2023-02-13 15:24:40 -05:00
Nikita Vasilevsky
734f424247 Support composite foreign keys in associations
Foundations to support associations with composite foreign keys like:
`Comment.belongs_to :blog_post, foreign_key: [:blog_id, :blog_post_id],
primary_key: [:blog_id, :id]`
2023-01-31 17:46:41 +00:00
fatkodima
cf940daa76 Fix ActiveRecord grouped calculations on joined tables on column present in both tables 2023-01-07 21:56:13 +02:00
Jonathan Hefner
76b440f287 Rework association model class error messages
Follow-up to #46605.

For a scenario like:

  ```ruby
  class Cat < ActiveRecord::Base
    has_many :lives
  end

  class Life
  end
  ```

this commit changes the error from:

> Rails couldn't find a valid model for the lives association. Use the
> :class_name option on the association declaration to tell Rails which
> model to use.

to:

> The Life model class for the Cat#lives association is not an
> ActiveRecord::Base subclass.

Additionally, for a scenario like:

  ```ruby
  class Cat < ActiveRecord::Base
    has_many :lives
  end

  class Live < ActiveRecord::Base
  end
  ```

this commit changes the error from:

> Rails couldn't find a valid model for the lives association. Use the
> :class_name option on the association declaration to tell Rails which
> model to use.

to:

> Missing model class Life for the Cat#lives association. You can
> specify a different model class with the :class_name option.
2022-12-02 15:59:25 -06:00
Alex Ghiculescu
04ecf983ce More detailed error message for compute_class errors
ref: https://github.com/rails/rails/pull/46560#discussion_r1030718963
ref: https://github.com/rails/rails/pull/42124
ref: https://github.com/rails/rails/pull/40836

As suggested by @matthewd, this PR makes `compute_class` error message smarter based on how the association is set up. The message will now differ based on if a `class_name` option is provided to the association.
2022-11-29 11:59:23 -06:00
Jean Boussier
fae0baef41
Merge pull request #46515 from lazaronixon/refactor-singular-association-creation
Small refactor on singular association creation
2022-11-28 10:40:38 +01:00
Nixon
55e76f124f Remove set_new_record from singular association creation 2022-11-23 14:26:29 -03:00
Nikita Vasilevsky
fb5cef98bb Configure query_constraints_list to use primary_key by default
Every `ActiveRecord::Base` model now should have `query_constraints_list`
configured by default and its value set to the model's `primary_key`
2022-11-21 22:21:55 +00:00
Jean Boussier
2d2fdc941e
Merge pull request #46410 from lazaronixon/fix-touch-has-one-parent
Fix touching has_one grand parent associations when the child is touched
2022-11-15 09:38:02 +01:00
fatkodima
3aa2001201 Initialize encrypted attributes when using ActiveRecord::Relation#first_or_initialize/first_or_create 2022-11-14 03:03:59 +02:00
Nixon
c68c360e2a Fix touching has_one grandparent associations 2022-11-05 22:09:40 -03:00
Nikita Vasilevsky
415e6b6391 Allow specifying columns to use in ActiveRecord::Base object queries
Renames `_primary_key_constraints_hash` private method to avoid implying
that it strictly represents a constraint based on the model primary key

Allows columns list used in query constraints to be configurable using
`ActiveRecord::Base.query_constraints` macro
2022-11-01 15:52:34 +00:00
Dooor
31c15b4791 Fix a bug where using groups and counts with long table names would return incorrect results.
Fixed a bug that caused the alias name of "group by" to be too long and the first half of the name would be the same in both cases if it was cut by max identifier length.

Fix #46285

Co-authored-by: Yusaku ONO <yono@users.noreply.github.com>
2022-10-20 16:15:45 +09:00
Rafael Mendonça França
514ae381a3
Merge pull request #45877 from neilvilela/nc-composed-of-hash
Clarify `composed_of` allows a Hash for `mapping`
2022-09-14 17:39:53 -04:00
Jacopo
2f5136a3be Normalize virtual attributes on ActiveRecord::Persistence#becomes
When source and target classes have a different set of attributes adapts
attributes such that the extra attributes from target are added.

Fixes #41195

Co-authored-by: SampsonCrowley <sampsonsprojects@gmail.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2022-08-24 22:51:57 +02:00
Neil Carvalho
ccb2393979 Clarify composed_of allows a Hash for mapping
[`composed_of`](https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html#method-i-composed_of)
is a feature that is not widely used and its API is somewhat confusing,
especially for beginners. It was even deprecated for a while, but 10
years after its deprecation, it's still here.

The Rails documentation includes these examples including mapping:
```ruby
composed_of :temperature, mapping: %w(reading celsius)
composed_of :balance, class_name: "Money", mapping: %w(balance amount)
composed_of :address, mapping: [ %w(address_street street),
%w(address_city city) ]
```

Hashes are accepted kind-of accidentally for the `mapping` option. Using
a hash, instead of an array or array of arrays makes the documentation
more beginner-friendly.

```ruby
composed_of :temperature, mapping: { reading: :celsius }
composed_of :balance, class_name: "Money", mapping: { balance: :amount }
composed_of :address, mapping: { address_street: :street, address_city:
:city }
```

Before Ruby 1.9, looping through a hash didn't have deterministic order,
and the mapping order is important, as that's the same order Rails uses
when initializing the value object. Since Ruby 1.9, this isn't an issue
anymore, so we can change the documentation to use hashes instead.

This commit changes the documentation for `composed_of`, clarifying that
any key-value format (both a `Hash` and an `Array`) are accepted for the
`mapping` option. It also adds tests to ensure hashes are also accepted.
2022-08-23 15:57:54 -03:00
Zack Deveau
611990f1a6
Change ActiveRecord::Coders::YAMLColumn default to safe_load
In Psych >= 4.0.0, load defaults to safe_load. This commit
makes the ActiveRecord::Coders::YAMLColum class use Psych safe_load
as the Rails default.

This default is configurable via ActiveRecord.use_yaml_unsafe_load

We conditionally fallback to the correct unsafe load if use_yaml_unsafe_load
is set to true. unsafe_load was introduced in Psych 4.0.0

The list of safe_load permitted classes is configurable via
ActiveRecord.yaml_column_permitted_classes

[CVE-2022-32224]
2022-07-12 09:26:46 -07:00
Vlado Cingel
098b0eb5db .with query method added.
Construct common table expressions with ease and get `ActiveRecord::Relation` back.
2022-07-01 17:44:51 +02:00
Matt Lawrence
ae9beace34 Fix eager loading models without primary keys
Co-authored-by: chopraanmol1 <chopraanmol1@gmail.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2022-05-28 17:02:15 -05:00
Nikita Vasilevsky
07bae8b2f0 Change DeveloperWithDefaultNilableMentorScopeAllQueries name 2022-05-24 21:48:28 +00:00
Jonathan Hefner
23ba1188fd
Merge pull request #45125 from fatkodima/touch-readonly-column
Fix `touch` to raise an error for readonly columns

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
Co-authored-by: Guillermo Iguaran <guilleiguaran@gmail.com>
2022-05-19 11:22:06 -05:00
Luan Vieira
453f83cb8d Update primary key for dl_keyed_join
The primary_key for the `dl_keyed_join` relation was incorrect. It
expected to use  a `joins_key` attribute on the parent record in order
to link to the `dl_keyed_join` record. The parent class does not have a
`joins_key` attribute at all, which means that association was never
setup correctly.

Co-authored-by: Daniel Colson <composerinteralia@github.com>
2022-05-18 11:04:15 -07:00
fatkodima
4184ee7323 Fix touch to raise an error for readonly columns 2022-05-18 19:46:53 +03:00
fatkodima
fd2afb331e Allow using aliased attributes with insert_all/upsert_all 2022-05-11 16:46:26 +03:00
Nathaniel Watts
da05fa3381
Fix for multiple default_scope all_queries options
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>
2022-05-09 16:34:18 -04:00
Matthew Draper
8d04150709 Ensure Contract#metadata is reliably orderable
This gets an annoyingly verbose comment, because otherwise it could be
removed and continue passing almost all the time.
2022-02-26 03:39:02 +10:30
Matthew Draper
0b2772e675 Don't require encryption credentials when using a custom key provider
Instead of pre-validating the configuration, we now check for the
required values when they're used.

Co-authored-by: Alex Ghiculescu <alex@tanda.co>
Co-authored-by: Jorge Manrubia <jorge.manrubia@gmail.com>
Co-authored-by: John Hawthorn <john@hawthorn.email>
2022-02-24 20:03:01 +10:30
Dorian Marié
61ea3c286d Fix error when saving an association with a relation named record
ArgumentError: wrong number of arguments (given 3, expected 0)
    /Users/dorianmariefr/.rvm/rubies/ruby-3.0.2/lib/ruby/gems/3.0.0/gems/activerecord-7.0.1/lib/active_record/associations/builder/belongs_to.rb:132:in `record_changed?'
    /Users/dorianmariefr/.rvm/rubies/ruby-3.0.2/lib/ruby/gems/3.0.0/gems/activerecord-7.0.1/lib/active_record/autosave_association.rb:450:in `save_has_one_association'
2022-01-17 21:46:54 +01:00