# Welcome

Welcome to the [Lightning Rails](https://lightningrails.com) Wiki! This documentation serves as a comprehensive guide for developers looking to kickstart their projects with our Ruby on Rails boilerplate.

Lightning Rails provides a **set of pre-configured tools and design components**, so you can launch your MVP at lightning speed. Whether you're a **beginner or intermediate developer**, this wiki will help you navigate through the features and functionalities of Lightning Rails, ensuring a smooth development experience.

Explore the sections to find installation instructions, configuration details, troubleshooting tips, and much more.

Happy Creating!

***

**Current Version 1.5  (30/06/2025)**

> **Ruby:** 3.3.6 (Previously 3.3.5)
>
> **Rails:** 8.0.0 (Previously 7.0.8.4)
>
> **Bundler:** 2.5.2p
>
> **Postgresql**: 14.8
>
> **tailwindcss:** v4.1.10
>
> **DaisyUI:** 5.0.43
>
> **Puma:** 6.5.0

### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Access Lightning Rails, The best Ruby on Rails Boilerplate for solopreneurs.</td><td></td><td><a href="/files/KJYbXJksKJpLQ8uCjepF">/files/KJYbXJksKJpLQ8uCjepF</a></td><td></td><td></td></tr><tr><td><strong>Getting Started</strong></td><td>Clone the repo and get your server started</td><td><a href="/files/EQLr6OiEYNhp3a4gTKtG">/files/EQLr6OiEYNhp3a4gTKtG</a></td><td></td><td><a href="/pages/Zvukm7MAGL6ooitMTmpY">/pages/Zvukm7MAGL6ooitMTmpY</a></td></tr><tr><td><strong>Themes and styling</strong></td><td>Make the project your own by defining a theme and landing page components</td><td><a href="/files/9e15j3cwiVJRk42GyABp">/files/9e15j3cwiVJRk42GyABp</a></td><td></td><td><a href="/pages/JjjojIyKxaiBPzzLwvtg">/pages/JjjojIyKxaiBPzzLwvtg</a></td></tr></tbody></table>


# Quickstart

{% embed url="<https://www.youtube.com/watch?v=Tq6-evYIARA>" %}
Quick demo
{% endembed %}

After accepting the Github Invite, Follow these three easy steps to quickly set up your new Ruby on Rails application using the Lightning Rails boilerplate.

{% hint style="info" %}
If you doubt you have the correct Ruby on Rails Setup, you can refer to [Le Wagon setup guide](https://github.com/lewagon/setup), one of the most complete.
{% endhint %}

### Step 1: Clone the Repository

First, clone the Lightning Rails repository to your local machine. Open your terminal and run:

```bash
gh repo clone LightningRails/lightning-rails mysupercoolapp
```

{% hint style="info" %}
If you are getting `ERROR: Repository not found. fatal: Could not read from remote repository.`

`Make sure that you have accepted the Github Invitation on your email. Or that the github account in your terminal is the same you signed up with.`
{% endhint %}

This creates a clone of the lightning rails repo, which is great! But we only want the source code, not the GitHub origin or all your code would be pushed to the Source repo 🫣

Let's fix it by removing the Origin and creating a new one...

### Step 2: Run the setup script

After cloning the repository, navigate to the project directory:

```bash
cd mysupercoolapp
```

This 1 min script will ask you information about your project, if you don't have the data, don't worry, you can always go back and fill it in the `meta.yml` file.

```bash
bash generators/setup.sh
```

### Step 3: Start the Server 🚀

Now you are ready to start your development server:

```
bin/dev
```

We use `bin/dev` instead of `rails s` so that Tailwind.css is compiled along with the server.

And that's it, enjoy the beautiful look of your new Rails app starter kit.


# Themes

How to change the branding of your project from a list of 20+ options by changing one word.

{% embed url="<https://youtu.be/zCKpxC96ROU>" %}

### **Tailwind CSS 4 Setup  🆕**

LightningRails uses Tailwind CSS 4 with the tailwindcss-rails gem for optimal performance and a better development experience. The configuration is located in app/assets/tailwind/application.css.Key benefits of this setup:

* Faster build times
* Better integration with Rails asset pipeline
* More flexibility for custom configurations
* Automatic purging of unused CSS

### Changing Themes with DaisyUI

DaisyUI offers a variety of themes that you can easily apply to your Ruby on Rails application. To change the theme with DaisyUI default configuration, you would need to add and change `data-theme` attribute in the `<html>` tag of your `application.html.erb` layout file.&#x20;

However, Lightning Rails comes by default with a theme toggle for dark and light theme. To change the tehme follow these steps:

#### (Option A) Change the Dark/Light Theme

1. Open your `config/meta.yml` file.
2. Locate the `LIGHT_THEME` and `DARK_THEME` meta's
3. Update value with the desired theme name. For example:

```yaml
# config/meta.yml
LIGHT_THEME: "cupcake"
DARK_THEME: "forest"
```

You can replace "cupcake" with any theme name provided by DaisyUI.

{% hint style="info" %}
Don't forget to **restart your server** for every change you do int the meta.yml 💡
{% endhint %}

#### (Option B) Change the config to only have one Theme

1. Comment out or delete the theme toggle in `_navbar.html.erb`&#x20;
2. Comment out or delete the variable in meta.yml:
3. in `application.html.erb` replace:&#x20;
   1. <pre data-overflow="wrap"><code>&#x3C;html data-light-theme="&#x3C;%= DEFAULT_META['LIGHT_THEME'] %>" data-dark-theme="&#x3C;%= DEFAULT_META['DARK_THEME'] %>">
      </code></pre>
   2. By your theme of choice: `<html data-theme="cupcake">`
4. Comment out or delete the `theme_controller.js` file..

Et voila, those are the two ways of changing your theme with Lightning Rails, either by keeping the Dark/light logic or removing it and changing a single theme at a time.&#x20;

#### Available Themes

To see the different themes you can choose from, check the DaisyUI documentation or their themes page below:

[Check DaisyUI Themes](https://daisyui.com/docs/themes/)

<figure><img src="/files/zvgi6KL7emN5WOG0yIxr" alt=""><figcaption><p>All the themes available from DaisyUI and therefore LighningRails</p></figcaption></figure>

### How do we change the theme?

In `application.html.erb` by changing the `data-theme` attribute in the `<body>` tag, you can quickly switch between these themes, allowing for a customizable look and feel for your application!

### Can we change other Tailwind libraries?

Most definitely! DaisyUi has a lot of components but frankly lacks pre-designed sections, for us developers who don't want to spend hours creating for scratch. You can easily find more Tailwind components in the [HyperUI](https://www.hyperui.dev/) library.

<figure><img src="/files/1tKROHtludoOATQXfQzi" alt=""><figcaption><p>HyperUI library with many components</p></figcaption></figure>

Find a component you like? Just copy/paste it into your LightningRails project.

{% hint style="info" %}
**Pro Tip:** Avoid hard-coded color classes in non-DaisyUI components (like HyperUI). For example, using a class like `bg-white` won’t adapt to theme changes. This can result in a dark theme with a mismatched white background—definitely not ideal. 😅
{% endhint %}

### Custom Theme

If you wish to add a custom theme or modify an existing one. We won't be able to do it on the tailwind config file. However, Lightning Rails now uses Tailwind CSS 4 with the tailwindcss-rails gem, which provides better performance and more flexibility than the CDN approach. DaisyUI is integrated via CDN import in the Tailwind configuration.

Here is how to create your custom theme:

#### Custom Theme Colors

In app/assets/tailwind/application.css, create a custom theme using the new Tailwind CSS 4 syntax:

```css
/* tailwind/application.css */

@import "tailwindcss" source(none);
@source "../../../public/*.html";
@source "../../../app/helpers/**/*.rb";
@source "../../../app/javascript/**/*.js";
@source "../../../app/views/**/*";

@plugin "./daisyui.js";

/* Optional for custom themes – Docs: https://daisyui.com/docs/themes/#how-to-add-a-new-custom-theme */
@plugin "./daisyui-theme.js"{
  /* custom theme here */
  name: "lightning-theme";
  default: true; /* set as default */
  prefersdark: false; /* set as default dark mode (prefers-color-scheme:dark) */
  color-scheme: light; /* color of browser-provided UI */

  --color-base-100: oklch(98% 0.02 240);
  --color-base-200: oklch(95% 0.03 240);
  --color-base-300: oklch(92% 0.04 240);
  --color-base-content: oklch(20% 0.05 240);
  --color-primary: oklch(55% 0.3 240);
  --color-primary-content: oklch(98% 0.01 240);
  --color-secondary: oklch(70% 0.25 200);
  --color-secondary-content: oklch(98% 0.01 200);
  --color-accent: oklch(65% 0.25 160);
  --color-accent-content: oklch(98% 0.01 160);
  --color-neutral: oklch(50% 0.05 240);
  --color-neutral-content: oklch(98% 0.01 240);
  --color-info: oklch(70% 0.2 220);
  --color-info-content: oklch(98% 0.01 220);
  --color-success: oklch(65% 0.25 140);
  --color-success-content: oklch(98% 0.01 140);
  --color-warning: oklch(80% 0.25 80);
  --color-warning-content: oklch(20% 0.05 80);
  --color-error: oklch(65% 0.3 30);
  --color-error-content: oklch(98% 0.01 30);

  /* border radius */
  --radius-selector: 1rem;
  --radius-field: 0.25rem;
  --radius-box: 0.5rem;

  /* base sizes */
  --size-selector: 0.25rem;
  --size-field: 0.25rem;

  /* border size */
  --border: 1px;

  /* effects */
  --depth: 1;
  --noise: 0;
}
```

Then call the new theme in your `meta.yml`

```css
/* config/meta.yml */

LIGHT_THEME: "lightning-theme"
DARK_THEME: "forest"

```

See all the DaisyUI variables you can customize in your custom theme [here](https://daisyui.com/docs/utilities/#component-specific-css-variables).

#### Customize fonts

To customize the default fonts of your projects, import the custom fonts  in `tailwind/application.css` and assign them to your headers and paragraphs in the same file.

```css
/* tailwind/application.css */

@import url('https://fonts.googleapis.com/css2?family=Jersey+15&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Pacifico&display=swap');

h1, h2, h3 {
  font-family: "Pacifico", serif;
  font-weight: 400;
  font-style: normal;
}
```


# Customize the views

How to change the views using LightningRails partials/components

## Using Tailwind Front-end Components in Your Ruby on Rails Application

In our Ruby on Rails application, we utilize Tailwind CSS for styling, complemented by components from the DaisyUI library. You will find the custom Tailwind components organized in the `views > components` folder.

### Using DaisyUI Components

<figure><img src="/files/EVKN3vEOrQsOjOc7L6ne" alt=""><figcaption></figcaption></figure>

We use DaisyUI to enhance our Tailwind components with pre-designed UI elements. DaisyUI provides various components that you can easily integrate into your application, allowing for rapid and responsive UI design.

You can explore the full range of DaisyUI components on their website: [DaisyUI](https://daisyui.com/).

Feel free to check out their library and incorporate any components that fit your design needs into our application!

***

### Rendering Tailwind Components included by default in Lightning Rails

To include Tailwind components in your views, simply use the `render` ERB tag. This makes it super easy to integrate components throughout your application.

How easy is it? The entire LightningRails home page is built with just 7 lines of code (already included in the boilerplate) 🤯:

<pre class="language-css"><code class="lang-css"><strong># views/home.html.erb
</strong><strong>&#x3C;%= render "components/hero" %>
</strong>&#x3C;%= render "components/featured_on" %>
&#x3C;%= render "components/text_image" %>
&#x3C;%= render "components/feature_tabs" %>
&#x3C;%= render "components/price_cards" %>
&#x3C;%= render "components/faq" %>
&#x3C;%= render "components/cta_horizontal" %>
</code></pre>

#### (Optional) - Passing Instance Variables to Components

If you have instance variables in your controller and want to pass them to a button component, here's how you can do it:

1. **Pass any variable to the component:**

   When rendering the button component in your view:

   ```erb
   <%= render 'components/button', locals: { label: @button_label, color: @button_color } %>
   ```
2. **Access the variables in your component:**

   In your `_button.html.erb` partial, you can now use the `label` and `color` variables to customize the button:

   ```erb
   <button class="<%= color %> text-white font-bold py-2 px-4 rounded">
     <%= label %>
   </button>
   ```

###


# Lightning Landing

This doc will guide through creating a simple landing page for your portfolio or business with Lightning Landing, the landing page boilerplate that can be hosted for FREE on Github pages.

#### <mark style="color:blue;">Checkout our DEMO:</mark>

{% embed url="<https://lightningrails.github.io/lightning-landing/>" %}
Lightning Landing Demo
{% endembed %}

**Access the repo:**

{% embed url="<https://lightningrails.com/lightning-landing>" %}

**Current Version 1.0 (01/12/2024)**

🎁 100% Free tech stack

> **HTML:** Landing page structure
>
> **DaisyUI: T**ailwind library for Styling
>
> **Github:** Free hosting for life
>
> **Get Waitlist:** Free email collection & CRM


# Quickstart

How to get started with Landing Lightning

### Step 1: Get access to the repository

Create a Lightning Rails account on the offical website. As the account is created with Github Oauth, you will receive an automatic invitation to [Lightning Landing Repo](https://github.com/LightningRails/lightning-landing).

{% embed url="<https://lightningrails.com>" %}
Create an account here.
{% endembed %}

After accepting the Github Invite, Follow these steps to quickly set up your new portfolio/landing page using the Lightning Landing boilerplate.

### Step 1: Clone the Repository

First, clone the [Lightning Landing repository](https://github.com/LightningRails/lightning-landing) to your local machine. Open your terminal and run:

```bash
gh repo clone LightningRails/lightning-landing mysupercoolpage
```

{% hint style="info" %}
If you are getting `ERROR: Repository not found. fatal: Could not read from remote repository.`

`Make sure that you have accepted the Github Invitation on your email after creating the account License. Or that the github account in your terminal is the same you signed up with.`
{% endhint %}

This creates a clone of the lightning landing repo, which is great! But we only want the source code, not the GitHub origin or all your code would be pushed to the source repo 🫣

Let's fix it by removing the Origin and creating a new one...

### Step 2: Remove and change the remote

After cloning the repository, navigate to the project directory:

```bash
cd mysupercoolpage
```

First, let's remove the source code remote in your terminal:

```bash
git remote remove origin
```

Then we will create a new repository in our GitHub account and assign the current directory as source.

```bash
 gh repo create --public --source=.
```

Commit and push your first commit

```bash
ga .
gc -m "First commit"
git push origin master
gh repo view -w
```

Start your server in your terminal:

```bash
serve
```

Et Voila! You officially have the Lightning Landing template on your own GitHub account. Now let's look at how fast we can build a static landing page.

### Optional: Create a Get Waitlist account

If you wish to gather emails to validate your next SaaS business, you can create a get waitlist account and add the button in your Landing Page CTA's

{% embed url="<https://getwaitlist.com/>" %}


# Theme and branding

Make Lightning landing your own. Choose one of the 24 availables themes and leave the branding hassle for later.

## Available Themes

To see the different themes you can choose from, check the DaisyUI documentation or their themes page below:

[Check DaisyUI Themes](https://daisyui.com/docs/themes/)

<figure><img src="/files/zvgi6KL7emN5WOG0yIxr" alt=""><figcaption><p>All the themes available from DaisyUI and therefore LighningRails</p></figcaption></figure>

### How do we change the theme?

In `index.html` by changing the `data-theme` attribute in the `<body>` tag, you can quickly switch between these themes.

<figure><img src="/files/EV2Tgu8ugmzqhAbFF2H2" alt=""><figcaption></figcaption></figure>

### Can we change other Tailwind libraries?

Most definitely! DaisyUi has a lot of components but frankly lacks pre-designed sections, for us developers who don't want to spend hours creating from scratch. You can easily find more Tailwind components in the [HyperUI](https://www.hyperui.dev/) library.

### Can I add my custom theme?

Of course! Just add this code before your closing \</body> tag and adapt the variables to your taste:

```html
<script>
  tailwind.config = {
    theme: {
      extend: {
        fontFamily: {
          sans: ['Inter', 'sans-serif'], // Set your custom font as the default
        },
      },
    },
    plugins: [daisyui],
    daisyui: {
      themes: [
        {
          customtheme: {
            "primary": "#4CAF50",
            "secondary": "#FFC107",
            "accent": "#673AB7",
            "neutral": "#333333",
            "base-100": "#FFFFFF",
            "info": "#2196F3",
            "success": "#4CAF50",
            "warning": "#FF9800",
            "error": "#F44336",
            "fontFamily": "sans", // Use the extended font in the theme
          },
        },
      ],
    },
  };
</script>

```

That's it for branding and theme setup, next you will need to adapt the sections of the landing page.

### Custom Logo & Favicon

To make the landing page your own. You will need to change the logo file in `images/logo.png` by your logo. Feel free to create your custom Logo with [Logofa.st.](https://shipfa.st/tools/logo-fast)

\
Then, change the favicon in `images/favicon.ico` by your own. You can create your own favicon in this [favicon generator.](https://favicon.io/)

Happy UI design! 🎨


# Page structure

Let's decide what makes a great landing page, do we want Testimonials? Pricing? ....

{% hint style="info" %}
Lightning Landing comes with one landing page design by default. All the sections are carefully designed and placed to guide the visitor toward converting into a user or potential customer.\
\
Please feel free to change any existing section with any other design from the following libraries, The boilerplate has DaisyUI and Tailwind by default so any of the components can easily be copy-pasted:<br>

* [**DaisyUI**](https://daisyui.com/components) components library
* [**HyperUI** ](https://www.hyperui.dev/)Tailwind components library
  {% endhint %}

### Navigation Bar

<figure><img src="/files/75OrJPBteRXhZfrOqRvB" alt=""><figcaption><p>Navbar in Lightning Landing boilerplate</p></figcaption></figure>

The fixed navigation bar stays at the top of the page and contains your logo, company name, and main menu items.

{% hint style="success" %}
Pro Tips:

* Keep menu items minimal (3-4 items) to avoid overwhelming visitors
* Use clear, action-oriented labels
* Include your logo and company name for immediate brand recognition
  {% endhint %}

### Hero Section

<figure><img src="/files/l3mz9w1Ypg4NYS6vVoLy" alt=""><figcaption><p>Example Hero in Lightning Landing</p></figcaption></figure>

The hero section is the first thing visitors see. Lightning Landing comes with a default split text image hero.&#x20;

{% hint style="success" %}
Pro Tips:

* A compelling headline focused on value proposition
* A brief supporting description
* Primary call-to-action button
* Social proof elements (user avatars and trust indicators)
* Hero image showcasing your product/service **Tip**: Focus on benefits rather than features in your headline. Use action-oriented language that speaks directly to your target audience's needs
  {% endhint %}

### Problem Statement

<figure><img src="/files/n7Et2A2zcTZIOSqP6Nwn" alt=""><figcaption><p>Problem statement section in Lightning Landing</p></figcaption></figure>

This section highlights the problem your service solves. It comes in second position on the landing page, so users identify with the pain.

{% hint style="success" %}
Pro Tips:

* Uses data and statistics to build credibility
* Presents the market average cost
* Includes a visual representation of the problem **Tip**: Use specific numbers and data points to make your argument more convincing and relatable.
  {% endhint %}

### Services/Benefits

<figure><img src="/files/LzLwTLKuAtuHlK9XLlLe" alt=""><figcaption><p>Services section on Lightning Landing</p></figcaption></figure>

A visual grid showcasing your key services or benefits. Use descriptions and visual elements like screenshots or illustrations.

{% hint style="success" %}
Pro Tips:

* Includes high-quality images for each service. Use real project screenshots or high-quality stock photos that align with your services
  {% endhint %}

### Pricing Section

<figure><img src="/files/sW4PzG9jfzKiKtuiClhT" alt=""><figcaption><p>Pricing section in Lightning Landing</p></figcaption></figure>

Many startup pages hide their pricing. This can be frustrating for users who come to your site to assess whether you are the correct option for them. So, if you believe you have competitive pricing, don't hesitate to add it to the homepage. It might increase your conversion rates.

{% hint style="success" %}
Pro Tips:

* Single, focused package to reduce decision paralysis
* Detailed list of included features
* Prominent display of price point
* Clear call-to-action **Tip**: Highlight the value proposition by comparing your price to the market average
  {% endhint %}

### Testimonials

<figure><img src="/files/Sg3pKQJB5DJ9bd3LE6jr" alt=""><figcaption><p>Testimonials section in Lightning Landing</p></figcaption></figure>

Social proof section featuring customer reviews, if you can get videos on these testimonial cards, your social proof will be as strong as possible.

{% hint style="success" %}
Pro Tips

* Individual cards for each testimonial
* Customer photos and ratings, videos are even better
* Use real customer photos and testimonials whenever possible. If using placeholders, ensure they look authentic and diverse
  {% endhint %}

### FAQ Section

<figure><img src="/files/YXYWaRcHLUcOC69E3fIn" alt=""><figcaption><p>FAQ section in Lightning Landing</p></figcaption></figure>

Addresses common customer questions, this section is often placed right after the pricing section as it should answer the exact questions that arose from looking at your pricing description. It should do the trick to convert the visitor into a user by clearing any doubts they might have.

{% hint style="success" %}
Pro Tips:

* Base your FAQ on real customer questions and update it regularly as new questions arise
  {% endhint %}

### Final CTA

<figure><img src="/files/U9izr0D8YkvRIBO9D0WP" alt=""><figcaption><p>Final CTA sction in Lightning Landing</p></figcaption></figure>

This is the last chance to convert visitors, remind the visitors of your value proposition, and make a clear call to action.

{% hint style="success" %}
Pro Tips:

* Clear, compelling headline
* Urgency-creating subtext
* Prominent call-to-action button
* Reiterate your main value proposition and create urgency with limited-time offers or exclusive benefits
  {% endhint %}

### Footer

<figure><img src="/files/GMRGhmfrxtGuCOJHV5hP" alt=""><figcaption><p>Footer in Lightning Landing boilerplate</p></figcaption></figure>

Contains essential links and information.

{% hint style="success" %}
Pro Tips:

* Company information
* Social media links
* Brand elements
* Keep it simple
  {% endhint %}

***

\
That's it for the HTML structure of your landing page.

Nest, we will look at how to publish your Landing page for free on Github 🚀


# Publish your landing page

Let's publish our landing page for free forever.

### 1. Enable GitHub pages

After pushing your latest changes, go to the project Settings > Pages

<figure><img src="/files/SLPYpPIfKKnfgHhYEK2u" alt=""><figcaption><p>Project settings > pages</p></figcaption></figure>

Once in the page settings, select a branch that will be the source of your page. In our case, we will choose `master`.

Now you can click on "visit site, and you will have your landing page hosted on GitHub servers For Free, Forever. No more paying 14€ to Wix or Squarespace for a simple landing page.

### 2. Add a custom Domain

If you want a professional landing page, We strongly suggest adding a custom domain, it's as easy as adding the custom domain on the GitHub page settings:

<figure><img src="/files/6iI60qKgQxygVtH0II09" alt=""><figcaption><p>Add a custom domain</p></figcaption></figure>

Et Voila, every time you modify your HTML and push to the GitHub repo, your website will get updated. 🤝

### Going Further

If you liked this simple boilerplate, check Lightning Rails, the best Ruby on Rails boilerplate to build SaaS web apps in no time.

{% embed url="<https://lightningrails.com>" %}


# AI & code (MCPs)

In this section I will guide you towards my favorite MCP's for coding faster and better code.


# Context7 MCP (beta)

How to use Context 7 MCP to increase your AI code quality.

### What is Context7 MCP?

We all know that writing code with AI clients like Cursor or Claude Code can lead to a never ending loop of bugs.&#x20;

"You broke it again!", "This is not how you initialize the class, FOCUSSSSS!" <- This used to be me after a whole morning of letting Claude code for me 😅

These bugs often come from a lack of context given to the LLM, for example if you ask it to implement a feature with the Ruby LLM gem, it might not have the latest version of the documentation and suggest an older syntax. The solution? connect the documentation directly in the LLM's chat thanks to [Context7 MCP.](https://context7.com/)

In this library you will find thousands of documentation ready to be used.

<figure><img src="/files/eoLOVD591TJE4uErwczj" alt=""><figcaption></figcaption></figure>

All the documentation I code with is related to the  Lightning Rails Stack:

* Ruby on Rails
* Lightning Rails (Of course ☺️ )
* Lucide rails
* Tailwind CSS
* DaisyUI
* ...

### How to Use Context7 MCP with Cursor AI?

<figure><img src="/files/6PXBcPd618DyG7mMqz7J" alt=""><figcaption></figcaption></figure>

1. First we will head to <https://docs.cursor.com/tools/mcp>
2. Search for context7
3. Click on "+ Add context7 to cursor"
4. And voila, you should see a new MCP in your cursor settings with 2 new tools.


# Images & media

## Image Processing and Storage with Cloudinary

<figure><img src="/files/89gkl0gOQLGD0BvonweR" alt=""><figcaption><p>Cloudinary gives you many credits for free, renewed monthly. Basically Free for MVPs 🍻</p></figcaption></figure>

Welcome to the Image Processing and Storage section of our wiki! In this guide, we will explore how to efficiently handle image management within our application using Cloudinary.

Cloudinary is a powerful cloud-based service that provides an extensive suite of tools for image upload, storage, and manipulation. It allows developers to easily integrate image processing capabilities into their applications, making it simple to transform, resize, and optimize images on-the-fly. And the best part is it has a very generous free plan as the free credits renew monthly 🎉

By leveraging Cloudinary, we can enhance our application's performance and improve the user experience through faster image delivery and efficient management. Let's get started!

### Setting up Cloudinary in Your Application

To get started with Cloudinary, you need to [create an account on their website](https://cloudinary.com/), find your API key, and add it to your .env file in your LightningRails application.

```
# .env
CLOUDINARY_URL=cloudinary://298522699261255:***********************8@Qa1ZfO4syfbOC # Your Key
```

### Installing Active Records

Active Storage enables the upload of files to cloud storage services, such as Cloudinary, and allows you to link these files to Active Record models.

```
rails active_storage:install
rails db:migrate
```

This sets up two database tables to manage the relationships between files uploaded to Cloudinary and any model within our application.

### Attaching Images to Your Models

Once Cloudinary is set up, you can easily attach images to your models using Active Storage or another file attachment library. This section will cover how to associate image uploads with your database models, ensuring that your images are properly linked and accessible.

**Setup for one image**

```
# models/car.rb
class Car < ApplicationRecord
  has_one_attached :photo
end
```

In your controller:

```
# app/controllers/cars_controller.rb
def car_params
  params.require(:car).permit(:title, :photo)
end
```

In your view:

```
<!-- app/views/cars/_form.html.erb -->
<%= form_for(car) do |f| %>
  <!-- [...] -->
  <%= f.file_field :photo %>
  <!-- [...] -->
<% end %>
```

**Setup for multiple images**

```
# models/car.rb
class Car < ApplicationRecord
  has_many_attached :photos
end
```

In your controller:

```
# app/controllers/cars_controller.rb
def article_params
  params.require(:car).permit(:title, photos: [])
end
```

In your view:

```
<!-- app/views/cars/_form.html.erb -->
<%= simple_form_for(car) do |f| %>
  <!-- [...] -->
  <%= f.input :photos, as: :file, input_html: { multiple: true } %>
  <!-- [...] -->
<% end %>
```

### Displaying the image in the view

To display the image on your view, we will need to change the image\_tag by cl\_image\_tag and call its key:

```
<%= cl_image_tag @car.photo.key, height: 500, width: 500, crop: :fill %>
```

Et voila! 🥖 You have a free cloud provider for your rails images.


# Admin Dashboard

Add a fully functional admin dashboard in just 3 terminal commands.

{% embed url="<https://youtu.be/xtGjQtiFnRg?si=Gn_ryuxBN80L2nKz>" %}

## Installation of Motor Admin

Motor Admin is a Ruby on Rails admin panel that can be easily integrated into your application. Follow the steps below to install it in your Rails project.

See the <mark style="color:green;">live demo!</mark>

{% hint style="info" %}
We recommend installing Motor Admin after you have completed the first version of the Data Brick (Models, migrations, routes, associations, validations). This will save you manual work down the line.
{% endhint %}

### Setup

All the setup is already integrated by default, you only need to create a new user and assign it as an admin.

For this, open the rails console in the terminal with `rails c`

```
> User.create(email: "youremail@gmail.com", password:"somesecurepassword", admin: true)
```

Et voila! You can now login as this user and access your brand new rails admin dashboard <http://localhost:3000/motor_admin>

### Configuration

After installation, you may need to configure the Motor Admin settings according to your application's requirements. Refer to the [Motor Admin Documentation](https://www.getmotoradmin.com/) for detailed setup instructions.

### Usage

Once installed, you can access the admin panel at /motor\_admin by default. You can customize the interface and manage your application's data easily.


# Search Engine Optimization

Easily setup your app to rank high on google with search engine optimisation (SEO)

### Basic Setup

LightningRails comes with a default SEO setup loaded in the initializers. To rank on your ideal keywords, visit the meta.yml file and modify the values:

```yaml
# meta.yml
meta_product_name: "LightningRails"
meta_title: "LightningRails - The fastest way to kickstart your Rails app"
meta_description: "LightningRails is a Rails template that helps you kickstart your Rails app in minutes. It comes with a set of pre-configured tools and best practices to help you build your app faster."
meta_image: "cover.png" # should be in `app/assets/images/`
twitter_account: "@product_twitter_account"
```

These values will appear on every page by default, this is a great start but not ideal. We recommend leaving the default values on your homepage and overriding the variable manually on all the other pages with the following lines:

<pre><code><strong># Modify content to adapt to each page, for example with pricing.html.erb
</strong><strong>&#x3C;% content_for :meta_title, "No trick pricing on #{DEFAULT_META["meta_product_name"]}" %>
</strong>&#x3C;% content_for :meta_description, "Get more for less, access our pro membership to build rails apps in seconds" %>
&#x3C;% content_for :meta_image, "cover.png" %>

&#x3C;h1>Pricing&#x3C;/h1>
[...]
</code></pre>

{% hint style="info" %}
Pro Tip: For show pages, add instance variables in the meta tags to have them tailored to each instance page. For example, if you have a car shop, interpolate "@car.name" in the meta\_title so you start ranking with all the different car brand keywords with just one line! 🏎️
{% endhint %}

### How to rank on Google&#x20;

To start ranking on Google, [**create a search console account**](https://search.google.com/search-console/about) and submit the new links you wish to rank for. Make sure to link pages between each other for Google spiders to be able to crawl more effectively across your site, if done right, you will send traffic from some of your pages towards the main ones, the ones that transform and make money, it is called a **Keyword tree 🌳**

{% hint style="info" %}
Pro Tip: Don't forget to add "skip\_before\_action :authenticate\_user" to all the actions you wish to rank for. If the page is hidden behind an authentication wall google won't be able to access it and won't index it.
{% endhint %}


# Automatic Emails

For transactional emails, I have added 2 different guides for the two main service providers. Both offer 100 Free emails per month. More than enough for testing an MVP.

**Postmark vs. Resend: What's the Difference?**

Here’s the lowdown:

* **Postmark** has been around for a while and is known for being super reliable. But it allows only transactional emails—things like password resets or order confirmations— So if you're looking to send some marketing content through your app, you might get blocked. Moreover, the apply process takes longer as they have to vet your account.
* **Resend** is newer and focuses on simplicity. It’s straightforward to set up, no application process, with a clean API that doesn’t overcomplicate things. Resend is great if you want to send both transactional emails an marketing emails.

**Which One to Pick?**\
I recently changed it to Resend for the reasons I mentioned above. but I still keep my older apps with postmarks, that way I can benefit from 100 free emails from both platforms which gives me more runaway to test my apps.

<table data-view="cards"><thead><tr><th></th><th data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Postmark Setup</td><td><a href="/pages/cEHgDv7NGBquDGHeLyQ4">/pages/cEHgDv7NGBquDGHeLyQ4</a></td><td><a href="/files/SYTWAl215xmLCTOrHtA2">/files/SYTWAl215xmLCTOrHtA2</a></td></tr><tr><td>Resend Setup</td><td><a href="/pages/FhvQWkQW8ICo36uBGRWC">/pages/FhvQWkQW8ICo36uBGRWC</a></td><td><a href="/files/Cko6Y1bnzxV8VxhZgnCX">/files/Cko6Y1bnzxV8VxhZgnCX</a></td></tr></tbody></table>


# Postmark

<figure><img src="/files/SYTWAl215xmLCTOrHtA2" alt=""><figcaption><p>Postmark landing page</p></figcaption></figure>

### Step 1: Create an Account on Postmark

1. **Visit the Postmark Website**: Go to [Postmark's official website](https://postmarkapp.com/).
2. **Sign Up**: Click on the “Sign Up” button typically located at the top right of the homepage.
3. **Fill in Your Details**: Enter your email address, password, and any other required information to create a new account.
4. **Confirm Your Email**: Check your inbox for a confirmation email from Postmark. Click the link in the email to verify your account.
5. **Set Up Your Account**: Log in to your newly created account and complete the setup process as prompted. This may include providing additional information about your business or intended use of the service. ❗️ Make sure to mention that **you are using postmark for transactional emails** (welcome emails, payment confirmation etc...) and in NO case for promotional emails.

### Step 2: Add Your Domain

Once your account is set up, you need to verify and add your domain:

1. **Navigate to the Domains Section**: In your Postmark dashboard, find the "Domains" section.
2. **Add Your Domain**: Click the “Add Domain” button and follow the instructions to verify your domain. This usually involves adding DNS records provided by Postmark to your domain's DNS settings in [Namecheap](https://docs.lightningrails.com/features-setup/automatic-emails/www.namecheap.com) or other domain providers.

### Step 3: Configure Your Application

After your domain is set up, configure your application:

1. **Add Your Domain Name**: Open your `application.rb` file and add your domain name as necessary.

```
    config.action_mailer.delivery_method     = :postmark
    config.action_mailer.postmark_settings   = { api_token: ENV['POSTMARK_API_KEY'] }
    config.action_mailer.default_url_options = { host: "www.yourdomain.io" }
```

2. For Development, we don't want to use our Postmark credits. So we will use the [gem Letter Opener ](https://github.com/ryanb/letter_opener)instead, which is already installed by default in LR.  Open `development.rb` and add this line:

```
config.action_mailer.delivery_method = :letter_oppener
```

The Gem will open a new .html tab with the view of the email. Use it for styling 🎨

2. **Set Your API Key**: Create a `.env` file in the root of your project (if it doesn't already exist) and add your Postmark API key. You can find your API key in the Postmark dashboard under the “API Tokens” section.

```
# .env
POSTMARK_API_KEY=your_postmark_api_key
```

***

### Step 4 (optional): Examples of use

Create a welcome mailer to trigger after a user creates an account:

```
rails generate mailer UserMailer
```

Change the sender's default email:&#x20;

```
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout "mailer"
end
```

This will create a few files under the mailers and views folder. Go to `user_mailer.rb` and write the following code:

```
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def welcome(user)
    mail(to: user.email, subject: 'You are in :)')
  end
end
```

Then create the mailer view:

```
<p>Hi!</p>

<p>Thank you for signing up to LightningRails. After <%= link_to "purchasing your first license", checkout_url %>  you will be able to download the boilerplate from the source repository and start building your app.</p>

<p>Visit the <a href="https://docs.lightningrails.com">documentation</a> to get started and learn how to implement the different features (authentication, payments, etc), how to use the different components, and how to deploy your app.</p>

<p>Cheers!</p>
<p>Dani, maker of LightningRails©</p>
```

In user.rb we want to create a callback method to automatically send the welcome email as soon as the user is created.

```
class User < ApplicationRecord
    after_create :send_welcome_email # <-- Dont forget this 👀
    
    [...]
    
    private
    
    def send_welcome_email
        UserMailer.welcome(self).deliver_now
    end
end
```

Et voila! Your users will receive transactional emails, so make sure to watch out for your free credits, you should have 100 emails per month for free, enough to get your first MVP users 🚀


# Resend

This guide outlines how to install and configure the Resend Gem for sending transactional emails in your Lightning Rails project.

<figure><img src="/files/Cko6Y1bnzxV8VxhZgnCX" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
Recommended: Use the **code generator** for a 1-line setup from the terminal and skip all the manual setup 🚀  \
\
`bash generators/setup_resend.sh`&#x20;
{% endhint %}

***

Or follow these steps:

### Step 1: Create a Resend Account

1. Go to the [Resend website](https://resend.com).
2. Sign up and create an account to access your API key.

***

### Step 2: Add the Resend Gem

1. Open your `Gemfile`.
2. Add the Resend gem in your Gemfile:

   ```ruby
   gem "resend"
   ```
3. Run `bundle install` in your terminal to install the gem.

   ```bash
   bundle install
   ```

***

### Step 3: Configure Your API Key

1. Locate your API key from the Resend dashboard.
2. Add the API key to your `.env` file:

   ```env
   RESEND_API_KEY="re_***************"
   ```
3. Ensure you have the .env file setup as specified in the [quick](/getting-started/quickstart)[start](https://docs.lightningrails.com/features-setup/automatic-emails/pages/CyH2xJQs9yWJ1S8BYNav#create-.env-file) guide.

***

### Step 4: Set Up Action Mailer

1. **Development Environment**: Open `config/environments/development.rb` and set the delivery method to `:letter_opener` for testing emails locally (The Gem is already installed in the boilerplate):

   ```ruby
   config.action_mailer.delivery_method = :letter_opener
   ```
2. **Production Environment delivery method**: Open `config/environments/production.rb` and set the delivery method to `:resend`:

   ```ruby
   config.action_mailer.delivery_method = :resend
   ```
3. **API key in production**
   1. Create a new initializer file:  `touch app/config/initializers/mailer.rb`
   2. Paste your API key, and make sure your API key is set in your hosting provider:&#x20;

      <pre class="language-ruby"><code class="lang-ruby"><strong>Resend.api_key = ENV["RESEND_API_KEY"]
      </strong></code></pre>

***

### (Optional)  Welcome Email

### Step 5: Create Your First Mailer

1. Generate a new mailer:

   ```bash
   rails generate mailer UserMailer
   ```
2. Open the newly created file `app/mailers/user_mailer.rb` and define a mailer method. For example:

   ```ruby
   class UserMailer < ApplicationMailer
     def welcome_email
       @user = params[:user]
       mail(to: @user.email, subject: "Welcome to Our App!")
     end
   end
   ```

***

### Step 6: Add HTML Content for the Email

1. Create a corresponding view file for the mailer:

```
touch app/views/user_mailer/welcome_email.html.erb
```

* Path: `app/views/user_mailer/welcome_email.html.erb`
* Content example:

  ```html
  <h1>Welcome to Our App, <%= @user.name %>!</h1>
  <p>We’re excited to have you on board.</p>
  ```

***

### Step 7: Initialize and Send the Mailer

1. Open the Rails console:

   ```bash
   rails console
   ```
2. Initialize the mailer with a sample user object:

   ```ruby
   u = User.new(name: "Derich", email: "derich@example.com")
   mailer = UserMailer.with(user: u).welcome_email
   ```
3. Send the email:

   ```ruby
   mailer.deliver_now!
   ```
4. Add an auto-welcome message to welcome your users in `user.rb`

```
class User < ApplicationRecord
# [...]
  after_create :send_welcome_email

  def send_welcome_email
    UserMailer.with(user: self).welcome_email.deliver_now
  end
end
```

***

### Step 8: Verify the Email

* Check the recipient's inbox to confirm the email has been sent successfully.
* If testing in development, verify the email opens correctly using the `letter_opener` gem.

***

That's it! You’ve successfully set up the Resend Gem for sending transactional an marketing emails in your Lightning Rails project, you can enjoy 100 free emails per month.


# Login with Devise

## Set Up Devise

Follow these steps to configure Devise for user authentication in your Lightning Rails application.

### Configure Devise

Open the production.rb file at `config/environments/production.rb` and customize the host domain to match your production domain.

```
# config/environments/production.rb
  config.action_mailer.default_url_options = { host: 'example.com' } # Change this to your domain
```

### Configure email address for Devise::Mailer

```
  #config/initializers/devise.rb
  config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
```

### (Optional): Further Customization of Devise

To customize the User model by adding additional fields like `first_name` and `last_name`, follow these steps:

#### Generate a Migration

Generate a migration to add the new fields to your User model:

```
rails generate migration AddFieldsToUsers first_name:string last_name:string
```

#### Migrate the Database

Run the migration to update the database schema:

```
rails db:migrate
```

#### Permit Additional Parameters

To allow these new fields during sign-up and account update, you need to update the Devise parameters. Open or create the `app/controllers/application_controller.rb` file and add the following code:

```
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name])
    devise_parameter_sanitizer.permit(:account_update, keys: [:first_name, :last_name])
  end
end
```

#### Update Views (Optional)

If you want users to input these additional fields during sign up and account editing, you must update the Devise views. Edit the `app/views/devise/registrations/new.html.erb` and `app/views/devise/registrations/edit.html.erb` files to include the new fields:

```
<div class="field">
  <%= f.label :first_name %>
  <%= f.text_field :first_name, autofocus: true %>
</div>

<div class="field">
  <%= f.label :last_name %>
  <%= f.text_field :last_name %>
</div>
```

This will allow users to provide their first and last names when they sign up or update their account information.

#### Freeing a Page from Authentication

To allow access to a specific action without requiring user authentication, you can use the following code in the corresponding controller:

```
class YourControllerName < ApplicationController
  # Freeing the action from authentication
  skip_before_action :authenticate_user!, only: :action_name

  def action_name
    # Your action code here
  end
end
```

Make sure to change "action\_name" to your new action name and "YourControllerName" to your new or existing controller name.

### Additional Resources

For more information and advanced configurations, we invite you to check out the official Devise repository on GitHub:

[Devise GitHub Repository](https://github.com/heartcombo/devise)

{% hint style="info" %}
If you care about your users, consider setting up magi links instead of passwords 😉 See the next feature: [Magic Links](/features-setup/magic-link-signup)
{% endhint %}


# Magic Link Signup

Who likes passwords? Easily setup magic link in your LightningRails app.

<figure><img src="/files/UUecQZoixkUD2wk7btuz" alt=""><figcaption><p>Magic link just needs an email, no password 🥳</p></figcaption></figure>

If you want to increase your signups, ditching the password is the best way.&#x20;

{% hint style="info" %}
Before starting with this setup, make sure that transactional emails are ready and set, [follow the LightningRails guide here.](/features-setup/automatic-emails/editor)  With Postmark set up, you will get 100 free emails per month.
{% endhint %}

### Config

If it hasn't been done yet, make sure that the environment is set to the correct email delivery method:

```ruby
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
```

```ruby
#config/environments/production.rb
config.action_mailer.delivery_method = :postmark
```

### Model

To send Magic links, generate an instance of a magic Link with a unique token, think of it as an invitation instance:

```bash
rails g model email_link token expires_at:datetime user:references
```

Migrate:

```
rails db:migrate
```

In your model file `email_link.rb`

```ruby
class EmailLink < ApplicationRecord
  belongs_to :user
  after_create :send_mail
  
  def self.generate(email)
    user = User.find_by(email: email)
    if !user
      user = User.create(email: email, password: Devise.friendly_token.first(8))
    end
    create(user: user, expires_at: Date.today + 1.day, token: generate_token)
  end

  def self.generate_token
    Devise.friendly_token.first(16)
  end

  private
  def send_mail
    # Defined in mailer class
    EmailLinkMailer.sign_in_mail(self).deliver_now
  end
end
```

In your `user.rb` file

```ruby
class User < ApplicationRecord
  has_many :email_links, dependent: :destroy
end
```

### Generate Mailer

In the model we are creating a send\_mail method, now we define it in our mailer method:

```bash
rails g mailer email_link
```

This should generate `email_link_mailer.rb` a few other files, in this mailer file, add this code:

```ruby
class EmailLinkMailer < ApplicationMailer
  def sign_in_mail(email_link)
    @token = email_link.token
    @user = email_link.user

    mail to: @user.email, subject: "Here is your magic link! 🚀" # modify the subject to your liking
  end
end
```

Let's create the view file:

```bash
touch app/views/email_link_mailer/sign_in_mail.html.erb
```

Now let's write a nice welcome email, no need to go above the board, the user just needs a link to sign up:

```ruby
# app/views/email_link_mailer/sign_in_mail.html.erb

<p>Hello, <%= @user.email %>!</p>
<p>Someone requested a sign-in link for your account. Click the link below to sign in:</p>

<%= link_to "Sign in to my account", email_link_url(token: @token) %>

Cheers,
Bob, Founder
```

### Route

We will need new routes to make this work.

```ruby
  post 'email_links/create', as: :magic_link
  get 'email_links/validate', as: :email_link
```

### Controller

We will also need a new controller.

```bash
rails g controller email_links
```

In the controller, we will add these methods. One method to create the new link, and one to validate that the link is correct, the latter will be linked directly in the email.

```ruby
class EmailLinksController < ApplicationController
  skip_before_action :authenticate_user!

  def create
    @email_link = EmailLink.generate(params[:email])
    if @email_link
      flash[:notice] = "Access email sent! Please, check your inbox to enter"
      redirect_to root_path
    else
      flash[:alert] = "There was an error, please try again!"
      redirect_to new_user_session_path
    end
  end

  def validate
    email_link = EmailLink.where(token: params[:token]).where("expires_at > ?", DateTime.now).first

    unless email_link
      flash[:alert] = "Invalid or expired token!"
      redirect_to new_user_session_path
    end

    sign_in(email_link.user, scope: :user)
    flash[:notice] = "Welcome! You signed in successfully 🚀"
    redirect_to root_path
  end
end
```

### Views

The last thing we have to do is go to the new sessions path and replace the existing form with this new one:

```ruby
 # app/views/devise/sessions/new.html.erb

 <%= form_with(url: magic_link_path, method: :post) do %>
     <div class="col-span-6">
          <label class="input input-bordered flex items-center gap-2">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 16 16"
              fill="currentColor"
              class="h-4 w-4 opacity-70">
              <path
                d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
              <path
                d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
            </svg>
            <%= email_field_tag :email, nil, placeholder: "mclovin@gmail.com" %>
          </label>
          <div class="col-span-6 sm:flex sm:items-center sm:gap-4 mt-6">
            <%= submit_tag "Log in", class: "btn btn-primary" %>
          </div>
      </div>
  <% end %>
```

This form will work for both Signup and Login, so no need to do the same setup in registrations. Just change all the signup links to redirect to sessions/new by default.

The reason I recommend adding the sessions new instead of registration is that the authenticated\_user method redirects to sessions, so you can reuse that feature with your new magic link 🚀&#x20;

{% hint style="info" %}
Don't forget to restart your server as we modified the config files 😉
{% endhint %}

Happy Magic linking! 🦄


# Stripe Payment Gateway

## Stripe setup

{% hint style="info" %}
The boilerplate comes by default with Single payments stripe checkout. We are working on adding subscriptions, in the meantime, you can modify the existing checkout controller with a few lines to activate subscriptions.
{% endhint %}

We use stripe checkout and pay gem for easy paywall setup and webhooks. To start using Stripe follow this guide:

### Create account

Go to [Stripe](https://docs.lightningrails.com/features-setup/www.stripe.com)[ ](https://docs.lightningrails.com/features-setup/www.stripe.com)and create an account.

### Install stripe CLI in your terminal

For us to listen to stripe webhooks and test in development, we need to install stripe CLI in our terminal:

For macOS:

```
brew install stripe/stripe-cli/stripe
```

Or for Ubuntu, check the [official documentation.](https://docs.stripe.com/stripe-cli?install-method=apt)

**Then login:**

```
stripe login -i
```

This should ask you for an API key, you will find it in [Test > Developers > API Keys ](https://dashboard.stripe.com/apikeys)> Secret Key

### API keys and webhook secrets

Start listening to your webhook events by running in your terminal:

```
stripe listen --forward-to localhost:3000/pay/webhooks/stripe
```

This webhook comes by default with the pay gem. If you wish to create custom webhooks refer to the [Pay Gem](https://github.com/pay-rails/pay/tree/main) documentation.

Add the API Keys in the Rails credentials file. Open the file from the terminal with the following line:

```
EDITOR="code --wait" rails credentials:edit
```

Add the keys in the file with this exact wording for the keys and save:

<figure><img src="/files/6cYfEjtq9rJqJxBiFzZL" alt=""><figcaption><p>Credentials Edit file</p></figcaption></figure>

## Pay gem

All the [Pay Gem](https://github.com/pay-rails/pay/tree/main) setup is taken care of by LightningRails, the only setup needed from your side to get the gem to work is:

* Go to application.rb and replace the example domain with your domain so Pay can generate links (for features like Stripe Checkout).
* Create a product on the stripe product catalog, copy the product ID, and paste it on the checkouts\_controller.rb action Show:

```
# controllers/checkouts_controller.rb

  def show
    current_user.set_payment_processor :stripe
    current_user.payment_processor.customer

    @checkout_session = current_user.payment_processor.checkout(
      mode: 'payment',
      line_items: 'price_1JZ9J3J9jgZ2Qj5vz1ZzZzZz',
      success_url: checkout_success_url
    )
  end
```

### Set your pricing

Now that you have created your product on Stripe, ensure all the UI has the correct pricing, and modify the price on the \_price\_cards.rb component.

### Premium Authorisation

Hide your premium features behind the paywall by adding the following line in the premium controllers/actions

```
class MyPremiumController < ApplicationController
before_action :verify_premium, only: [:some_action]

[...]

def verify_premium
   unless current_user.premium? # This is an instance method in the user.rb model
     flash[:notice] = "Go premium to access this feature"
     redirect_to checkout_path
   end
end
```

<br>


# Github Signup

Follow this tutorial to signup your users with Github. You will be able to gather their profile picture, name and email.

{% embed url="<https://youtu.be/YIJ6ybDlFAk>" %}
Tutorial for adding github authentiaciton to Lightning Rails
{% endembed %}

## Install Github authentication Gem

The first step will be to install the [omniauth Github gem.](https://github.com/omniauth/omniauth) In our Gemfile, let's add

```ruby
gem 'omniauth'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-github'
```

As usual, we will bundle

```
bundle install
```

As we wish to have our users sign up with Github, we must let devise know. Let's go into the initializers/devise.rb file and add this line:

```ruby
# config/initializers/devise.rb

# ==> OmniAuth
  config.omniauth :github, ENV["GITHUB_ID"], ENV["GITHUB_SECRET"], scope: 'user,public_repo'
  
```

## Create a Github OAuth App

Now that we have done some config, we can see that we are missing the API keys that will connect our app to GitHub. Unlike Google OAuth, Github is very easy to create.

* [ ] **Create** a new app [here.](https://github.com/settings/applications/new)
* [ ] Add your authentication code if required.
* [ ] **Give your application the name** "My Super Cool App Development"
* [ ] **Give a Homepage URL:** <http://localhost:3000/> (we will create a second OAuth app for production and real domain)
* [ ] Add an application description for your users
* [ ] Add exactly this URL as app callback: <http://localhost:3000/users/auth/github/callback> (we will change it for the production environment)
* [ ] Save and add your logo if you have one

You will see that you have a CLIENT ID but still need to generate a new secret key, so let's do it by clicking "Generate a new client secret".

## Add keys to .env

Copy the Secret key and add it to your .env file along with the client ID:

```
# .env

# modify with your keys
GITHUB_ID=Ov23*********
GITHUB_SECRET=3cc73c***********************
```

## Routes

Now that we have a good config, we'll need to activate new routes, and replace the existing `devise_for :user` by this line:

```ruby
# routes.rb

devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
```

## Controller

Let's create that new controller we added to the routes, in the terminal run:

```
mkdir app/controllers/users
```

And&#x20;

```
touch app/controllers/users/omniauth_callbacks_controller.rb
```

Now in our brand new controller file, we will add the GitHub OmniAuth logic:

```ruby
# app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def github
      @user = User.from_omniauth(request.env['omniauth.auth'])

      if @user.persisted?
        sign_in_and_redirect @user, event: :authentication
        flash[:notice] = I18n.t 'devise.omniauth_callbacks.success', kind: 'Github'
      else
        session['devise.github_data'] = request.env['omniauth.auth'].except('extra')
        redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
      end
  end
end
```

## Model

After adding the proper routes and controller action we will need to activate the Omniauth option from devise. In `user.rb` uncomment and add the following symbols to the devise options:

```ruby
# models/user.rb
class User < ApplicationRecord
    # Include default devise modules. Others available are:
    # :confirmable, :lockable, :timeoutable, :trackable
    
    devise :database_authenticatable, :registerable,
           :recoverable, :rememberable, :validatable, 
           :omniauthable, omniauth_providers: [:github] # <- The new options
   # [...]
 end
```

We will also add the Omniauthable custom method:

```ruby
# models/user.rb
class User < ApplicationRecord
  # has_one_attached :avatar <- Uncomment If you want to attach a picture from github
  # [...]
  
  def self.from_omniauth(access_token)
    data = access_token.info
    user = User.where(email: data['email']).first
    if user
       user.update(
          # Optional: You can create a migration for users to add GitHub username and name to the user instance.
          # github_username: data['nickname'], 
          # name: data['name'], 
          email: data['email']
       )
    else
        user = User.create(
           # Optional: You can create a migration for users to add GitHub username and name to the user instance.
           # github_username: data['nickname'],
           # name: data['name'],
           email: data['email'],
           password: Devise.friendly_token[0,20]
        )
    end
     # Optional: Uncomment for adding avatar image from github, make sure you have a cloudinary API_KEY in your .env
     # user.avatar.purge if user.avatar.attached?
     # user.avatar.attach(io: URI.open(data['image']), filename: 'avatar.jpg', content_type: 'image/jpg')
    user
  end
end
```

## Views

Let's add the GitHub signup to the sessions/new file. I like to add it to the login instead of the signup as to not lose the before\_action :authenticate\_user! auto-redirect function. As our code either updates or creates. You can now ignore the signup page and unlink it from all the buttons on your page.

```erb
#app/views/sessions/new.html.erb

# [...] 

# Add this code where you want the GitHub sign in button
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= button_to omniauth_authorize_path(resource_name, provider), data: { turbo: false }, class: "btn btn-primary mt-12" do %>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-70" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
      Sign in with Github<br />
     <% end %>
     <small class="text-xs text-gray-500">Your Github Username to receive an automatic invite to LightningRails repo.</small>
  <% end %>
<% end %>

# [...]
```

## In Production

To have this code work on production as well as on development, you will need to create a second Github app and follow these steps again:

* [ ] **Create** a new app [here.](https://github.com/settings/applications/new)
* [ ] Add your authentication code if required.
* [ ] **Give your application the name** "My Super Cool App Production"
* [ ] **Give a Homepage URL:** <https://yourdomain.com> (we will create a second OAuth app for production and real domain)
* [ ] Add an application description for your users
* [ ] Add exactly this URL as app callback: <https://yourdomain.com/users/auth/github/callback> (change this domain to your final live domain)
* [ ] Save and add your logo if you have one

Now you should have two GitHub apps. One for development and one for production.

The only step left will be to add the production ID and key to Heroku from your terminal:\
*(replace the API key by your actual production API key and ID)*

<pre><code><strong>heroku config:set GITHUB_SECRET=YOUR_API_KEY
</strong></code></pre>

And&#x20;

```
heroku config:set GITHUB_ID=YOUR_GITHUB_ID
```

Et voila! You have a brand-new GitHub authentication setup.&#x20;

Your techy users will thank you! 🙏

***

{% hint style="info" %}
Common Errors 🐞:&#x20;

* Omniauthable is undefined: Did you restart your server after installing the Omniauthable Gem? If not do it an it should get fixed.
* Must supply API\_KEY: Are you adding avatars without a Cloudinary key in your .env file? That's probably the cause, head to Cloudinary, and add your API key in the .env file.
  {% endhint %}


# Lucide icons

Easily add SVG icons to your project with only one line of code.

{% embed url="<https://youtu.be/_yDUD1brEGU>" %}
Short tutorial on how to sue the Lucide Library
{% endembed %}

{% hint style="info" %}
**Lucide.dev License disclaimer**

ISC License

Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
{% endhint %}

### How to add Lucide Icons to your project

The first step we want to do is to [visit the Lucide Library ](https://lucide.dev/icons/)to find our ideal icon, in this case, I am looking for an arrow that goes right:

<figure><img src="/files/2B7dI0f0V3h4LywBlsJV" alt=""><figcaption><p>You can use the search bar to quickly find the icons</p></figcaption></figure>

Once I find the icon I need I will copy its name to my clipboard 📋 with ctrl + c

In our view we will add the following line:

```erb
<%= lucide_icon("move-right") %>
```

Remember that icons behave like text, so if you want to change the size or color of the icon, simply add it as an argument:

```
<%= lucide_icon("move-right", "class" => "h-4 w-4") %>
```

Now my icon will be smaller than its default size.&#x20;

Now you can forget about generic FontAwesome Icons with complicated setup and generic look and feel.&#x20;

Happy Iconing! 🚀


# Multi-provider AI

A guide on how to use the new Ruby LLM library, which allows you to connect to several LLMs instead of just using Open AI

{% embed url="<https://youtu.be/FRoRgwewSmU?si=jB01r_lW2XyGRf0->" %}
Tutorial on how to add RubyLLM gem to your Rails app
{% endembed %}

### Overview

The [`ruby_llm` gem](https://github.com/crmne/ruby_llm) is a powerful, multi-provider LLM client for Ruby that makes it simple to interact with models like OpenAI, Anthropic, Gemini (Google), and Deepseek. It offers rich features like:

* Real-time streaming responses
* Multi-modal inputs (images, audio, PDFs)
* Easy-to-define tools
* Native Rails integration with `acts_as_chat`

This guide walks you through how to add the **`ruby_llm`** gem in a Lightning Rails project, including configuration, usage, and upgrading of any existing service classes.

***

### Step 1: Install the `ruby_llm` Gem

Add the new [gem](https://github.com/crmne/ruby_llm) to your `Gemfile`:

```ruby
gem "ruby_llm"
```

Then install it:

```bash
bundle install
```

Alternatively, install it directly:

```bash
gem install ruby_llm
```

***

### Step 2: Set Up Your API Keys

In your `.env` file (Lightning Rails uses `dotenv-rails` by default), add:

```bash
OPENAI_API_KEY=sk-...
# Add others as needed
# ANTHROPIC_API_KEY=...
# GEMINI_API_KEY=...
# DEEPSEEK_API_KEY=...
```

Then create a RubyLLM config initializer:

```bash
touch config/initializers/ruby_llm.rb
```

```ruby
# config/initializers/ruby_llm.rb
RubyLLM.configure do |config|
  config.openai_api_key = ENV['OPENAI_API_KEY']
  config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
  config.gemini_api_key = ENV['GEMINI_API_KEY']
  config.deepseek_api_key = ENV['DEEPSEEK_API_KEY']
end
```

***

### Step 3: Create a new ruby\_llm service

Create `app/services/MultiProviderModels.rb`:

```ruby
# app/services/MultiProviderModels.rb
class MultiProviderModels
  def initialize
    @chat = RubyLLM.chat(model: "gpt-4o-mini") # pass the parameters here :)
  end

  def analyze_product(product)
    # Add a system prompt to give it context for better results.
    @chat.add_message role: :system, content: "You are a Product Hunt expert. Always include examples in your responses and explain them line by line."

    prompt = <<~PROMPT
      You are a product analyst expert specialized in evaluating digital products and services. 
      You have deep knowledge of market trends, user experience, and business models. 
      Your analysis should be structured, data-driven, and actionable.

      Here is a product to analyze: #{product}

      Please follow this process:
      1. Identify the key features and unique selling points
      2. Evaluate the market potential and target audience
      3. Analyze pricing strategy and business model
      4. Assess technical implementation and scalability
      5. Provide specific recommendations for improvement

      Format your response in clear sections with bullet points where appropriate. 
      Be concise but thorough in your analysis.
    PROMPT

    @chat.ask(prompt)
  end
end
```

***

### Step 4: Enable Streaming (Optional)

If you are creating a Chatbot and don't want the users to have to reload their pages for every message, you can stream responses in real-time, by modifying the `ask` method:

```ruby
@chat.ask(prompt) do |chunk|
  print chunk.content
end
```

Or, in a controller with Turbo Streams:

```ruby
@chat.ask(prompt) do |chunk|
  Turbo::StreamsChannel.broadcast_append_to(
    "ai_response",
    target: "response",
    partial: "messages/chunk",
    locals: { chunk: chunk }
  )
end
```

***

### Bonus: Enable Native Rails Models (Optional)

If you'd like to track (Save/update) chats and messages in the database, use the `acts_as_chat` setup:

```ruby
# app/models/chat.rb
class Chat < ApplicationRecord
  acts_as_chat
  broadcasts_to ->(chat) { "chat_#{chat.id}" }
end

# app/models/message.rb
class Message < ApplicationRecord
  acts_as_message
end

# app/models/tool_call.rb
class ToolCall < ApplicationRecord
  acts_as_tool_call
end
```

***

### Best Practices

* Prefer `RubyLLM.chat(model: "gpt-4o-mini")` for model-specific tasks.
* Use streaming when showing responses live in the UI.
* Leverage multi-modal inputs (images, PDFs, audio) for advanced functionality.
* Use `RubyLLM::Tool` to define reusable actions the LLM can call dynamically.
* Always sanitize and validate input before using it in prompts if user-submitted.

***

### Troubleshooting

**❌ Getting “Missing API key” errors?**

Make sure your `.env` file includes `OPENAI_API_KEY` (or the LLM of your choice) and it is loaded via `dotenv`.

**❌ Getting `undefined method acts_as_chat`?**

Ensure you’ve added the proper ActiveRecord models with `acts_as_chat`, and that the `ruby_llm` gem is loaded correctly. Also, don't forget to restart your server after installing the gem.

**❌ No response from `ask` method?**

Wrap your call in a `puts` or log output. Also, try a basic prompt like:

```ruby
RubyLLM.chat.ask("Hello, how are you?")
```

***

### Real-World Use Cases

* Product analysis and market research
* Image captioning or PDF summarization
* Building internal tools with custom RubyLLM::Tool classes
* Streaming AI-powered responses in chat UIs or dashboards

***

Read more in [the official documentation](https://rubyllm.com/) 🔥

Let me know if you’d like a Stimulus controller example or UI integration using Turbo Streams for this!


# Open AI API

Integrating OpenAI API and DeepSeek to your Lightning Rails Project

<figure><img src="/files/QVvASoMlJXD4gjdASfZe" alt=""><figcaption><p>The anatomy of the perfect Prompt - From Open AI CEO</p></figcaption></figure>

### Step 1: Get an OpenAI API Key

1. Sign up or log in to [OpenAI](https://platform.openai.com/).
2. Navigate to **API Keys** in the OpenAI dashboard.
3. Generate a new API key and **copy** it. You will not be able to see it again.

### Step 2: Install the [OpenAI Ruby Gem](https://github.com/alexrudall/ruby-openai)

In your **Gemfile**, add:

```
gem "ruby-openai"
```

Then, run:

```
bundle install
```

### Step 3: Configure API Key

Store your API key securely in your **.env** file:

```
OPENAI_ACCESS_TOKEN=sk-...MIQ4
```

### Step 4: Create an OpenAI Service

Create a service file **open\_ai\_api.rb** inside `app/services/`:

```
# app/services/open_ai_api.rb
class OpenAiApi
  def initialize
    @client = OpenAI::Client.new(access_token: ENV["OPENAI_ACCESS_TOKEN"])
  end

  def analyze_product(product)
    chatgpt_response = @client.chat(parameters: {
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: "You are a product analyst expert specialized in evaluating digital products and services. You have deep knowledge of market trends, user experience, and business models. Your analysis should be structured, data-driven, and actionable." },
        { role: "user", content: "Here is a product to analyze: #{product}. Please follow this process:\n\n1. Identify the key features and unique selling points\n2. Evaluate the market potential and target audience\n3. Analyze pricing strategy and business model\n4. Assess technical implementation and scalability\n5. Provide specific recommendations for improvement\n\nFormat your response in clear sections with bullet points where appropriate. Be concise but thorough in your analysis." }
      ]
    })
    chatgpt_response["choices"][0]["message"]["content"]
  end
end
```

<details>

<summary>💡 Pro Tip: Use this master prompt for better results</summary>

Copy and adapt this prompt  to your needs, it was published on twitter by Greg Brockman, the current president of Open AI:&#x20;

```
messages: [
  { role: "system", content: "You are an expert copywriter specializing in creating engaging, persuasive, and high-converting copy for product launches. Your expertise includes analyzing industry trends, top-performing messaging strategies, and user psychology to craft compelling and attention-grabbing text." },

  { role: "user", content: "
### **🎯 Goal**
Your task is to generate **engaging, high-quality marketing copy** for a new product based on inspiration from past successful launches. Your response should include:
- **A compelling product title** (short, catchy, and memorable).
- **A tagline** (one-liner that sparks curiosity and communicates value).
- **A persuasive product description** (concise, engaging, and benefit-driven).
- **A first comment/post to introduce the product** (structured for maximum engagement).

---
### **📊 Context: Past Successful Launches**
Below are examples of **top-performing product launches**, including their titles, taglines, and descriptions. Use these as **inspiration** to generate high-quality copy:

**Top Titles:**  
{TOP_TITLES_PLACEHOLDER}

**Top Taglines:**  
{TOP_TAGLINES_PLACEHOLDER}

**Top Descriptions:**  
{TOP_DESCRIPTIONS_PLACEHOLDER}

---
### **📝 New Product Information**
The new product is described as follows:

**Product Name:** {PRODUCT_NAME_PLACEHOLDER}  
**Category:** {CATEGORY_PLACEHOLDER}  
**Product Summary:**  
{PRODUCT_SUMMARY_PLACEHOLDER}  

---
### **⚠️ Warnings & Constraints**
- The **title must be under 7 words** and be memorable.  
- The **tagline should create curiosity and communicate value clearly**.  
- The **description should be benefit-driven, avoiding unnecessary fluff**.  
- The **first comment/post should feel natural, engaging, and encourage interaction**.  
- Avoid **generic language**—be concise and persuasive.  
- Structure the response so it is **easy to read and scan quickly**.

---
### **🗣️ First Product Introduction Comment Template**
Use this structure to craft the first comment/post:

🔥 **Hey everyone!** 🎯  

I'm super excited to introduce **{PRODUCT_NAME_PLACEHOLDER}** today! 🚀  

As {TARGET_AUDIENCE_PLACEHOLDER}, we all struggle with **{PROBLEM_PLACEHOLDER}**. It can be frustrating to {describe the pain point in a relatable way}. 🤔  

I built **{PRODUCT_NAME_PLACEHOLDER}** to **{briefly describe how it solves the problem}**, making {your audience’s goal} easier and more effective. Some cool features:  
✅ **{FEATURE_1_PLACEHOLDER}** - {Short description}  
✅ **{FEATURE_2_PLACEHOLDER}** - {Short description}  
✅ **{FEATURE_3_PLACEHOLDER}** - {Short description}  

Would love to hear your thoughts! **How would you use {PRODUCT_NAME_PLACEHOLDER}?** Let me know in the comments! 💡👇

---
### **📑 Return Format**
Ensure your response follows this structure for easy readability:

**Title:**  
(Short, engaging title)  

**Tagline:**  
(One-liner tagline)  

**Description:**  
(A concise, persuasive product description)  

**First Comment/Post:**  
(Engaging, structured introduction post following the given template)  

---
💡 **Final Notes:**  
- Be **creative but keep it natural**—avoid sounding robotic.  
- Use **persuasive storytelling techniques**—highlight real-world benefits.  
- Keep everything **clear, engaging, and optimized for conversions**.  
"}]
```

</details>

### Step 5: Use the Service in a Controller

For example, in `products_controller.rb`:

```
class ProductsController < ApplicationController
  def analyze
    @product = params[:product]
    openai_service = OpenAiApi.new
    @analysis = openai_service.analyze_product(@product)
  end
end
```

Then, you can create a route in **config/routes.rb**:

```
post "/analyze_product", to: "products#analyze"
```

***

## Switching to DeepSeek API

If you want to switch from OpenAI to **DeepSeek**, follow these modifications:

### Step 1: Change API Key

Sign up for an API key at [DeepSeek](https://deepseek.com/) and store it in `.env`:

```
DEEPSEEK_ACCESS_TOKEN=ds-...MIQ4
```

### Step 2: Use the Same OpenAI Wrapper for DeepSeek

DeepSeek API should compatible with the OpenAI client wrapper, meaning you only need to modify your environment variable:

```
class DeepSeekApi
  def initialize
    @client = OpenAI::Client.new(access_token: ENV["DEEPSEEK_ACCESS_TOKEN"], uri_base: "https://api.deepseek.com/v1")
  end

  def analyze_product(product)
    chatgpt_response = @client.chat(parameters: {
      model: "deepseek-chat",
      messages: [
        { role: "system", content: "You are a product analyst expert..." },
        { role: "user", content: "Here is a product to analyze: #{product}..." }
      ]
    })
    chatgpt_response["choices"][0]["message"]["content"]
  end
end
```

### Step 3: Use DeepSeek Instead

Replace **OpenAiApi** references with **DeepSeekApi** in your controllers:

```
deep_seek_service = DeepSeekApi.new
@analysis = deep_seek_service.analyze_product(@product)
```

That's it! You have now integrated OpenAI with your **Lightning Rails** project and learned how to switch to **DeepSeek** using the same API wrapper.


# Multi-Step Form Wizard

Break long forms into manageable steps

### Overview

The multi-step form wizard allows you to break long forms into manageable steps, improving the user experience by only displaying one step at a time. It's powered by a lightweight Stimulus.js controller and integrates seamlessly with `simple_form` and Tailwind CSS in Lightning Rails.

### Installation & Setup

Follow these steps to implement a form wizard in your Lightning Rails project.

#### 1. Create the Form View

Wrap your form in a `<div data-controller="wizard">` and split it into multiple steps using `data-wizard-target="step"` on each section.

```erb
<div data-controller="wizard" class="space-y-6">
  <%= form_with model: @model, local: true, html: { class: "space-y-4" } do |f| %>
    <!-- Step 1 -->
    <div data-wizard-target="step" class="card bg-base-100 shadow p-6">
      <div class="form-control">
        <%= f.label :first_name, class: "label" %>
        <%= f.text_field :first_name, class: "input input-bordered" %>
      </div>

      <div class="mt-4 flex justify-end">
        <%= button_tag "Next", type: "button",
          data: {
            action: "click->wizard#goToNext",
            next_step: "1"
          },
          class: "btn btn-primary" %>
      </div>
    </div>

    <!-- Step 2 -->
    <div data-wizard-target="step" class="card bg-base-100 shadow p-6 hidden">
      <div class="form-control">
        <%= f.label :last_name, class: "label" %>
        <%= f.text_field :last_name, class: "input input-bordered" %>
      </div>

      <div class="mt-4 flex justify-between">
        <%= button_tag "Previous", type: "button",
          data: {
            action: "click->wizard#goToPrevious",
            previous_step: "1"
          },
          class: "btn btn-outline" %>

        <%= button_tag "Next", type: "button",
          data: {
            action: "click->wizard#goToNext",
            next_step: "2"
          },
          class: "btn btn-primary" %>
      </div>
    </div>

    <!-- Step 3 -->
    <div data-wizard-target="step" class="card bg-base-100 shadow p-6 hidden">
      <div class="form-control">
        <%= f.label :email, class: "label" %>
        <%= f.email_field :email, class: "input input-bordered" %>
      </div>

      <div class="mt-4 flex justify-between">
        <%= button_tag "Previous", type: "button",
          data: {
            action: "click->wizard#goToPrevious",
            previous_step: "2"
          },
          class: "btn btn-outline" %>

        <%= f.submit "Submit", class: "btn btn-success" %>
      </div>
    </div>
  <% end %>
</div>
```

#### 2. Create the Stimulus Controller

Add this Stimulus controller in `app/javascript/controllers/wizard_controller.js`:

```sh
rails g stimulus wizard
```

```js
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  static targets = ['step']

  goToNext(event) {
    const nextStep = event.target.dataset.nextStep - 1
    const actualStep = event.target.dataset.nextStep

    this.stepTargets[nextStep].classList.add("hidden")
    this.stepTargets[actualStep].classList.remove("hidden")
  }

  goToPrevious(event) {
    const previousStep = event.target.dataset.previousStep - 1
    const actualStep = event.target.dataset.previousStep

    this.stepTargets[actualStep].classList.add("hidden")
    this.stepTargets[previousStep].classList.remove("hidden")
  }
}
```

Make sure the controllers are registered in `application.js` :

```js
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
```

### Usage

* Each step is wrapped in a `div` with `data-wizard-target="step"`.
* Use `class="hidden"` (Tailwind utility) to hide inactive steps.
* Navigation is handled via `button_tag` with `data-action` for Stimulus event bindings.
* `next_step` and `previous_step` indicate the index of the step to show/hide.

### Best Practices

* Keep each step concise and focused.
* Validate inputs before allowing the user to proceed (add validations or checks in the controller if needed).
* Use consistent styling with Tailwind to provide visual cues for step transitions.

### Troubleshooting

**Issue:** Nothing happens when clicking "Next" or "Previous".\
**Solution:**

* Ensure the Stimulus controller is properly registered and compiled.
* Verify that `data-controller="wizard"` and `data-wizard-target="step"` are correctly placed.
* Make sure the buttons have the correct `data-action`, `next_step`, or `previous_step`.

**Issue:** Steps aren't hiding/showing properly.\
**Solution:**

* Confirm that `class="hidden"` is being toggled correctly.
* Check that `stepTargets` correspond to the order of steps in your HTML.

***

Let me know on Slack if you'd like an enhanced version with progress indicators or form validation!


# Internationalization I18n

A quick guide to outline how to install and configure internationalization support in your Lightning Rails project for multi-language applications.

{% embed url="<https://youtu.be/Q9pxgV4exMo>" %}

### 🚀 Generate I18N config

If you haven't generated the I18n config on setup, you can add it to your Lightning Rails project by running this command:

```bash
bash generators/setup_I18n.sh
```

### Features

#### ✅ What's Included

1. **Multi-language Support**: English, Spanish, French, German
2. **Devise Integration**: Complete Devise translations for all languages
3. **Fixed Position Language Switcher**: Always accessible in the bottom-right corner
4. **Session-based Locale Switching**: No authentication required
5. **Comprehensive Translations**: Common UI elements, navigation, forms, errors

#### 🎨 Language Switcher

<figure><img src="/files/dv0l502yrTy1Ip1oulnG" alt=""><figcaption></figcaption></figure>

The language switcher is positioned as a **fixed floating button** in the bottom-right corner of the screen:

* **Location**: Fixed position, always visible
* **Style**: DaisyUI primary button with shadow effects
* **Accessibility**: Available on all pages without authentication
* **Animation**: Smooth hover transitions

### 🔧 Technical Implementation

This is coded automatically by the generator; feel free to look at the code to understand it and modify it to your own use.

#### **ApplicationController Integration&#x20;*****(Created by the generator)***

```ruby
class ApplicationController < ActionController::Base
  before_action :set_locale
  [...]
  private

  def set_locale
    I18n.locale = extract_locale || I18n.default_locale
  end

  def extract_locale
    parsed_locale = params[:locale] || session[:locale] || request.env['HTTP_ACCEPT_LANGUAGE']&.scan(/^[a-z]{2}/)&.first
    parsed_locale if I18n.available_locales.map(&:to_s).include?(parsed_locale)
  end

  def default_url_options
    { locale: I18n.locale == I18n.default_locale ? nil : I18n.locale }
  end
end
```

### 🔗 URL Structure

The setup automatically creates clean, locale-prefixed URLs:

* **English**: `yoursite.com/en/` (default)
* **Spanish**: `yoursite.com/es/`
* **French**: `yoursite.com/fr/`
* **German**: `yoursite.com/de/`
* **Language switching**: `yoursite.com/locale/fr` (redirects to `/fr/`)

#### Automatic Redirects

* `yoursite.com/` → `yoursite.com/en/`
* `yoursite.com/pages` → `yoursite.com/en/pages`
* `yoursite.com/locale/fr` → `yoursite.com/fr/`

### 📝 Using Translations

#### In Views

```erb
<!-- Basic translation -->
<h1><%= t('pages.home.title') %></h1>

<!-- With interpolation -->
<p><%= t('welcome_message', name: @user.name) %></p>

<!-- Pluralization -->
<p><%= t('items', count: @items.count) %></p>
```

#### In Controllers

```ruby
# Set flash messages
flash[:notice] = t('users.updated_successfully')

# Redirect with translation
redirect_to root_path, notice: t('users.welcome_back')
```

#### In Models

```ruby
# Validation messages
validates :name, presence: { message: :blank }
```

### 🎨 Customizing the Language Switcher

#### Position

The switcher is positioned with:

```css
.fixed.bottom-4.right-4.z-50
```

#### Styling

* **Button**: `btn-circle btn-primary shadow-lg`
* **Dropdown**: `shadow-2xl bg-base-100 rounded-box`
* **Hover**: `hover:shadow-xl transition-shadow duration-200`

#### Customization

Edit `app/views/components/_language_switcher.html.erb` to:

* Change position (e.g., `top-4` instead of `bottom-4`)
* Modify styling (e.g., different button colors)
* Add animations
* Change icon

### 🌍 Adding New Languages

1. **Create locale file**:

   ```bash
   cp config/locales/en.yml config/locales/it.yml
   ```
2. **Create Devise locale file**:

   ```bash
   cp config/locales/devise.en.yml config/locales/devise.it.yml
   ```
3. **Update configuration** in `config/application.rb`:

   ```ruby
   config.i18n.available_locales = [:en, :es, :fr, :de, :it]
   ```
4. **Add translations** to the new locale files

### 🔧 Troubleshooting

#### Language switcher not showing

* Check that the component is rendered in `app/views/layouts/application.html.erb`
* Verify the component file exists at `app/views/components/_language_switcher.html.erb`

#### Translations not working

* Ensure locale files are in `config/locales/`
* Check that the locale is in `config.i18n.available_locales`
* Verify the translation key exists in the locale file

#### Routes not working

* Check that the locale change route is properly added
* Ensure `LocaleController` exists and has `skip_before_action :authenticate_user!`

### 📚 Best Practices

1. **Use translation keys consistently**
2. **Group related translations** (e.g., `pages.home.title`, `pages.about.title`)
3. **Use interpolation for dynamic content**
4. **Test all languages** after adding new translations
5. **Keep translations organized** in logical sections

### 🚀 Advanced Features

#### RTL Support

For right-to-left languages, add CSS classes:

```erb
<html dir="<%= I18n.locale == :ar ? 'rtl' : 'ltr' %>">
```

#### Number/Date Formatting

```ruby
# In locale files
number:
  currency:
    format:
      unit: "$"
      precision: 2
      separator: "."
      delimiter: ","

# In views
<%= number_to_currency(@price) %>
<%= l(@date, format: :long) %>
```

And voilà! We've given you a good start, but it's now up to you to continue with the internationalization of your app. Make sure to go page by page, adding the dynamic translation tags, and don't hesitate to use AI to help you. It's worth doing this operation in a branch on a client like Cursor, as it will make this lengthy process much faster.&#x20;

Happy translating! ᬍ


# Heros

All the components pre-built in LightningRails for FAQ sections - Design from DaisyUI, TailwindCss and Custom

### Hero center

<figure><img src="/files/OjgRfakaWzzb3Lq8EEv9" alt=""><figcaption><p>Hero section central</p></figcaption></figure>

To add a beautifully designed Hero section to your landing page, copy and paste this line of code where you need this section to appear in your html.erb page:

<pre><code><strong>&#x3C;%= render "components/hero" %>
</strong></code></pre>

### Hero with background

<figure><img src="/files/7M3ae1wZDsd7wTShyWsl" alt=""><figcaption></figcaption></figure>

To add the Hero section with a background frame, copy and paste this code:

```
<%= render "components/hero_bg" %>
```

### Hero image split

<figure><img src="/files/XAwz95jXLD6CPESub4cp" alt=""><figcaption><p>Hero with image</p></figcaption></figure>

To add the Hero section with a background frame and a split view with image, copy and paste this code:

```
<%= render "components/hero_split" %>
```

***

### See more Hero designs:

* [HyperUi Hero](#user-content-fn-1)[^1]
* [DaisyUI Hero](https://daisyui.com/components/hero/)

[^1]: <https://www.hyperui.dev/components/marketing/banners>


# FAQs

All the components pre-built in LightningRails for FAQ sections - Design from DaisyUI + TailwindCSS + custom

### Basic FAQ

<figure><img src="/files/wtZftWgkQW4N0u2S3zZk" alt=""><figcaption><p>LightningRails Basic FAQ</p></figcaption></figure>

To add this section copy and paste the following code into your html.erb file:

```
<%= render "components/faq" %>
```

***

### FAQ full width

<figure><img src="/files/3zTOdmbXQjm13Z8oVaWF" alt=""><figcaption><p>FAQ section with full width and + signs</p></figcaption></figure>

If you prefer this design copy paste the following:

```
<%= render "components/faq_full" %>
```

More designs coming soon! 🚀

***

### See more FAQ designs:

* [HyperUi faq](https://www.hyperui.dev/components/marketing/faqs)
* [DaisyUI faq](https://daisyui.com/components/collapse/)


# cards

All the components pre-built in LightningRails for cards - Design from DaisyUI

### Card Product

<figure><img src="/files/kv7e7qjBKu8l2Z9J4bAT" alt=""><figcaption><p>Product card design</p></figcaption></figure>

To use this card in your project, copy and paste the following card into your html.erb file:

```
<%= render "components/card_product_image"
```

***

### Card without image

<figure><img src="/files/ROLzLF5HV2eM4Q2KNRqB" alt=""><figcaption></figcaption></figure>

To use this card in your project, copy and paste the following card into your html.erb file:

```
<%= render "components/card_no_image"
```

***

### See more card designs:

* [HyperUi cards](https://www.hyperui.dev/components/marketing/cards)
* [DaisyUI cards](https://daisyui.com/components/card/)


# Testimonials

All the components pre-built in LightningRails for testimonials - Design from DaisyUI, HyperUI or custom

### Love wall testimonials

<figure><img src="/files/Sg3pKQJB5DJ9bd3LE6jr" alt=""><figcaption><p>Testimonials HyperUI - love-wall</p></figcaption></figure>

To add this design to your page, add the following line:

```
<%= render "components/testimonials" %>
```

***

### Testimonials horizontal

<figure><img src="/files/Bd3K22D8HjGTitikzNFW" alt=""><figcaption><p>Custom design from HyperUI testimonials, adapted to DaisyUI</p></figcaption></figure>

To add this design, copy and paste the following line:

```
<%= render "components/testimonials_horizontal" %>
```


# Call To Actions

All the components pre-built in LightningRails for call to actions (CTAs) sections - Design from DaisyUI, HyperUI or custom

### Horizontal CTA

<figure><img src="/files/RFIV8Wa3LkDAYYislPZA" alt=""><figcaption><p>CTA horizontal</p></figcaption></figure>

To add this design to your page, add the following line:

```
<%= render "components/cta_horizontal" %>
```

***

### CTA with form

<figure><img src="/files/ndsHt8X0kASd9HxKGhns" alt=""><figcaption><p>CTA with form</p></figcaption></figure>

To add this design, copy and paste the following line:

```
<%= render "components/cta_form" %>
```


# Features

All the components pre-built in LightningRails for Features showcase sections - Design from DaisyUI, HyperUI or custom

<figure><img src="/files/k2QDv9vHsNGEf79FwZpC" alt=""><figcaption><p>Left title and a grid with SVG's and descriptions of all the features</p></figcaption></figure>

To add this design to your page, add the following line:

```
<%= render "components/features_grid" %>
```

{% hint style="info" %}
**Pro Tip:** If you want to change the SVG icons, [checkout Lucile.dev icon's library](/features-setup/lucide-icons), built-in Lightning Rails by default.
{% endhint %}


# Sliplane Deploy

A guide on how to deploy your LightningRails app to sliplane

Sliplane is now my favorite hosting provider, it is very easy to deploy and you can have several apps under the same server, great if your apps haven't picked up momentum without having to spend hundreds of $ on Heroku servers.

***

After doing the setup, Sliplane’s mental model is:

1. **Create a Server** (your machine)
2. **Create a Project** (a group for one app and its services)
3. Add services inside the project (Postgres, Redis, your Rails app from GitHub)
4. Add env vars, deploy, then optionally add a custom domain
5. Use the built-in console (SSH) when you need to run Rails commands

{% hint style="info" %}
Bonus: **you can host several apps on the same server**, which is awesome for keeping costs down. Each app is just another Project on that same Server.
{% endhint %}

***

### 0. Prerequisites

* Your Lightning Rails app is pushed to GitHub (main branch recommended)
* You have a Sliplane account
* You are ready to set production env vars (Cloudinary, OpenAI, Stripe, etc if your app uses them) from your `.env` file

***

### 1. Prepare your app (Choose based on your case)

<details>

<summary>💡 <strong>For updating existing apps</strong></summary>

```
# One-time setup (if not already done)
gh auth login

# Fetch the Sliplane setup files and add them to your project

# 1. Fetch the rake task
gh api repos/LightningRails/lightning-rails/contents/lib/tasks/sliplane_setup.rake \
  --jq '.content' | base64 -d > lib/tasks/sliplane_setup.rake

# 2. Fetch the bash script
gh api repos/LightningRails/lightning-rails/contents/bin/setup-sliplane \
  --jq '.content' | base64 -d > bin/setup-sliplane

# 3. Make the bash script executable
chmod +x bin/setup-sliplane

# 4. Commit the changes
git add lib/tasks/sliplane_setup.rake bin/setup-sliplane
git commit -m "Added Sliplane deployment setup"

# 5. You're ready to deploy!
rails sliplane:setup
```

</details>

<details>

<summary><strong>🆕 For a new app</strong></summary>

From your project root:

```
bin/rails sliplane:setup
```

</details>

This command will:

* Create a production-ready `Dockerfile`
* Create `.dockerignore`
* Create `bin/docker-entrypoint`
* Create `.env.example`
* Generate a `SECRET_KEY_BASE`
* Print all required environment variables
* Show the Sliplane deployment checklist

Then push your code:

```
git add .
git commit -m "Prepare for Sliplane"
git push origin main
```

That’s it on the code side.

***

### 2. Create a Server in Sliplane

1. Go to Sliplane
2. Click **Create Server**
3. Choose a region
4. Choose an instance size (start small, scale later)
5. Wait until the server is ready

You can deploy multiple apps on the same server by creating multiple Projects.

***

### 3. Create a Project (your app)

1. Click **Create Project**
2. Name it something like:

```
myapp-production
```

Inside this project, you’ll add services.

***

### 4. Add a PostgreSQL service (Required)

Inside your project:

1. Click **Add Service**
2. Choose **PostgreSQL**
3. Set it to **Private** (not public) ⚠️
4. Add a Volume (Leave default)
5. Set (Leave default):

* `POSTGRES_USER`
* `POSTGRES_PASSWORD` (mark as Secret)
* `POSTGRES_DB`

6. Add&#x20;

Deploy it.

Once live, note the variables above and the internal hostname\
You’ll need it for `DATABASE_URL`.

***

### 5. Add Redis (Optional)

If your app uses:

* Caching
* ActionCable
* Very complex Background jobs
* Sidekiq

Then:

1. Add Service → Redis
2. Set to **Private**
3. Add a volume (optional)
4. Deploy

If not, you can skip this and use a SolidQueue setup (Guide coming soon)

***

### 6. Add Your Rails App (Repository Service)

Inside your project:

1. Click **Add Service**
2. Choose **Repository**
3. Connect your GitHub repo
4. Select branch (usually `main` or `master`)
5. Sliplane will detect your `Dockerfile`
6. ⚠️ Make sure the privacy toggle is set to TRUE! Or you won't be able to add a domain.

Set (Default):

* Public: **Enabled**
* Protocol: **HTTP**
* Healthcheck path: `/`

Deploy.

Sliplane will now build your Docker image and compile assets.

***

### 7. Add Environment Variables

Go to your Rails service → Settings → Environment Variables.

Add all the variables found in your `.env` file.

#### Required

```
RAILS_ENV=production
SECRET_KEY_BASE="Find this key in the terminal when you ran the script"
RAILS_SERVE_STATIC_FILES=true
RAILS_LOG_TO_STDOUT=true
```

#### Database URL

Go to your PostgreSQL service and find the variables in "settings". Here is where we link our brand new PostgreSQL service with our brand new repository service.

Build it using your Postgres service:

```
postgresql://USER:PASSWORD@INTERNAL_HOST_URL:5432/DB_NAME
```

Example shape:

```
postgresql://app_user:superSecret123@postgres-lhlg.internal:5432/production_db
```

Add it as:

```
DATABASE_URL=...
```

***

#### Third-Party Keys (if your app uses them)

If your Lightning Rails app uses external services, add them here:

Examples:

* `CLOUDINARY_URL`
* `OPENAI_API_KEY`
* `STRIPE_PUBLIC_KEY`
* `STRIPE_SECRET_KEY`
* `POSTMARK_API_TOKEN`

If it exists in your `.env` locally or in your previous hosting provider's config, it likely belongs here in production.

***

### 8. First Deploy Checklist

Once deployed:

* Visit your Sliplane URL (`*.sliplane.app`)
* Confirm styles are loading
* Test login / registration
* Check logs if anything fails

If styles are missing, make sure:

```
RAILS_SERVE_STATIC_FILES=true
```

is set.

***

### 9. Add a Custom Domain (Private Domain)

To use your own domain:

1. Open your Rails service
2. Go to **Domains**
3. Add your domain (example: `app.yourdomain.com`)
4. Sliplane will show required DNS records

Usually:

* For subdomain → create a **CNAME**
* For root domain → follow the exact record Sliplane provides

Once DNS propagates, SSL is handled automatically.

You can then update:

```
RAILS_HOST=app.yourdomain.com
```

in environment variables if needed.

***

### 10. Open Console (SSH)

Useful for:

* Running migrations
* Seeding data
* Rails console

#### Step 1 – Add your SSH key

In Sliplane account settings, add your public SSH key.

#### Step 2 – Open the service console

In your Rails service, click **Console / SSH**.\
Sliplane provides a ready-to-copy SSH command.

Use that command.

#### Step 3 – Navigate to the app directory

```
cd /your-app
```

#### Step 4 – Run commands

```
bundle exec rails db:migrate
bundle exec rails db:seed
bundle exec rails c
```

***

<details>

<summary>💡 Migrate PostgreSQL DB from Heroku (Optional)</summary>

We assume:

* Heroku app has a Postgres addon
* Sliplane has an internal Postgres service (like `postgres-lhlg.internal:5432`)
* You can run commands locally (recommended)

***

## Phase 1 — Freeze Heroku

### 1️⃣ Put Heroku in maintenance mode

```
heroku maintenance:on -a YOUR_HEROKU_APP
```

This prevents new writes while you migrate.

***

### 2️⃣ Take final backup (insurance)

```
heroku pg:backups:capture -a YOUR_HEROKU_APP
heroku pg:backups:download -a YOUR_HEROKU_APP
```

You now have `latest.dump` locally.

Keep it safe.

***

## Phase 2 — Restore to Sliplane

(You already did this, but here’s the official version)

### 3️⃣ Use pg\_restore 17 locally

```
PGPASSWORD='YOUR_DB_PASSWORD' $(brew --prefix postgresql@17)/bin/pg_restore \
  --verbose \
  --clean --if-exists \
  --no-owner --no-privileges \
  -h YOUR_PUBLIC_DB_HOST \
  -p YOUR_PUBLIC_DB_PORT \
  -U postgres \
  -d mydb \
  latest.dump
```

Let it finish.

***

### 4️⃣ Verify restore

```
PGPASSWORD='YOUR_DB_PASSWORD' psql \
  -h YOUR_PUBLIC_DB_HOST \
  -p YOUR_PUBLIC_DB_PORT \
  -U postgres \
  -d mydb \
  -c "\dt"
```

Then test row counts:

```
select count(*) from users;
```

Match with Heroku:

```
heroku run rails console -a YOUR_HEROKU_APP
User.count
```

If counts match → DB migration successful.

***

## Phase 3 — Point Rails to Sliplane DB

Now update your Sliplane Rails service:

Use **internal DB URL** (important for security & performance):

```
postgresql://postgres:PASSWORD@postgres-q6uu.internal:5432/mydb
```

Set this as:

```
DATABASE_URL
```

in Sliplane environment variables.

***

### 5️⃣ Redeploy Rails app

Then test:

* Login works
* New records save
* Background jobs work
* Stripe webhooks work
* Emails send
* ActiveStorage works

Create a test record and delete it.

***

## Phase 4 — Switch Traffic

If not already done:

* Update DNS to Sliplane
* Wait propagation
* Confirm production domain works

Monitor logs for 10–15 minutes.

***

## Phase 5 — Lock Down Database

Very important.

Once everything works:

* Remove public DB access
* Remove `0.0.0.0/0`
* Make Postgres internal-only

Your Rails app should now talk only via internal hostname.

***

## Phase 6 — Kill Heroku (After Confidence Window)

Leave Heroku in maintenance mode for a few hours (optional safety buffer).

When ready:

```
heroku apps:destroy -a YOUR_HEROKU_APP
```

Also remove:

* Heroku Postgres addon
* Heroku Redis
* Any scheduler addons

Double-check nothing else depends on it.

</details>

### Cost Optimization Tip

You can host **multiple Lightning Rails apps on the same Sliplane server**.

Each app:

* Has its own Project
* Has its own Postgres service
* Shares the same server resources

This keeps infrastructure simple and affordable.

***

### Summary

Deploying Lightning Rails to Sliplane is:

1. Run one setup command
2. Create server
3. Add Postgres
4. Add Repository service
5. Paste env variables
6. Deploy

Clean, simple, production-ready.


# Heroku Deploy

We currently use Heroku for deploying to production but LightningRails works with other popular options as well. We will add a guide to other ones very soon.

To install Heroku CLI in your terminal, follow these instructions:

### macOS installation <a href="#macos-installation" id="macos-installation"></a>

```bash
brew tap heroku/brew && brew install heroku
heroku --version # It should display heroku/x.x.x
```

⚠️ If you get this error:

```bash
Bad CPU type in executable
```

Execute this command:

```bash
softwareupdate --install-rosetta --agree-to-license
heroku --version # Now it should display heroku/x.x.x
```

### Ubuntu installation <a href="#ubuntu-installation" id="ubuntu-installation"></a>

```bash
curl https://cli-assets.heroku.com/install.sh | sh
heroku --version # It should display heroku/x.x.x
```

### Login <a href="#login" id="login"></a>

Signin from the terminal to be able to push directly from the terminal

```bash
heroku login
```

### Create your heroku app

To create an app and automatically connect it to your current directory of code through the GitHub remotes:

```bash
heroku create myawesomeapp --region eu
```

{% hint style="info" %}
It's important to set the correct region, otherwise your methods like Time.now will have the wrong time zone, plus you might get in trouble down the line with cookies and users data storage 🍪
{% endhint %}

### Push to production

After committing your last line of code, you can now push to production with a simple command

```bash
git push heroku master
```

### Migrate your database

Remember that your development database is only in your computer and stored locally, heroku in turn is in the cloud, which means it will have a different database, that you must migrate and seed.

```bash
heroku run rails db:migrate  # Run pending migrations in production
heroku run rails db:seed     # Run the seed in production
```

### Credential variables

Along the same lines as we set our API keys in our credentials file and .env file, you must set it to your production environment

```
heroku config:set postmark_api=YOUR_API_KEY
# repeat for all the API keys you have setup
```

### Stripe credentials

You will need to go to your stripe dashboard and toggle the "Live" button, to access the live api keys "developers" > API. Once done, copy the keys  in your terminal with the following command to&#x20;

```bash
heroku config:set STRIPE_PUBLIC_KEY=****
heroku config:set STRIPE_PRIVATE_KEY=****
```

You will also need to create a webhook under "Webhooks":

1. Click on "add an endpoint"
2. Add the endpoint url: *`"https://example.org/pay/webhooks/stripe"`*
3. Select events: Charge + checkout
4. Copy the key and paste it in your terminal with the following command

```bash
heroku config:set STRIPE_SIGNING_SECRET=*****

```

### Error Handling in Heroku

If you run into an error when opening your app in the browser with

```bash
heroku open
```

You will need to investigate by looking at the logs

```bash
heroku logs --tail
```


# Security

A section with guides on how to protect your rails app against attacks.

**To protect your app against Bots:**

{% content-ref url="/pages/cdX1835pbzpUUqjVfwwN" %}
[Rate Limiting](/deploying-to-production/security/rate-limiting)
{% endcontent-ref %}


# Rate Limiting

Implementing Rate Limiting in Your Ruby on Rails App with rack-attack

Implement rate limiting using the rack-attack gem to enhance the security of your Rails application and prevent abuse. This gem helps throttle excessive requests and block abusive traffic efficiently.

Follow these step-by-step instructions to integrate **rack-attack** into your Lightning Rails app.

### **Step 1: Add the rack-attack Gem**

First, add the [**rack-attack** gem](https://github.com/rack/rack-attack) to your `Gemfile`:

```ruby
gem 'rack-attack'
```

Then, install the gem by running:

```sh
bundle install
```

### **Step 2: Configure rack-attack**

Now, configure the rate limiting rules by creating an initializer file:

```sh
touch config/initializers/rack_attack.rb
```

Open the file and add the following configuration:

```ruby
class Rack::Attack
  # Throttle requests from the same IP address to 5 requests per second
  throttle('req/ip', limit: 5, period: 1.second) do |req|
    req.ip
  end

  # Block IPs that fail authentication too many times
  Rack::Attack.blocklist('block bad IPs') do |req|
    Rack::Attack::Fail2Ban.filter("bad-ips", maxretry: 5, findtime: 1.minute, bantime: 5.minutes) do
      req.ip if req.path == "users/sign_in" || "users/sign_up" && req.post?
    end
  end

  # Allow whitelisted IPs to bypass rate limits
  safelist('allow from localhost') do |req|
    '127.0.0.1' == req.ip
  end

  # Log blocked requests
  ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, payload|
    Rails.logger.info "[Rack::Attack] Throttled: #{payload[:request].ip}" if payload[:request]
  end
end
```

This configuration:

* Limits all requests to **5 per second per IP**.
* Blocks IPs that exceed **5 failed login attempts within a minute**.
* Allows **localhost (127.0.0.1)** to bypass rate limits, you can add other IPs if needed
* Logs blocked requests for monitoring on your production server.

This last point was very usefull to me as when I suspected a bot attack, I checked the IP address and realised it was Google's IP address trying to index my site 😅

### **Step 3: Test Your Configuration**

1. **Restart Your Server**\
   Run the following command to apply the changes:

   ```sh
   shCopyEditrails restart
   ```
2. **Monitor Logs**\
   Check your Rails logs (`log/development.log`) to see if any IPs are being throttled or blocked:

   ```sh
   shCopyEdittail -f log/development.log
   ```

   If rate limits are hit, you will see messages like:

   ```
   cppCopyEdit[Rack::Attack] Throttled: 192.168.1.100
   ```
3. **Adjust Limits**
   * Modify the `limit` and `period` values to suit your needs.
   * Tweak `maxretry`, `findtime`, and `bantime` for authentication-related restrictions.
   * Add **custom rules** to protect specific endpoints like API requests.

***

### **Step 4: Deploy and Monitor**

Once tested locally, deploy your changes to production. Monitor logs and adjust limits based on real-world traffic patterns.

You can also integrate **Redis** for better performance with large-scale applications.

***

### **Final Thoughts**

By following these steps, you've successfully added rate limiting to your Rails app using **rack-attack**. This helps prevent abuse, enhance security, and optimize resource usage efficiently.

Happy coding! 🚀


# Error Monitoring with Sentry

Receive errors directly on your email with Sentry free plan.

<figure><img src="/files/bV38IbAXxoZbUHFs9Pok" alt=""><figcaption></figcaption></figure>

Sentry is a powerful error tracking tool that helps you monitor and fix crashes in real-time. Adding it to your Lightning Rails project gives you instant visibility into production issues with stack traces, request context, and more.

### Installation

The Sentry SDK for Rails comes as two gems. Add them to your `Gemfile`:

```ruby
gem "sentry-ruby"
gem "sentry-rails"
```

Then install the gems:

```bash
bundle install
```

***

### Configure SDK

Generate the initializer with:

```bash
rails generate sentry
```

This creates `config/initializers/sentry.rb`.

Now edit the generated file to include your configuration:

```ruby
if Rails.env.production?
  Sentry.init do |config|
    config.dsn = 'https://8f1bc0b22a55bb483283e664f9fb421f@o4509708042043392.ingest.us.sentry.io/4509789067608064'
    config.breadcrumbs_logger = [:active_support_logger, :http_logger]
  
    # Add data like request headers and IP for users,
    # see https://docs.sentry.io/platforms/ruby/data-management/data-collected/ for more info
    config.send_default_pii = true
  end
end
```

> 💡 Make sure to only enable this in production by wrapping it in `if Rails.env.production?` , we don't want to spam our email with error messages when developing ;)&#x20;

***

### Verify It’s Working

You can test the setup by triggering a sample error in the production console.

```ruby
begin
  1 / 0
rescue ZeroDivisionError => exception
  Sentry.capture_exception(exception)
end

Sentry.capture_message("test message")
```

This should send both the exception and the message to your Sentry dashboard.

***

### Optional: Trace Propagation (Frontend + Backend)

If you're using frontend Sentry, you can enable distributed tracing by leaving this in your layout:

```erb
<%= Sentry.get_trace_propagation_meta.html_safe %>
```

It should have been added to your header when you used the generator above.  `<head>` tag in `app/views/layouts/application.html.erb`.

Happy Bug Tracking! 🐛🦟🕷️🪲


# MetaMask Authentication

Sign in with MetaMask (SIWE) using OmniAuth OpenID Connect

<figure><img src="/files/6UQhQNbNM3kaMtBOFnI3" alt=""><figcaption></figcaption></figure>

This guide adds “Sign in with MetaMask” (Sign-In with Ethereum, **SIWE**) to your Lightning Rails app using **Devise** + **OmniAuth OpenID Connect**. It keeps your regular Devise auth and adds a wallet-based login. No private keys ever touch your server—users only sign a message.

You’ll:

* Install and configure OmniAuth OIDC
* Register an OIDC client for **development** and **production**
* Add a Devise callback controller
* Store the user’s **wallet address** and show it in the navbar

### 1) Add gems

```ruby
# Gemfile
gem "omniauth"
gem "omniauth-rails_csrf_protection"
gem "omniauth_openid_connect", ">= 0.8.0"
```

```bash
bundle install
```

***

### 2) Add User fields

Generate and run the migration to store the wallet address and a timestamp:

```bash
rails g migration AddWeb3ToUsers wallet_address:string last_siwe_at:datetime
bin/rails db:migrate
```

If you prefer the exact file from the PR:

```ruby
# db/migrate/xxxxxx_add_web3_to_users.rb
class AddWeb3ToUsers < ActiveRecord::Migration[8.0]
  def change
    add_column :users, :wallet_address, :string
    add_index  :users, :wallet_address
    add_column :users, :last_siwe_at, :datetime
  end
end
```

***

### 3) Make your Devise model omniauthable

```ruby
# app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: [:openid_connect]
end
```

> We use the provider name `:openid_connect` to match routes and callback.

***

### 4) OmniAuth configuration

Create the OmniAuth initializer and allow POST/GET (GET helps during local testing; you can lock to POST later).

```ruby
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :openid_connect,
    name: :openid_connect,                        # Devise works best with this name
    scope: %i[openid profile],
    response_type: :code,
    issuer: ENV.fetch("SIWE_ISSUER"),             # e.g. https://oidc.login.xyz/
    discovery: true,
    client_options: {
      identifier:   ENV.fetch("SIWE_CLIENT_ID"),
      secret:       ENV.fetch("SIWE_CLIENT_SECRET"),
      redirect_uri: ENV.fetch("SIWE_REDIRECT_URI")
    }
end
```

```ruby
# config/initializers/omniauth_protection.rb
OmniAuth.config.allowed_request_methods = %i[post get]
```

***

### 5) Devise routes

```ruby
# config/routes.rb
devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
```

This generates:

* Authorize: `/users/auth/openid_connect`
* Callback: `/users/auth/openid_connect/callback`

***

### 6) OmniAuth callbacks controller

Generate the controller:

```bash
rails g controller users/omniauth_callbacks
```

Replace its content with:

```ruby
# app/controllers/users/omniauth_callbacks_controller.rb
module Users
  class OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def openid_connect
      auth  = request.env["omniauth.auth"] || {}
      info  = auth["info"]  || {}
      extra = auth["extra"] || {}
      raw   = (extra["raw_info"] || {}) # userinfo claims

      # Common places SIWE providers put the address (varies by issuer)
      addr = (info["address"] ||
              raw["address"] ||
              raw["wallet_address"] ||
              auth["uid"]).to_s.downcase

      if addr.blank?
        Rails.logger.warn("SIWE: missing address. auth=#{auth.inspect}")
        redirect_to new_user_session_path, alert: "Wallet address missing from SIWE response"
        return
      end

      user = User.find_or_initialize_by(wallet_address: addr)
      if user.new_record?
        user.email    = "eth-#{addr}@example.invalid"    # or prompt later
        user.password = Devise.friendly_token[0, 32]
      end
      user.last_siwe_at = Time.current
      user.save!

      sign_in_and_redirect user, event: :authentication
      set_flash_message(:notice, :success, kind: "MetaMask") if is_navigational_format?
    rescue => e
      Rails.logger.error("SIWE callback error: #{e.class} #{e.message}")
      redirect_to new_user_session_path, alert: "Sign-in failed"
    end

    def failure
      redirect_to new_user_session_path, alert: "Sign-in canceled"
    end
  end
end
```

***

### 7) Add the “Sign in with MetaMask” button

On your Devise sessions page (Lightning Rails’ sign-in):

```erb
# app/views/devise/sessions/new.html.erb
<%= button_to user_openid_connect_omniauth_authorize_path,
      method: :post,
      data: { turbo: false },
      class: "btn btn-primary gap-2 mt-4" do %>
  <!-- MetaMask logo (optional SVG) -->
  <svg ... class="w-6 h-6">...</svg>
  Sign in with MetaMask
<% end %>
```

> OmniAuth 2 prefers **POST** for the authorize request; using `button_to` handles that.

***

### 8) Show the wallet in the navbar (optional)

Helper:

```ruby
# app/helpers/application_helper.rb
module ApplicationHelper
  def truncate_address(address)
    return "" unless address.present?
    core = extract_core_address(address)
    "0x...#{core[-4..]}"
  end

  def extract_core_address(address)
    return "" unless address.present?

    # Handle eip155:1:0xabc...
    if address.include?(":")
      parts = address.split(":")
      return parts.last if parts.last.start_with?("0x")
    end
    address.start_with?("0x") ? address : "0x#{address}"
  end
end
```

Navbar snippet:

```erb
# app/views/shared/_navbar.html.erb (or your layout’s nav)
<% if user_signed_in? && current_user.wallet_address.present? %>
  <%= truncate_address(current_user.wallet_address) %>
<% else %>
  <%= link_to "Get started", new_user_registration_path, class: "btn btn-primary" %>
<% end %>
```

***

### 9) Environment variables (.env)

Lightning Rails already loads `.env`. Add these:

```
# Development issuer (example: login.xyz)
SIWE_ISSUER=https://oidc.login.xyz/
SIWE_CLIENT_ID=your_dev_client_id
SIWE_CLIENT_SECRET=your_dev_client_secret
SIWE_REDIRECT_URI=http://localhost:3000/users/auth/openid_connect/callback
```

> **Important:** the redirect path **must** match your provider name `:openid_connect`.\
> If you change the provider name to `:siwe`, use `/users/auth/siwe/callback` and update all references.

***

### 10) Register your OIDC client

You need a client **per environment** (dev and prod). Most SIWE OIDC issuers support dynamic registration.

#### Development registration (example)

```bash
curl -X POST 'https://oidc.login.xyz/register' \
  -H 'Content-Type: application/json' \
  --data '{
    "redirect_uris": ["http://localhost:3000/users/auth/openid_connect/callback"]
  }'
```

You’ll receive JSON with `client_id` and `client_secret`. Put them in your **dev** `.env`.

#### Production registration

Use your production domain:

```bash
curl -X POST 'https://oidc.login.xyz/register' \
  -H 'Content-Type: application/json' \
  --data '{
    "redirect_uris": ["https://YOUR_DOMAIN.com/users/auth/openid_connect/callback"]
  }'
```

Add the prod values to your production environment (e.g., Render/Heroku/Fly secrets) as:

```
SIWE_ISSUER=https://oidc.login.xyz/
SIWE_CLIENT_ID=your_prod_client_id
SIWE_CLIENT_SECRET=your_prod_client_secret
SIWE_REDIRECT_URI=https://YOUR_DOMAIN.com/users/auth/openid_connect/callback
```

> If you use another issuer (e.g., `https://oidc.signinwithethereum.org/`), the steps are identical—just change `SIWE_ISSUER` and the registration URL.

***

### 11) Sanity checks

After you’ve set everything:

```bash
rails routes | grep openid_connect
```

You should see:

```
user_openid_connect_omniauth_authorize  /users/auth/openid_connect(.:format)
user_openid_connect_omniauth_callback   /users/auth/openid_connect/callback(.:format)
```

Common fixes:

* **Routing error for `/auth/...`**&#x59;our Devise routes live under `/users/auth/...`. Make sure the **registered redirect URI** matches exactly.
* **“Mapping omniauth\_callbacks on a resource that is not omniauthable”**: ensure `:omniauthable` and `omniauth_providers: [:openid_connect]` are in `User`.
* **Path helper not found**: ensure the **provider name** matches in the initializer, model, controller method, and the button helper.

***

### 12) What you have now

* Users can sign in with MetaMask (SIWE) via a trusted OIDC provider.
* You store their **wallet address** on the `users` table.
* You can display it in the UI and later fetch balances/holdings.

From here, you can add:

* A “Link/Unlink wallet” page
* ETH balance fetch on the dashboard
* Parallel **Solana** login (Phantom) if you want multi-chain auth

That’s it—SIWE is live in your Lightning Rails app.


# Updating your project

How to Update Your LightningRails Project

This guide shows you how to safely update your existing LightningRails project to get the latest features, bug fixes, and improvements.

You can decide to update the [whole project with our Update Script](#update-script-heavy-conflicts-resotution), or cherry-pick specific Pull requests.

### 📋 Prerequisites

Before you start, make sure you have:

* ✅ A clean git working directory (commit or stash your changes)
* ✅ A backup of your project (or be on a feature branch)
* ✅ 5-10 minutes of time
* ✅ Basic familiarity with git commands

### 📃 UPDATE SCRIPT (Heavy conflicts resotution)

#### 🚨 For Projects Before v1.5.0 (Before July 2025)

If your LightningRails project was created before v1.5.0, you won't have the update script yet. Don't worry - we'll get you set up!

#### Option 1: Download the Script (Recommended)

From your project's root directory

```bash
# Make generators directory
mkdir generators

# One-time setup per user
gh auth login

# Fetch the update file and add it to your folder
gh api repos/LightningRails/lightning-rails/contents/generators/update_project.sh \
  --jq '.content' | base64 -d > generators/update_project.sh

# Commit this change
ga . && gc -m "Added generator folder"

# You're ready to go!
bash generators/update_project.sh
```

### 🚀 How to Use the Update Script

#### Step 1: Choose to Apply Updates

When prompted, choose option 1 to apply all updates.

<figure><img src="/files/sqTxO4Ip0KxS4cS1r4OD" alt=""><figcaption></figcaption></figure>

#### Step 3: Resolve Conflicts (If Any)

If you get merge conflicts, here's the general rule of thumb:

📁 For View Files (your customizations):

* Keep YOUR CURRENT changes in app/views/ files
* These contain your branding and custom content

⚙️ For Config Files (framework updates):

* Keep Most INCOMING changes in config/ files
* These contain important framework updates

🔍 Always check case by case - don't blindly accept all changes from either side.

#### Step 4: Complete the Update

After resolving conflicts, commit the merge

`git commit -m "Update: Merge latest LightningRails improvements"`

Install any new dependencies

`bundle install`

Run any new migrations

`rails db:migrate`

Test your application

`dev`

### 🍒 Cherry picking specific PRs

If you don't want to update the whole project, but bring a specific PR to your project.

1. **Add the Lightning Rails repo as a remote:**

```bash
git remote add lightning-rails https://github.com/LightningRails/lightning-rails.git
git fetch lightning-rails
```

2. **Find the commit(s) in the PR:**

From the PR page: <https://github.com/LightningRails/lightning-rails/pull/128>\
At the time of writing, the main commit is likely something like:

```bash
# Example (replace with actual commit SHA from the PR)
git cherry-pick abcdef1234567890
```

You can see this by clicking the “Commits” tab in the PR and grabbing the SHA(s).

3. **Resolve any conflicts** and commit the changes.
4. **Remove the remote (optional):**

```bash
git remote remove lightning-rails
```

### 🆘 Quick Troubleshooting

If something breaks after updating:

Return to your backup

`git checkout backup-before-update-[timestamp]`

If you're unsure about a conflict:

* When in doubt, keep your current changes for anything customer-facing
* Keep incoming changes for technical/framework files

### 💡 Pro Tips

* Update regularly - smaller, frequent updates are easier than big ones
* Test immediately - catch issues early
* Update during low-traffic times - be safe with production updates

That's it! The script handles the complex parts - you just need to make smart decisions about which changes to keep.

***

Remember: Your customizations in views are precious - protect them. Framework updates in config files are usually safe to accept.

Happy updating!&#x20;


# Design Resources

A collection of Free resources to help you pimp your Rails app


