Coming from Rails
ORM::ActiveRecord ports Active Record to Raku concept-for-concept. If you know Rails, most of what you know carries over once you adjust for two Raku rules about method names.
The two naming rules
Raku method names cannot end in ! or ?, and the ecosystem prefers hyphens to
underscores:
- A bang method drops the
!and gains a-bangsuffix:save!→save-bang. - A predicate drops the
?and gains anis-prefix:valid?→is-valid,persisted?→is-persisted,new_record?→is-new-record. - Everything else swaps
_for-:find_by→find-by,has_many→has-many,update_all→update-all.
That is most of the translation. The tables below cover the rest.
Models and tables
| Rails | ORM::ActiveRecord |
|---|---|
class User < ApplicationRecord |
class User is Model { } |
self.table_name = 'people' |
method table-name { 'people' } |
belongs_to :author |
self.belongs-to: author => class-name => 'Author' |
has_many :posts |
self.has-many: posts => class-name => 'Post' |
has_one :profile |
self.has-one: profile => class-name => 'Profile' |
has_and_belongs_to_many :tags |
self.has-and-belongs-to-many: tags => class-name => 'Tag' |
has_many :through |
self.has-many: magazines => %(through => :subscriptions, ...) |
Associations and validations are declared in submethod BUILD. See
Models and the association cookbook.
Creating and updating
| Rails | ORM::ActiveRecord |
|---|---|
User.create(attrs) |
User.create({ ... }) |
User.create!(attrs) |
User.create-bang({ ... }) |
user.save / user.save! |
user.save / user.save-bang |
user.update(attrs) / update! |
user.update({ ... }) / update-bang |
user.destroy / User.destroy_all |
user.destroy / User.destroy-all |
user.persisted? / new_record? |
user.is-persisted / user.is-new-record |
user.reload |
user.reload |
user.touch |
user.touch |
See Persistence.
Querying
| Rails | ORM::ActiveRecord |
|---|---|
User.find(id) |
User.find(id) |
User.find_by(name: 'x') |
User.find-by({ name => 'x' }) |
User.find_by!(...) |
User.find-by-bang({ ... }) |
User.where(active: true) |
User.where({ active => True }) |
.where.not(...) |
.not({ ... }) |
.order(:name) |
.order('name') |
.limit(10).offset(20) |
.limit(10).offset(20) |
.first / .last |
.first / .last |
.count / .sum(:col) |
.count / .sum('col') |
.pluck(:name) |
.pluck('name') |
.exists? |
.exists |
.update_all(...) / .delete_all |
.update-all(...) / .delete-all |
A relation is lazy; it runs when you call .all / .perform, iterate it, or
ask for a count. See Finders and Queries.
Eager loading
| Rails | ORM::ActiveRecord |
|---|---|
.preload(:posts) |
.preload(:posts) |
.includes(:posts) |
.includes(:posts) |
.eager_load(:posts) |
.eager-load(:posts) |
.references(:posts) |
.references(:posts) |
.includes(posts: :comments) |
.preload(posts => :comments) |
See the eager-loading cookbook.
Validations
| Rails | ORM::ActiveRecord |
|---|---|
validates :name, presence: true |
self.validate: 'name', { :presence } |
validates :age, numericality: { ... } |
self.validate: 'age', { numericality => { ... } } |
validates_with MyValidator |
self.validates-with(MyValidator.new) |
validates_each :a, :b do ... end |
self.validates-each: <a b>, -> $rec, $attr, $value { ... } |
record.valid? / invalid? |
record.is-valid / record.is-invalid |
record.errors.full_messages |
record.errors.full-messages |
See Validations and the custom-validator cookbook.
Callbacks
| Rails | ORM::ActiveRecord |
|---|---|
before_save :m |
self.before-save: -> { ... } or self.before-save: 'method-name' |
after_create, after_update, ... |
self.after-create, self.after-update, ... |
after_commit, after_rollback |
self.after-commit, self.after-rollback |
See Callbacks.
Transactions
| Rails | ORM::ActiveRecord |
|---|---|
ActiveRecord::Base.transaction { } |
DB.shared.transaction({ }) |
Model.transaction { } |
Model.transaction({ }) |
requires_new: true |
:requires-new |
raise ActiveRecord::Rollback |
die X::Rollback.new |
isolation: :read_committed |
:isolation<read_committed> |
See the transactions cookbook.
Higher-level features
| Rails | ORM::ActiveRecord |
|---|---|
enum status: { ... } |
self.enum: 'status', { ... } (Enums) |
serialize :prefs, JSON |
self.serialize('prefs', JsonCoder.new) (Attribute types) |
normalizes :email, with: ... |
self.normalizes('email', :with(-> $v { ... })) (Normalisation) |
encrypts :ssn |
self.encrypts('ssn') (Encryption) |
has_secure_password |
self.has-secure-password (Tokens & secure data) |
STI type column |
Single-table inheritance |
| discard gem | Soft deletes |
Tooling
| Rails | ORM::ActiveRecord |
|---|---|
rails db:migrate |
active-record db:migrate |
rails db:rollback |
active-record db:rollback |
rails db:seed |
active-record db:seed |
rails g model User name:string |
active-record generate model User name:string |
rails console / runner |
active-record console / active-record runner |
rails dbconsole |
active-record dbconsole |
See Database tasks, Generators, and Runtime tasks.
What's different
- Hashes are passed explicitly:
User.where({ active => True }), not bareword keyword arguments. True/False/Nilreplacetrue/false/nil.- A model class binds itself into
GLOBAL(GLOBAL::<User> := User;) so that associations resolve by class name — the generators emit this for you. - There is no autoloading:
usethe modules and models you reference.