Permit checking whether a belongs_to association has been pointed to a new target record in the previous save and whether it will point to a new target record in the next save. post.category # => #<Category id: 1, name: "Ruby"> post.category = Category.second # => #<Category id: 2, name: "Programming"> post.category_changed? # => true post.category_previously_changed? # => false post.save! post.category_changed? # => false post.category_previously_changed? # => true
30 KiB
-
Two change tracking methods are added for
belongs_to
associations.The
association_changed?
method (assuming an association named:association
) returns true if a different associated object has been assigned and the foreign key will be updated in the next save.The
association_previously_changed?
method returns true if the previous save updated the association to reference a different associated object.George Claghorn
-
Add option to disable schema dump per-database
Dumping the schema is on by default for all databases in an application. To turn it off for a specific database use the
schema_dump
option:# config/database.yml production: schema_dump: false
Luis Vasconcellos, Eileen M. Uchitelle
-
Fix
eager_loading?
when ordering withHash
syntaxeager_loading?
is triggered correctly when usingorder
with hash syntax on an outer table.Post.includes(:comments).order({ "comments.label": :ASC }).eager_loading? => true
Jacopo Beschi
-
Move the forcing of clear text encoding to the
ActiveRecord::Encryption::Encryptor
.Fixes #42699.
J Smith
-
partial_inserts
is now disabled by default in new apps.This will be the default for new apps in Rails 7. To opt in:
config.active_record.partial_inserts = false
If a migration remove the default value of a column, this option would cause old processes to no longer be able to create new records.
If you need to remove a column, you should first use
ignored_columns
to stop using it.Jean Boussier
-
Rails can now verify foreign keys after loading fixtures in tests.
This will be the default for new apps in Rails 7. To opt in:
config.active_record.verify_foreign_keys_for_fixtures = true
Tests will not run if there is a foreign key constraint violation in your fixture data.
The feature is supported by SQLite and PostgreSQL, other adapters can also add support for it.
Alex Ghiculescu
-
Clear cached
has_one
association after settingbelongs_to
association tonil
.After setting a
belongs_to
relation tonil
and updating an unrelated attribute on the owner, the owner should still returnnil
on thehas_one
relation.Fixes #42597.
Michiel de Mare
-
OpenSSL constants are now used for Digest computations.
Dirkjan Bussink
-
Adds support for
if_not_exists
toadd_foreign_key
andif_exists
toremove_foreign_key
.Applications can set their migrations to ignore exceptions raised when adding a foreign key that already exists or when removing a foreign key that does not exist.
Example Usage:
class AddAuthorsForeignKeyToArticles < ActiveRecord::Migration[7.0] def change add_foreign_key :articles, :authors, if_not_exists: true end end
class RemoveAuthorsForeignKeyFromArticles < ActiveRecord::Migration[7.0] def change remove_foreign_key :articles, :authors, if_exists: true end end
Roberto Miranda
-
Prevent polluting ENV during postgresql structure dump/load
Some configuration parameters were provided to pg_dump / psql via environment variables which persisted beyond the command being run, and may have caused subsequent commands and connections to fail. Tasks running across multiple postgresql databases like
rails db:test:prepare
may have been affected.Samuel Cochran
-
Set precision 6 by default for
datetime
columnsBy default, datetime columns will have microseconds precision instead of seconds precision.
Roberto Miranda
-
Allow preloading of associations with instance dependent scopes
John Hawthorn, John Crepezzi, Adam Hess, Eileen M. Uchitelle, Dinah Shi
-
Do not try to rollback transactions that failed due to a
ActiveRecord::TransactionRollbackError
.Jamie McCarthy
-
Active Record Encryption will now encode values as UTF-8 when using deterministic encryption. The encoding is part of the encrypted payload, so different encodings for different values result in different ciphertexts. This can break unique constraints and queries.
The new behavior is configurable via
active_record.encryption.forced_encoding_for_deterministic_encryption
that isEncoding::UTF_8
by default. It can be disabled by setting it tonil
.Jorge Manrubia
-
The MySQL adapter now cast numbers and booleans bind parameters to string for safety reasons.
When comparing a string and a number in a query, MySQL converts the string to a number. So for instance
"foo" = 0
, will implicitly cast"foo"
to0
and will evaluate toTRUE
which can lead to security vulnerabilities.Active Record already protect against that vulnerability when it knows the type of the column being compared, however until now it was still vulnerable when using bind parameters:
User.where("login_token = ?", 0).first
Would perform:
SELECT * FROM `users` WHERE `login_token` = 0 LIMIT 1;
Now it will perform:
SELECT * FROM `users` WHERE `login_token` = '0' LIMIT 1;
Jean Boussier
-
Fixture configurations (
_fixture
) are now strictly validated.If an error will be raised if that entry contains unknown keys while previously it would silently have no effects.
Jean Boussier
-
Add
ActiveRecord::Base.update!
that works likeActiveRecord::Base.update
but raises exceptions.This allows for the same behavior as the instance method
#update!
at a class level.Person.update!(:all, state: "confirmed")
Dorian Marié
-
Add
ActiveRecord::Base#attributes_for_database
Returns attributes with values for assignment to the database.
Chris Salzberg
-
Use an empty query to check if the PostgreSQL connection is still active
An empty query is faster than
SELECT 1
.Heinrich Lee Yu
-
Add
ActiveRecord::Base#previously_persisted?
Returns
true
if the object has been previously persisted but now it has been deleted. -
Deprecate
partial_writes
in favor ofpartial_inserts
andpartial_updates
.This allows to have a different behavior on update and create.
Jean Boussier
-
Fix compatibility with
psych >= 4
.Starting in Psych 4.0.0
YAML.load
behaves likeYAML.safe_load
. To preserve compatibility Active Record's schema cache loader andYAMLColumn
now usesYAML.unsafe_load
if available.Jean Boussier
-
ActiveRecord::Base.logger
is now aclass_attribute
.This means it can no longer be accessed directly through
@@logger
, and that settinglogger =
on a subclass won't change the parent's logger.Jean Boussier
-
Add
.asc.nulls_first
for all databases. Unfortunately MySQL still doesn't likenulls_last
.Keenan Brock
-
Improve performance of
one?
andmany?
by limiting the generated count query to 2 results.Gonzalo Riestra
-
Don't check type when using
if_not_exists
onadd_column
.Previously, if a migration called
add_column
with theif_not_exists
option set to true thecolumn_exists?
check would look for a column with the same name and type as the migration.Recently it was discovered that the type passed to the migration is not always the same type as the column after migration. For example a column set to
:mediumblob
in the migration will be casted tobinary
when callingcolumn.type
. Since there is no straightforward way to cast the type to the database type without running the migration, we opted to drop the type check fromadd_column
. This means that migrations adding a duplicate column with a different type will no longer raise an error.Eileen M. Uchitelle
-
Log a warning message when running SQLite in production
Using SQLite in production ENV is generally discouraged. SQLite is also the default adapter in a new Rails application. For the above reasons log a warning message when running SQLite in production.
The warning can be disabled by setting
config.active_record.sqlite3_production_warning=false
.Jacopo Beschi
-
Add option to disable joins for
has_one
associations.In a multiple database application, associations can't join across databases. When set, this option instructs Rails to generate 2 or more queries rather than generating joins for
has_one
associations.Set the option on a has one through association:
class Person belongs_to :dog has_one :veterinarian, through: :dog, disable_joins: true end
Then instead of generating join SQL, two queries are used for
@person.veterinarian
:SELECT "dogs"."id" FROM "dogs" WHERE "dogs"."person_id" = ? [["person_id", 1]] SELECT "veterinarians".* FROM "veterinarians" WHERE "veterinarians"."dog_id" = ? [["dog_id", 1]]
Sarah Vessels, Eileen M. Uchitelle
-
Arel::Visitors::Dot
now renders a complete set of properties when visitingArel::Nodes::SelectCore
,SelectStatement
,InsertStatement
,UpdateStatement
, andDeleteStatement
, which fixes #42026. Previously, some properties were omitted.Mike Dalessio
-
Arel::Visitors::Dot
now supportsArel::Nodes::Bin
,Case
,CurrentRow
,Distinct
,DistinctOn
,Else
,Except
,InfixOperation
,Intersect
,Lock
,NotRegexp
,Quoted
,Regexp
,UnaryOperation
,Union
,UnionAll
,When
, andWith
. Previously, these node types caused an exception to be raised byArel::Visitors::Dot#accept
.Mike Dalessio
-
Optimize
remove_columns
to use a single SQL statement.remove_columns :my_table, :col_one, :col_two
Now results in the following SQL:
ALTER TABLE "my_table" DROP COLUMN "col_one", DROP COLUMN "col_two"
Jon Dufresne
-
Ensure
has_one
autosave association callbacks get called once.Change the
has_one
autosave callback to be non cyclic as well. By doing this the autosave callback are made more consistent for all 3 cases:has_many
,has_one
, andbelongs_to
.Petrik de Heus
-
Add option to disable joins for associations.
In a multiple database application, associations can't join across databases. When set, this option instructs Rails to generate 2 or more queries rather than generating joins for associations.
Set the option on a has many through association:
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]]
Eileen M. Uchitelle, Aaron Patterson, Lee Quarella
-
Add setting for enumerating column names in SELECT statements.
Adding a column to a PostgreSQL database, for example, while the application is running can change the result of wildcard
SELECT *
queries, which invalidates the result of cached prepared statements and raises aPreparedStatementCacheExpired
error.When enabled, Active Record will avoid wildcards and always include column names in
SELECT
queries, which will return consistent results and avoid prepared statement errors.Before:
Book.limit(5) # SELECT * FROM books LIMIT 5
After:
# config/application.rb module MyApp class Application < Rails::Application config.active_record.enumerate_columns_in_select_statements = true end end # or, configure per-model class Book < ApplicationRecord self.enumerate_columns_in_select_statements = true end
Book.limit(5) # SELECT id, author_id, name, format, status, language, etc FROM books LIMIT 5
Matt Duszynski
-
Allow passing SQL as
on_duplicate
value to#upsert_all
to make it possible to use raw SQL to update columns on conflict:Book.upsert_all( [{ id: 1, status: 1 }, { id: 2, status: 1 }], on_duplicate: Arel.sql("status = GREATEST(books.status, EXCLUDED.status)") )
Vladimir Dementyev
-
Allow passing SQL as
returning
statement to#upsert_all
:Article.insert_all( [ { title: "Article 1", slug: "article-1", published: false }, { title: "Article 2", slug: "article-2", published: false } ], returning: Arel.sql("id, (xmax = '0') as inserted, name as new_name") )
Vladimir Dementyev
-
Deprecate
legacy_connection_handling
.Eileen M. Uchitelle
-
Add attribute encryption support.
Encrypted attributes are declared at the model level. These are regular Active Record attributes backed by a column with the same name. The system will transparently encrypt these attributes before saving them into the database and will decrypt them when retrieving their values.
class Person < ApplicationRecord encrypts :name encrypts :email_address, deterministic: true end
You can learn more in the Active Record Encryption guide.
Jorge Manrubia
-
Changed Arel predications
contains
andoverlaps
to usequoted_node
so that PostgreSQL arrays are quoted properly.Bradley Priest
-
Add mode argument to record level
strict_loading!
This argument can be used when enabling strict loading for a single record to specify that we only want to raise on n plus one queries.
developer.strict_loading!(mode: :n_plus_one_only) developer.projects.to_a # Does not raise developer.projects.first.client # Raises StrictLoadingViolationError
Previously, enabling strict loading would cause any lazily loaded association to raise an error. Using
n_plus_one_only
mode allows us to lazily load belongs_to, has_many, and other associations that are fetched through a single query.Dinah Shi
-
Fix Float::INFINITY assignment to datetime column with postgresql adapter
Before:
# With this config ActiveRecord::Base.time_zone_aware_attributes = true # and the following schema: create_table "postgresql_infinities" do |t| t.datetime "datetime" end # This test fails record = PostgresqlInfinity.create!(datetime: Float::INFINITY) assert_equal Float::INFINITY, record.datetime # record.datetime gets nil
After this commit,
record.datetime
getsFloat::INFINITY
as expected.Shunichi Ikegami
-
Type cast enum values by the original attribute type.
The notable thing about this change is that unknown labels will no longer match 0 on MySQL.
class Book < ActiveRecord::Base enum :status, { proposed: 0, written: 1, published: 2 } end
Before:
# SELECT `books`.* FROM `books` WHERE `books`.`status` = 'prohibited' LIMIT 1 Book.find_by(status: :prohibited) # => #<Book id: 1, status: "proposed", ...> (for mysql2 adapter) # => ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR: invalid input syntax for type integer: "prohibited" (for postgresql adapter) # => nil (for sqlite3 adapter)
After:
# SELECT `books`.* FROM `books` WHERE `books`.`status` IS NULL LIMIT 1 Book.find_by(status: :prohibited) # => nil (for all adapters)
Ryuta Kamizono
-
Fixtures for
has_many :through
associations now load timestamps on join tablesGiven this fixture:
### monkeys.yml george: name: George the Monkey fruits: apple ### fruits.yml apple: name: apple
If the join table (
fruit_monkeys
) containscreated_at
orupdated_at
columns, these will now be populated when loading the fixture. Previously, fixture loading would crash if these columns were required, and leave them as null otherwise.Alex Ghiculescu
-
Allow applications to configure the thread pool for async queries
Some applications may want one thread pool per database whereas others want to use a single global thread pool for all queries. By default, Rails will set
async_query_executor
tonil
which will not initialize any executor. Ifload_async
is called and no executor has been configured, the query will be executed in the foreground.To create one thread pool for all database connections to use applications can set
config.active_record.async_query_executor
to:global_thread_pool
and optionally defineconfig.active_record.global_executor_concurrency
. This defaults to 4. For applications that want to have a thread pool for each database connection,config.active_record.async_query_executor
can be set to:multi_thread_pool
. The configuration for each thread pool is set in the database configuration.Eileen M. Uchitelle
-
Allow new syntax for
enum
to avoid leading_
from reserved options.Before:
class Book < ActiveRecord::Base enum status: [ :proposed, :written ], _prefix: true, _scopes: false enum cover: [ :hard, :soft ], _suffix: true, _default: :hard end
After:
class Book < ActiveRecord::Base enum :status, [ :proposed, :written ], prefix: true, scopes: false enum :cover, [ :hard, :soft ], suffix: true, default: :hard end
Ryuta Kamizono
-
Add
ActiveRecord::Relation#load_async
.This method schedules the query to be performed asynchronously from a thread pool.
If the result is accessed before a background thread had the opportunity to perform the query, it will be performed in the foreground.
This is useful for queries that can be performed long enough before their result will be needed, or for controllers which need to perform several independent queries.
def index @categories = Category.some_complex_scope.load_async @posts = Post.some_complex_scope.load_async end
Active Record logs will also include timing info for the duration of how long the main thread had to wait to access the result. This timing is useful to know whether or not it's worth to load the query asynchronously.
DEBUG -- : Category Load (62.1ms) SELECT * FROM `categories` LIMIT 50 DEBUG -- : ASYNC Post Load (64ms) (db time 126.1ms) SELECT * FROM `posts` LIMIT 100
The duration in the first set of parens is how long the main thread was blocked waiting for the results, and the second set of parens with "db time" is how long the entire query took to execute.
Jean Boussier
-
Implemented
ActiveRecord::Relation#excluding
method.This method excludes the specified record (or collection of records) from the resulting relation:
Post.excluding(post) Post.excluding(post_one, post_two)
Also works on associations:
post.comments.excluding(comment) post.comments.excluding(comment_one, comment_two)
This is short-hand for
Post.where.not(id: post.id)
(for a single record) andPost.where.not(id: [post_one.id, post_two.id])
(for a collection).Glen Crawford
-
Skip optimised #exist? query when #include? is called on a relation with a having clause
Relations that have aliased select values AND a having clause that references an aliased select value would generate an error when #include? was called, due to an optimisation that would generate call #exists? on the relation instead, which effectively alters the select values of the query (and thus removes the aliased select values), but leaves the having clause intact. Because the having clause is then referencing an aliased column that is no longer present in the simplified query, an ActiveRecord::InvalidStatement error was raised.
A sample query affected by this problem:
Author.select('COUNT(*) as total_posts', 'authors.*') .joins(:posts) .group(:id) .having('total_posts > 2') .include?(Author.first)
This change adds an addition check to the condition that skips the simplified #exists? query, which simply checks for the presence of a having clause.
Fixes #41417
Michael Smart
-
Increment postgres prepared statement counter before making a prepared statement, so if the statement is aborted without Rails knowledge (e.g., if app gets killed during long-running query or due to Rack::Timeout), app won't end up in perpetual crash state for being inconsistent with Postgres.
wbharding, Martin Tepper
-
Add ability to apply
scoping
toall_queries
.Some applications may want to use the
scoping
method but previously it only worked on certain types of queries. This change allows thescoping
method to apply to all queries for a model in a block.Post.where(blog_id: post.blog_id).scoping(all_queries: true) do post.update(title: "a post title") # adds `posts.blog_id = 1` to the query end
Eileen M. Uchitelle
-
ActiveRecord::Calculations.calculate
called with:average
(aliased asActiveRecord::Calculations.average
) will now use column-based type casting. This means that floating-point number columns will now be aggregated asFloat
and decimal columns will be aggregated asBigDecimal
.Integers are handled as a special case returning
BigDecimal
always (this was the case before already).# With the following schema: create_table "measurements" do |t| t.float "temperature" end # Before: Measurement.average(:temperature).class # => BigDecimal # After: Measurement.average(:temperature).class # => Float
Before this change, Rails just called
to_d
on average aggregates from the database adapter. This is not the case anymore. If you relied on that kind of magic, you now need to register your ownActiveRecord::Type
(seeActiveRecord::Attributes::ClassMethods
for documentation).Josua Schmid
-
PostgreSQL: introduce
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type
This setting controls what native type Active Record should use when you call
datetime
in a migration or schema. It takes a symbol which must correspond to one of the configuredNATIVE_DATABASE_TYPES
. The default is:timestamp
, meaningt.datetime
in a migration will create a "timestamp without time zone" column. To use "timestamp with time zone", change this to:timestamptz
in an initializer.You should run
bin/rails db:migrate
to rebuild your schema.rb if you change this.Alex Ghiculescu
-
PostgreSQL: handle
timestamp with time zone
columns correctly inschema.rb
.Previously they dumped as
t.datetime :column_name
, now they dump ast.timestamptz :column_name
, and are created astimestamptz
columns when the schema is loaded.Alex Ghiculescu
-
Removing trailing whitespace when matching columns in
ActiveRecord::Sanitization.disallow_raw_sql!
.Gannon McGibbon, Adrian Hirt
-
Expose a way for applications to set a
primary_abstract_class
Multiple database applications that use a primary abstract class that is not named
ApplicationRecord
can now set a specific class to be theprimary_abstract_class
.class PrimaryApplicationRecord self.primary_abstract_class end
When an application boots it automatically connects to the primary or first database in the database configuration file. In a multiple database application that then call
connects_to
needs to know that the default connection is the same as theApplicationRecord
connection. However, some applications have a differently namedApplicationRecord
. This prevents Active Record from opening duplicate connections to the same database.Eileen M. Uchitelle, John Crepezzi
-
Support hash config for
structure_dump_flags
andstructure_load_flags
flags Now that Active Record supports multiple databases configuration we need a way to pass specific flags for dump/load databases since the options are not the same for different adapters. We can use in the original way:ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = ['--no-defaults', '--skip-add-drop-table'] #or ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = '--no-defaults --skip-add-drop-table'
And also use it passing a hash, with one or more keys, where the key is the adapter
ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = { mysql2: ['--no-defaults', '--skip-add-drop-table'], postgres: '--no-tablespaces' }
Gustavo Gonzalez
-
Connection specification now passes the "url" key as a configuration for the adapter if the "url" protocol is "jdbc", "http", or "https". Previously only urls with the "jdbc" prefix were passed to the Active Record Adapter, others are assumed to be adapter specification urls.
Fixes #41137.
Jonathan Bracy
-
Allow to opt-out of
strict_loading
mode on a per-record base.This is useful when strict loading is enabled application wide or on a model level.
class User < ApplicationRecord has_many :bookmarks has_many :articles, strict_loading: true end user = User.first user.articles # => ActiveRecord::StrictLoadingViolationError user.bookmarks # => #<ActiveRecord::Associations::CollectionProxy> user.strict_loading!(true) # => true user.bookmarks # => ActiveRecord::StrictLoadingViolationError user.strict_loading!(false) # => false user.bookmarks # => #<ActiveRecord::Associations::CollectionProxy> user.articles.strict_loading!(false) # => #<ActiveRecord::Associations::CollectionProxy>
Ayrton De Craene
-
Add
FinderMethods#sole
and#find_sole_by
to find and assert the presence of exactly one record.Used when you need a single row, but also want to assert that there aren't multiple rows matching the condition; especially for when database constraints aren't enough or are impractical.
Product.where(["price = %?", price]).sole # => ActiveRecord::RecordNotFound (if no Product with given price) # => #<Product ...> (if one Product with given price) # => ActiveRecord::SoleRecordExceeded (if more than one Product with given price) user.api_keys.find_sole_by(key: key) # as above
Asherah Connor
-
Makes
ActiveRecord::AttributeMethods::Query
respect the getter overrides defined in the model.Before:
class User def admin false # Overriding the getter to always return false end end user = User.first user.update(admin: true) user.admin # false (as expected, due to the getter overwrite) user.admin? # true (not expected, returned the DB column value)
After this commit,
user.admin?
above returns false, as expected.Fixes #40771.
Felipe
-
Allow delegated_type to be specified primary_key and foreign_key.
Since delegated_type assumes that the foreign_key ends with
_id
,singular_id
defined by it does not work when the foreign_key does not end withid
. This change fixes it by taking into accountprimary_key
andforeign_key
in the options.Ryota Egusa
-
Expose an
invert_where
method that will invert all scope conditions.class User scope :active, -> { where(accepted: true, locked: false) } end User.active # ... WHERE `accepted` = 1 AND `locked` = 0 User.active.invert_where # ... WHERE NOT (`accepted` = 1 AND `locked` = 0)
Kevin Deisz
-
Restore possibility of passing
false
to :polymorphic option ofbelongs_to
.Previously, passing
false
would trigger the option validation logic to throw an error saying :polymorphic would not be a valid option.glaszig
-
Remove deprecated
database
kwarg fromconnected_to
.Eileen M. Uchitelle, John Crepezzi
-
Allow adding nonnamed expression indexes to be revertible.
Fixes #40732.
Previously, the following code would raise an error, when executed while rolling back, and the index name should be specified explicitly. Now, the index name is inferred automatically.
add_index(:items, "to_tsvector('english', description)")
fatkodima
-
Only warn about negative enums if a positive form that would cause conflicts exists.
Fixes #39065.
Alex Ghiculescu
-
Add option to run
default_scope
on all queries.Previously, a
default_scope
would only run on select or insert queries. In some cases, like non-Rails tenant sharding solutions, it may be desirable to rundefault_scope
on all queries in order to ensure queries are including a foreign key for the shard (i.e.blog_id
).Now applications can add an option to run on all queries including select, insert, delete, and update by adding an
all_queries
option to the default scope definition.class Article < ApplicationRecord default_scope -> { where(blog_id: Current.blog.id) }, all_queries: true end
Eileen M. Uchitelle
-
Add
where.associated
to check for the presence of an association.# Before: account.users.joins(:contact).where.not(contact_id: nil) # After: account.users.where.associated(:contact)
Also mirrors
where.missing
.Kasper Timm Hansen
-
Allow constructors (
build_association
andcreate_association
) onhas_one :through
associations.Santiago Perez Perret
Please check 6-1-stable for previous changes.