Revisiting Rails
Date: Apr 25, 2021
It's been a while since I used Ruby on Rails (RoR) framework. In fact, it's been a while since I used Ruby. The last time I can remember using Ruby was to create Logstash plugin.
Recently, a new platform similar to Heroku has been released: railway.app. It allows you deploy containerized apps as well as its stateful components such as database (Postgres or MySQL) and caching (Redis).
notion imagenotion image
The platform name (railway.app) is tempting me to deploy a rails app just so I can say "Rails on Rail". So I created a github repo and deployed it using railway.app at https://rails-on-rail-production.up.railway.app. The deployment process (rails v6.1.3 + postgres) is quite straightforward and took me around 10 minutes.
My "Rails on Rail" deployment. Choo Choo!My "Rails on Rail" deployment. Choo Choo!
My "Rails on Rail" deployment. Choo Choo!
While fiddling with Rails on Rail, I took some time to revisit Rails (as a framework). Here's what I noted:

Mental Model

  1. Just like any other frameworks (Laravel, Django), there are conventions (aka "magics") that you need to be aware. Knowing these will help you navigate the framework and reason about how things work.
  1. Watch out for N+1 queries. FWIW, this applies to all situations where you use ORM regardless of framework choice.
  1. Use ActiveSupport::CurrentAttributes to add attributes that persist in request's lifetime (per-request attributes). See: https://fullstackheroes.com/rails/current-attributes/.
  1. Unlike PHP or Node, you don't need to use use or require or import keywords to use packages (gems).
  1. Starting from Rails 6.1, you can use signed_id to create a verifiable id of related model. Example:
    1. # Create signed token
      user = User.find(1)
      signed_user_id = user.signed_id(purpose: :password_reset, expires_in: 15.minutes)
      # Find user by signed token
  1. You can use binding.irb as debugger. See: https://jemma.dev/blog/binding-irb
  1. rescue is the equivalent of catch in languages that support try and catch exception. See: https://crodrigues.com/try-catch-finally-equivalent-in-ruby/
  1. Form error is stored in model instance
  1. Usebundle add <package_name> to add gem to your project (equivalent to yarn add <package_name> in nodejs)
  1. It's quite common for rails packages to have the accompanying rails :install command. Example:
    1. bundle add hotwire-rails # add hotwire gem
      rails hotwire:install # install hotwire
  1. Use rails credentials:edit to manage your encrypted configs
  1. Use Rails.application.credentials.dig(:some_symbol) to retrieve your encrypted config
  1. A common workflow when developing new feature that needs a data model is:
    1. Generate model using rails generate model <ModelName> <Column>
    2. Migrate the generated schema to database: rails db:migrate
  1. In Ruby, all methods are public by default. Use private in your Rails controller to define methods that should not be exposed as actions. Example:
    1. class UserController < ApplicationController
      	def create
      		@user = User.create(user_params)
      	def user_params
      		params.require(:user).permit(:username, :password)
  1. In Ruby, methods that ends with exclamation mark (aka bang) indicate a mutation. Example:
    1. numbers = [1, 2, 3]
      # Multiply each number by two and return the value
      numbers.collect{|n| puts n*2}
      # Multiply each number by two and *mutate* the array
      numbers.collect!{|n| puts n*2}
  1. You can use Rails.logger.info as an equivalent to console.log (in JS)
  1. Starting from Rails 6, you can use upsert and upsert_all in your models. See: https://bigbinary.com/blog/bulk-insert-support-in-rails-6
  1. Use sqlite as database for local development to simplify your local setup
  1. Use resource in your route file to automatically handle all CRUD operations for given model
  1. button_to and link_to will automatically append the id of related model
  1. Use form.object.persisted? to determine if form's model is stored in database
  1. Rails' built-in job scheduler (ActiveJob) makes it easy to handle asynchronous jobs (scheduled tasks). Here's a snippet from Rails for Beginners course:
    1. Instead of cancelling a job, we can just check if the job should run (which simplifies the logic).Instead of cancelling a job, we can just check if the job should run (which simplifies the logic).
      Instead of cancelling a job, we can just check if the job should run (which simplifies the logic).
  1. Use mperham/sidekiq if you need to handle background jobs that don't disappear after server reboot (you'll likely want to have this for production apps)
  1. Run rails console to start rails console (similar to artisan tinker in Laravel)

Syntax and Patterns

  1. Use #{} to interpolate string in (Ruby feature). Example: print "Hello #{1+1}"
  1. Use ? suffix in method name to for methods which return value can be evaluated to true or false (Ruby convention). For example, create def subscribed? instead of def is_subscribed,
  1. Use nil operator ||= to assign value to variable if the variable is nil. Example:
    1. a = nil
      b = 100
      a ||= b
      print a # This will output `100`
  1. You can use before_action to act as a "middleware". Example:
    1. # app/controllers/application_controller.rb
      class PrivatePagesController < ApplicationController
      	# Run `authenticate_user` before any actions
      	before_action :authenticate_user!
  1. Use @ (aka. "instance variable") to make variables available in views. Example:
    1. class SomeController < ApplicationController
        def new
      		# This will be available from view
          @user = "John Doe"
      		# This won't be available from view (only current block, aka. "local scope")
          city = "Portlan"
  1. Add has_secure_password to models that needs to handle password authentication (eg. user model). Example:
    1. # app/models/user.rb
      class User < ApplicationRecord
      	validates: :email, presence: true, uniqueness: true
  1. You can use a simple session to handle authentication. Example:
    1. # app/controllers/auth_controller.rb
      class AuthController < ApplicationController
      	# Log the user in
      	def login
      		session[:user] = User.find_by_id(:some_id)
      	# Log the user out
      	def logout
      		session[:user] = nil
  1. Use t helper to translate value or l to localize value. Example:
    1. <!-- app/views/home/index.html.erb -->
      <h1><%= t :hello_world %></h1>
      <p><%= l Time.now, format: :short %></p>
  1. Use dependent to handle cascading delete for models with has_many relation. Example:
    1. class UserAccount < ApplicationRecord
      	has_many :tweets, dependent: :destroy
  1. If your model has a lot of children, doing dependent: :destroy can be slow. Consider using dependent: :delete_all or dependent: :nullify instead (or use database's built-in cascade mechanism). See: https://blog.getcensus.com/cascading-deletes-in-rails/.


Rails for Beginners at GoRails.comRails for Beginners at GoRails.com
Rails for Beginners at GoRails.com
Was this helpful?
โ€ฆ views

Loading Comments...