New Rails 7 features, Before and After · GitHub
source link: https://gist.github.com/zakariaf/534ff8dfc3a807779133dc078114b969
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rails 7 new features. Before and After
Add ComparisonValidator
Before Rails 7
class Event < ApplicationRecord
validates :start_date, presence: true
validates :end_date, presence: true
validate :end_date_is_after_start_date
private
def end_date_is_after_start_date
if end_date < start_date
errors.add(:end_date, 'cannot be before the start date')
end
end
end
After Rails 7
class Event < ApplicationRecord
validates :start_date, presence: true
validates :end_date, presence: true
validates_comparison_of :end_date, greater_than: :start_date
end
more details: https://blog.kiprosh.com/rails7-activerecord-comparison-validator/
PostgreSQL generated columns
Before
One of the options was using callbacks:
# == Schema Information
#
# Table name: prders
#
# id :bigint
# price :decimal, precision: 8, scale: 2
# tax :decimal, precision: 8, scale: 2
# total :decimal, precision: 8, scale: 2
# created_at :datetime
# updated_at :datetime
class Order < ApplicationRecord
before_save :calculate_total
private
def calculate_total
self[:total] = price + tax
end
end
Result:
order = Order.create!(price: 12, tax: 1)
order.total => 13
After
You just need to use virtual
and all will be done automatically by postgres
create_table :orders, force: true do |t|
t.decimal :price, precision: 8, scale: 2
t.decimal :tax, precision: 8, scale: 2
t.virtual :total, type: :decimal, as: 'price + tax', stored: true
end
Result: You need to reload data to get the calculated value form the DB
order = Order.create!(price: 12, tax: 1)
order.total => nil
order.reload
order.total => 13
More details: https://tejasbubane.github.io/posts/2021-12-18-rails-7-postgres-generated-columns/
PostgreSQL custom enum types
Before
def up
execute <<-SQL
CREATE TYPE mood_status AS ENUM ('happy', 'sad');
SQL
add_column :cats, :current_mood, :mood_status
end
And we had to set config.active_record.schema_format = :sql
to use structure.sql
instead of schema.rb
After
In migrations, use create_enum
to add a new enum type, and t.enum
to add a column.
def up
create_enum :mood, ["happy", "sad"]
change_table :cats do |t|
t.enum :current_mood, enum_type: "mood", default: "happy", null: false
end
end
Enums will be presented correctly in schema.rb
, means no need to switch to structure.sql
anymore :D
Tutorial for Rails < 7: https://medium.com/@diegocasmo/using-postgres-enum-type-in-rails-799db99117ff
Add tracking of belongs_to
association
class Event
belongs_to :organizer
end
class Organizer
has_many :events
end
association_changed?
method
The
association_changed?
method tells if a different associated object has been assigned and the foreign key will be updated in the next save.
Before
Tracking the target of a belongs_to
association was able by checking its foreign key.
class Event
belongs_to :organizer
before_save :track_change
private
def track_change
if organizer_id_changed?
#track something
end
end
end
After
It's doable by using association_changed?
method
class Event
belongs_to :organizer
before_save :track_change
private
def track_change
if organizer_changed?
#track something
end
end
end
association_previously_changed?
method
The
association_previously_changed?
method tells if the previous save updated the association to reference a different associated object.
> event.organizer
=> #<Organizer id: 1, name: "Organization 1">
> event.organizer = Organizer.second
=> #<Organizer id: 2, name: "Organization 2">
> event.organizer_changed?
=> true
> event.organizer_previously_changed?
=> false
> event.save!
=> true
> event.organizer_changed?
=> false
> event.organizer_previously_changed?
=> true
More details: https://blog.kiprosh.com/rails-7-supports-tracking-of-belongs_to-association/
Add invert_where
method
Allows you to invert an entire where clause instead of manually applying conditions.
class User
scope :active, -> { where(accepted: true, locked: false) }
end
Before
active_users = User.active
inactive_users = User.where.not(id: User.active.ids)
After
active_users = User.active
inactive_users = User.active.invert_where
Add associated
method
It returns the list of all records that have an association
Before
User.where.not(contact_id: nil)
After
User.where.associated(:contact)
more examples: https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods/WhereChain.html#method-i-associated
Add missing
method
It returns the list of all records that don't have an association. opposite of associated
Before
User.where(contact_id: nil)
After
User.where.missing(:contact)
more examples: https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods/WhereChain.html#method-i-missing
Active Record Encryption
without encryption
> Post.create(title: 'Rails 7')
INSERT INTO "posts" ("title") VALUES (?) [["title", "Rails 7"]]
Encryption Before
We had to write a lot of extra codes, and use a gem (e.g. https://github.com/attr-encrypted/attr_encrypted) or play with ActiveSupport::MessageEncryptor
(tutorial here: https://pawelurbanek.com/rails-secure-encrypt-decrypt)
After
class Post < ApplicationRecord
encrypts :title
end
> Post.create(title: 'Rails 7')
INSERT INTO `posts` (`title`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')
Querying non-deterministically encrypted data is impossible:
> Post.find_by title: 'Rails 7'
# => nil
If you want to directly query an encrypted column attribute, you'd need to use the deterministic approach. For this, simply use the deterministic: true option during declaration.
class Post < ApplicationRecord
encrypts :title, deterministic: true
end
> Post.create(title: 'Rails 7')
INSERT INTO `posts` (`title`) VALUES ('{\"p\":\"n7J0/ol+a7DRMeaE\",\"h\":{\"iv\":\"DXZMDWUKfp3bg/Yu\",\"at\":\"X1/YjMHbHD4talgF9dt61A==\"}}')
> Post.find_by title: 'Rails 7'
# => <Post:0x00 id: 1, title: "Rails 7"...>
Disable partial_inserts as default
# == Schema Information
#
# Table name: posts
#
# id :bigint
# title :string
# description :text
# created_at :datetime
# updated_at :datetime
class Post < ApplicationRecord
end
Before
It's enabled as default
Rails.configuration.active_record.partial_inserts => true
The INSERT
command does not include description
as we are just passing title
to the Post.new
command
> Post.new(title: 'Rails 7').save
Post Create (1.7ms) INSERT INTO "posts" ("title", "created_at", "updated_at") VALUES (?, ?, ?) [["title", "Rails 7"], ["created_at", "2021-12-25 20:31:01.420712"], ["updated_at", "2021-12-25 20:31:01.420712"]]
After
It's disabled as default
Rails.configuration.active_record.partial_inserts => false
The INSERT
command includes description
too, even when we don't pass description
to the Post.new
command
> Post.new(title: 'Rails 7').save
Post Create (1.7ms) INSERT INTO "posts" ("title", "description", "created_at", "updated_at") VALUES (?, ?, ?) [["title", "Rails 7"], ["description", ""], ["created_at", "2021-12-25 20:31:01.420712"], ["updated_at", "2021-12-25 20:31:01.420712"]]
More details: https://blog.kiprosh.com/rails-7-introduces-partial-inserts-config-for-activerecord/
Active storage pre-defined variants
Before
class Puppy < ApplicationRecord
has_one_attached :photo
end
<%= image_tag puppy.photo.variant(resize_to_fill: [250, 250]) %>
After
class Puppy < ApplicationRecord
has_one_attached :photo do |attachable|
attachable.variant :thumb, resize: "100x100"
attachable.variant :medium, resize: "300x300", monochrome: true
end
end
<%= image_tag puppy.photo.variant(:thumb) %>
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK