Setup 🛠 Rails 8 App – Part 16: Implementing Authentication, Users, Orders, and Order Items

Let’s now move onto create Authentication for our application.

Modern e‑commerce applications need robust user authentication, clear role‑based access, and an intuitive ordering system. In this post, we’ll walk through how to:

  1. Add Rails’ built‑in authentication via has_secure_password.
  2. Create a users table with roles for customers and admins.
  3. Build an orders table to capture overall transactions.
  4. Create order_items to track each product variant in an order.

Throughout, we’ll leverage PostgreSQL’s JSONB for flexible metadata, and we’ll use Rails 8 conventions for migrations and models.


Automatic Authentication For Rails 8 Apps

bin/rails generate authentication

This creates all the necessary files for users and sessions.

Create Authentication Manually

1. Create users table and user model

✗ rails g migration create_users

# users migration
class CreateUsers < ActiveRecord::Migration[8.0]
  def change
    create_table :users do |t|
      t.string   :email,           null: false, index: { unique: true }
      t.string   :password_digest, null: false
      t.string   :role,            null: false, default: "customer"
      t.string   :first_name
      t.string   :last_name
      t.jsonb    :metadata,        null: false, default: {}
      t.timestamps
    end

    # You can later set up an enum in the User model:
    # enum role: { customer: "customer", admin: "admin" }
  end
end

✗ rails g model user

# User model
class User < ApplicationRecord
  has_secure_password
  enum :role, {
    customer:  "customer",  
    admin:     "admin"      
  }
  has_many :orders
end

2. Authenticating with has_secure_password

Rails ships with bcrypt support out of the box. To enable it:

  1. Uncomment the following line in your Gemfile.
    # gem "bcrypt", "~> 3.1.7"
  2. Run bundle install.
  3. In your migration, create a password_digest column:
create_table :users do |t|
  t.string :email,           null: false, index: { unique: true }
  t.string :password_digest, null: false
  # ... other fields ...
end

  1. In app/models/user.rb, enable:
class User < ApplicationRecord
  has_secure_password
  # ...
end

This gives you user.authenticate(plain_text_password) and built‑in validation that a password is present on create.

3. Setting Up Users with Roles

We often need both customers and admins. Let’s create a role column with a default of "customer":

create_table :users do |t|
  t.string :role, null: false, default: "customer"
  # ...
end

In the User model you can then define an enum:

class User < ApplicationRecord
  ......
  enum :role, {
    customer:  "customer",  
    admin:     "admin"      
  }
end

This lets you call current_user.admin? or User.customers for scopes.

user.customer!   # sets role to "customer"
user.admin?      # => false

Rails built-in enum gives you a quick way to map a column to a fixed set of values, and it:

  1. Defines predicate and bang methods
  2. Adds query scopes
  3. Provides convenient helpers for serialization, validations, etc.

4. Building the Orders Table

Every purchase is represented by an Order. Key fields:

  • user_id (foreign key)
  • total_price (decimal with scale 2)
  • status (string; e.g. pending, paid, shipped)
  • shipping_address (JSONB): allows storing a full address object with flexible fields (street, city, postcode, country, and even geolocation) without altering your schema. You can index JSONB columns (GIN) to efficiently query nested fields, and you avoid creating a separate addresses table unless you need relationships or reuse.
  • placed_at (datetime, optional): records the exact moment the order was completed, independent of when the record was created. Making this optional lets you distinguish between draft/in-progress orders (no placed_at yet) and finalized purchases.
  • Timestamps
  • placed_at (datetime, optional): records the exact moment the order was completed, independent of when the record was created. Making this optional lets you distinguish between draft/in-progress orders (no placed_at yet) and finalized purchases.
  • Timestamps and an optional placed_at datetime
✗ rails g migration create_orders

# orders migration
class CreateOrders < ActiveRecord::Migration[8.0]
  def change
    create_table :orders do |t|
      t.references :user, null: false, foreign_key: true, index: true
      t.decimal    :total_price, precision: 12, scale: 2, null: false, default: 0.0
      t.string     :status,      null: false, default: "pending", index: true
      t.jsonb      :shipping_address, null: false, default: {}
      t.datetime   :placed_at
      t.timestamps
    end

    # Example statuses: pending, paid, shipped, cancelled
  end
end

In app/models/order.rb:

✗ rails g model order

class Order < ApplicationRecord
  belongs_to :user
  has_many   :order_items, dependent: :destroy
  has_many   :product_variants, through: :order_items

  STATUSES = %w[pending paid shipped cancelled]
  validates :status, inclusion: { in: STATUSES }
end

5. Capturing Each Item: order_items

To connect products to orders, we use an order_items join table. Each row stores:

  • order_id and product_variant_id as FKs
  • quantity, unit_price, and any discount_percent
  • Optional JSONB metadata for special instructions
✗ rails g migration create_order_items

# order_items migration
class CreateOrderItems < ActiveRecord::Migration[8.0]
  def change
    create_table :order_items do |t|
      t.references :order,           null: false, foreign_key: true, index: true
      t.references :product_variant, null: false, foreign_key: true, index: true
      t.integer    :quantity,        null: false, default: 1
      t.decimal    :unit_price,      precision: 10, scale: 2, null: false
      t.decimal    :discount_percent, precision: 5, scale: 2, default: 0.0
      t.jsonb      :metadata,        null: false, default: {}
      t.timestamps
    end

    # Composite unique index to prevent duplicate variant per order
    add_index :order_items, [:order_id, :product_variant_id], unique: true, name: "idx_order_items_on_order_and_variant"
  end

Model associations:

✗ rails g model order_item

class OrderItem < ApplicationRecord
  belongs_to :order
  belongs_to :product_variant

  validates :quantity, numericality: { greater_than: 0 }
end

6. Next Steps: Controllers & Authorization

  • Controllers: Scaffold UsersController, SessionsController (login/logout), OrdersController, and nested OrderItemsController under orders or use a service object to build carts.
  • Authorization: Once role is set, integrate Pundit or CanCanCan to restrict admin actions (creating products, managing variants) and customer actions (viewing own orders).
  • Views/Frontend: Tie it all together with forms for signup/login, a product catalog with “Add to Cart”, a checkout flow, and an admin dashboard for product management.

7. Scaffolding Controllers & Views (TailwindCSS Rails 4.2.3)

Generate Controllers & Routes

✗ rails generate controller Users new create index show edit update destroy --skip-routes
create  app/controllers/users_controller.rb
      invoke  tailwindcss
      create    app/views/users
      create    app/views/users/new.html.erb
      create    app/views/users/create.html.erb
      create    app/views/users/index.html.erb
      create    app/views/users/show.html.erb
      create    app/views/users/edit.html.erb
      create    app/views/users/update.html.erb
      create    app/views/users/destroy.html.erb
      invoke  test_unit
      create    test/controllers/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
✗ rails generate controller Sessions new create destroy --skip-routes
create  app/controllers/sessions_controller.rb
      invoke  tailwindcss
      create    app/views/sessions
      create    app/views/sessions/new.html.erb
      create    app/views/sessions/create.html.erb
      create    app/views/sessions/destroy.html.erb
      invoke  test_unit
      create    test/controllers/sessions_controller_test.rb
      invoke  helper
      create    app/helpers/sessions_helper.rb
      invoke    test_unit
✗ rails generate controller Orders index show new create edit update destroy --skip-routes
      create  app/controllers/orders_controller.rb
      invoke  tailwindcss
      create    app/views/orders
      create    app/views/orders/index.html.erb
      create    app/views/orders/show.html.erb
      create    app/views/orders/new.html.erb
      create    app/views/orders/create.html.erb
      create    app/views/orders/edit.html.erb
      create    app/views/orders/update.html.erb
      create    app/views/orders/destroy.html.erb
      invoke  test_unit
      create    test/controllers/orders_controller_test.rb
      invoke  helper
      create    app/helpers/orders_helper.rb
      invoke    test_unit
 ✗ rails generate controller OrderItems create update destroy --skip-routes
      create  app/controllers/order_items_controller.rb
      invoke  tailwindcss
      create    app/views/order_items
      create    app/views/order_items/create.html.erb
      create    app/views/order_items/update.html.erb
      create    app/views/order_items/destroy.html.erb
      invoke  test_unit
      create    test/controllers/order_items_controller_test.rb
      invoke  helper
      create    app/helpers/order_items_helper.rb
      invoke    test_unit

In config/routes.rb, nest order_items under orders and add session routes:

Rails.application.routes.draw do
  resources :users
n
  resources :sessions, only: %i[new create destroy]
  get    '/login',  to: 'sessions#new'
  post   '/login',  to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'

  resources :orders do
    resources :order_items, only: %i[create update destroy]
  end

  root 'products#index'
end

By the end, you’ll have a fully functional e‑commerce back end: secure auth, order tracking, and clear user roles.


How to setup your First User🙍🏻‍♂️ in the system

The very first user you should set up is:

An admin user — to create/manage products, variants, and handle backend tasks.

Here’s the best approach:

Best Practice: Seed an Admin User

Instead of manually creating it through the UI (when no one can log in yet), the best and safest approach is to use db/seeds.rb to create an initial admin user.

Why?

  • You can reliably recreate it on any environment (local, staging, production).
  • You can script strong defaults (like setting a secure admin email/password).

🔒 Tip: Use ENV Variables

For production, never hardcode admin passwords directly in seeds.rb. Instead, do:

admin_password = ENV.fetch("ADMIN_PASSWORD")

and pass it as:

ADMIN_PASSWORD=SomeStrongPassword rails db:seed

This keeps credentials out of your Git history.

🛠 Option 1: Add Seed Data db/seeds.rb

Add a block in db/seeds.rb that checks for (or creates) an admin user:

# db/seeds.rb

email    = ENV.fetch("ADMIN_EMAIL") { abort "Set ADMIN_EMAIL" }
password = ENV.fetch("ADMIN_PASSWORD") { abort "Set ADMIN_PASSWORD" }

User.find_or_create_by!(email: admin_email) do |user|
  user.password              = admin_password
  user.password_confirmation = admin_password
  user.role                   = "admin"
  user.first_name             = "Site"
  user.last_name              = "Admin"
end

puts "→ Admin user: #{admin_email}"

Then run:

rails db:seed
  1. Pros:
    • Fully automated and idempotent—you can run db:seed anytime without creating duplicates.
    • Seed logic lives with your code, so onboarding new team members is smoother.
    • You can wire up ENV vars for different credentials in each environment (dev/staging/prod).
  2. Cons:
    • Seeds can get cluttered over time if you add lots of test data.
    • Must remember to re-run seeds after resetting the database.

🛠 Option 2: Custom Rake task or Thor script

Create a dedicated task under lib/tasks/create_admin.rake:

namespace :admin do
  desc "Create or update the first admin user"
  task create: :environment do
    email    = ENV.fetch("ADMIN_EMAIL")    { abort "Set ADMIN_EMAIL" }
    password = ENV.fetch("ADMIN_PASSWORD") { abort "Set ADMIN_PASSWORD" }

    user = User.find_or_initialize_by(email: email)
    user.password              = password
    user.password_confirmation = password
    user.role                   = "admin"
    user.save!

    puts "✅ Admin user #{email} created/updated"
  end
end

Run it with:

ADMIN_EMAIL=foo@bar.com ADMIN_PASSWORD=topsecret rails admin:create
  1. Pros:
    • Keeps seed file lean—admin-creation logic lives in a focused task.
    • Enforces presence of ENV vars (you won’t accidentally use a default password in prod).
  2. Cons:
    • Slightly more setup than plain seeds, though it’s still easy to run.

I choose for Option 2, because it is namespaced and clear what is the purpose. But in seed there will be lot of seed data together make it difficult to identify a particular task.

🛡 Why is This Better?

✅ No need to expose a sign-up page to create the very first admin.
✅ You avoid manual DB entry or Rails console commands.
✅ You can control/rotate the admin credentials easily.
✅ You can add additional seed users later if needed (for demo or testing).

📝 Summary

Seed an initial admin user
✅ Add a role check (admin? method)
✅ Lock down sensitive parts of the app to admin
✅ Use ENV vars in production for passwords


Enjoy Rails 🚀!

Setup 🛠 Rails 8 App – Part 1: Setup All Necessary Configurations | Ruby | Rails setup | Kamal | Rails Generations

Ruby on Rails 8 introduces several improvements that make development easier, more secure, and more maintainable. In this guide, we’ll walk through setting up a new Rails 8 application while noting the significant configurations and features that come out of the box.

1. Check Your Ruby and Rails Versions

If not installed Ruby 3.4 and Rails 8.0 please check: https://railsdrop.com/2025/02/11/installing-and-setup-ruby-3-rails-8-vscode-ide-on-macos-in-2025/

Before starting, ensure that you have the correct versions of Ruby and Rails installed:

$ ruby -v
ruby 3.4.1

$ rails -v
Rails 8.0.1

If you don’t have these versions installed, update them using your package manager or version manager (like rbenv or RVM).

2. Create a New Rails 8 Application

Run the following command to create a new Rails app:

$ rails new design_studio

Noteworthy Files and Directories Created

Here are some interesting files and directories that are generated with a new Rails 8 app:

 create  .ruby-version
 create  bin/brakeman
 create  bin/rubocop
 create  bin/docker-entrypoint
 create  .rubocop.yml
 create  .github/workflows
 create  .github/workflows/ci.yml
 create  config/cable.yml
 create  config/storage.yml
 create  config/initializers/content_security_policy.rb
 create  config/initializers/filter_parameter_logging.rb
 create  config/initializers/new_framework_defaults_8_0.rb

Key Takeaways:

  • Security & Code Quality Tools: Brakeman (security scanner) and RuboCop (code style linter) are included by default.
  • Docker Support: The presence of bin/docker-entrypoint suggests better built-in support for containerized deployment.
  • GitHub Actions Workflow: The .github/workflows/ci.yml file provides default CI configurations.
  • Enhanced Security: The content_security_policy.rb initializer helps enforce a strict security policy.
  • New Rails Defaults: The new_framework_defaults_8_0.rb initializer helps manage breaking changes in Rails 8.

Rails automatically creates the following during the creation of the rails new app.

a. Configuring Import Maps and Installing Turbo & Stimulus

Rails 8 still defaults to Import Maps for JavaScript package management, avoiding the need for Node.js and Webpack:

$ rails turbo:install stimulus:install

This creates the following files:

create    config/importmap.rb
create    app/javascript/controllers
create    app/javascript/controllers/index.js
create    app/javascript/controllers/hello_controller.js
append    config/importmap.rb

Key Takeaways:

  • Import Maps: Defined in config/importmap.rb, allowing dependency management without npm.
  • Hotwired Support: Turbo and Stimulus are automatically configured for modern front-end development.
  • Generated Controllers: Stimulus controllers are pre-configured inside app/javascript/controllers/.

b. Deploying with Kamal

Kamal simplifies deployment with Docker and Kubernetes. Rails 8 includes built-in support:

$ bundle binstubs kamal
$ bundle exec kamal init

This results in:

Created .kamal/secrets file
Created sample hooks in .kamal/hooks

Key Takeaways:

  • Automated Deployment Setup: Kamal provides easy-to-use deployment scripts.
  • Secret Management: The .kamal/secrets file ensures secure handling of credentials.
  • Deployment Hooks: Custom hooks allow pre- and post-deployment scripts for automation.

c. Setting Up Caching and Queues with Solid Cache, Queue, and Cable

NOTE: Rails automatically creates this for you while creating the rails app.

Rails 8 includes Solid Cache, Solid Queue, and Solid Cable for enhanced performance and scalability:

$ rails solid_cache:install solid_queue:install solid_cable:install

This creates:

create  config/cache.yml
create  db/cache_schema.rb
create  config/queue.yml

Key Takeaways:

  • Caching Support: config/cache.yml manages application-wide caching.
  • Database-Powered Queue System: Solid Queue simplifies background job management without requiring external dependencies like Sidekiq.
  • Real-Time WebSockets: Solid Cable offers Action Cable improvements for real-time features.

3. Rails 8 Migration Enhancements

Rails 8 provides new shortcuts and syntax improvements for database migrations:

NOT NULL Constraints with ! Shortcut

You can impose NOT NULL constraints directly from the command line using !:

# Example for not null constraints: 
➜ rails generate model User name:string!

Type Modifiers in Migrations

Rails 8 allows passing commonly used type modifiers directly via the command line. These modifiers are enclosed in curly braces {} after the field type.

# Example for model generation: 
➜ rails generate model Product name:string description:text
# Example for passing modifiers: 
➜ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}

Generating a Scaffold for the Product Model

Let’s generate a complete scaffold for our Product model:

✗ rails generate scaffold product title:string! description:text category:string color:string 'size:string{10}' 'mrp:decimal{7,2}' 'discount:decimal{7,2}' 'rating:decimal{1,1}'
➜  design_studio git:(main) ✗ rails -v
Rails 8.0.1
➜  design_studio git:(main) ✗ ruby -v
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [arm64-darwin24]
➜  design_studio git:(main) ✗ rails generate scaffold product title:string! description:text category:string color:string 'size:string{10}' 'mrp:decimal{7,2}' 'discount:decimal{7,2}' 'rating:decimal{1,1}'

Using the Rails Resource Generator

The rails g resource command is a powerful way to generate models, controllers, migrations, and routes all in one go. This is particularly useful when setting up RESTful resources in a Rails application.

Basic Syntax

➜ rails g resource product

This command creates the necessary files for a new resource, including:

  • A model (app/models/product.rb)
  • A migration file (db/migrate/)
  • A controller (app/controllers/product_controller.rb)
  • Routes in config/routes.rb
  • A test file (test/controllers/product_controller_test.rb or spec/)

Example Usage

To generate a Post resource with attributes:

➜ rails g resource Product title:string! description:text brand:references

This will:

  1. Create a Product model with title and description attributes.
  2. Add a brand_id foreign key as a reference.
  3. Apply a NOT NULL constraint on title (! shortcut).
  4. Generate a corresponding migration file.
  5. Set up routes automatically (resources :products).

Running the Migration

After generating a resource, apply the migration to update the database:

➜ rails db:migrate


Difference Between resource and scaffold

Rails provides both rails g resource and rails g scaffold, but they serve different purposes:

Featurerails g resourcerails g scaffold
Generates a Model
Generates a Migration
Generates a Controller✅ (empty actions)✅ (full CRUD actions)
Generates Views (HTML/ERB)✅ (index, show, new, edit, etc.)
Generates Routes
Generates Helper Files
Generates Tests
  • rails g resource is minimal—it generates only essential files without view templates. It’s useful when you want more control over how your views and controller actions are built.
  • rails g scaffold is more opinionated and generates full CRUD functionality with prebuilt forms and views, making it ideal for rapid prototyping.

If you need full CRUD functionality quickly, use scaffold. If you prefer a leaner setup with manual control over implementation, use resource.

Conclusion

Rails 8 significantly enhances the development experience with built-in security tools, CI/CD workflows, streamlined deployment via Kamal, and modern front-end support with Turbo & Stimulus. It also improves caching, background jobs, and real-time features with Solid tools.

These improvements make Rails 8 a robust framework for modern web applications, reducing the need for additional dependencies while keeping development efficient and secure.

Enjoy Rails! 🚀