4

New Rails 7 features, Before and After · GitHub

 1 year ago
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) %>


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK