Rails 7.0 was released almost a year ago (december 2021), so a new Rails release might be just around the corner. Time to look into the CHANGELOG.md of the various Core-Gems. The following features are just a small selection by me of all the Changelog.md’s. There are tons and tons of fixes all around, but like all huge frameworks, not all parts are used by everyone.
I just have been downloading the CHANGELOG.md of the various projects:
%w[actionmailer actioncable activemodel actionpack actionmailbox actiontext actionview activejob activerecord activestorage activesupport railties].each do |gem|
url = "https://raw.githubusercontent.com/rails/rails/main/#{gem}/CHANGELOG.md"
end
Methodology of getting all the changes.
ActiveRecord, ActiveModel
has_secure_password password_challenge
has_secure_password
now supports password challenges via a password_challenge
accessor and validation.
Creates a virtual column password_challenge
and adds a validation that the password must match when updating the core model itself, IF it is not nil. E.g. User must provide “old” password when updating email/password.
Safes some boilerplate and integrates nicely in the core validation flow.
password_params = params.require(:user).permit(
:password_challenge, :password, :password_confirmation,
).with_defaults(password_challenge: "")
# Important: MUST not be nil to activate the validation
if current_user.update(password_params)
# ...
end
ActiveRecord::Base::generates_token_for
Automatically generate and validate various “tokens” for the user, think: Password Reset Token, Login Token etc.
class User < ActiveRecord::Base
has_secure_password
generates_token_for :password_reset, expires_in: 15.minutes do
# A password's BCrypt salt changes when the password is updated.
# By embedding (part of) the salt in a token, the token will
# expire when the password is updated.
BCrypt::Password.new(password_digest).salt[-10..]
end
end
user = User.first
token = user.generate_token_for(:password_reset)
User.find_by_token_for(:password_reset, token) # => user
user.update!(password: "new password")
User.find_by_token_for(:password_reset, token) # => nil
Infinity Ranges on on Validators:
validates_length_of :first_name, in: ..30
validates_inclusion_of :birth_date, in: -> { (..Date.today) }
Allow ActiveRecord::QueryMethods#select to receive hash values.
FINALLY, select can be used with hash syntax, too.
Post.joins(:comments).
select(posts: [:id, :title, :created_at], comments: [:id, :body, :author_id])
Post.joins(:comments).
# also with selection-aliases
select(posts: { id: :post_id, title: :post_title }, comments: { id: :comment_id, body: :comment_body })
find_or_create_by
now try to find a second time if it hits a unicity constraint.
Sound’s useful for some race conditions.
CTE Support
Sometimes, it’s nice to define “Common Table Expressions” which are supported by some databases, to clean up huge SQL. In the past one had to fallback to raw SQL for this, but now it is easier to do it with AREL:
Post.with(posts_with_comments: Post.where("comments_count > ?", 0))
# => ActiveRecord::Relation
# WITH posts_with_comments AS (SELECT * FROM posts WHERE (comments_count > 0)) SELECT * FROM posts
Ignore tables for schema-dump
Nice, if you have a shared database other (legacy?) tables that you don’t care about:
ActiveRecord::SchemaDumper.ignore_tables = [/^_/]
alias_attribute (not new, but TIL)
I read about a fixed bug in alias_attribute
, and thus found out about it the first time. Appareantly it is part of ActiveRecord for a long time, but I never stumbled upon it. I can sometimes think of aliasing old columns to better names, esp. when working with legacy schemata.
class Book < ApplicationRecord
alias_attribute :title, :name
end
User.authenticate_by
Instead of manually loading the user by mail and THEN validating the password:
User.find_by(email: "...")&.authenticate("...")
Use the new method, which is also supposed to be timing-Attack resistant:
User.authenticate_by(email: "...", password: "...")
Composite primary keys
Preliminary work has been merged, that allows Rails to better handle composite primary keys (on a later stage as noted in the PR)
class Developer < ActiveRecord::Base
query_constraints :company_id, :id
end
developer = Developer.first.update(name: "Bob")
# => UPDATE "developers" SET "name" = 'Bob' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
ActionPack View, Railties etc.
Trailing slashes in Routes
Force trailing slashes for various URL demands:
get '/test' => "test#index", as: :test, trailing_slash: true
test_path() # => "/test/"
Allow (ERB) templates to set strict locals
.
Define, which locales (not controller instance vars) are required by the template
<%# locals: (message:) -%>
<%= message %>
Default values can also be provided:
<%# locals: (message: "Hello, world!") -%>
<%= message %>
Maybe ok, to prevent typos in the variable names.
Find unused routes
rails routes --unused
Tries to find defined routes without a controller and missing action OR missing template (either is needed).
Grep routes in command
rails routes --grep
Not sure why this is needed on a UNIX/Linux system, but ok, if it is there.
Limit log size
config.log_file_size = 100.megabytes
No gigantic (5GB in some cases…) development.log or test.log anymore! Yay!
Rails.cache with options
ActiveSupport::Cache:Store#fetch` now passes an options accessor to the block.
It makes possible to override cache options:
Rails.cache.fetch("3rd-party-token") do |name, options|
token = fetch_token_from_remote
# set cache's TTL to match token's TTL
options.expires_in = token.expires_in
token
end