Demystifying Rails - Ruby Oceania / AU Melbourne talk
Action Plan
1. how to demystify *Ruby on Rails*
2. some demo code
3. parts forming *Ruby on Rails*:
- Bundler, Rake, Rack
- ActiveSupport, ActiveModel / ActiveRecord
- ActionMailer, ActionCable, ActionView, ActionController
- ActiveJob, Routes, DSLs, ...
About me
class Friendlyantz
def initialize
@name = 'Anton' || :friendlyantz
@title = 'Extreme Programmer'
@work = FRESHO🌶️
end
def find_more = 'https://friendlyantz.me/'
Thailand
Ruby Conf AU - Sydney2024
Demystifying Rails
Why
- Understand where Ruby ends and Rails starts
- Make architectural decisions and when/how to use RoR
- Break RoR conventions, go outside MVC
DSL?
https://www.youtube.com/watch?v=gXwRs-FwcmE
Active or Action?
Bundler
bundle exec [command]
bundle install
bundle init # create Gemfile
bundle add activesupport # it adds versions, so remove them if required
bundle remove [gemname]
Bundle more
bundle show # show all gems in Gemfile
bundle show [gemname] # show path
bundle info [gemname]
bundle open [gemname] # open gem in editor
bundle console # open irb with gems loaded
# [DEPRECATED] bundle console will be replaced by `bin/console` generated by `bundle gem <name>`
Rake
task default: :create_files
directory 'ship_it🚢'
task :touch => 'ship_it🚢' do
touch 'ship_it🚢/file1.txt'
end
# RTFM
ri FileUtils
Rack
# config.ru
run do |env|
[200, { "some_header" => "lalala"}, ["Hello World"]]
end
rackup
curl -i http://127.0.0.1:9292
Active Support
presence
/ blank?
/ present?
[{a:1, b: 2}, {a:2}].pluck(:b) # [3, nil]
1_234_567_890_123.to_fs(:human_size) # => 1.12 TB
{ a: 1, b: 1 }.merge(a: 0, c: 2) # {:a=>0, :b=>1, :c=>2}
" \n foo\n\r \t bar \n".squish # => "foo bar"
numbers.extract! { |number| number.odd? }
invoices.index_by(&:number)
[1,2,4].exclude? 3 # true
class Account
with_options dependent: :destroy do |assoc|
assoc.has_many :customers
assoc.has_many :products
assoc.has_many :invoices
assoc.has_many :expenses
end
# has_many :customers, dependent: :destroy
with_options on: :invoicing do
validates :something, presence: true
end
end
class User < ApplicationRecord
has_one :profile
delegate :name, to: :profile
end
ActiveModel
# similar to `ActiveRecord::Base`
include ActiveModel::API
include ActiveModel::Validations
extend ActiveModel::Translation # i18n gem integration
include ActiveModel::Conversion # .persisted? and `id` methods
extend ActiveModel::Callbacks # before_update :reset_me
include ActiveModel::AttributeMethods # define meta attributes
include ActiveModel::Dirty # model.changed?
include ActiveModel::Serialization
include ActiveModel::SecurePassword
ActiveRecord
ActiveRecord
.is_kind_of_an ActiveModel
.with DB persistence layer
# named after a **Active Record** design pattern:
"An object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data."
> Martin Fowler
require 'active_record'
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'sample.sqlite3'
)
class User < ActiveRecord::Base
validates_presence_of :name, on: :create
has_many :posts
end
ActiveRecord scripting can be used for data manipulation / relocation / sanitisation scripts
module ActiveRecord # add in spec_helper.rb, or other test setup files
class LogSubscriber < ActiveSupport::LogSubscriber
def sql(event)
if /FROM "some_table" WHERE "some_condition"/.match?(event.payload[:sql])
Rails.logger.info "SQL FOUND #{caller_locations[15...150]}"
binding.irb if ENV["QUERY_BINDING"]
end
end
end
end
ActiveRecord::LogSubscriber.attach_to :active_record
ActiveJob
- Sidekiq- Redis backed. does not need Rails!
- Good Job - PostgreSQL backed. needs Rails
- Resque - Redis-backed. does not need Rails!
- Solid Queue - new Rails OG. MySQL, PostgreSQL or SQLite backed. inspired by
Resque
andGoodJob
- Que
- Sneakers
- Sucker Punch
- Queue Classic
- Delayed Job
ActionMailer
require 'action_mailer'
class LalaMailer < ActionMailer::Base
def notify
attachments['roo.png'] = File.read('./roo.png')
mail(
to: '[email protected]',
from: '[email protected]',
subject: 'Hello, World!'
) do |format|
format.text { 'This is my text message' }
format.html { '<h1>this is my html message</h1>' }
end
end
end
LalaMailer.notify.deliver_now
- ActionCable
- ActionView
- Templating
- ActionController
- ActionPack
- ActiveStorage
- Routing -> try Hanami, Sinatra, etc
- Zeitwerk code loader for Ruby, used in Rails now
- brakeman - will be part of Rails 8 by default
run minimalistic rails
rails new myapp --minimal # 5 sec to generate, vs 17 sec normal (hot start)
rails new myapp --api
rails new myapp --skip-action-mailer # etc
rails new --help
Rails CLI
rails --help
rails generate --help
rails generate model --help
Conclusion
- try and build stuff in isolation:
- `Rake` scripts, `Rack`up server,
- `bundle init/add/console`
- use `Active Support` to extend Ruby for code challenges
- user `ActiveRecord + Rack` to build a basic app
- start simple and minimal, YAGNI, use minimal presets
rails new myapp --minimal
References
- free Rake course at ‘graceful.dev’ by Avdi Grim
- rubyonrails guides
- Ruby on Rulers by Noah Gibbs
- rebuilding Rails by Noah Gibbs
- RubyConfTH 2022 - Dissecting Rails Talk
- My demo sandbox
Rails new commands
rails new app_name \
--database=sqlite3 \
--css=tailwind \
--skip-test
Kamal
kamal init
Leave a comment