10 minute read

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

image image image


fit_2048


Ruby Conf AU - Sydney2024

image


image


Demystifying Rails


Why

  1. Understand where Ruby ends and Rails starts
  2. Make architectural decisions and when/how to use RoR
  3. Break RoR conventions, go outside MVC

image 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? image


[{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

sauce


ActiveJob



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

  1. free Rake course at ‘graceful.dev’ by Avdi Grim
  2. rubyonrails guides
  3. Ruby on Rulers by Noah Gibbs
  4. rebuilding Rails by Noah Gibbs
  5. RubyConfTH 2022 - Dissecting Rails Talk
  6. My demo sandbox

Rails new commands

rails new app_name \
--database=sqlite3 \
--css=tailwind \
--skip-test

Kamal

kamal init

Leave a comment