DEV Community

Cover image for Hash Replacement with `sub` and `gsub` in Ruby on Rails
reinteractive
reinteractive

Posted on

Hash Replacement with `sub` and `gsub` in Ruby on Rails

Credited to: Suman Awal

sub/gsub is the widely used substitution method in ruby. These methods replace (substitute) content of the string with the
new string based on the provided logic. In SAAS application, we offen encounter the condition where we need to generate dynamic content for a single action based on the customer. For example generating a dynamic welcome message to the customer for the different client. There are lots of ways to get the result however in this article we will use the one of the mostly used ruby method sub and gsub

sub and gsub ruby methods

Before we get started, let's understand what sub and gsub do:

  • sub: Replaces the first occurrence of pattern in a string with replacement string.
  • gsub: Replaces all occurrences of pattern in a string with replacement string.

Both methods use a regular expression as the pattern and a string or a block as the replacement. Here we will explain using a block (hash) for dynamic replacement based on our hash.

Here's a simple example:

replacements = {
  'name' => 'Glenn Maxwell',
  'country' => 'Australia'
}

template = "Hi, my name is {{name}} and I am from {{country}}."

result = template.gsub(/{{(.*?)}}/) { |match| replacements[$1] || match }

puts result # Output: "Hi, my name is Glenn Maxwell and I am from Australia"
Enter fullscreen mode Exit fullscreen mode

In this example:

  1. We define a replacements hash containing the key-value pairs we want to use for the replacement in the string.
  2. We define a template string containing placeholders enclosed in double curly braces ({{}}).
  3. We use gsub with the regular expression /{{(.*?)}}/ to find all occurrences of these placeholders.
  4. The block is executed for each match. Inside the block:

Using sub for Single Replacements

If you only need to replace the first occurrence of a pattern, you can use sub instead of gsub. The logic remains the same.

replacements = {
  'name' => 'Glenn Maxwell'
}

template = "Hi, my name is {{name}} and my friend's name is also {{name}}."
result = template.sub(/{{(.*?)}}/) { |match| replacements[$1] || match }
# Output: Hi, my name is Glenn Maxwell and my friend's name is also {{name}}.
Enter fullscreen mode Exit fullscreen mode

Real-World Rails Examples

This technique is useful in various Rails scenarios:

  • Generate dynamic emails: You can store email templates with placeholders in your database and replace them with user-specific data.
  • Create dynamic reports: Generate reports with data pulled from various sources, using a hash to map placeholders to the correct values.
  • Localize content: Store localized strings in a hash and replace placeholders in your views based on the user's locale.

Here you can find one of the widely used example to Generate dynamic emails for the SAAS application.

Generate dynamic emails using hash replacement

Scenario

You have a Rails application that serves multiple clients. Each client has their own set of customers. When a new customer registers for a specific client, the application sends a welcome email. The content of the welcome email is dynamically generated based on a template stored in the database, which is specific to each client.

Sample codes

  • Create models
  # app/models/client.rb
  class Client < ApplicationRecord
    has_many :customers
    has_one :welcome_email_template
  end

  # app/models/customer.rb
  class Customer < ApplicationRecord
    belongs_to :client
  end

  # app/models/welcome_email_template.rb
  class WelcomeEmailTemplate < ApplicationRecord
    belongs_to :client
  end
Enter fullscreen mode Exit fullscreen mode
  • Migrations
  # db/migrate/xxxxxx_create_clients.rb
  class CreateClients < ActiveRecord::Migration[7.1]
    def change
      create_table :clients do |t|
        t.string :name
        t.string :subdomain # For identifying clients (e.g., client1.example.com)

        t.timestamps
      end
    end
  end

  # db/migrate/xxxxxx_create_customers.rb
  class CreateCustomers < ActiveRecord::Migration[7.1]
    def change
      create_table :customers do |t|
        t.string :email
        t.string :name
        t.references :client, foreign_key: true

        t.timestamps
      end
    end
  end

  # db/migrate/xxxxxx_create_welcome_email_templates.rb
  class CreateWelcomeEmailTemplates < ActiveRecord::Migration[7.1]
    def change
      create_table :welcome_email_templates do |t|
        t.references :client, foreign_key: true
        t.text :template # The email template with placeholders

        t.timestamps
      end
    end
  end
Enter fullscreen mode Exit fullscreen mode
  • Database seed
# db/seeds.rb
Client.destroy_all
Customer.destroy_all
WelcomeEmailTemplate.destroy_all

client1 = Client.create!(name: 'Client One', subdomain: 'client1')
client2 = Client.create!(name: 'Client Two', subdomain: 'client2')

WelcomeEmailTemplate.create!(
  client: client1,
  template: "Welcome, {{customer_name}}!\n\nThank you for joining Client One. Your account has been created.\n\nBest regards,\nThe Client One Team"
)

WelcomeEmailTemplate.create!(
  client: client2,
  template: "Hello {{customer_name}},\n\nWelcome to Client Two! We're excited to have you on board.\n\nSincerely,\nThe Client Two Team"
)
Enter fullscreen mode Exit fullscreen mode
  • Customer Registration
# app/controllers/customers_controller.rb
class CustomersController < ApplicationController
  before_action :set_client

  def new
    @customer = @client.customers.build
  end

  def create
    @customer = @client.customers.build(customer_params)

    if @customer.save
      send_welcome_email(@customer)
      redirect_to root_path, notice: 'Customer registered successfully!'
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_client
    # Assumes you have a way to identify the client, e.g., via subdomain
    @client = Client.find_by(subdomain: request.subdomain)
    unless @client
      render plain: "Client not found", status: :not_found
    end
  end

  def customer_params
    params.require(:customer).permit(:email, :name)
  end

  def send_welcome_email(customer)
    template = @client.welcome_email_template.template
    welcome_message = generate_welcome_message(customer, template)
    CustomerMailer.welcome_email(customer, welcome_message).deliver_later
  end

  def generate_welcome_message(customer, template)
    replacements = {
      'customer_name' => customer.name
    }

    template.gsub(/{{(.*?)}}/) { |match| replacements[$1] || match }
  end
end
Enter fullscreen mode Exit fullscreen mode
  • Routes
# config/routes.rb
constraints subdomain: 'client1' do
  scope module: 'client1', as: 'client1' do
    resources :customers, only: [:new, :create]
  end
end

constraints subdomain: 'client2' do
  scope module: 'client2', as: 'client2' do
    resources :customers, only: [:new, :create]
  end
end

# Non-subdomain routes (e.g., for admin panel)
resources :clients
Enter fullscreen mode Exit fullscreen mode

This example provides a basic application for handling multiple clients with customized welcome messages for their customer.

Conclusion

Using sub and gsub with hash replacement provides a flexible and efficient way to dynamically generate string and can be used in real application.

Top comments (0)