rails app tutorial

Create rails app

rails new your-app -T -d postgresql -c tailwind

Add gems to Gemfile

gem "devise"
gem "slim-rails"
gem 'pagy'
gem "pundit"
gem "sidekiq"
gem 'pg_search'
gem 'friendly_id'
gem 'discard'
gem "interactor", "~> 3.0"
gem 'strada-rails'

group :development, :test do
  gem 'brakeman'
  gem 'bullet'
  gem 'bundler-audit'
  gem 'pry'
  gem 'rubocop-rails', require: false
end

group :development do
  gem 'kamal'
  gem 'letter_opener'
  gem 'web-console'
end

group :test do
  gem 'factory_bot_rails'
  gem 'ffaker'
  gem 'rspec-rails', '~> 6.1.0'
end
rails action_text:install

Install gems

bundle

Update config/environments/development.rb

  config.action_mailer.delivery_method = :letter_opener
  config.action_mailer.perform_deliveries = true

Update config/application.rb

  config.generators do |g|
    g.template_engine :slim
  end

  config.active_job.queue_adapter = :sidekiq

Update config/database.yml

production:
  <<: *default
  host: <%= ENV["DB_HOST"] %>
  database: <%= ENV["POSTGRES_DB"] %>
  username: <%= ENV["POSTGRES_USER"] %>
  password: <%= ENV["POSTGRES_PASSWORD"] %>

Commands && scaffold

rails g devise:install
rails g devise user
rails g scaffold post title content:rich_text slug:uniq user:references discarded_at:datetime:index
rails g pundit:install
rails g pundit:policy post
rails g bullet:install
rails strada:install

Generate migration for pg plugin

rails g migration enable_pg_trgm

Update #change with

enable_extension 'pg_trgm'

Prepare database

rails db:prepare

Add to post model

  extend FriendlyId
  include Discard::Model
  include PgSearch::Model

  friendly_id :title, use: %i[slugged finders]

  pg_search_scope :by_title,
                against: :title,
                using: { trigram: { word_similarity: true } }

Add to application_controller

  include Pundit::Authorization
  include Pagy::Backend

  before_action :authenticate_user!

Add to application_helper

  include Pagy::Frontend

Rubocop

rubocop --init

Add to file

require: rubocop-rails

Style/Documentation:
  Enabled: false

AllCops:
  Exclude:
   - 'db/**/*'
   - 'bin/*'
   - 'config/**/*'
   - 'vendor/**/*'

Run

rubocop -A

Update routs root

  root "posts#index"

Generate factories

rails generate factory_bot:model user email password
rails g factory_bot:model psot title content:rich_text user:references

Remove generated specs and create one simple one, for example

  it { expect(true).to be_truthy }

Add .github/workflows/rubyonrails.yml

name: "Ruby on Rails CI"
on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:11-alpine
        ports:
          - "5432:5432"
        env:
          POSTGRES_DB: easy_rails_app_test
          POSTGRES_USER: rails
          POSTGRES_PASSWORD: password
    env:
      RAILS_ENV: test
      DATABASE_URL: "postgres://rails:password@localhost:5432/easy_rails_app_test"
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Install Ruby and gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - name: Set up database schema
        run: bin/rails db:prepare
      - name: Run tests
        run: bundle exec rspec

  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Install Ruby and gems
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - name: Security audit dependencies
        run: bundle exec bundler-audit --update
      - name: Security audit application code
        run: bundle exec brakeman -q -w2
      - name: Lint Ruby files
        run: bundle exec rubocop --parallel

Run

kamal init

Having a dockerhub account is required in this setup and two VPS machines too for main/secondary services and db(separately). You also need to have SSH access via pub/private keys to VPS you deploy

Example of .env

KAMAL_REGISTRY_PASSWORD=dckr_pat_o-dfsd$asd-e-dsadad123
RAILS_MASTER_KEY=644123asdf3423ab4a0a1790182312asd
POSTGRES_DB=easy_rails_app_production
POSTGRES_USER=postgres
POSTGRES_PASSWORD=HxXXXM93Nunq9c
DB_HOST=192.168.0.2
APP_HOST=192.168.0.1
REDIS_URL=redis://192.168.0.1:6379

Example of config/deploy.yml

service: your-app
image: your-name/your-app
servers:
  web:
    hosts:
      - <%= ENV.fetch('APP_HOST') %>
  sidekiq:
    cmd: bundle exec sidekiq
    hosts:
      - <%= ENV.fetch('APP_HOST') %>
registry:
  username: your-name
  password:
    - KAMAL_REGISTRY_PASSWORD
env:
  secret:
    - RAILS_MASTER_KEY
    - DB_HOST
    - POSTGRES_DB
    - POSTGRES_USER
    - POSTGRES_PASSWORD
    - REDIS_URL
builder:
  multiarch: false
accessories:
  db:
    image: postgres:latest
    host: <%= ENV.fetch('DB_HOST') %>
    port: 5432
    env:
      clear:
        POSTGRES_USER: "postgres"
        POSTGRES_DB: "your_app_production"
      secret:
        - POSTGRES_PASSWORD
    files:
      # - config/mysql/production.cnf:/etc/mysql/my.cnf
      - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
    directories:
      - data:/var/lib/postgresql/data
  redis:
    image: redis:7.0
    host: <%= ENV.fetch('APP_HOST') %>
    port: 6379
    directories:
      - redis-data:/data

Example of db/production.sql

CREATE DATABASE your_app_production;
kamal deploy

Mobile apps

Install native app initializer

gem install turbo-native-initializer

Android(android studio is required)

ATTENTION: This is going to create a new repository!

turbo-native-initializer EasyRailsApp --platform=android

Update app/src/main/java/dev/hotwire/turbo/easyrailsapp/util/Constants.kt

private const val DEVELOPMENT_URL = "http://localhost:3000"
private const val PRODUCTION_URL  = "https://your-app.com"