๐Ÿ” SSL: The Security Foundation of the Modern Web

๐Ÿ‘‹ Introduction

In today’s digital landscape, SSL (Secure Sockets Layer) and its successor TLS (Transport Layer Security) form the backbone of internet security. Every time you see that reassuring padlock icon in your browser’s address bar, you’re witnessing SSL/TLS in action. But what exactly is SSL, how does it work, and why has it become so crucial for every website owner? Let’s dive deep into the world of SSL certificates and explore how they’ve transformed the web.

โš™๏ธ What is SSL and How Does It Work?

SSL (Secure Sockets Layer) is a cryptographic protocol designed to provide secure communication over a computer network. While SSL has been largely replaced by TLS (Transport Layer Security), the term “SSL” is still commonly used to refer to both protocols.

The SSL Handshake Process

When you visit a website with SSL enabled, a complex but lightning-fast process occurs:

  1. Client Hello: Your browser sends a “hello” message to the server, including supported encryption methods
  2. Server Hello: The server responds with its chosen encryption method and sends its SSL certificate
  3. Certificate Verification: Your browser verifies the certificate’s authenticity against trusted Certificate Authorities (CAs)
  4. Key Exchange: Both parties establish a shared secret key for encryption
  5. Secure Connection: All subsequent communication is encrypted using the established key

Encryption Types

SSL uses two types of encryption:

  • Symmetric Encryption: Fast encryption using the same key for both encryption and decryption
  • Asymmetric Encryption: Uses a pair of keys (public and private) for initial handshake and key exchange

๐ŸŒ How SSL Transformed the Web

Before SSL: The Wild West of the Internet

In the early days of the web, all data transmitted between browsers and servers was sent in plain text. This meant:

  • No Privacy: Anyone intercepting traffic could read sensitive information
  • No Integrity: Data could be modified without detection
  • No Authentication: No way to verify you were communicating with the intended server

The SSL Revolution

SSL implementation brought three fundamental security principles to the web:

  1. Confidentiality: Data encryption ensures only intended recipients can read the information
  2. Integrity: Cryptographic hashes detect any tampering with data during transmission
  3. Authentication: Digital certificates verify the identity of websites

Impact on E-commerce and Online Services

SSL made modern e-commerce possible by:

  • Enabling secure credit card transactions
  • Building user trust in online services
  • Protecting sensitive personal information
  • Facilitating the growth of online banking and financial services

๐Ÿ“œ SSL Certificates: Your Digital Identity Card

An SSL certificate is a digital document that:

  • Proves the identity of a website
  • Contains the website’s public key
  • Is digitally signed by a trusted Certificate Authority (CA)

Types of SSL Certificates

1. Domain Validated (DV) Certificates

  • Validation: Only verifies domain ownership
  • Trust Level: Basic
  • Use Case: Personal websites, blogs
  • Issuance Time: Minutes to hours

2. Organization Validated (OV) Certificates

  • Validation: Verifies domain ownership and organization details
  • Trust Level: Medium
  • Use Case: Business websites
  • Issuance Time: 1-3 days

3. Extended Validation (EV) Certificates

  • Validation: Rigorous verification of organization’s legal existence
  • Trust Level: Highest
  • Use Case: E-commerce, banking, high-security sites
  • Issuance Time: 1-2 weeks

Certificate Coverage Options

  • Single Domain: Protects one specific domain (e.g., http://www.example.com)
  • Multi-Domain (SAN): Protects multiple different domains
  • Wildcard: Protects a domain and all its subdomains (e.g., *.example.com)

๐Ÿ› ๏ธ How to Get and Implement SSL Certificates

Step 1: Choose Your SSL Provider

Select from various Certificate Authorities based on your needs:

  • Free Options: Let’s Encrypt, SSL.com Free
  • Commercial Providers: DigiCert, GlobalSign, Sectigo, GoDaddy

Step 2: Generate a Certificate Signing Request (CSR)

# Example using OpenSSL
openssl req -new -newkey rsa:2048 -nodes -keyout yourdomain.key -out yourdomain.csr

Step 3: Validate Domain Ownership

Certificate Authorities typically offer three validation methods:

  • Email Validation: Receive validation email at admin@yourdomain.com
  • DNS Validation: Add a specific TXT record to your DNS
  • HTTP File Upload: Upload a verification file to your website

Step 4: Install the Certificate

Installation varies by server type:

Apache

<VirtualHost *:443>
    ServerName www.yourdomain.com
    SSLEngine on
    SSLCertificateFile /path/to/yourdomain.crt
    SSLCertificateKeyFile /path/to/yourdomain.key
    SSLCertificateChainFile /path/to/intermediate.crt
</VirtualHost>

Nginx

server {
    listen 443 ssl;
    server_name www.yourdomain.com;

    ssl_certificate /path/to/yourdomain.crt;
    ssl_certificate_key /path/to/yourdomain.key;
    ssl_protocols TLSv1.2 TLSv1.3;
}

Step 5: Configure HTTP to HTTPS Redirect

# Apache .htaccess
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

โš ๏ธ The Cost of Not Having SSL

SEO Impact

  • Google Ranking Factor: HTTPS is a confirmed ranking signal
  • Browser Warnings: Modern browsers flag non-HTTPS sites as “Not Secure”
  • User Trust: Visitors are likely to leave unsecured sites

Security Risks

  • Data Interception: Sensitive information transmitted in plain text
  • Man-in-the-Middle Attacks: Attackers can intercept and modify communications
  • Session Hijacking: User sessions can be stolen on unsecured networks

Business Consequences

  • Lost Revenue: Users abandon transactions on insecure sites
  • Compliance Issues: Many regulations require encryption (GDPR, PCI DSS)
  • Reputation Damage: Security breaches can destroy customer trust

๐Ÿ’ฐ SSL Providers: Free vs. Paid Services

Free SSL Providers

Let’s Encrypt

  • Cost: Completely free
  • Validity: 90 days (auto-renewable)
  • Support: Domain and wildcard certificates
  • Automation: Excellent with tools like Certbot
  • Limitation: DV certificates only
# Install Let's Encrypt certificate with Certbot
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com

SSL.com Free

  • Cost: Free for basic DV certificates
  • Validity: 90 days
  • Features: Basic domain validation

Cloudflare SSL

  • Cost: Free with Cloudflare service
  • Features: Universal SSL for all domains
  • Limitation: Requires using Cloudflare as CDN/proxy

Commercial SSL Providers

DigiCert

  • Reputation: Industry leader with highest trust
  • Features: EV, OV, DV certificates with extensive support
  • Price Range: $175-$595+ annually
  • Benefits: 24/7 support, warranty, advanced features

GlobalSign

  • Strengths: Enterprise-focused solutions
  • Features: Complete certificate lifecycle management
  • Price Range: $149-$649+ annually

Sectigo (formerly Comodo)

  • Position: Largest commercial CA by volume
  • Features: Wide range of certificate types
  • Price Range: $89-$299+ annually

GoDaddy

  • Advantage: Integration with hosting services
  • Features: Easy installation for beginners
  • Price Range: $69-$199+ annually

Cloud Provider SSL Solutions

AWS Certificate Manager (ACM)

  • Cost: Free for AWS services
  • Integration: Seamless with CloudFront, Load Balancers, API Gateway
  • Automation: Automatic renewal and deployment
  • Limitation: Only works within AWS ecosystem
# Request certificate via AWS CLI
aws acm request-certificate \
    --domain-name yourdomain.com \
    --subject-alternative-names www.yourdomain.com \
    --validation-method DNS

Google Trust Services

  • Integration: Works with Google Cloud Platform
  • Features: Managed certificates for Google Cloud Load Balancer
  • Cost: Free for Google Cloud services
  • Automation: Automatic provisioning and renewal

Azure SSL

  • Service: App Service Certificates
  • Integration: Native Azure integration
  • Features: Wildcard and standard certificates available

โœ… Best Practices for SSL Implementation

Security Configuration

  1. Use Strong Ciphers: Disable weak encryption algorithms
  2. Enable HSTS: Force HTTPS connections
  3. Configure Perfect Forward Secrecy: Protect past communications
  4. Regular Updates: Keep SSL/TLS libraries updated

Monitoring and Maintenance

  • Certificate Expiration Monitoring: Set up alerts before expiration
  • Security Scanning: Regular vulnerability assessments
  • Performance Monitoring: Track SSL handshake performance

Common Pitfalls to Avoid

  • Mixed Content: Ensure all resources load over HTTPS
  • Certificate Chain Issues: Include intermediate certificates
  • Weak Configurations: Avoid outdated protocols and ciphers

๐Ÿš€ The Future of SSL/TLS

TLS 1.3 Adoption

  • Faster handshakes
  • Improved security
  • Better performance

Certificate Transparency

  • Public logs of all certificates
  • Enhanced security monitoring
  • Improved detection of unauthorized certificates

Automated Certificate Management

  • ACME protocol standardization
  • Integration with CI/CD pipelines
  • Infrastructure as Code compatibility

๐ŸŽฏ Conclusion

SSL/TLS has evolved from a nice-to-have security feature to an absolute necessity for any serious web presence. Whether you choose a free solution like Let’s Encrypt for basic protection or invest in enterprise-grade certificates from providers like DigiCert, implementing SSL is no longer optionalโ€”it’s essential.

The transformation from an insecure web to today’s encrypted-by-default internet represents one of the most significant security improvements in computing history. As we move forward, SSL/TLS will continue to evolve, becoming faster, more secure, and easier to implement.

For website owners, the message is clear: implement SSL today, keep your certificates updated, and follow security best practices. Your users’ trust and your website’s success depend on it.


Remember: Security is not a destination but a journey. Stay informed about the latest SSL/TLS developments and regularly review your security configurations to ensure optimal protection for your users and your business.

Happy Web coding! ๐Ÿš€

Setup ๐Ÿ›  Rails 8 App โ€“ Partย 6: Attach images to Product model

To attach multiple images to a Product model in Rails 8, Active Storage provides the best way using has_many_attached. Below are the steps to set up multiple image attachments in a local development environment.


1๏ธโƒฃ Install Active Storage (if not already installed)

We have already done this step if you are following this series. Else run the following command to generate the necessary database migrations:

rails active_storage:install
rails db:migrate

This will create two tables in your database:

  • active_storage_blobs โ†’ Stores metadata of uploaded files.
  • active_storage_attachments โ†’ Creates associations between models and uploaded files.

2๏ธโƒฃ Update the Product Model

Configuring specific variants is done the same way as has_one_attached, by calling the variant method on the yielded attachable object:

add in app/models/product.rb:

class Product < ApplicationRecord
  has_many_attached :images do |attachable|
    attachable.variant :normal, resize_to_limit: [540, 720]
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

You just have to mention the above and rails will create everything for you!

Variants rely on ImageProcessing gem for the actual transformations of the file, so you must add gem "image_processing" to your Gemfile if you wish to use variants.

By default, images will be processed with ImageMagick using the MiniMagick gem, but you can also switch to the libvips processor operated by the ruby-vips gem.

Rails.application.config.active_storage.variant_processor
# => :mini_magick

Rails.application.config.active_storage.variant_processor = :vips
# => :vips

3๏ธโƒฃ Configure Active Storage for Local Development

By default, Rails stores uploaded files in storage/ under your project directory.

Ensure your config/environments/development.rb has:

config.active_storage.service = :local

And check config/storage.yml to ensure you have:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

This will store the uploaded files in storage/.


4๏ธโƒฃ Add File Uploads in Controller

Modify app/controllers/products_controller.rb to allow multiple image uploads:

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)

    if @product.save
      redirect_to @product, notice: "Product was successfully created."
    else
      render :new
    end
  end

  private

  def product_params
    params.require(:product).permit(:name, :description, images: [])
  end
end

Notice images: [] โ†’ This allows multiple images to be uploaded.


5๏ธโƒฃ Update Form for Multiple Image Uploads

Modify app/views/products/_form.html.erb:

<%= form_with model: @product, local: true do |form| %>
  <%= form.label :name %>
  <%= form.text_field :name %>

  <%= form.label :description %>
  <%= form.text_area :description %>

  <%= form.label :images %>
  <%= form.file_field :images, multiple: true %>

  <%= form.submit "Create Product" %>
<% end %>

๐Ÿ”น multiple: true โ†’ Allows selecting multiple files.


6๏ธโƒฃ Display Images in View

Modify app/views/products/_product.html.erb:

<h1><%= product.name %></h1>
<p><%= product.description %></p>

<h3>Product Images:</h3>
<% product.images.each do |image| %>
  <%= image_tag image.variant(:thumb), alt: "Product Image" %>
<% end %>
<% product.images.each do |image| %>
  <%= image_tag image, alt: "Product Image" %>
<% end %>

Replacing vs Adding Attachments

By default in Rails, attaching files to a has_many_attached association will replace any existing attachments.

To keep existing attachments, you can use hidden form fields with the signed_id of each attached file:

<% @message.images.each do |image| %>
  <%= form.hidden_field :images, multiple: true, value: image.signed_id %>
<% end %>

<%= form.file_field :images, multiple: true %>

This has the advantage of making it possible to remove existing attachments selectively, e.g. by using JavaScript to remove individual hidden fields.


7๏ธโƒฃ Get Image URLs

In Rails Console (rails c):

product = Product.last
product.images.each do |image|
  puts Rails.application.routes.url_helpers.rails_blob_url(image, host: "http://localhost:3000")
end

This generates a direct URL for each attached image.


8๏ธโƒฃ Delete an Attached Image

To remove an image from a product:

product = Product.last
product.images.first.purge  # Deletes a single image

To remove all images:

product.images.purge_later


โœ… Final Thoughts

  • has_many_attached :images is the best approach for multiple image uploads.
  • Local storage (storage/) is great for development, but for production, use S3 or another cloud storage.
  • Variants allow resizing images before displaying them.

Check: https://guides.rubyonrails.org/active_storage_overview.html https://github.com/<username>/<project>/tree/main/app/views/products

Enjoy Rails! ๐Ÿš€

to be continued..

Setup ๐Ÿ›  Rails 8 App – Partย 5: Active Storage File Uploads ๐Ÿ“ค

Meanwhile we are setting up some UI for our app using Tailwind CSS, I have uploaded 2 images to our product in the rich text editor. Let’s discuss about this in this post.

Understanding Active Storage in Rails 8: A Deep Dive into Image Uploads

In our Rails 8 application, we recently tested uploading two images to a product using the rich text editor. This process internally triggers several actions within Active Storage. Let’s break down what happens behind the scenes.

How Active Storage Handles Image Uploads

When an image is uploaded, Rails 8 processes it through Active Storage, creating a new blob entry and storing it in the disk service. The following request is fired:

Processing by ActiveStorage::DirectUploadsController#create as JSON

Parameters: {"blob" => {"filename" => "floral-kurtha.jpg", "content_type" => "image/jpeg", "byte_size" => 107508, "checksum" => "GgNgNxxxxxxxjdPOLw=="}}

This request initiates a database entry in active_storage_blobs:

INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "service_name", "byte_size", "checksum", "created_at")
VALUES ('huk9dxxxxxxxx09e2cyiq', 'floral-kurtha.jpg', 'image/jpeg', 'local', 107312, 'Fxxxxxxd+bpRibo2EfvA==', '2025-03-31 08:10:07.232453')

Storing Files and Generating URLs

Once the blob entry is created, Rails stores the file on disk and generates a URL:

http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsiYSI6eyJxxxxxxx

This process triggers the ActiveStorage::DiskController, handling file storage via a PUT request:

Started PUT "/rails/active_storage/disk/eyJfcmFpbHMiOxxxxx"
Disk Storage (0.9ms) Uploaded file to key: hut9d0zxssxxxxxx
Completed 204 No Content in 96ms

Retrieving Images from Active Storage

After successfully storing the file, the application fetches the image via a GET request:

Started GET "/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOxxxxxxxxxxfQ==--f9c556012577xxxxxxxxxxxxfa21/floral-kurtha-2.jpg"

This request is handled by:

Processing by ActiveStorage::Blobs::RedirectController#show as JPEG

The file is then served via the ActiveStorage::DiskController#show:

Redirected to http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsiZGxxxxxxxxxd048aae4ab5c30/floral-kurtha-2.jpg

Updating Records with Active Storage Attachments

When updating a product, the system also updates its associated images. The following Active Storage updates occur:

UPDATE "action_text_rich_texts" SET "body" = .... WHERE "action_text_rich_texts"."id" = 1

UPDATE "active_storage_blobs" SET "metadata" = '{"identified":true}' WHERE "active_storage_blobs"."id" = 3

INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('embeds', 'ActionText::RichText', 1, 3, '2025-03-31 11:46:13.464597')

Additionally, Rails updates the updated_at timestamp of the associated records:

UPDATE "products" SET "updated_at" = '2025-03-31 11:46:13.523640' WHERE "products"."id" = 1

Best Practices for Active Storage in Rails 8

  1. Use Direct Uploads: This improves performance by uploading files directly to cloud storage (e.g., AWS S3, Google Cloud Storage) instead of routing them through your Rails server.
  2. Attach Images Efficiently: Use has_one_attached or has_many_attached for file associations in models.
  3. Avoid Serving Files via Rails: Use a CDN or proxy service to serve images instead of relying on Rails controllers.
  4. Clean Up Unused Blobs: Regularly remove orphaned blob records using ActiveStorage::Blob.unattached.destroy_all.
  5. Optimize Image Processing: Use variants (image.variant(resize: "300x300").processed) to generate resized images efficiently.

In Rails 8, Active Storage uses two main tables for handling file uploads:

1. active_storage_blobs Table

This table stores metadata about the uploaded files but not the actual files. Each row represents a unique file (or “blob”) uploaded to Active Storage.

Columns in active_storage_blobs Table:

  • id โ€“ Unique identifier for the blob.
  • key โ€“ A unique key used to retrieve the file.
  • filename โ€“ The original name of the uploaded file.
  • content_type โ€“ The MIME type (e.g., image/jpeg, application/pdf).
  • metadata โ€“ JSON data storing additional information (e.g., width/height for images).
  • service_name โ€“ The storage service (e.g., local, amazon, google).
  • byte_size โ€“ File size in bytes.
  • checksum โ€“ A checksum to verify file integrity.
  • created_at โ€“ Timestamp when the file was uploaded.

Example Entry in active_storage_blobs:

INSERT INTO "active_storage_blobs" 
("key", "filename", "content_type", "service_name", "byte_size", "checksum", "created_at") 
VALUES ('avevnp6eg1xxxxxxsz8it6267eou7', 'floral-kurtha-2.jpg', 'image/jpeg', 'local', 204800, '0U0cXxxxxxxxxx/1u47Szg==', '2025-03-31 11:45:07.232453');

๐Ÿ‘‰ Purpose: This table acts as a record of stored files and their metadata.


2. active_storage_attachments Table

This table links blobs (files) to Active Record models. Instead of storing files directly in the database, Rails stores a reference to the blob.

Columns in active_storage_attachments Table:

  • id โ€“ Unique identifier for the attachment.
  • name โ€“ Name of the attachment (:avatar, :images, etc.).
  • record_type โ€“ The model type associated with the file (User, Post, etc.).
  • record_id โ€“ The ID of the record in the model (users.id, posts.id).
  • blob_id โ€“ The corresponding ID from active_storage_blobs.
  • created_at โ€“ Timestamp when the association was created.

Example Entry in active_storage_attachments:

INSERT INTO "active_storage_attachments" 
("name", "record_type", "record_id", "blob_id", "created_at") 
VALUES ('avatar', 'User', 1, 42, '2025-03-31 08:15:20.123456');

INSERT INTO "active_storage_attachments" 
("name", "record_type", "record_id", "blob_id", "created_at") 
VALUES ('embeds', 'ActionText::RichText', 1, 4, '2025-03-31 11:46:20.123456');

๐Ÿ‘‰ Purpose: This table allows a single file to be attached to multiple records without duplicating the file itself.


Why Does Rails Need Both Tables?

  1. Separation of Concerns:
    • active_storage_blobs tracks the files themselves.
    • active_storage_attachments links them to models.
  2. Efficient File Management:
    • The same file can be used in multiple places without storing it multiple times.
    • If a file is no longer attached to any record, Rails can remove it safely.
  3. Supports Different Attachments:
    • A model can have different types of attachments (avatar, cover_photo, documents).
    • A single model can have multiple files attached (has_many_attached).

Example Usage in Rails 8

class User < ApplicationRecord
  has_one_attached :avatar   # Single file
  has_many_attached :photos  # Multiple files
end

When a file is uploaded, an entry is added to active_storage_blobs, and an association is created in active_storage_attachments.

How Rails Queries These Tables

user.avatar # Fetches from `active_storage_blobs` via `active_storage_attachments`
user.photos.each { |photo| puts photo.filename } # Fetches multiple attached files

Conclusion

Rails 8 uses two tables to decouple file storage from model associations, enabling better efficiency, flexibility, and reusability. This structure allows models to reference files without duplicating them, making Active Storage a powerful solution for file management in Rails applications. ๐Ÿš€


Where Are Files Stored in Rails 8 by Default?

By default, Rails 8 stores uploaded files using Active Storage’s disk service, meaning files are saved in the storage/ directory within your Rails project.

Default Storage Location:

  • Files are stored in:
    storage/
    โ”œโ”€โ”€ cache/ (temporary files)
    โ”œโ”€โ”€ store/ (permanent storage)
    โ””โ”€โ”€ variant/ (image transformations like resizing)
  • The exact file path inside storage/ is determined by the key column in the active_storage_blobs table. For example, if a blob entry has: key = 'xyz123abcd' then the file is stored at: storage/store/xyz123abcd

How to Change the Storage Location?

You can configure storage in config/storage.yml. For example:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: your_own_bucket-<%= Rails.env %>

Then, update config/environments/development.rb (or production.rb) to use:

config.active_storage.service = :local  # or :amazon for S3


How to Get the Stored File Path in Rails 8 Active Storage

Since Rails stores files in a structured directory inside storage/, the actual file path can be determined using the key stored in the active_storage_blobs table.

Get the File Path in Local Storage

If you’re using the Disk service (default for development and test), you can retrieve the stored file path manually:

blob = ActiveStorage::Blob.last
file_path = Rails.root.join("storage", "store", blob.key)
puts file_path

๐Ÿ”น Example Output:

/your_project/storage/store/xyz123abcd

๐Ÿ’ก This path is internal and cannot be accessed directly from a browser.


How to Get the File URL

Instead of accessing the internal path, Active Storage provides methods to generate URLs for public access.

1. Generate a URL for Direct Access

If you want a publicly accessible URL, you can use:

Rails.application.routes.url_helpers.rails_blob_url(blob, host: "http://localhost:3000")

๐Ÿ”น Example Output:

http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiO...--filename.jpg

This redirects to the actual file storage location.

2. Get a Temporary Signed URL

For direct storage services like S3 or Google Cloud Storage, you can generate a signed URL:

blob.service_url

๐Ÿ”น Example Output (for S3 storage):

https://your-s3-bucket.s3.amazonaws.com/xyz123abcd?X-Amz-Signature=...

๐Ÿ”น Example Output (for local storage, using Disk service):

http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiO...

This signed URL expires after a set time (default is a few minutes).

3. Get a Variant URL for an Image

If your file is an image and you want a resized version, use:

variant = blob.variant(resize: "300x300").processed
Rails.application.routes.url_helpers.rails_representation_url(variant, host: "http://localhost:3000")

๐Ÿ”น Example Output:

http://localhost:3000/rails/active_storage/representations/abcdxyz.../resize_300x300.jpg

Summary

TaskCommand
Get internal file pathRails.root.join("storage", "store", blob.key)
Get public file URLRails.application.routes.url_helpers.rails_blob_url(blob, host: "http://localhost:3000")
Get signed (temporary) URL (If your model has has_one/many_attached)blob.service_url
Get resized image URLRails.application.routes.url_helpers.rails_representation_url(blob.variant(resize: "300x300").processed, host: "http://localhost:3000")
  • Files are stored in the storage/ directory by default.
  • Use rails_blob_url or service_url to get an accessible URL.
  • Use variant to generate resized versions.
  • For production, it’s best to use a cloud storage service like Amazon S3.

Understanding has_one_attached and has_many_attached in Rails 8

Rails 8 provides a built-in way to handle file attachments through Active Storage. The key methods for attaching files to models are:

  1. has_one_attached โ€“ For a single file attachment.
  2. has_many_attached โ€“ For multiple file attachments.

Let’s break down what they do and why they are useful.

1. has_one_attached

This is used when a model should have a single file attachment. For example, a User model may have only one profile picture.

Usage:

class User < ApplicationRecord
  has_one_attached :avatar
end

How It Works:

  • When you upload a file, Active Storage creates an entry in the active_storage_blobs table.
  • The active_storage_attachments table links this file to the record.
  • If a new file is attached, the old one is automatically replaced.

Example: Attaching and Displaying an Image

user = User.find(1)
user.avatar.attach(io: File.open("/path/to/avatar.jpg"), filename: "avatar.jpg", content_type: "image/jpeg")

# Checking if an avatar exists
user.avatar.attached? # => true

# Displaying the image in a view
<%= image_tag user.avatar.variant(resize: "100x100").processed if user.avatar.attached? %>

2. has_many_attached

Use this when a model can have multiple file attachments. For instance, a Product model may have multiple images.

Usage:

class Product < ApplicationRecord
  has_many_attached :images
end

How It Works:

  • Multiple files can be attached to a single record.
  • Active Storage tracks all file uploads in the active_storage_blobs and active_storage_attachments tables.
  • Deleting an attachment removes it from storage.

Example: Attaching and Displaying Multiple Images

product = Product.find(1)
product.images.attach([
  { io: File.open("/path/to/image1.jpg"), filename: "image1.jpg", content_type: "image/jpeg" },
  { io: File.open("/path/to/image2.jpg"), filename: "image2.jpg", content_type: "image/jpeg" }
])

# Checking if images exist
product.images.attached? # => true

# Displaying all images in a view
<% if product.images.attached? %>
  <% product.images.each do |image| %>
    <%= image_tag image.variant(resize: "200x200").processed %>
  <% end %>
<% end %>

Benefits of Using has_one_attached & has_many_attached

  1. Simplifies File Attachments โ€“ Directly associates files with Active Record models.
  2. No Need for Extra Tables โ€“ Unlike some gems (e.g., CarrierWave), Active Storage doesn’t require additional tables for storing file paths.
  3. Easy Cloud Storage Integration โ€“ Works seamlessly with Amazon S3, Google Cloud Storage, and Azure.
  4. Variant Processing โ€“ Generates resized versions of images using variant (e.g., thumbnails).
  5. Automatic Cleanup โ€“ Old attachments are automatically removed when replaced.

Final Thoughts

Active Storage in Rails 8 provides a seamless way to manage file uploads, integrating directly with models while handling storage efficiently. By understanding how it processes uploads internally, we can better optimize performance and ensure a smooth user experience.

In an upcoming blog, we’ll dive deeper into Turbo Streams and how they enhance real-time updates in Rails applications.

Setup Nginx, SSL , Firewall | Moving micro-services into AWS EC2 instance โ€“ Part 4

Install Nginx proxy server. Nginx also act like a load-balacer which is helpful for the balancing of network traffic.

sudo apt-get update
sudo apt-get install nginx

Commands to stop, start, restart, check status

sudo systemctl stop nginx
sudo systemctl start nginx
sudo systemctl restart nginx

# after making configuration changes
sudo systemctl reload nginx
sudo systemctl disable nginx
sudo systemctl enable nginx

Install SSL – Letsencrypt

Install packages needed for ssl

sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx

Install the SSL Certificate:

certbot -d '*.domain.com' -d domain.com --manual --preferred-challenges dns certonly

Your certificate and chain have been saved at:
   /etc/letsencrypt/live/domain.com/fullchain.pem

Your key file has been saved at:
   /etc/letsencrypt/live/domain.com/privkey.pem
SSL certificate auto renewal

Let’s Encrypt’s certificates are valid for 90 days. To automatically renew the certificates before they expire, the certbot package creates a cronjob which will run twice a day and will automatically renew any certificate 30 days before its expiration.

Since we are using the certbot webroot plug-in once the certificate is renewed we also have to reload the nginx service. To do so append –renew-hook “systemctl reload nginx” to the /etc/cron.d/certbot file so as it looks like this:

/etc/cron.d/certbot
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew --renew-hook "systemctl reload nginx"

To test the renewal process, use the certbot –dry-run switch:

sudo certbot renew --dry-run

Renew your EXPIRED certificate this way:

sudo certbot --force-renewal -d '*.domain.com' -d domain.com --manual --preferred-challenges dns certonly

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.<domain>.com with the following value:

O3bpxxxxxxxxxxxxxxxxxxxxxxxxxxY4TnNo

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

You need to update the DNS txt record for _acme-challenge.<domain>.com

sudo systemctl restart nginx # restart nginx to take effect

Configure the Firewall

Next, weโ€™ll update our firewall to allow HTTPS traffic.

Check firewall status in the system. If it is inactive enable firewall.

sudo ufw status # check status

# enable firewall
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow OpenSSH

Enable particular ports where your micro-services are running. Example:

sudo ufw allow 4031/tcp # Authentication service
sudo ufw allow 4131/tcp # File service
sudo ufw allow 4232/tcp # Search service

You can delete the ‘Authentication service’ firewall rule by:

sudo ufw delete allow 4031/tcp

Setup Ruby, ruby-build, rbenv-gemset | Conclusion – Moving micro-services into AWS EC2 instance โ€“ Part 3

In this post let’s setup Ruby and ruby gemsets for each project, so that your package versions are maintained.

Install ruby-build # ruby-build is a command-line utility for rbenv

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

# Add ruby build path

echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc # OR
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.zshrc

# load it

source ~/.bashrc # OR
source ~/.zshrc


For Mac users – iOS users


# verify rbenv
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/main/bin/rbenv-doctor | bash

If you are using zsh add the following to `~/.zshrc`

# rbenv configuration
eval "$(rbenv init -)"
export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)"

Install Ruby 2.5.1 using rbenv

rbenv install 2.5.1

rbenv global 2.5.1 # to make this version as default

ruby -v # must display 2.5.1 if installed correctly

which ruby # must show the fully qualified path of the executable

echo "gem: --no-document" > ~/.gemrc # to skip documentation while installing gem

rbenv rehash # latest version of rbenv apparently don't need this. Nevertheless, lets use it to avoid surprises.

gem env home # See related details

# If a new version of ruby was installed, ensure RubyGems is up to date.
gem update --system --no-document
๏ปฟ

Install rbenv gemset – https://github.com/jf/rbenv-gemset

git clone git://github.com/jf/rbenv-gemset.git ~/.rbenv/plugins/rbenv-gemset

If you are getting following issue:

fatal: remote error:
  The unauthenticated git protocol on port 9418 is no longer supported.
# Fix
 git clone https://github.com/jf/rbenv-gemset.git ~/.rbenv/plugins/rbenv-gemset

Now clone your project and go inside the project folder -Micro-service folder (say my-project) which has Gemfile in it and do the following commands.

cd my-project

my-project $ rbenv gemset init # NOTE: this will create the gemset under the current ruby version.

my-project $ rbenv gemset list # list all gemsets

my-project $ rbenv gemset active # check this in project folder

my-project $ gem install bundler -v '1.6.0'

my-project $ rbenv rehash

my-project $ bundle install  # install all the gems for the project inside the gemset.

my-project $ rails s -e production # start rails server
my-project $ puma -e production -p 3002 -C config/puma.rb # OR start puma server
# OR start the server you have configured with rails. 

Do this for all the services and see how this is running. The above will install all the gems inside the project gemset that acts like a namespace.

So our aim is to setup all the ruby micro-services in the same machine.

  • I started 10 services together in AWS EC2 (type: t3.small).
  • Database is running in t2.small instance with 2 volumes (EBS) attached.
  • For Background job DB (redis) is running in t2.micro instance.

So for 3 ec2 instance + 2 EBS volumes –$26 + elastic IP addresses ( aws charges some amount – $7.4) 1 month duration, it costs me around $77.8, almost 6k rupees. That means we reduced the aws-cloud cost to half of the previous cost.

Our Challenges with Microservices on AWS ECS

As part of our startup, our predecessors chose to use micro-services for our new website as it is a trending technology.

This decision has many benefits, such as:

  • Scaling a website becomes much easier when using micro-services, as each service can be scaled independently based on its individual needs.
  • The loosely coupled nature of micro-services also allows for easier development and maintenance, as changes to one service do not affect the functionality of other services.
  • Additionally, deployment can be focused on each individual service, making the overall process more efficient.
  • Micro-services also allow for the use of different technologies for each service, providing greater flexibility and the ability to choose the best tools for each task.
  • Finally, testing can be concentrated on one service at a time, allowing for more thorough and effective testing, which can result in higher quality code and a better user experience.

In developing our application with micro-services, we considered the potential problems that we may face in the future. However, it is important to note that we also need to consider whether these problems will have a significant impact compared to the potential disadvantages of using micro-services.

One factor to keep in mind is that our website is currently experiencing low traffic and we are acquiring clients gradually. As such, we need to consider whether the benefits of micro-services outweigh any potential drawbacks for our particular situation.

Regardless, some potential issues with micro-services include increased complexity and overhead in development, as well as potential performance issues when integrating multiple services. Additionally, managing multiple services and ensuring they communicate effectively can also be a challenge.

Despite the benefits of micro-services, we have faced some issues in implementing them. One significant challenge is the increased complexity of deployment and maintenance that comes with having multiple services. This can require more time and resources to manage and can potentially increase the likelihood of errors.

Additionally, the cost of using AWS ECS for hosting all of the micro-services can be higher than using other hosting solutions for a less traffic website. This is something to consider when weighing the benefits and drawbacks of using micro-services for our specific needs.

Another challenge we have faced is managing dependencies between services, which can be difficult to avoid. When one service goes offline, it can cause issues with other services, leading to a “No Service” issue on the website.

Finally, it can be very difficult to go back to a monolithic application even if we combine 3-4 services together, as they may use different software or software versions. This can make it challenging to make changes or updates to the application as a whole.

It is important to carefully consider whether micro-service architecture is the best fit for your business and current situation. If you have a less used website or are just starting your business, it may not be necessary or cost-effective to implement micro-services.

It is important to take the time to evaluate the benefits and drawbacks of using micro-services for your specific needs and budget. Keep in mind that hosting multiple micro-services can come with additional costs, so be prepared to pay a minimum amount for hosting if you decide to go this route.

Ultimately, the decision to use micro-services should be based on a thorough assessment of your business needs and available resources, rather than simply following a trend or industry hype.

Set up:

  • Used AWS ECS (ec2 launch type) with services and task definitions defined
  • 11 Micro-services, 11 containers are spinning
  • Cost: Rs.12k ($160) per month

Workaround:

  • Consider using AWS Fargate type but not sure these issues get resolved
  • Deploy all the services in one EC2 Instance without using ECS

AWS SNS: How to send SMS to a topic, OTP SMS, promotional or transactional SMS

You can send SMS in 2 ways using AWS SNS.

  1. Subscribe to a topic that is already created in AWS SNS and send sms to all numbers who has the subscription.
  2. Send SMS directly to a mobile number.

You can find the following aws doc as a starting point from web and it describes how to create a topic, subscribe a topic and sending sms to the mobile numbers.

https://docs.aws.amazon.com/code-samples/latest/catalog/code-catalog-ruby-example_code-sns.html

For the demonstration purpose, I use Ruby here.

  1. Subscribe to a topic and send SMS
require 'aws-sdk-sns'  # v2: require 'aws-sdk'

sns = Aws::SNS::Resource.new(region: 'us-west-2')

topic = sns.topic('arn:aws:sns:us-west-2:123456789:MySampleTextTopic')

topic.publish({
  message: 'Hello!'
})

This assumes you already created a topic inside your aws console:

Goto AWS Console

  1. Open AWS SNS
  2. Goto Left side -> Mobile -> text messaging sms
  3. Create a topic

You can also follow the above link to perform an API to create topic, subscribe to a topic and send sms

2. Send SMS directly to a mobile number (eg: Send OTP SMS)

Either you can use aws console to send the sms or SNS API.

AWS console:

  1. Open AWS SNS
  2. Goto Left side -> Mobile -> text messaging sms
  3. We are using transactional text messages
  4. Goto publish text message
  5. Select transactional, add mobile number and publish it

Almost all cases we use an API for sending OTP SMS. For that follow the steps.

SNS API – Steps

gem install aws-sdk-sns 
require 'aws-sdk-sns'
otp = generate_otp
set_sns_client
set_sns_client_attrs
response = publish_sms(otp)
  • Generate an OTP
def generate_otp
    (1000..9999).to_a.sample
end
  • Set SNS client
def set_sns_client
    @sns_client = Aws::SNS::Client.new(
      region: ENV['AWS_SNS_REGION'],
      access_key_id: ENV['AWS_SNS_ACCESS_KEY'],
      secret_access_key: ENV['AWS_SNS_SECRET_KEY']
    )
end

  • Set SNS client attributes
 def set_sns_client_attrs
    @sns_client.set_sms_attributes({
                                     attributes: {
                                       'DefaultSenderID' => SENDER_ID,
                                       'DefaultSMSType' => SMS_TYPE
                                     }
                                   })
end

  • Publish SMS
def publish_sms(otp)
    @sns_client.publish({
                          phone_number: @mobile_no,
                          message: "#{OTM_MSG} #{otp}"
                        })
  end

Here ,

OTM_MSG: ‘Your OTP for login is’

SMS_TYPE : ‘Transactional’ or ‘Promotional’

If you want to send OTP, then it is ‘Transactional’. Else if you want to send some promotional sms of your product then it is ‘Promotional’

SENDER_ID: is the sms header that you already registered with TRAI.

The Steps to add Sender ID in AWS is given below:

For example suppose your sender id is: Zomato

Follow the steps to add our SENDER ID – Zomato to AWS SNS

  1. Sign in to the Amazon SNS console – https://console.aws.amazon.com/sns/home
  2. On the navigation panel, choose Mobile, Text messaging (SMS).
  3. On the Mobile text messaging (SMS) page, in the Text messaging preferences section, choose Edit.
  4. On the Edit text messaging preferences page, in the Details section, do the following:
  5. For Default sender ID , enter the provided sender ID to be used (Zomato) as the default for all messages from your account.
  6. Choose Save changes.

If you don’t want to register sender id, then skip this method: set_sns_client_attrs and publish the sms. It take the sms as ‘Promotional’ and sender id will be 8 character random number. Amazon use this type of sms from International route and it costs you almost $0.02 (Rs. 1.5) per sms. Very high rate. So I recommend to register any sender id that resembles your product or company name, from Jio trueconnect (that is free, link given below) and use it in SNS.

If you don’t know how to register sender id, follow this:

For AWS SNS service, there is 2 way of sending sms.

  1. Local route
  2. International route

For local route the price is Rs. 0.20 per sms
For international route the price will be Rs 1.58 per sms – too high

by default AWS SNS use International route

If you are from India follow the TRAI registration
For considering local route we have to register our use case and message templates with TRAI .

So first register here:
https://www.vilpower.in/
as an enterprise / company with all company details and our purpose

These registration requirements are designed to reduce the number of unsolicited messages that Indian consumers receive, and to protect consumers from potentially harmful messages

You can Register in Jio for free:

The link to register:
https://trueconnect.jio.com/#/home/entity-registration
Select Principal entity and continue

Recently Indian Govt made DLT Registration mandatory for sending sms.

Example:
Take Msg from AD-ZOMATO , here ZOMATO is 6 char sender id that we can give in the service provider and send sms before. But now we have to register this in DLT then only our service provider can use this.

After registering DLT we get an ENTITY ID. This entity id need to be attached in our’s otp service provider for sending otp msgs.

If you are using SNS service for the first time you should increase your SMS quota:

AWS says:

If you're new to SMS messaging with Amazon SNS, request a monthly SMS spending threshold that meets the expected demands of your SMS use case. By default, your monthly spending threshold is $1.00 (USD). You can request to increase your spending threshold in the same support case that includes your request for a sender ID

Because Amazon SNS is a distributed system, it stops sending SMS messages within minutes of the spending quota being exceeded. During this period, if you continue to send SMS messages, you might incur costs that exceed your quota.

https://docs.aws.amazon.com/sns/latest/dg/channels-sms-awssupport-sender-id.html

Requesting increases to your monthly SMS spending quota for Amazon SNS:

https://docs.aws.amazon.com/sns/latest/dg/channels-sms-awssupport-spend-threshold.html

Currently, Amazon SNS supports SMS messaging in the following AWS Regions:

https://docs.aws.amazon.com/sns/latest/dg/sns-supported-regions-countries.html

Reference:

https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Aws/SNS/Client.html