Guide: Integrating React ⚛️ into a Rails 8 Application – Part 4 | Synthetic Event System | Event Methods | Props

This is about React’s Synthetic Event System and naming conventions. Let me explain why React uses onClick instead of click.

React’s Synthetic Event System

React doesn’t use native DOM events directly. Instead, it creates its own Synthetic Events system that wraps around native DOM events.

HTML vs React Comparison:

<!-- Regular HTML -->
<button onclick="myFunction()">Click me</button>

<!-- React JSX -->
<button onClick={myFunction}>Click me</button>

Key Differences:

1. Naming Convention – camelCase vs lowercase:

// ❌ HTML style (lowercase) - doesn't work in React
<button click={addTodo}>Add Todo</button>
<input change={handleChange} />
<form submit={handleSubmit}>

// ✅ React style (camelCase) - correct
<button onClick={addTodo}>Add Todo</button>
<input onChange={handleChange} />
<form onSubmit={handleSubmit}>

2. More Event Examples:

// HTML → React
onclick    → onClick
onchange   → onChange
onsubmit   → onSubmit
onkeydown  → onKeyDown
onkeyup    → onKeyUp
onmouseenter → onMouseEnter
onmouseleave → onMouseLeave
onfocus    → onFocus
onblur     → onBlur

❓ Why React Uses Synthetic Events?

1. Cross-Browser Compatibility:

// Without React - you'd need to handle browser differences
function addEventListenerCrossBrowser(element, event, handler) {
  if (element.addEventListener) {
    element.addEventListener(event, handler); // Modern browsers
  } else if (element.attachEvent) {
    element.attachEvent('on' + event, handler); // Old IE
  }
}

// With React - it handles all browser differences for you
<button onClick={addTodo}>Add Todo</button>

2. Consistent API:

// React events work the same across all browsers
const handleClick = (e) => {
  e.preventDefault();    // Always works
  e.stopPropagation();   // Always works
  console.log(e.target); // Always works
};

3. Performance Optimization:

// React uses event delegation - attaches one listener to document
// and manages all events from there (more efficient)
<div>
  <button onClick={handleClick1}>Button 1</button>
  <button onClick={handleClick2}>Button 2</button>
  <button onClick={handleClick3}>Button 3</button>
  {/* React doesn't attach 3 separate listeners */}
</div>

What happens behind the scenes:

When you write:
<button onClick={addTodo}>Add Todo</button>
React internally:
  1. Wraps the native click event in a SyntheticEvent
  2. Normalizes the event across different browsers
  3. Delegates the event handling to the document root
  4. Calls your addTodo function with the synthetic event

Example of React’s event object:

const handleClick = (e) => {
  console.log(e.type);        // "click"
  console.log(e.target);      // The button element
  console.log(e.currentTarget); // The button element
  console.log(e.preventDefault); // Function to prevent default

  // React provides the native event if needed
  console.log(e.nativeEvent); // Original DOM event
};

💡 Common Mistakes:

❌ Don’t do this:
// Wrong - lowercase (HTML style)
<button onclick={addTodo}>Add Todo</button>

// Wrong - calling the function immediately
<button onClick={addTodo()}>Add Todo</button>

// Wrong - using string (HTML style)
<button onClick="addTodo()">Add Todo</button>
✅ Do this:
// Correct - camelCase and function reference
<button onClick={addTodo}>Add Todo</button>

// Correct - arrow function for parameters
<button onClick={() => deleteTodo(id)}>Delete</button>

// Correct - arrow function for event access
<button onClick={(e) => handleClick(e, id)}>Click</button>

Full Event System Examples:

const MyComponent = () => {
  const handleClick = (e) => {
    console.log('Button clicked!');
    e.preventDefault(); // Prevent default behavior
  };

  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed!');
    }
  };

  const handleMouseEnter = () => {
    console.log('Mouse entered!');
  };

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
      <input onKeyPress={handleKeyPress} />
      <div onMouseEnter={handleMouseEnter}>Hover me</div>
    </div>
  );
};

📝 Summary:

React uses onClick (camelCase) instead of click (lowercase) because:

  1. Consistency – All React props use camelCase
  2. Synthetic Events – React wraps native events for better cross-browser support
  3. Performance – React optimizes event handling with event delegation
  4. Predictability – Same API across all browsers and components

The onClick you use in React is actually more powerful and reliable than the native onclick in HTML!

Questions

1. How Event Delegation Improves Performance?

Event delegation is a powerful optimization technique. Let me show you the difference:

Without Event Delegation (Traditional DOM):

// Imagine you have 1000 buttons - traditional approach
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
  button.addEventListener('click', handleClick); // 1000 event listeners!
});

With Event Delegation (React’s approach):

// React attaches ONE listener to the document root
document.addEventListener('click', (e) => {
  if (e.target.matches('button')) {
    handleClick(e); // Only one listener for all buttons!
  }
});

Performance Benefits:

Memory Usage:

// Without delegation: 1000 buttons = 1000 event listeners in memory
// With delegation: 1000 buttons = 1 event listener in memory

DOM Manipulation:

// Without delegation: Adding/removing elements requires managing listeners
const newButton = document.createElement('button');
newButton.addEventListener('click', handleClick); // Must remember to add
document.body.appendChild(newButton);

// With delegation: New elements automatically work
const newButton = document.createElement('button');
document.body.appendChild(newButton); // Automatically handles clicks!

Real React Example:

// Even with 1000 todos, React only has ONE click listener
<ul>
  {todos.map(todo => (
    <li key={todo.id}>
      <button onClick={() => deleteTodo(todo.id)}>Delete</button>
      <button onClick={() => editTodo(todo.id)}>Edit</button>
    </li>
  ))}
</ul>
// React manages all these clicks with ONE delegated listener

2. Event Parameter Naming – why always use ‘e’ for events?

We can absolutely use different names! e is just a convention, not a requirement.

All of these are valid:

// Common conventions
<button onClick={(e) => handleClick(e, id)}>Click</button>
<button onClick={(event) => handleClick(event, id)}>Click</button>
<button onClick={(evt) => handleClick(evt, id)}>Click</button>

// Your custom names
<button onClick={(v) => handleClick(v, id)}>Click</button>
<button onClick={(ev) => handleClick(ev, id)}>Click</button>
<button onClick={(clickEvent) => handleClick(clickEvent, id)}>Click</button>

Why developers use ‘e’:

// 'e' is short and widely recognized
const handleClick = (e) => {
  console.log(e.target);
};

// 'event' is more descriptive but longer
const handleClick = (event) => {
  console.log(event.target);
};

// Custom names work but might confuse other developers
const handleClick = (banana) => {
  console.log(banana.target); // Works but confusing!
};

Best Practice:

// Use 'e' for short inline handlers
<button onClick={(e) => e.preventDefault()}>Click</button>

// Use 'event' for longer, more complex handlers
const handleSubmit = (event) => {
  event.preventDefault();
  event.stopPropagation();
  // ... more logic
};

3. Event Methods and Properties Explained

Explain preventDefault, stopPropagation, currentTarget, target, type etc.

Let me create a comprehensive example:

const EventDemo = () => {
  const handleClick = (e) => {
    console.log('=== EVENT PROPERTIES ===');
    console.log('e.type:', e.type);                    // "click"
    console.log('e.target:', e.target);                // Element that triggered event
    console.log('e.currentTarget:', e.currentTarget);  // Element with event listener

    console.log('=== EVENT METHODS ===');
    // e.preventDefault(); // Prevents default behavior
    // e.stopPropagation(); // Stops event from bubbling up
  };

  return (
    <div onClick={handleClick} style={{padding: '20px', border: '1px solid blue'}}>
      <h3>Parent Div</h3>
      <button onClick={handleClick}>Child Button</button>
    </div>
  );
};

e.target vs e.currentTarget:

const Example = () => {
  const handleClick = (e) => {
    console.log('target:', e.target.tagName);        // What you clicked
    console.log('currentTarget:', e.currentTarget.tagName); // What has the listener
  };

  return (
    <div onClick={handleClick}>  {/* currentTarget will be DIV */}
      <button>Click me</button>  {/* target will be BUTTON */}
    </div>
  );
};

preventDefault() – Stops Default Browser Behavior:

const FormExample = () => {
  const handleSubmit = (e) => {
    e.preventDefault(); // Prevents form from submitting and page refresh
    console.log('Form submitted via JavaScript instead!');
  };

  const handleLinkClick = (e) => {
    e.preventDefault(); // Prevents link from navigating
    console.log('Link clicked but not navigating!');
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" />
        <button type="submit">Submit</button>
      </form>

      <a href="https://google.com" onClick={handleLinkClick}>
        This link won't navigate
      </a>
    </div>
  );
};

stopPropagation() – Stops Event Bubbling:

const BubblingExample = () => {
  const handleParentClick = () => {
    console.log('Parent clicked!');
  };

  const handleChildClick = (e) => {
    console.log('Child clicked!');
    e.stopPropagation(); // Prevents parent from also firing
  };

  return (
    <div onClick={handleParentClick} style={{padding: '20px', backgroundColor: 'lightblue'}}>
      Parent
      <button onClick={handleChildClick}>
        Child (click me - parent won't fire)
      </button>
    </div>
  );
};

e.type – Event Type:

const MultiEventExample = () => {
  const handleEvent = (e) => {
    switch(e.type) {
      case 'click':
        console.log('Button was clicked!');
        break;
      case 'mouseenter':
        console.log('Mouse entered button!');
        break;
      case 'mouseleave':
        console.log('Mouse left button!');
        break;
    }
  };

  return (
    <button 
      onClick={handleEvent}
      onMouseEnter={handleEvent}
      onMouseLeave={handleEvent}
    >
      Multi-event button
    </button>
  );
};

4. React Props 📦 Explained

Props (properties) are how you pass data from parent components to child components.

Basic Props Example:

// Parent component
const App = () => {
  const userName = "John";
  const userAge = 25;

  return (
    <div>
      <UserCard name={userName} age={userAge} />
    </div>
  );
};

// Child component receives props
const UserCard = (props) => {
  return (
    <div>
      <h2>Name: {props.name}</h2>
      <p>Age: {props.age}</p>
    </div>
  );
};

Props with Destructuring (More Common):

// Instead of using props.name, props.age
const UserCard = ({ name, age }) => {
  return (
    <div>
      <h2>Name: {name}</h2>
      <p>Age: {age}</p>
    </div>
  );
};

Different Types of Props:

const ComponentExample = () => {
  const user = { name: "Alice", email: "alice@example.com" };
  const numbers = [1, 2, 3, 4, 5];
  const isActive = true;

  return (
    <MyComponent 
      // String prop
      title="Hello World"

      // Number prop
      count={42}

      // Boolean prop
      isVisible={isActive}

      // Object prop
      user={user}

      // Array prop
      items={numbers}

      // Function prop
      onButtonClick={() => console.log('Clicked!')}
    />
  );
};

const MyComponent = ({ title, count, isVisible, user, items, onButtonClick }) => {
  return (
    <div>
      <h1>{title}</h1>
      <p>Count: {count}</p>
      {isVisible && <p>This is visible!</p>}
      <p>User: {user.name} ({user.email})</p>
      <ul>
        {items.map(item => <li key={item}>{item}</li>)}
      </ul>
      <button onClick={onButtonClick}>Click me</button>
    </div>
  );
};

Props in Our Todo App:

// We can break our todo app into smaller components
const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = () => {
    // ... add todo logic
  };

  const deleteTodo = (id) => {
    // ... delete todo logic
  };

  return (
    <div>
      <AddTodoForm 
        value={inputValue}
        onChange={setInputValue}
        onAdd={addTodo}
      />
      <TodoList 
        todos={todos}
        onDelete={deleteTodo}
      />
    </div>
  );
};

// Child component receives props
const AddTodoForm = ({ value, onChange, onAdd }) => {
  return (
    <div>
      <input 
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
      <button onClick={onAdd}>Add Todo</button>
    </div>
  );
};

const TodoList = ({ todos, onDelete }) => {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem 
          key={todo.id}
          todo={todo}
          onDelete={onDelete}
        />
      ))}
    </ul>
  );
};

const TodoItem = ({ todo, onDelete }) => {
  return (
    <li>
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
};

Props Rules:

  1. Props are read-only – child components cannot modify props
  2. Props flow down – from parent to child, not the other way up
  3. Props can be any data type – strings, numbers, objects, arrays, functions
  4. Props are optional – you can provide default values
// Default props
const Greeting = ({ name = "World", enthusiasm = 1 }) => {
  return <h1>Hello {name}{"!".repeat(enthusiasm)}</h1>;
};

// Usage
<Greeting />                    // "Hello World!"
<Greeting name="Alice" />       // "Hello Alice!"
<Greeting name="Bob" enthusiasm={3} /> // "Hello Bob!!!"

🎯 Summary:

  1. Event Delegation = One listener handles many elements = Better performance
  2. Event parameter naming = Use any name you want (e, event, evt, v, etc.)
  3. Event methods: preventDefault() stops default behavior, stopPropagation() stops bubbling
  4. Event properties: target = what triggered event, currentTarget = what has listener
  5. Props = Data passed from parent to child components

Let’s see in Part 5. Happy React Development! 🚀

Guide: Integrating React ⚛️ into a Rails 8 Application – Part 3 | Start developing react

Let’s move on to quick development of more react components now. Before that let’s check what we have now and understand it very clear.

📄 File 1:

Our app/javascript/components/App.jsx file:

import React from 'react';

function App() {
  return (
    <div>
      <h1>React is working fine!</h1>
      <p>Welcome to Rails + React App</p>
    </div>
  );
}

export default App;

Let’s examine this React component step by step:

Line 1: Import React

import React from 'react';
  • import – ES6 module syntax to bring in external code
  • React – The main React library
  • from 'react' – Importing from the npm package named “react”
  • Why needed? Even though we use --jsx=automatic, we still import React for any hooks or React features we might use.

Function Component: Line 3-9

A React function component is a simple JavaScript function that serves as a building block for user interfaces in React applications. These components are designed to be reusable and self-contained, encapsulating a specific part of the UI and its associated logic.

function App() {
  return (
    <div>
      <h1>React is working fine!</h1>
      <p>Welcome to Rails + React App</p>
    </div>
  );
}

🔍 Breaking this down:

Line 3: Component Declaration

function App() {
  • function App() – This is a React Function Component
  • Component naming – Must start with capital letter (App, not app)
  • What it is – A JavaScript function that returns JSX (user interface)

Line 4-8: JSX Return

return (
  <div>
    <h1>React is working fine!</h1>
    <p>Welcome to Rails + React App</p>
  </div>
);
  • return – Every React component must return something
  • JSX – Looks like HTML, but it’s actually JavaScript
  • <div> – Must have one parent element (React Fragment rule)
  • <h1> & <p> – Regular HTML elements, but processed by React

Line 11: Export

export default App;
  • export default – ES6 syntax to make this component available to other files
  • App – The component name we’re exporting
  • Why needed? So application.js can import and use this component

📄 File 2:

Our app/javascript/application.js file:

// Entry point for the build script in your package.json
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './components/App';

document.addEventListener('DOMContentLoaded', () => {
  const container = document.getElementById('react-root');

  if(container) {
    const root = createRoot(container);
    root.render(<App />);
  }
});

This is the entry point that connects React to your Rails app:

    Imports: Line 2-4

    import React from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './components/App';
    

    🔍 Breaking down each import:

    Line 2:

    import React from 'react';
    
    • Same as before – importing the React library

    Line 3:

    import { createRoot } from 'react-dom/client';
    
    • { createRoot }Named import (notice the curly braces)
    • react-dom/client – ReactDOM library for browser/DOM manipulation
    • createRoot – New React 18+ API for rendering components to DOM

    Line 4:

    import App from './components/App';
    
    • AppDefault import (no curly braces)
    • ./components/App – Relative path to our App component
    • Note: We don’t need .jsx extension, esbuild figures it out

    DOM Integration: Line 6-12

    document.addEventListener('DOMContentLoaded', () => {
      const container = document.getElementById('react-root');
    
      if(container) {
        const root = createRoot(container);
        root.render(<App />);
      }
    });
    

    🔍 Step by step breakdown:

    Line 6:

    document.addEventListener('DOMContentLoaded', () => {
    
    • document.addEventListener – Standard browser API
    • 'DOMContentLoaded' – Wait until HTML is fully loaded
    • () => { – Arrow function (ES6 syntax)
    • Why needed? Ensures the HTML exists before React tries to find elements

    Line 7:

    const container = document.getElementById('react-root');
    
    • const container – Create a variable to hold the DOM element
    • document.getElementById('react-root') – Find HTML element with id="react-root"
    • Where is it? In your Rails view file: app/views/home/index.html.erb

    Line 9:

    if(container) {
    
    • Safety check – Only proceed if the element exists
    • Prevents errors – If someone visits a page without react-root element

    Line 10-11:

    const root = createRoot(container);
    root.render(<App />);
    
    • createRoot(container) – Create a React “root” at the DOM element
    • root.render(<App />) – Render our App component inside the container
    • <App /> – JSX syntax for using our component (self-closing tag)

    🎯 Key React Concepts You Just Learned:

    1. Components

    • Functions that return JSX
    • Must start with capital letter
    • Reusable pieces of UI

    2. JSX

    • Looks like HTML, actually JavaScript
    • Must return single parent element
    • Processed by esbuild into regular JavaScript

    3. Import/Export

    • Default exports: export default Appimport App from './App'
    • Named exports: export { createRoot }import { createRoot } from 'package'

    4. React DOM

    • createRoot() – Modern way to mount React apps (React 18+)
    • render() – Display components in the browser

    5. Rails Integration

    • Rails serves the HTML page
    • React takes over the #react-root element
    • esbuild bundles everything together

    🚀 This pattern is the foundation of every React app! We create components, import them, and render them to the DOM.


    📚 Step-by-Step React Learning with Todo List

    Now let’s build a Todo List app step by step. I’ll explain each React concept thoroughly as we go. Here’s our learning roadmap:

    Step 1: Understanding JSX and Basic Component Structure

    First, let’s update our App.jsx to create the basic structure of our Todo app:

    import React from 'react';
    
    function App() {
      return (
        <div className="todo-app">
          <h1>My Todo List</h1>
          <p>Let's learn React by building a todo app!</p>
    
          {/* This is a JSX comment */}
          <div className="todo-container">
            <h2>Add a new todo</h2>
            <input type="text" placeholder="Enter a todo..." />
            <button>Add Todo</button>
    
            <h2>My Todos</h2>
            <ul>
              <li>Learn React basics</li>
              <li>Build a todo app</li>
              <li>Master React hooks</li>
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
    

    🎯 Key Concepts Explained:

    JSX (JavaScript XML):

    • JSX lets you write HTML-like syntax directly in JavaScript
    • It’s a syntax extension for JavaScript, not actual HTML
    • JSX gets compiled to JavaScript function calls
    • You can use {} to embed JavaScript expressions inside JSX

    Important JSX Rules:

    • Use className instead of class (because class is a reserved word in JavaScript)
    • You can use single quotes for className values in JSX. Both work perfectly fine:
    // Both of these are valid:
    <div className='todo-app'>    // Single quotes ✅
    <div className="todo-app">    // Double quotes ✅
    

    Quote Usage in JSX/JavaScript:

    Single quotes vs Double quotes:

    • JavaScript treats them identically
    • It’s mostly a matter of personal/team preference
    • The key is to be consistent throughout your project

    Common conventions:

    // Option 1: Single quotes for JSX attributes
    <div className='todo-app'>
      <input type='text' placeholder='Enter todo...' />
    </div>
    
    // Option 2: Double quotes for JSX attributes  
    <div className="todo-app">
      <input type="text" placeholder="Enter todo..." />
    </div>
    
    // Option 3: Mixed (but stay consistent within each context)
    const message = 'Hello World';  // Single for JS strings
    <div className="todo-app">      // Double for JSX attributes
    

    When you MUST use specific quotes:

    // When the string contains the same quote type
    <div className="It's a great day">        // Double quotes needed
    <div className='He said "Hello"'>        // Single quotes needed
    
    // Or use escape characters
    <div className='It\'s a great day'>       // Escaping single quote
    <div className="He said \"Hello\"">      // Escaping double quote
    

    💡 Tip: Many teams use tools like Prettier or ESLint to automatically format and enforce consistent quote usage across the entire project.

    • All tags must be closed (self-closing tags need / at the end)
    • JSX comments use {/* */} syntax
    • Return a single parent element (or use React Fragment <>...</>)

    Try updating our App.jsx with this code and see it in your browser!


    Step 2: Introduction to State with useState

    Now let’s add state to make our app interactive. State is data that can change over time.

    import React, { useState } from 'react';
    
    function App() {
      // useState Hook - creates state variable and setter function
      const [todos, setTodos] = useState([
        { id: 1, text: 'Learn React basics', completed: false },
        { id: 2, text: 'Build a todo app', completed: false },
        { id: 3, text: 'Master React hooks', completed: true }
      ]);
    
      const [inputValue, setInputValue] = useState('');
    
      return (
        <div className="todo-app">
          <h1>My Todo List</h1>
    
          <div className="todo-container">
            <h2>Add a new todo</h2>
            <input 
              type="text" 
              placeholder="Enter a todo..." 
              value={inputValue}
              onChange={(e) => setInputValue(e.target.value)}
            />
            <button>Add Todo</button>
    
            <h2>My Todos ({todos.length})</h2>
            <ul>
              {todos.map(todo => (
                <li key={todo.id}>
                  {todo.text} {todo.completed ? '✅' : '⏳'}
                </li>
              ))}
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
    

    🎯 Key Concepts Explained:

    useState Hook:

    • useState is a React Hook that lets you add state to functional components
    • It returns an array with two elements: [currentValue, setterFunction]
    • const [todos, setTodos] = useState([]) creates a state variable todos and a function setTodos to update it
    • The initial value is passed as an argument to useState

    Controlled Components:

    • The input field is now “controlled” by React state
    • value={inputValue} makes the input show what’s in state
    • onChange={(e) => setInputValue(e.target.value)} updates state when user types

    Array.map() for Rendering Lists:

    • todos.map() transforms each todo into a JSX element
    • Each list item needs a unique key prop for React’s optimization
    • {todo.text} embeds the todo text using JSX expressions

    Try this code and notice how the input field now responds to typing!


    Step 3: Event Handling and Adding Todos

    Let’s make the “Add Todo” button work:

    import React, { useState } from 'react';
    
    function App() {
      const [todos, setTodos] = useState([
        { id: 1, text: 'Learn React basics', completed: false },
        { id: 2, text: 'Build a todo app', completed: false },
        { id: 3, text: 'Master React hooks', completed: true }
      ]);
    
      const [inputValue, setInputValue] = useState('');
    
      // Function to add a new todo
      const addTodo = () => {
        if (inputValue.trim() !== '') {
          const newTodo = {
            id: Date.now(), // Simple ID generation
            text: inputValue,
            completed: false
          };
    
          setTodos([...todos, newTodo]); // Spread operator to add new todo
          setInputValue(''); // Clear the input field
        }
      };
    
      // Function to handle Enter key press
      const handleKeyPress = (e) => {
        if (e.key === 'Enter') {
          addTodo();
        }
      };
    
      return (
        <div className="todo-app">
          <h1>My Todo List</h1>
    
          <div className="todo-container">
            <h2>Add a new todo</h2>
            <input 
              type="text" 
              placeholder="Enter a todo..." 
              value={inputValue}
              onChange={(e) => setInputValue(e.target.value)}
              onKeyPress={handleKeyPress}
            />
            <button onClick={addTodo}>Add Todo</button>
    
            <h2>My Todos ({todos.length})</h2>
            <ul>
              {todos.map(todo => (
                <li key={todo.id}>
                  {todo.text} {todo.completed ? '✅' : '⏳'}
                </li>
              ))}
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
    

    🎯 Key Concepts Explained:

    Event Handlers:

    • onClick={addTodo} – function runs when button is clicked
    • onKeyPress={handleKeyPress} – function runs when key is pressed
    • Event handlers receive an event object (e) with information about the event

    State Updates:

    • setTodos([...todos, newTodo]) – creates a new array with all existing todos plus the new one
    • Important: Always create new arrays/objects instead of mutating existing ones
    • React compares old and new state to determine if re-render is needed

    Spread Operator (...):

    • ...todos spreads out all elements of the todos array
    • This is the React way to add items to an array in state

    Try adding new todos now!

    🎯 Function Syntax Options in JavaScript:

    const addTodo = () => {} is a function syntax – specifically an arrow function. Let me explain why it’s declared as const and the different ways to write functions in JavaScript.

    1. Function Declaration (Traditional)
    function addTodo() {
      // function body
    }
    
    2. Function Expression with Arrow Function
    const addTodo = () => {
      // function body
    };
    
    3. Function Expression (Traditional)
    const addTodo = function() {
      // function body
    };
    

    🤔 Why use const for functions?

    Arrow functions are expressions, not declarations:

    // This is a DECLARATION - creates a function named addTodo
    function addTodo() { }
    
    // This is an EXPRESSION - creates a function and assigns it to a variable
    const addTodo = () => { };
    

    Why const specifically?

    // ❌ Could be reassigned accidentally
    let addTodo = () => { };
    addTodo = "oops"; // Function is now gone!
    
    // ❌ Could be reassigned accidentally  
    var addTodo = () => { };
    addTodo = null; // Function is now gone!
    
    // ✅ Cannot be reassigned - prevents bugs
    const addTodo = () => { };
    addTodo = "something"; // ERROR: Assignment to constant variable
    

    📚 Key Differences:

    Function Declaration vs Arrow Function:

    // Function Declaration
    function addTodo() {
      console.log("Adding todo");
    }
    
    // Arrow Function (assigned to const)
    const addTodo = () => {
      console.log("Adding todo");
    };
    

    Hoisting Behavior:

    // ✅ This works - function declarations are "hoisted"
    sayHello(); // "Hello!"
    
    function sayHello() {
      console.log("Hello!");
    }
    
    // ❌ This doesn't work - arrow functions are not hoisted
    sayGoodbye(); // Error: Cannot access 'sayGoodbye' before initialization
    
    const sayGoodbye = () => {
      console.log("Goodbye!");
    };
    

    this Binding:

    // Function declaration has its own 'this'
    function regularFunction() {
      console.log(this); // 'this' can change based on how it's called
    }
    
    // Arrow function inherits 'this' from surrounding scope
    const arrowFunction = () => {
      console.log(this); // 'this' is inherited from parent scope
    };
    

    🚀 In React Context:

    In React functional components, we typically use arrow functions with const because:

    1. Prevents accidental reassignment – our function won’t get overwritten
    2. Consistent with modern JavaScript – ES6+ standard
    3. Cleaner syntax – less verbose than traditional function expressions
    4. Better for event handlersthis behavior is more predictable

    All these are equivalent in React:

    // Option 1: Arrow function with const (most common)
    const addTodo = () => {
      if (inputValue.trim() !== '') {
        // ... logic
      }
    };
    
    // Option 2: Traditional function declaration
    function addTodo() {
      if (inputValue.trim() !== '') {
        // ... logic  
      }
    }
    
    // Option 3: Function expression with const
    const addTodo = function() {
      if (inputValue.trim() !== '') {
        // ... logic
      }
    };
    

    💡 Why React developers prefer arrow functions:

    1. Shorter syntax for simple functions
    2. Consistent variable declaration (everything uses const)
    3. No hoisting confusion – functions are defined before they’re used
    4. Better with modern tooling – ESLint, Prettier handle them well

    So yes, const addTodo = () => {} is definitely a function! It’s just a modern way to write functions that prevents accidental reassignment and has cleaner syntax.

    🎯 What is Hoisting?

    Hoisting is a fundamental JavaScript concept that can be confusing at first. Let me explain it clearly with examples.

    Hoisting is JavaScript’s behavior of moving declarations to the top of their scope during the compilation phase, before the code is executed.

    Think of it like JavaScript “hoists” (lifts up) your variable and function declarations to the top of their scope.

    📚 How Hoisting Works:

    Function Declarations are Hoisted:

    // This works even though we call the function before declaring it!
    sayHello(); // Outputs: "Hello!"
    
    function sayHello() {
      console.log("Hello!");
    }
    

    Behind the scenes, JavaScript treats it like this:

    // JavaScript internally reorganizes it like this:
    function sayHello() {
      console.log("Hello!");
    }
    
    sayHello(); // Now it makes sense!
    

    Variable Declarations (with var) are Hoisted:

    console.log(myVar); // Outputs: undefined (not an error!)
    var myVar = "Hello";
    console.log(myVar); // Outputs: "Hello"
    

    Behind the scenes:

    // JavaScript internally treats it like this:
    var myVar; // Declaration is hoisted to the top
    console.log(myVar); // undefined (declared but not assigned)
    myVar = "Hello"; // Assignment stays in place
    console.log(myVar); // "Hello"
    

    🚫 What is NOT Hoisted:

    let and const variables:

    // ❌ This throws an error!
    console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
    let myLet = "Hello";
    
    // ❌ This also throws an error!
    console.log(myConst); // ReferenceError: Cannot access 'myConst' before initialization
    const myConst = "Hello";
    

    Arrow Functions (Function Expressions):

    // ❌ This throws an error!
    sayGoodbye(); // ReferenceError: Cannot access 'sayGoodbye' before initialization
    
    const sayGoodbye = () => {
      console.log("Goodbye!");
    };
    

    Function Expressions:

    // ❌ This throws an error!
    myFunc(); // TypeError: myFunc is not a function
    
    var myFunc = function() {
      console.log("Hello!");
    };
    

    🎭 Visual Examples:

    Example 1: Function Declaration Hoisting

    // What you write:
    console.log("1. Start");
    myFunction(); // This works!
    console.log("3. End");
    
    function myFunction() {
      console.log("2. Inside function");
    }
    
    // Output:
    // 1. Start
    // 2. Inside function  
    // 3. End
    

    Example 2: Variable Hoisting with var

    // What you write:
    console.log("Value is:", myVar); // undefined
    var myVar = 42;
    console.log("Value is:", myVar); // 42
    
    // What JavaScript internally does:
    var myVar; // Hoisted to top
    console.log("Value is:", myVar); // undefined
    myVar = 42; // Assignment stays here
    console.log("Value is:", myVar); // 42
    

    Example 3: Why let and const are different

    // Temporal Dead Zone
    console.log("Before declaration");
    
    // ❌ This area is the "Temporal Dead Zone" for myLet
    console.log(myLet); // ReferenceError!
    
    let myLet = "Hello"; // Declaration and initialization
    console.log(myLet); // "Hello"
    

    🎯 Practical Implications:

    Why Function Declarations work everywhere:

    // This is valid JavaScript
    function main() {
      helper(); // Can call helper before it's defined
    
      function helper() {
        console.log("Helper function");
      }
    }
    

    Why Arrow Functions must be defined first:

    // ❌ This doesn't work
    const main = () => {
      helper(); // Error! helper is not defined yet
    
      const helper = () => {
        console.log("Helper function");
      };
    };
    
    // ✅ This works
    const main = () => {
      const helper = () => {
        console.log("Helper function");
      };
    
      helper(); // Now it works!
    };
    

    💡 Best Practices:

    1. Use const and let instead of var to avoid hoisting confusion
    2. Define functions before using them (even though function declarations are hoisted)
    3. Use arrow functions for consistency and to avoid hoisting surprises
    4. Initialize variables when you declare them

    🔗 Back to Our React Example:

    // This is why we use const for arrow functions
    const App = () => {
      // ✅ Good: Function defined before use
      const addTodo = () => {
        // function logic
      };
    
      // ❌ Bad: Would cause error if we tried to call it here
      // deleteTodo(); // Error!
    
      const deleteTodo = () => {
        // function logic
      };
    
      return (
        <div>
          <button onClick={addTodo}>Add</button>
          <button onClick={deleteTodo}>Delete</button>
        </div>
      );
    };
    

    Hoisting is JavaScript’s way of making function declarations available throughout their scope, but it doesn’t apply to modern variable declarations (let, const) or arrow functions. That’s why we define our functions with const and make sure to declare them before we use them!


    Step 4: Toggling Todo Completion

    Let’s add the ability to mark todos as complete/incomplete:

    import React, { useState } from 'react';
    
    function App() {
      const [todos, setTodos] = useState([
        { id: 1, text: 'Learn React basics', completed: false },
        { id: 2, text: 'Build a todo app', completed: false },
        { id: 3, text: 'Master React hooks', completed: true }
      ]);
    
      const [inputValue, setInputValue] = useState('');
    
      const addTodo = () => {
        if (inputValue.trim() !== '') {
          const newTodo = {
            id: Date.now(),
            text: inputValue,
            completed: false
          };
    
          setTodos([...todos, newTodo]);
          setInputValue('');
        }
      };
    
      // Function to toggle todo completion
      const toggleTodo = (id) => {
        setTodos(todos.map(todo => 
          todo.id === id 
            ? { ...todo, completed: !todo.completed } // Toggle completed status
            : todo // Keep todo unchanged
        ));
      };
    
      const handleKeyPress = (e) => {
        if (e.key === 'Enter') {
          addTodo();
        }
      };
    
      return (
        <div className="todo-app">
          <h1>My Todo List</h1>
    
          <div className="todo-container">
            <h2>Add a new todo</h2>
            <input 
              type="text" 
              placeholder="Enter a todo..." 
              value={inputValue}
              onChange={(e) => setInputValue(e.target.value)}
              onKeyPress={handleKeyPress}
            />
            <button onClick={addTodo}>Add Todo</button>
    
            <h2>My Todos ({todos.length})</h2>
            <ul>
              {todos.map(todo => (
                <li key={todo.id}>
                  <span 
                    onClick={() => toggleTodo(todo.id)}
                    style={{ 
                      textDecoration: todo.completed ? 'line-through' : 'none',
                      cursor: 'pointer',
                      color: todo.completed ? '#888' : '#000'
                    }}
                  >
                    {todo.text}
                  </span>
                  {todo.completed ? ' ✅' : ' ⏳'}
                </li>
              ))}
            </ul>
          </div>
        </div>
      );
    }
    
    export default App;
    

    🎯 Key Concepts Explained:

    Array.map() for Updates:

    • todos.map() creates a new array where each todo is either updated or kept the same
    • todo.id === id ? { ...todo, completed: !todo.completed } : todo
    • This pattern is very common in React for updating items in arrays

    Conditional Rendering:

    • todo.completed ? '✅' : '⏳' – ternary operator for conditional display
    • textDecoration: todo.completed ? 'line-through' : 'none' – conditional styling

    Arrow Functions in JSX:

    • onClick={() => toggleTodo(todo.id)} – arrow function to pass arguments to event handlers
    • Without the arrow function, toggleTodo(todo.id) would execute immediately

    Now you can click on todos to toggle their completion status!


    🤔 What we’ve learned so far:

    1. ✅ JSX syntax and rules
    2. ✅ useState hook for state management
    3. ✅ Event handling (onClick, onChange, onKeyPress)
    4. ✅ Controlled components
    5. ✅ Array mapping for rendering lists
    6. ✅ Conditional rendering
    7. ✅ State updates with spread operator

    Next Steps: In the following steps, we’ll cover:

    • Deleting todos
    • Component composition (breaking into smaller components)
    • Props passing
    • Filtering todos
    • More advanced state management

    Let’s see in Part 4. Happy React Development! 🚀

    📦 Sprockets vs 🧵 Propshaft in Ruby on Rails 7/8 – What’s the Difference?

    When working with asset pipelines in Ruby on Rails 7 and 8, you might encounter Sprockets and Propshaft—two asset handling libraries. While both aim to serve static assets like JavaScript, CSS, images, and fonts, they do so in different ways.

    This post will walk you through what each does, how they differ, and when you might want to use one over the other.


    📦 What is Sprockets?

    Sprockets is the original Rails asset pipeline system, introduced way back in Rails 3.1. It allows developers to:

    • Concatenate and minify JavaScript and CSS
    • Preprocess assets using things like SCSS, CoffeeScript, ERB, etc.
    • Fingerprint assets for cache busting
    • Compile assets at deploy time

    It works well for traditional Rails applications where the frontend and backend are tightly coupled.

    Pros:

    • Mature and stable
    • Rich preprocessing pipeline (SCSS, CoffeeScript, ERB, etc.)
    • Supports advanced directives like //= require_tree .

    Cons:

    • Complex internal logic
    • Slower compilation times
    • Relies on a manifest file that can get messy
    • Tightly coupled with older Rails asset practices

    🧵 What is Propshaft?

    Propshaft is the newer asset pipeline introduced by the Rails team as an alternative to Sprockets. It focuses on simplicity and modern best practices. Propshaft was added as an optional asset pipeline starting in Rails 7 and is included by default in some new apps.

    Design Philosophy:
    Propshaft aims to work like a static file server with fingerprinting and logical path mapping, rather than a full asset compiler.

    Key Features:

    • Uses logical paths (e.g., /assets/application.css)
    • No preprocessing pipeline by default (but supports it via extensions like Tailwind or Sass)
    • Supports digesting (fingerprinting) of assets
    • Leaner and faster than Sprockets
    • Easier to integrate with modern JavaScript bundlers (like importmaps, esbuild, or webpack)

    Pros:

    • Lightweight and fast
    • Easier to debug
    • Works great with importmaps and Hotwire
    • Modern, forward-looking approach

    Cons:

    • No advanced preprocessing by default
    • Limited plugin ecosystem (still maturing)
    • Doesn’t support old Sprockets directives

    🔍 Key Differences at a Glance

    FeatureSprocketsPropshaft
    Introduced InRails 3.1Rails 7
    Default in RailsRails 6 and earlierOptional from Rails 7+
    Preprocessing SupportYes (SCSS, ERB, CoffeeScript, etc.)No (only raw assets by default)
    SpeedSlowerFaster
    Configuration ComplexityHigherMinimal
    Plugin EcosystemLarge and matureNew and growing
    Use With Importmaps/HotwireCan work, but heavierIdeal
    DebuggingHarder due to complexityEasier

    🧰 When Should You Use Sprockets?

    Choose Sprockets if:

    • You are upgrading a legacy Rails app
    • Your project already relies on Sprockets
    • You use heavy asset preprocessing
    • You need compatibility with gems that depend on Sprockets

    ⚡ When Should You Use Propshaft?

    Choose Propshaft if:

    • You are starting a new Rails 7/8 project
    • You use Importmaps or Hotwire/Turbo
    • You prefer faster and simpler asset handling
    • You don’t need complex preprocessing

    Propshaft pairs particularly well with modern frontend workflows like Tailwind CSS (via build tools) or StimulusJS (with importmaps).

    🛠️ Switching from Sprockets to Propshaft

    If you’re migrating, here are basic steps:

    1. Remove sprockets-rails gem from your Gemfile: # Gemfile # gem "sprockets-rails"
    2. Add propshaft: gem "propshaft"
    3. Update config/application.rb: config.assets.resolver = Propshaft::Resolver.new( paths: [Rails.root.join("app/assets")] )
    4. Remove app/assets/config/manifest.js (used by Sprockets)
    5. Move all assets to the correct logical paths under app/assets
    6. Use digested URLs as needed (asset_path("application.css") etc.)

    🧪 Real Example in Rails 8

    Here’s how your application.html.erb might look using Propshaft:

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
    
    

    And your app/assets/builds/application.css could be compiled via Tailwind or SCSS using a toolchain.


    🧠 Final Thoughts

    Sprockets has served Rails well for over a decade, but Propshaft is the new lightweight future. If you’re starting fresh, Propshaft is a strong choice, especially when used alongside Hotwire, Importmaps, or modern JS bundlers.

    However, don’t feel pressured to switch if your current Sprockets setup works fine—Rails continues to support both.


    ✨ TL;DR

    • Sprockets = older, feature-rich, best for legacy apps
    • Propshaft = newer, minimal, better for modern workflows

    Choose based on your app’s needs and complexity. Cheers! 🚀

    Design Studio – Rails 8 e-commerce application: Integrate active admin

    Now let’s create an admin interface for our e-commerce Application.

    We have a well-structured e-commerce Rails application with:

    Models: User, Product, ProductVariant, Order, OrderItem
    Authentication: Custom session-based auth with user roles (customer/admin)
    Authorization: Already has admin role checking

    Admin Interface Recommendations

    Here are the best options for Rails admin interfaces, ranked by suitability for our project:

    1. ActiveAdmin (Recommended ⭐)
      Best fit for e-commerce with complex associations
      Excellent filtering, search, and batch operations
      Great customization options and ecosystem
      Handles your Product → ProductVariant → OrderItem relationships well
    2. Administrate (Modern Alternative)
      Clean, Rails-way approach by Thoughtbot
      Good for custom UIs, less configuration
      More work to set up initially
    3. Rails Admin (What you asked about)
      Quick setup but limited customization
      Less actively maintained
      Good for simple admin needs
    4. Avo (Modern Premium)
      Beautiful modern UI
      Some features require paid version

      https://avohq.io/rails-admin
      https://docs.avohq.io/3.0/

    Choose ActiveAdmin for our e-commerce application. Let’s integrate it with our existing authentication system

    Add in Gemfile:

    gem "activeadmin"
    gem "sassc-rails" # Required for ActiveAdmin
    gem "image_processing", "~> 1.2" # For variant processing if not already present
    

    Bundle Install and run the Active Admin Generator:

    $ bundle install
    $ rails generate active_admin:install --skip-users
    definition of Rules was here
    create app/assets/javascripts/active_admin.js
    create app/assets/stylesheets/active_admin.scss
    create db/migrate/20250710083516_create_active_admin_comments.rb
    

    Migration File created by Active Admin:

    class CreateActiveAdminComments < ActiveRecord::Migration[8.0]
      def self.up
        create_table :active_admin_comments do |t|
          t.string :namespace
          t.text   :body
          t.references :resource, polymorphic: true
          t.references :author, polymorphic: true
          t.timestamps
        end
        add_index :active_admin_comments, [ :namespace ]
      end
    
      def self.down
        drop_table :active_admin_comments
      end
    end
    

    Run database migration:

    $ rails db:migrate
    

    in app/initializers/active_admin.rb

    # This setting changes the method which Active Admin calls
      # within the application controller.
      config.authentication_method = :authenticate_admin_user!
    ....
    # This setting changes the method which Active Admin calls
      # (within the application controller) to return the currently logged in user.
      config.current_user_method = :current_admin_user
    ....
     # Default:
      config.logout_link_path = :destroy_session_path
    

    in app/controllers/application_controller.rb

    private
    
      def authenticate_admin_user!
        require_authentication
        ensure_admin
      end
    
      def current_admin_user
        Current.user if Current.user&.admin?
      end
    

    Run the active admin user, product generator:

    rails generate active_admin:resource User
    rails generate active_admin:resource Product
    rails generate active_admin:resource ProductVariant
    rails generate active_admin:resource Order
    rails generate active_admin:resource OrderItem
    

    Let’s update all the active admin resources with fields, filters, attributes, panels etc.

    Let’s add accepts_nested_attributes_for :variants, allow_destroy: true in Product Model.

    accepts_nested_attributes_for is a Rails feature that allows a parent model to accept and process attributes for its associated child models through nested parameters. Here’s what it does:

    What it enables:

    1. Nested Forms: You can create/update a Product and its ProductVariants in a single form submission
    2. Mass Assignment: Allows passing nested attributes through strong parameters
    3. CRUD Operations: Create, update, and delete associated records through the parent

    In our Product model

    class Product < ApplicationRecord
      has_many :variants, dependent: :destroy, class_name: "ProductVariant"
      accepts_nested_attributes_for :variants, allow_destroy: true
    end
    

    What this allows:

    Before: You’d need separate forms/requests for Product and ProductVariant

    # Create product first
    product = Product.create(name: "T-Shirt", brand: "Nike")
    
    # Then create variants separately
    product.variants.create(size: "M", color: "Red", sku: "NIKE-001-M-RED")
    product.variants.create(size: "L", color: "Blue", sku: "NIKE-001-L-BLUE")
    

    After: You can do it all in one go:

    Product.create(
      name: "T-Shirt",
      brand: "Nike",
      variants_attributes: [
        { size: "M", color: "Red", sku: "NIKE-001-M-RED" },
        { size: "L", color: "Blue", sku: "NIKE-001-L-BLUE" }
      ]
    )
    

    In ActiveAdmin context:

    This enables the nested form I created in app/admin/products.rb:

    f.inputs "Product Variants" do
      f.has_many :variants, allow_destroy: true, new_record: true do |v|
        v.input :size
        v.input :color
        v.input :sku
        # ... other variant fields
      end
    end
    
    The allow_destroy: true option:
    • Allows deleting associated records by passing _destroy: ‘1’
    • Essential for updating existing products with variants
    Strong Parameters:

    In the controller, you need to permit the nested attributes:

    permit_params :name, :brand, :category, 
                  variants_attributes: [:id, :size, :color, :sku, :mrp, :discount_percent, :_destroy]
    

    Propshaft vs Sprockets

    We have an error after integrating ActiveAdmin:

     Rendered layout layouts/application.html.erb (Duration: 13.9ms | GC: 0.5ms)
    Completed 500 Internal Server Error in 118ms (ActiveRecord: 28.7ms (1 query, 0 cached) | GC: 27.0ms)
    
    ActionView::Template::Error (undefined method 'load_path' for an instance of Sprockets::Environment)
    Caused by: NoMethodError (undefined method 'load_path' for an instance of Sprockets::Environment)
    
    Information for: ActionView::Template::Error (undefined method 'load_path' for an instance of Sprockets::Environment):
        14:     <link rel="icon" href="/icon.svg" type="image/svg+xml">
        15:     <link rel="apple-touch-icon" href="/icon.png">
    

    This error occurs because Rails 8 uses Propshaft by default, but ActiveAdmin requires Sprockets. There’s a conflict between the two asset pipelines. Let’s fix this by switching to Sprockets completely:

    Add in Gemfile:

    # Use Sprockets for asset pipeline (required for ActiveAdmin)
    gem "sprockets-rails"
    
    $ bundle install
    
    # application.rb
    
    # Use Sprockets for asset pipeline
    config.assets.enabled = true
    
    

    Let’s create an admin user for testing our ActiveAdmin:

    # seed.rb
    # Create admin user
    admin_user = User.find_or_create_by(email: "admin@designstudio.com") do |user|
      user.password = "password123"
      user.role = "admin"
    end
    
    puts "Admin user created: #{admin_user.email}" if admin_user.persisted?
    
    # run seed
    ✗ rails db:seed
    

    ✅ We have Successfully Integrated ActiveAdmin with Nested Attributes!

    I’ve successfully converted your Rails application to use nested attributes (variants_attributes) across the entire stack and integrated ActiveAdmin as your admin interface. Here’s what I accomplished:

    🔧 What I Fixed & Updated:

    1. Asset Pipeline Issue:

    • Switched from Propshaft to Sprockets (required for ActiveAdmin)
    • Updated Gemfile and application configuration
    • Fixed the load_path error you encountered

    2. Model Changes:

    • Added accepts_nested_attributes_for :variants, allow_destroy: true to Product model

    3. Controller Updates:

    • Changed from singular product_variant to plural variants_attributes
    • Simplified create/update actions to use Rails nested attributes handling
    • Updated strong parameters to permit nested variant attributes

    4. View Improvements:

    • Updated product form to handle multiple variants with JavaScript
    • Added “Add Another Variant” functionality
    • Implemented proper nested form fields for all variant attributes

    5. ActiveAdmin Configuration:

    • Set up authentication using your existing user system
    • Created comprehensive admin resources for all models
    • Added nested variant management in product forms
    • Built an informative dashboard with key metrics

    🎯 Admin Features You Now Have:

    1. Dashboard – Key metrics, recent activity, revenue overview
    2. User Management – Role management, order history
    3. Product Management – Full CRUD with image uploads and nested variants
    4. Order Management – Status updates, batch actions
    5. Inventory Management – ProductVariant and OrderItem tracking

    🔑 Admin Access:

    • URL: http://localhost:3000/admin
    • Email: admin@designstudio.com
    • Password: password123

    📊 Comparison vs Rails Admin:

    ActiveAdmin Advantages:

    • ✅ Better for complex e-commerce relationships
    • ✅ Excellent filtering and search capabilities
    • ✅ Powerful batch operations
    • ✅ Highly customizable forms and views
    • ✅ Active development and community

    Rails Admin:

    • ❌ Less actively maintained
    • ❌ Limited customization options
    • ❌ Not as suitable for complex nested relationships

    🚀 What’s Working Now:

    1. Nested Variants: Create/edit products with multiple variants in one form
    2. Image Management: Upload multiple product images
    3. Order Processing: Admin can update order statuses with batch actions
    4. User Management: Promote users to admin, view order history
    5. Dashboard Analytics: Revenue tracking, popular products, recent activity

    Visit /admin to explore your new admin interface, Cheers!

    Check docs: https://activeadmin.info/0-installation.html

    ⚠️ Tailwind UI Issue after moving to Sprockets

    The issue is with <%= stylesheet_link_tag "/tailwind/application.css", "data-turbo-track": "reload" %> where we are trying to load Tailwind CSS from an incorrect path. The tailwindcss-rails gem serves Tailwind CSS differently. Let me fix this:

    ls -la app/assets/tailwind/
    
    curl -I http://localhost:3000/tailwind/application.css
    
    rails routes | grep -i tailwind
    

    The issue is that the /tailwind/application.css path is returning 404. The tailwindcss-rails gem works differently. Let’s check how it’s configured and fix this:

    find . -name "*.css" -path "*/public/*" | head -10
    
    rails assets:precompile
    

    Now let’s test if the Tailwind CSS is properly accessible:

    curl -I http://localhost:3000/assets/tailwind/application.css
    

    The issue is that with the tailwindcss-rails gem, Tailwind CSS gets compiled into the main application.css file rather than a separate file. Let’s check the current asset manifest and fix the layout:

    rails assets:precompile
    
    bundle exec tailwindcss build
    

    The Tailwind CSS is already built and located in app/assets/builds/tailwind.css. Let’s fix the configuration:

    rails assets:precompile
    
    curl -I http://localhost:3000/assets/tailwind.css
    

    The issue has been fixed. The Tailwind CSS is now properly accessible. Here’s what I did to resolve the problem:

    1. Identified the issue: The Tailwind CSS was being incorrectly referenced as /tailwind/application.css in the layout file, but it wasn’t being served properly through the asset pipeline.
    2. Found the correct location: Discovered that the tailwindcss-rails gem builds the CSS file to app/assets/builds/tailwind.css.
    3. Fixed the asset pipeline configuration:
    • Updated app/assets/config/manifest.js to include //= link_tree ../builds which includes the built Tailwind CSS
    • Removed the incorrect tailwind/application.css reference
    1. Updated the layout file:
    • Changed from <%= stylesheet_link_tag "/tailwind/application.css" %>
    • To the correct <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
    1. Verified the fix:
    • Successfully precompiled assets
    • Confirmed that http://localhost:3000/assets/tailwind.css returns a 200 OK status

    Install Rails Icons 🍁

    For better font-awesome assets, lets add rails-icons gem:

    # Rails Icons - Modern icon library support
    gem "rails_icons"
    
    $ rails generate rails_icons:install --help
    $ rails generate rails_icons:install --libraries=heroicons
    
    $ npm install @fortawesome/fontawesome-free
    

    How to migrate from the CDN to Rails Icons

    For a production Rails application, it’s generally better to use a gem rather than a CDN for Font Awesome. Here’s why:

    CDN Issues:

    • External dependency (can fail if CDN is down)
    • Privacy concerns (external requests)
    • No version control
    • Requires internet connection
    • Not cacheable with your assets

    Gem Benefits:

    • Self-hosted (no external dependencies)
    • Better performance (served with your assets)
    • Version control
    • Works offline
    • Follows Rails conventions
    • Better for production

    Based on my research, adding a gem is definitely the better approach for a production Rails application. Here are the best options:

    Option 1: Modern approach with npm package (Recommended)

    Since we’re using Rails 8 with modern tooling, I’d recommend using the npm package:

    yarn add @fortawesome/fontawesome-free
    
    npm --version
    
    which node
    

    However, given your current setup, I’d recommend Option 2 which is more Rails-native:

    Option 2: Rails Icons gem (Recommended for our setup)

    This is a modern gem that supports Font Awesome and other icon libraries with a clean Rails API:

    bundle install
    
    rails generate rails_icons:install --help
    
    rails generate rails_icons:install --libraries=heroicons
    

    Let’s remove the CDN and set up Rails Icons properly. Since we’re already using Font Awesome extensively, let’s add Font Awesome as a custom library:

    rails generate rails_icons:initializer --custom=fontawesome
    
    npm install @fortawesome/fontawesome-free
    
    find node_modules/@fortawesome/fontawesome-free -name "*.svg" -type f | head -5
    
    cp -r node_modules/@fortawesome/fontawesome-free/svgs/* app/assets/svg/icons/fontawesome/
    

    Rails Icons is now properly set up instead of the CDN. Here’s what we’ve accomplished:

    What’s Changed:

    1. Removed the Font Awesome CDN link (no more external dependency)
    2. Added rails_icons gem with Heroicons as the default library
    3. Added Font Awesome as a custom library with all variants (solid, regular, brands)
    4. Self-hosted all icons through Rails asset pipeline

    What is Ransack?

    Ransack is a popular Ruby gem that provides powerful search and filtering capabilities for Rails applications. It’s commonly used with ActiveAdmin (which your app uses) to create searchable, filterable, and sortable data tables.

    Ransackable Attributes and Associations

    ransackable_attributes

    This method defines which model attributes can be searched, filtered, or sorted through Ransack. It’s a security feature that prevents unauthorized access to sensitive data.

    def self.ransackable_attributes(auth_object = nil)
      %w[id email role created_at updated_at password_reset_token password_reset_sent_at]
    end
    

    What it does:

    • Allows searching/filtering by id, email, role, created_at, updated_at, etc.
    • Prevents searching by sensitive fields like password_digest
    • The auth_object parameter can be used for role-based access control

    ransackable_associations

    This method defines which model associations can be used in Ransack queries.

    def self.ransackable_associations(auth_object = nil)
      %w[orders sessions]
    end
    

    What it does:

    • Allows searching/filtering by related models (e.g., “users who have orders”)
    • Enables joins and complex queries across associations

    Why This Matters for Security

    Without these methods, Rails 7+ will raise warnings or errors because Ransack needs explicit permission to search certain fields. This prevents:

    1. Information disclosure – Searching sensitive fields like passwords
    2. Performance issues – Searching on unindexed or inappropriate fields
    3. Unauthorized access – Accessing data through unexpected query parameters

    Example Usage in ActiveAdmin

    In your ActiveAdmin dashboard, this enables features like:

    # In app/admin/users.rb
    ActiveAdmin.register User do
      # Users can now search by email, role, etc.
      # Users can filter by orders, sessions
      # Users can sort by created_at, updated_at
    end
    

    In Our User Model

    Looking at your User model:

    def self.ransackable_attributes(auth_object = nil)
      %w[id email role created_at updated_at password_reset_token password_reset_sent_at]
    end
    
    def self.ransackable_associations(auth_object = nil)
      %w[orders sessions]
    end
    

    This means:

    • ✅ Admins can search users by email, role, creation date
    • ✅ Admins can filter users by their orders or sessions
    • ✅ Admins can sort users by any of these attributes
    • ❌ Admins cannot search by password_digest (secure)
    • ❌ Admins cannot search by other sensitive fields

    Benefits

    1. Security: Explicit control over what can be searched
    2. Performance: Only searchable fields need indexes
    3. Usability: Provides powerful admin interface features
    4. Compliance: Helps meet data protection requirements

    Migration Guide:

    Old CDN way:

    <i class="fas fa-user"></i>          <!-- solid -->
    <i class="far fa-star"></i>          <!-- regular -->
    <i class="fab fa-facebook-f"></i>    <!-- brands -->
    

    New Rails Icons way:

    <%= icon "user", library: "fontawesome", variant: "solid", class: "size-6" %>
    <%= icon "star", library: "fontawesome", variant: "regular", class: "size-6" %>
    <%= icon "facebook-f", library: "fontawesome", variant: "brands", class: "size-6" %>
    

    Or use modern Heroicons (recommended for new icons):

    <%= icon "user", class: "size-6" %>
    <%= icon "star", class: "size-6" %>
    <%= icon "heart", class: "size-6" %>
    

    Benefits:

    • No external dependencies (works offline)
    • Better performance (served with your assets)
    • Version control over icons
    • Multiple libraries (Font Awesome + Heroicons + more)
    • Consistent API across all icon libraries
    • Better for production and privacy

    Our application should work exactly the same now, but with better performance and no external dependencies! You can gradually migrate icons from <i class="fas fa-x"> to <%= icon "x", library: "fontawesome", variant: "solid" %> as needed.

    Asset Issue Again

    ✅ Final Fix Applied:

    Added //= link_tree ../../javascript .js to the manifest, which tells Sprockets to include all JavaScript files from the app/javascript directory tree.

    ✅ What’s Working Now:

    1. All CSS files properly compiled:
    • tailwind.css ✅
    • application.css ✅
    • active_admin.css ✅
    1. All JavaScript files properly compiled:
    • application.js ✅
    • controllers/application.js ✅
    • controllers/decorative_background_controller.js ✅
    • controllers/hello_controller.js ✅
    • controllers/index.js ✅
    1. Asset Pipeline fully functional:
    • Sprockets serving all assets correctly
    • Proper cache headers and ETags
    • All asset links declared in manifest

    🎯 Current Status:

    Our Rails application now has:

    • ✅ Modern asset pipeline with Sprockets + Importmap
    • ✅ Tailwind CSS for styling (self-hosted)
    • ✅ ActiveAdmin for administration
    • ✅ Rails Icons instead of Font Awesome CDN
    • ✅ Stimulus controllers for JavaScript functionality
    • ✅ All assets properly precompiled and served

    Check Github: Active admin changes

    📋 Clean Commit History Summary:

    1. ✅ Phase 1: feat: Add Rails Icons gem with Font Awesome and Heroicons support #94
    • Replaced Font Awesome CDN with self-hosted icons
    • Added Rails Icons with unified API for multiple icon libraries
    1. ✅ Phase 2: task: Migrate from Propshaft to Sprockets asset pipeline #96
    • Switched from Propshaft to Sprockets for ActiveAdmin compatibility
    • Fixed asset compilation and linking issues
    1. ✅ Phase 3: feat: Integrate ActiveAdmin for comprehensive admin interface #94
    • Complete ActiveAdmin setup with authentication
    • Full admin resources for all e-commerce models
    1. ✅ Phase 4: fix: Resolve ActiveAdmin PostgreSQL and Ransack security issues #94
    • Fixed PostgreSQL GROUP BY errors in dashboard
    • Added Ransack security configuration for all models

    🚀 Our ActiveAdmin is now fully functional!

    You should now be able to:

    • ✅ Access the admin dashboard at localhost:3000/admin
    • ✅ View analytics and statistics without GROUP BY errors
    • ✅ Search and filter all resources safely with Ransack
    • ✅ Manage Users, Products, Variants, Orders, and Order Items
    • ✅ Use nested attributes for product variants
    • ✅ Perform batch operations and advanced filtering

    Test it out: Visit localhost:3000/admin and log in with your admin credentials to see the beautiful, fully-functional admin interface! 🎯

    to be continued 🚀…

    Guide: Integrating React.js ⚛️ into a Rails 8 Application – Part 2: Install React | Add esbuild, Jsx | Integrate React View

    Throw back:

    rails new design_studio_react --database=postgresql -j esbuild --skip-hotwire
    

    Here’s what our Rails app looks like after skipping Hotwire with the --skip-hotwire flag:

    Current JavaScript/Node.js Setup (Clean & Minimal)

    📦 Package Management:

    • package.json – Clean setup with only esbuild script
    • .node-version – Node.js version 24.1.0
    • No dependencies – Ready for React installation

    📁 JavaScript File Structure (Ultra-Clean):

    app/javascript/
    └── application.js          # Empty entry point (2 lines total!)
    

    app/javascript/application.js content:

    // Entry point for the build script in your package.json
    

    🚫 What Got Successfully Removed:

    • No Turbo/Stimulus imports in application.js
    • No controllers/ directory at all
    • No Hotwire gems in Gemfile (only jsbundling-rails remains)
    • No @hotwired/turbo-rails or @hotwired/stimulus dependencies

    ⚙️ Configuration Files (Minimal – Only 4):

    1. package.json – esbuild build script only
    2. .node-version – Node.js version pinning
    3. Procfile.dev – Development processes (js: yarn build --watch)
    4. app/javascript/application.js – Empty entry point

    🔧 esbuild Configuration:

    {
      "scripts": {
        "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets"
      }
    }
    

    📂 Build Output:

    • app/assets/builds/ – Contains only .keep file (empty, ready for bundles)

    🎯 HTML Integration:

    <!-- Still includes the JavaScript module correctly -->
    <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
    

    (Note: data-turbo-track is just an HTML attribute for cache busting, not the Turbo library)

    # create db
    ✗ rails db:migrate
    
    # run react-rails-app in port 3001
    ✗ rails s -p 3001
    

    🚀 Next Steps: Install & Setup React

    Step 1: Install react, react-dom

    Your app is now perfectly clean for React! Just run:

    brew install yarn
    yarn add react react-dom # check node_modules/ folder for what is installed
    yarn add --dev @types/react @types/react-dom  # Optional: for TypeScript support, check node_modules/@types folder
    

    Status: ✅ Minimal JavaScript foundation – No Hotwire bloat, perfect React starting point!

    Now that we have a clean Rails app with esbuild setup, here’s our step-by-step roadmap to get React working:

    Step 2: Create Your First React Component

    Create a simple React component to test the setup:

    mkdir app/javascript/components
    

    Then create app/javascript/components/App.jsx:

    import React from 'react';
    
    function App() {
      return (
        <div>
          <h1>React is Working!</h1>
          <p>Welcome to your Rails + React app</p>
        </div>
      );
    }
    
    export default App;
    

    Step 3: Update JavaScript Entry Point

    Modify app/javascript/application.js to render React:

    // Entry point for the build script in your package.json
    import React from 'react';
    import { createRoot } from 'react-dom/client';
    import App from './components/App';
    
    document.addEventListener('DOMContentLoaded', () => {
      const container = document.getElementById('react-root');
      if (container) {
        const root = createRoot(container);
        root.render(<App />);
      }
    });
    

    Step 4: Create a Controller & Route

    Generate a home controller:

    rails generate controller Home index
    

    Step 5: Add React Root to View

    Update app/views/home/index.html.erb:

    <div id="react-root"></div>
    

    Step 6: Set Root Route

    Update config/routes.rb:

    Rails.application.routes.draw do
      root "home#index"
      # ... other routes
    end
    

    Step 7: Start Development

    # update Procfile.dev assign port 3001
    web: env RUBY_DEBUG_OPEN=true bin/rails server -p 3001
    
    # run our rails-react app by
    
    ✗ bin/dev
    21:15:27 web.1  | started with pid 12619
    21:15:27 js.1   | started with pid 12620
    21:15:27 js.1   | yarn run v1.22.22
    21:15:27 js.1   | $ esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets --watch
    21:15:27 js.1   | /bin/sh: esbuild: command not found
    21:15:27 js.1   | error Command failed with exit code 127.
    21:15:27 js.1   | info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
    21:15:27 web.1  | => Booting Puma
    ..........
    

    🎯 What This Gives Us:

    • ✅ React components in app/javascript/components/
    • ✅ esbuild automatically bundles JSX
    • ✅ Hot reloading with yarn build --watch
    • ✅ Rails serves your React app

    🚨 Error Analysis:

    1. Rails server started fine ✅ (port 3001)

    🔧 Solution: Install JavaScript Dependencies

    You need to install esbuild and other JavaScript dependencies first:

    yarn install
    

    📋 Files yarn install Checks:

    1. Primary: package.json

    {
      "name": "app",
      "private": true,
      "scripts": {
        "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets"
      },
      "dependencies": {
        "react": "^19.1.0",           # ✅ Already added
        "react-dom": "^19.1.0"       # ✅ Already added  
      },
      "devDependencies": {
        "@types/react": "^19.1.8",      # ✅ Already added
        "@types/react-dom": "^19.1.6"   # ✅ Already added
      }
    }
    

    2. Lockfile: yarn.lock

    • Ensures exact versions for reproducible installs
    • Contains resolved URLs and integrity hashes

    🚨 The Problem: Missing esbuild!

    Our package.json calls esbuild in the build script, but esbuild is not listed as a dependency anywhere!

    Rails should have added it, but didn’t. We need to add esbuild:

    yarn add --dev esbuild
    

    🔧 What yarn install does:

    1. Reads package.json for dependency list
    2. Reads yarn.lock for exact versions (if exists)
    3. Downloads packages to node_modules/
    4. Updates yarn.lock with new resolutions

    Current issue: Our build script expects esbuild command, but it’s not installed in node_modules/.bin/esbuild

    Solution: Add esbuild as a dev dependency!

    Solved~ and start servers: Error Again!

    x bin/dev
    
    * Listening on http://[::1]:3001
    22:54:43 web.1  | Use Ctrl-C to stop
    22:54:44 js.1   | ✘ [ERROR] The JSX syntax extension is not currently enabled
    22:54:44 js.1   |
    22:54:44 js.1   |     app/javascript/application.js:11:16:
    22:54:44 js.1   |       11 │     root.render(<App />);
    22:54:44 js.1   |          ╵                 ^
    22:54:44 js.1   |
    22:54:44 js.1   |   The esbuild loader for this file is currently set to "js" but it must be set to "jsx" to be able to parse JSX syntax. You can use "--loader:.js=jsx" to do that.
    22:54:44 js.1   |
    22:54:44 js.1   | 1 error
    22:54:44 js.1   | [watch] build finished, watching for changes..
    

    This error occurs because esbuild doesn’t know how to handle JSX syntax! The <App /> is JSX, but esbuild needs to be configured to transform it.

    🚨 Problem: esbuild can’t process JSX syntax

    Your application.js contains JSX (<App />), but esbuild isn’t configured to transform JSX!

    JSX (JavaScript XML) is a syntax extension for JavaScript, commonly used with React, that allows you to write HTML-like code within JavaScript files.

    🔧 Solution: Configure esbuild for JSX

    Update your package.json build script to handle JSX:

    # add this to build
    --jsx=automatic --loader:.js=jsx 
    

    Fixed! Added JSX support:

    What I added:

    • --jsx=automatic – Enables React’s automatic JSX runtime
    • --loader:.js=jsx – Treats .js files as JSX files

    📝 What this means:

    • ✅ esbuild can now process <App /> syntax
    • ✅ You don’t need to import React in every JSX file
    • ✅ Your .js files can contain JSX
    bin/dev
    

    Whola!!

    Let’s see in Part 3. Happy React configuration! 🚀

    Rails 8 App: Create an Academic software app using SQL without using ActiveRecord – Part 1 | users | products | orders

    Let’s create a Rails 8 app which use SQL queries with raw SQL instead of ActiveRecord. Let’s use the full Rails environment with ActiveRecord for infrastructure, but bypass AR’s ORM features for pure SQL writing. Let me guide you through this step by step:

    Step 1: Create the Rails App with ActiveRecord and PostgreSQL (skipping unnecessary components)

    rails new academic-sql-software --database=postgresql --skip-action-cable --skip-jbuilder --skip-solid --skip-kamal
    

    What we’re skipping and why:

    • –skip-action-cable: No WebSocket functionality needed
    • –skip-jbuilder: No JSON API views needed for our SQL practice app
    • –skip-solid: Skips Solid Cache and Solid Queue (we don’t need caching or background jobs)
    • –skip-kamal: No deployment configuration needed

    What we’re keeping:

    • ActiveRecord: For database connection management and ActiveRecord::Base.connection.execute()
    • ActionController: For creating web interfaces to display our SQL query results
    • ActionView: For creating simple HTML pages to showcase our SQL learning exercises
    • PostgreSQL: Our database for practicing advanced SQL features

    Why this setup is perfect for App with raw SQL:

    • Minimal Rails app focused on database interactions
    • Full Rails environment for development conveniences
    • ActiveRecord infrastructure without ORM usage
    • Clean setup without unnecessary overhead

    => Open config/application.rb and comment the following for now:

    # require "active_job/railtie"
    ...
    # require "active_storage/engine"
    ...
    # require "action_mailer/railtie"
    # require "action_mailbox/engine"
    ...
    # require "action_cable/engine"
    

    => Open config/environments/development.rb config/environments/production.rb config/environments/test.rb comment action_mailer

    🤔 Why I am using ActiveRecord (even though I don’t want the ORM):

    • Database Connection Management: ActiveRecord provides robust connection pooling, reconnection handling, and connection management
    • Rails Integration: Seamless integration with Rails console, database tasks (rails db:create, rails db:migrate), and development tools
    • Raw SQL Execution: We get ActiveRecord::Base.connection.execute() which is perfect for our raw SQL writing.
    • Migration System: Easy table creation and schema management with migrations (even though we’ll query with raw SQL)
    • Database Configuration: Rails handles database.yml configuration, environment switching, and connection setup
    • Development Tools: Access to Rails console for testing queries, database tasks, and debugging

    Our Learning Strategy: We’ll use ActiveRecord’s infrastructure but completely bypass its ORM methods. Instead of Student.where(), we’ll use ActiveRecord::Base.connection.execute("SELECT * FROM students WHERE...")

    Step 2: Navigate to the project directory

    cd academic-sql-software
    

    Step 3: Verify PostgreSQL setup

    # Check if PostgreSQL is running
    brew services list | grep postgresql
    # or
    pg_ctl status
    

    Database Foundation: PostgreSQL gives us advanced SQL features:

    • Complex JOINs (INNER, LEFT, RIGHT, FULL OUTER)
    • Window functions (ROW_NUMBER, RANK, LAG, LEAD)
    • Common Table Expressions (CTEs)
    • Advanced aggregations and subqueries

    Step 4: Install dependencies

    bundle install
    

    What this gives us:

    • pg gem: Pure PostgreSQL adapter (already included with --database=postgresql)
    • ActiveRecord: For connection management only
    • Rails infrastructure: Console, generators, rake tasks

    Step 5: Create the PostgreSQL databases

    ✗ rails db:create
    Created database 'academic_sql_software_development'
    Created database 'academic_sql_software_test
    

    Our Development Environment:

    • Creates academic_sql_software_development and academic_sql_software_test
    • Sets up connection pooling and management
    • Enables us to use Rails console for testing queries: rails console then ActiveRecord::Base.connection.execute("SELECT 1")

    Our Raw SQL Approach:

    # We'll use this pattern throughout our app:
    connection = ActiveRecord::Base.connection
    result = connection.execute("SELECT s.name, t.subject FROM students s INNER JOIN teachers t ON s.teacher_id = t.id")
    

    Why not pure pg gem:

    • Would require manual connection management
    • No Rails integration (no console, no rake tasks)
    • More boilerplate code for connection handling
    • Loss of Rails development conveniences

    Why not pure ActiveRecord ORM:

    • We want to do SQL query writing, not ActiveRecord methods.
    • Need to understand database performance implications.
    • Want to practice complex queries that might be harder to express in ActiveRecord.

    Step 6: Create Users table

    mkdir -p db/migrate
    
    class CreateUsers < ActiveRecord::Migration[8.0]
      def up
        # create users table
        execute <<~SQL
          CREATE TABLE users (
            id INT,
            username VARCHAR(200),
            email VARCHAR(150),
            phone_number VARCHAR(20)
          );
        SQL
      end
    
      def down
        execute <<~SQL
          DROP TABLE users;
        SQL
      end
    end
    
    
    class CreateOrders < ActiveRecord::Migration[8.0]
      def up
        # create table orders
        execute <<~SQL
        SQL
      end
    
      def down
        execute <<~SQL
        SQL
      end
    end
    
    

    execute <<~SQL is a Rails migration method that allows you to run raw SQL statements. Let me break it down:

    Components:

    1. execute – A Rails migration method that executes raw SQL directly against the database
    2. <<~SQL – Ruby’s “squiggly heredoc” syntax for multi-line strings that automatically strips leading whitespace (read: https://www.rubyguides.com/2018/11/ruby-heredoc/)

    Usage:

    class SomeMigration < ActiveRecord::Migration[8.0]
      def change
        execute <<~SQL
          CREATE INDEX CONCURRENTLY idx_users_email_lower 
          ON users (LOWER(email));
        SQL
      end
    end
    

    Why use it?

    • Database-specific features: When you need PostgreSQL-specific syntax, MySQL features, etc.
    • Complex SQL: For operations that Rails DSL doesn’t support easily
    • Performance: Direct SQL can sometimes be more efficient
    • Raw SQL requirements: When you specifically need to write SQL (as mentioned in our requirement)

    Example for creating a users table with raw SQL:

    class CreateUsers < ActiveRecord::Migration[8.0]
      def up
        execute <<~SQL
          CREATE TABLE users (
            id BIGSERIAL PRIMARY KEY,
            name VARCHAR(255) NOT NULL,
            email VARCHAR(255) NOT NULL,
            created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
          );
    
          CREATE UNIQUE INDEX idx_users_email ON users(email);
        SQL
      end
    
      def down
        execute <<~SQL
          DROP TABLE IF EXISTS users;
        SQL
      end
    end
    

    The <<~SQL heredoc ends when it encounters the closing SQL keyword, and the ~ strips indentation so your SQL can be properly formatted within the Ruby code.

    Q) 🤔 Is it a best practise to repeat the database constraints in model validation in Rails? DB level vs App level? or both?

    Best Practice: Use BOTH 🎯

    Why both levels are recommended:

    Database Level (Last Line of Defense)

    -- Database constraints
    price DECIMAL(10,2) NOT NULL CHECK (price > 0),
    category product_category NOT NULL,
    title VARCHAR(250) NOT NULL
    

    Pros:

    • Data integrity guarantee – No bad data can enter, ever
    • Performance – Database enforces constraints efficiently
    • Bulletproof – Protects against bulk operations, direct SQL, data imports
    • Concurrency safe – Prevents race conditions
    • Multi-application protection – If other apps access same DB

    Application Level (User Experience)

    validates :price, presence: true, numericality: { greater_than: 0 }
    validates :category, inclusion: { in: %w[men women kids infants] }
    

    Pros:

    • User-friendly errors – “Price must be greater than 0” vs “CHECK constraint violated”
    • Better UX – Validation before form submission
    • Easier testing – Can unit test validations
    • Conditional logic – Complex business rules
    • Framework features – Callbacks, custom validators

    Real-world scenarios where each matters:

    Database saves you when:

    # Bulk operations bypass Rails validations
    Product.update_all(price: -10)  # DB constraint prevents this
    
    # Direct SQL injection attempts
    # DB constraints are your last line of defense
    

    App validations save you when:

    # User gets friendly error instead of:
    # PG::CheckViolation: ERROR: new row violates check constraint
    @product = Product.new(price: -5)
    @product.valid? # => false
    @product.errors.full_messages # => ["Price must be greater than 0"]
    

    Practical Implementation:

    class Product < ApplicationRecord
      # App-level validations for UX
      validates :title, presence: true, length: { maximum: 250 }
      validates :price, presence: true, numericality: { greater_than: 0 }
      validates :category, inclusion: { in: %w[men women kids infants] }
    
      # Don't duplicate precision validation if DB handles it
      # The DECIMAL(10,2) constraint is sufficient at DB level
    end
    
    -- DB-level constraints for data integrity
    CREATE TABLE products (
      id BIGSERIAL PRIMARY KEY,
      title VARCHAR(250) NOT NULL,
      price DECIMAL(10,2) NOT NULL CHECK (price > 0),
      category product_category NOT NULL,
      -- DB handles precision automatically with DECIMAL(10,2)
    );
    

    What NOT to duplicate:

    • Precision constraintsDECIMAL(10,2) handles this perfectly
    • Data type validation – DB enforces INTEGER, BOOLEAN, etc.
    • Complex regex patterns – Better handled in app layer

    Conclusion:

    Use both, but strategically:

    • Database: Core data integrity, type constraints, foreign keys
    • Application: User experience, business logic, conditional rules
    • Don’t over-duplicate simple type/precision constraints that DB handles well

    This approach gives you belt and suspenders protection with optimal user experience.

    to be continued … 🚀

    Design Studio v0.9.5: A Visual Improvement in E-commerce Experience 🎨

    Published: June 25, 2025

    I am thrilled to announce the release of Design Studio v0.9.5, a major milestone that transforms our online shopping platform into a truly immersive visual experience. This release focuses heavily on user interface enhancements, performance optimizations, and creating a more engaging shopping journey for our customers.

    🚀 What’s New in v0.9.5

    1. Stunning 10-Slide Hero Carousel

    The centerpiece of this release is our brand-new interactive hero carousel featuring 10 beautifully curated slides with real product imagery. Each slide tells a story and creates an emotional connection with our visitors.

    Dynamic Gradient Themes

    Each slide features its own unique gradient theme:

    <!-- Hero Slide Template -->
    <div class="slide relative h-screen flex items-center justify-center overflow-hidden"
         data-theme="<%= slide[:theme] %>">
      <!-- Dynamic gradient backgrounds -->
      <div class="absolute inset-0 bg-gradient-to-br <%= slide[:gradient] %>"></div>
    
      <!-- Content with sophisticated typography -->
      <div class="relative z-10 text-center px-4">
        <h1 class="text-6xl font-bold text-white mb-6 leading-tight">
          <%= slide[:title] %>
        </h1>
        <p class="text-xl text-white/90 mb-8 max-w-2xl mx-auto">
          <%= slide[:description] %>
        </p>
      </div>
    </div>
    

    Smart Auto-Cycling with Manual Controls

    // Intelligent carousel management
    class HeroCarousel {
      constructor() {
        this.currentSlide = 0;
        this.autoInterval = 4000; // 4-second intervals
        this.isPlaying = true;
      }
    
      startAutoPlay() {
        this.autoPlayTimer = setInterval(() => {
          if (this.isPlaying) {
            this.nextSlide();
          }
        }, this.autoInterval);
      }
    
      pauseOnInteraction() {
        // Pause auto-play when user interacts
        this.isPlaying = false;
        setTimeout(() => this.isPlaying = true, 10000); // Resume after 10s
      }
    }
    

    2. Modular Component Architecture

    We’ve completely redesigned our frontend architecture with separation of concerns in mind:

    <!-- Main Hero Slider Component -->
    <%= render 'home/hero_slider' %>
    
    <!-- Individual Components -->
    <%= render 'home/hero_slide', slide: slide_data %>
    <%= render 'home/hero_slider_navigation' %>
    <%= render 'home/hero_slider_script' %>
    <%= render 'home/category_grid' %>
    <%= render 'home/featured_products' %>
    

    Component-Based Development Benefits:

    • Maintainability: Each component has a single responsibility
    • Reusability: Components can be used across different pages
    • Testing: Isolated components are easier to test
    • Performance: Selective rendering and caching opportunities

    3. Enhanced Visual Design System

    Glass Morphism Effects

    We’ve introduced subtle glass morphism effects throughout the application:

    /* Modern glass effect implementation */
    .glass-effect {
      background: rgba(255, 255, 255, 0.1);
      backdrop-filter: blur(10px);
      border: 1px solid rgba(255, 255, 255, 0.2);
      border-radius: 16px;
      box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
    }
    
    /* Category cards with gradient overlays */
    .category-card {
      @apply relative overflow-hidden rounded-xl;
    
      &::before {
        content: '';
        @apply absolute inset-0 bg-gradient-to-t from-black/60 to-transparent;
      }
    }
    

    Dynamic Color Management

    Our new helper system automatically manages theme colors:

    # app/helpers/application_helper.rb
    def get_category_colors(gradient_class)
      case gradient_class
      when "from-pink-400 to-purple-500"
        "#f472b6, #8b5cf6"
      when "from-blue-400 to-indigo-500"  
        "#60a5fa, #6366f1"
      when "from-green-400 to-teal-500"
        "#4ade80, #14b8a6"
      else
        "#6366f1, #8b5cf6" # Elegant fallback
      end
    end
    
    def random_decorative_background
      themes = [:orange_pink, :blue_purple, :green_teal, :yellow_orange]
      decorative_background_config(themes.sample)
    end
    

    4. Mobile-First Responsive Design

    Every component is built with mobile-first approach:

    <!-- Responsive category grid -->
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
      <% categories.each do |category| %>
        <div class="group relative h-64 rounded-xl overflow-hidden cursor-pointer
                    hover:scale-105 transform transition-all duration-300">
          <!-- Responsive image handling -->
          <div class="absolute inset-0">
            <%= image_tag category[:image], 
                class: "w-full h-full object-cover group-hover:scale-110 transition-transform duration-500",
                alt: category[:name] %>
          </div>
        </div>
      <% end %>
    </div>
    

    5. Public Product Browsing

    We’ve opened up product browsing to all visitors:

    # app/controllers/products_controller.rb
    class ProductsController < ApplicationController
      # Allow public access to browsing
      allow_unauthenticated_access only: %i[index show]
    
      def index
        products = Product.all
    
        # Smart category filtering
        if params[:category].present?
          products = products.for_category(params[:category])
          @current_category = params[:category]
        end
    
        # Pagination for performance
        @pagy, @products = pagy(products)
      end
    end
    

    🔧 Technical Improvements

    Test Coverage Excellence

    I’ve achieved 73.91% test coverage (272/368 lines), ensuring code reliability:

    # Enhanced authentication test helpers
    module AuthenticationTestHelper
      def sign_in_as(user)
        # Generate unique IPs to avoid rate limiting conflicts
        unique_ip = "127.0.0.#{rand(1..254)}"
        @request.remote_addr = unique_ip
    
        session[:user_id] = user.id
        user
      end
    end
    

    Asset Pipeline Optimization

    Rails 8 compatibility with modern asset handling:

    # config/application.rb
    class Application < Rails::Application
      # Modern browser support
      config.allow_browser versions: :modern
    
      # Asset pipeline optimization
      config.assets.css_compressor = nil # Tailwind handles this
      config.assets.js_compressor = :terser
    end
    

    Security Enhancements

    # Role-based access control
    class ApplicationController < ActionController::Base
      include Authentication
    
      private
    
      def require_admin
        unless current_user&.admin?
          redirect_to root_path, alert: "Access denied."
        end
      end
    end
    

    📊 Performance Metrics

    Before vs After v0.9.5:

    MetricBeforeAfter v0.9.5Improvement
    Test Coverage45%73.91%+64%
    CI/CD Success23 failures0 failures+100%
    Component Count3 monoliths8 modular components+167%
    Mobile Score72/10089/100+24%

    🎨 Design Philosophy

    This release embodies our commitment to:

    1. Visual Excellence: Every pixel serves a purpose
    2. User Experience: Intuitive navigation and interaction
    3. Performance: Fast loading without sacrificing beauty
    4. Accessibility: Inclusive design for all users
    5. Maintainability: Clean, modular code architecture

    🔮 What’s Next?

    Version 0.9.5 sets the foundation for exciting upcoming features:

    • Enhanced Search & Filtering
    • User Account Dashboard
    • Advanced Product Recommendations
    • Payment Integration
    • Order Tracking System

    🎉 Try It Today!

    Experience the new Design Studio v0.9.5 and see the difference visual design makes in online shopping. Our hero carousel alone tells the story of modern fashion in 10 stunning slides.

    Key Benefits for Users:

    • ✨ Immersive visual shopping experience
    • 📱 Perfect on any device
    • ⚡ Lightning-fast performance
    • 🔒 Secure and reliable

    For Developers:

    • 🏗️ Clean, maintainable architecture
    • 🧪 Comprehensive test suite
    • 📚 Well-documented components
    • 🚀 Rails 8 compatibility

    Design Studio v0.9.5 – Where technology meets artistry in e-commerce.

    Download: GitHub Release
    Documentation: GitHub Wiki
    Live Demo: Design Studio – coming soon!


    Enjoy Rails 8 with Hotwire! 🚀

    Rails 8 Tests: 🔄 TDD vs 🎭 BDD | System Tests

    Test‑Driven Development (TDD) and Behavior‑Driven Development (BDD) are complementary testing approaches that help teams build robust, maintainable software by defining expected behaviour before writing production code. In TDD, developers write small, focused unit tests that fail initially, then implement just enough code to make them pass, ensuring each component meets its specification. BDD extends this idea by framing tests in a global language that all stakeholders—developers, QA, and product owners—can understand, using human-readable scenarios to describe system behaviour. While TDD emphasizes the correctness of individual units, BDD elevates collaboration and shared understanding by specifying the “why” and “how” of features in a narrative style, driving development through concrete examples of desired outcomes.

    🔄 TDD vs 🎭 BDD: Methodologies vs Frameworks

    🧠 Understanding the Concepts

    🔄 TDD (Test Driven Development)
    • Methodology/Process: Write test → Fail → Write code → Pass → Refactor
    • Focus: Testing the implementation and correctness
    • Mindset: “Does this code work correctly?”
    • Style: More technical, code-focused
    🎭 BDD (Behavior Driven Development)
    • Methodology/Process: Describe behavior → Write specs → Implement → Verify behavior
    • Focus: Testing the behavior and user requirements
    • Mindset: “Does this behave as expected from user’s perspective?”
    • Style: More natural language, business-focused

    🛠️ Frameworks Support Both Approaches

    📋 RSpec (Primarily BDD-oriented)
    # BDD Style - describing behavior
    describe "TwoSum" do
      context "when given an empty array" do
        it "should inform user about insufficient data" do
          expect(two_sum([], 9)).to eq('Provide an array with length 2 or more')
        end
      end
    end
    
    ⚙️ Minitest (Supports Both TDD and BDD)
    🔧 TDD Style with Minitest
    class TestTwoSum < Minitest::Test
      # Testing implementation correctness
      def test_empty_array_returns_error
        assert_equal 'Provide an array with length 2 or more', two_sum([], 9)
      end
    
      def test_valid_input_returns_indices
        assert_equal [0, 1], two_sum([2, 7], 9)
      end
    end
    
    🎭 BDD Style with Minitest
    describe "TwoSum behavior" do
      describe "when user provides empty array" do
        it "guides user to provide sufficient data" do
          _(two_sum([], 9)).must_equal 'Provide an array with length 2 or more'
        end
      end
    
      describe "when user provides valid input" do
        it "finds the correct pair indices" do
          _(two_sum([2, 7], 9)).must_equal [0, 1]
        end
      end
    end
    

    🎯 Key Differences in Practice

    🔄 TDD Approach
    # 1. Write failing test
    def test_two_sum_with_valid_input
      assert_equal [0, 1], two_sum([2, 7], 9)  # This will fail initially
    end
    
    # 2. Write minimal code to pass
    def two_sum(nums, target)
      [0, 1]  # Hardcoded to pass
    end
    
    # 3. Refactor and improve
    def two_sum(nums, target)
      # Actual implementation
    end
    
    🎭 BDD Approach
    # 1. Describe the behavior first
    describe "Finding two numbers that sum to target" do
      context "when valid numbers exist" do
        it "returns their indices" do
          # This describes WHAT should happen, not HOW
          expect(two_sum([2, 7, 11, 15], 9)).to eq([0, 1])
        end
      end
    end
    

    📊 Summary Table

    AspectTDDBDD
    FocusImplementation correctnessUser behavior
    LanguageTechnicalBusiness/Natural
    FrameworksAny (Minitest, RSpec, etc.)Any (RSpec, Minitest spec, etc.)
    Test Namestest_method_returns_value"it should behave like..."
    AudienceDevelopersStakeholders + Developers

    🎪 The Reality

    • RSpec encourages BDD but can be used for TDD
    • Minitest is framework-agnostic – supports both approaches equally
    • Your choice of methodology (TDD vs BDD) is independent of your framework choice
    • Many teams use hybrid approaches – BDD for acceptance tests, TDD for unit tests

    The syntax doesn’t determine the methodology – it’s about how you think and approach the problem!

    System Tests 💻⚙️

    System tests in Rails (located in test/system/*) are full-stack integration tests that simulate real user interactions with your web application. They’re the highest level of testing in the Rails testing hierarchy and provide the most realistic testing environment.

    System tests actually launch a real web browser (or headless browser) and interact with your application just like a real user would. Looking at our Rails app’s configuration: design_studio/test/application_system_test_case.rb

    driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
    

    This means our system tests run using:

    • Selenium WebDriver (browser automation tool)
    • Headless Chrome (Chrome browser without UI)
    • 1400×1400 screen size for consistent testing

    Code Snippets from:actionpack-8.0.2/lib/action_dispatch/system_test_case.rb

    # frozen_string_literal: true
    
    # :markup: markdown
    
    gem "capybara", ">= 3.26"
    
    require "capybara/dsl"
    require "capybara/minitest"
    require "action_controller"
    require "action_dispatch/system_testing/driver"
    require "action_dispatch/system_testing/browser"
    require "action_dispatch/system_testing/server"
    require "action_dispatch/system_testing/test_helpers/screenshot_helper"
    require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
    
    module ActionDispatch
      # # System Testing
      #
      # System tests let you test applications in the browser. Because system tests
      # use a real browser experience, you can test all of your JavaScript easily from
      # your test suite.
      #
      # To create a system test in your application, extend your test class from
      # `ApplicationSystemTestCase`. System tests use Capybara as a base and allow you
      # to configure the settings through your `application_system_test_case.rb` file
      # that is generated with a new application or scaffold.
      #
      # Here is an example system test:
      #
      #     require "application_system_test_case"
      #
      #     class Users::CreateTest < ApplicationSystemTestCase
      #       test "adding a new user" do
      #         visit users_path
      #         click_on 'New User'
      #
      #         fill_in 'Name', with: 'Arya'
      #         click_on 'Create User'
      #
      #         assert_text 'Arya'
      #       end
      #     end
      #
      # When generating an application or scaffold, an
      # `application_system_test_case.rb` file will also be generated containing the
      # base class for system testing. This is where you can change the driver, add
      # Capybara settings, and other configuration for your system tests.
      #
      #     require "test_helper"
      #
      #     class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
      #       driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
      #     end
      #
      # By default, `ActionDispatch::SystemTestCase` is driven by the Selenium driver,
      # with the Chrome browser, and a browser size of 1400x1400.
      #
      # Changing the driver configuration options is easy. Let's say you want to use
      # the Firefox browser instead of Chrome. In your
      # `application_system_test_case.rb` file add the following:
      #
      #     require "test_helper"
      #
      #     class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
      #       driven_by :selenium, using: :firefox
      #     end
      #
      # `driven_by` has a required argument for the driver name. The keyword arguments
      # are `:using` for the browser and `:screen_size` to change the size of the
      # browser screen. These two options are not applicable for headless drivers and
      # will be silently ignored if passed.
      #
      # Headless browsers such as headless Chrome and headless Firefox are also
      # supported. You can use these browsers by setting the `:using` argument to
      # `:headless_chrome` or `:headless_firefox`.
      #
      # To use a headless driver, like Cuprite, update your Gemfile to use Cuprite
      # instead of Selenium and then declare the driver name in the
      # `application_system_test_case.rb` file. In this case, you would leave out the
      # `:using` option because the driver is headless, but you can still use
      # `:screen_size` to change the size of the browser screen, also you can use
      # `:options` to pass options supported by the driver. Please refer to your
      # driver documentation to learn about supported options.
      #
      #     require "test_helper"
      #     require "capybara/cuprite"
      #
      #     class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
      #       driven_by :cuprite, screen_size: [1400, 1400], options:
      #         { js_errors: true }
      #     end
      #
      # Some drivers require browser capabilities to be passed as a block instead of
      # through the `options` hash.
      #
      # As an example, if you want to add mobile emulation on chrome, you'll have to
      # create an instance of selenium's `Chrome::Options` object and add capabilities
      # with a block.
      #
      # The block will be passed an instance of `<Driver>::Options` where you can
      # define the capabilities you want. Please refer to your driver documentation to
      # learn about supported options.
      #
      #     class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
      #       driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option|
      #         driver_option.add_emulation(device_name: 'iPhone 6')
      #         driver_option.add_extension('path/to/chrome_extension.crx')
      #       end
      #     end
      #
      # Because `ActionDispatch::SystemTestCase` is a shim between Capybara and Rails,
      # any driver that is supported by Capybara is supported by system tests as long
      # as you include the required gems and files.
      class SystemTestCase < ActiveSupport::TestCase
        include Capybara::DSL
        include Capybara::Minitest::Assertions
        include SystemTesting::TestHelpers::SetupAndTeardown
        include SystemTesting::TestHelpers::ScreenshotHelper
    
        ..........
    
    

    How They Work

    System tests can:

    • Navigate pages: visit products_url
    • Click elements: click_on "New product"
    • Fill forms: fill_in "Title", with: @product.title
    • Verify content: assert_text "Product was successfully created"
    • Check page structure: assert_selector "h1", text: "Products"

    Examples From Our Codebase

    Basic navigation test (from products_test.rb):

    test "visiting the index" do
      visit products_url
      assert_selector "h1", text: "Products"
    end
    

    Complex user workflow (from profile_test.rb):

    def sign_in_user(user)
      visit new_session_path
      fill_in "Email", with: user.email
      fill_in "Password", with: "password"
      click_button "Log In"
    
      # Wait for redirect and verify we're not on the login page anymore
      # Also wait for the success notice to appear
      assert_text "Logged in successfully", wait: 10
      assert_no_text "Log in to your account", wait: 5
    end
    

    Key Benefits

    1. End-to-end testing: Tests the complete user journey
    2. JavaScript testing: Can test dynamic frontend behavior
    3. Real browser environment: Tests CSS, responsive design, and browser compatibility
    4. User perspective: Validates the actual user experience

    When to Use System Tests

    • Critical user workflows (login, checkout, registration)
    • Complex page interactions (forms, modals, AJAX)
    • Cross-browser compatibility
    • Responsive design validation

    Our profile_test.rb is a great example – it tests the entire user authentication flow, profile page navigation, and various UI interactions that a real user would perform.

    Happy Testing 🚀

    Hotwire 〰 in Rails 8 World – And How My New Rails App Puts this into Work 🚀

    When you create a brand-new Rails 8 project today you automatically get a super-powerful front-end toolbox called Hotwire.

    Because it is baked into the framework, it can feel a little magical (“everything just works!”). This post demystifies Hotwire, shows how its two core libraries—Turbo and Stimulus—fit together, and then walks through the places where the design_studio codebase is already using them.


    1. What is Hotwire?

    Hotwire (HTML Over The Wire) is a set of conventions + JavaScript libraries that lets you build modern, reactive UIs without writing (much) custom JS or a separate SPA. Instead of pushing JSON to the browser and letting a JS framework patch the DOM, the server sends HTML fragments over WebSockets, SSE, or normal HTTP responses and the browser swaps them in efficiently.

    Hotwire is made of three parts:

    1. Turbo – the engine that intercepts normal links/forms, keeps your page state alive, and swaps HTML frames or streams into the DOM at 60fps.
    2. Stimulus – a “sprinkle-on” JavaScript framework for the little interactive bits that still need JS (dropdowns, clipboard buttons, etc.).
    3. (Optional) Strada – native-bridge helpers for mobile apps; not relevant to our web-only project.

    Because Rails 8 ships with both turbo-rails and stimulus-rails gems, simply creating a project wires everything up.


    2. How Turbo & Stimulus complement each other

    • Turbo keeps pages fresh – It handles navigation (Turbo Drive), partial page updates via <turbo-frame> (Turbo Frames), and real-time broadcasts with <turbo-stream> (Turbo Streams).
    • Stimulus adds behaviour – Tiny ES-module controllers attach to DOM elements and react to events/data attributes. Importantly, Stimulus plays nicely with Turbo’s DOM-swapping because controllers automatically disconnect/re-connect when elements are replaced.

    Think of Turbo as the transport layer for HTML and Stimulus as the behaviour layer for the small pieces that still need JavaScript logic.

    # server logs - still identify as HTML request, It handles navigation through (Turbo Drive)
    
    Started GET "/products/15" for ::1 at 2025-06-24 00:47:03 +0530
    Processing by ProductsController#show as HTML
      Parameters: {"id" => "15"}
    .......
    
    Started GET "/products?category=women" for ::1 at 2025-06-24 00:50:38 +0530
    Processing by ProductsController#index as HTML
      Parameters: {"category" => "women"}
    .......
    

    Javascript and css files that loads in our html head:

        <link rel="stylesheet" href="/assets/actiontext-e646701d.css" data-turbo-track="reload" />
    <link rel="stylesheet" href="/assets/application-8b441ae0.css" data-turbo-track="reload" />
    <link rel="stylesheet" href="/assets/tailwind-8bbb1409.css" data-turbo-track="reload" />
        <script type="importmap" data-turbo-track="reload">{
      "imports": {
        "application": "/assets/application-3da76259.js",
        "@hotwired/turbo-rails": "/assets/turbo.min-3a2e143f.js",
        "@hotwired/stimulus": "/assets/stimulus.min-4b1e420e.js",
        "@hotwired/stimulus-loading": "/assets/stimulus-loading-1fc53fe7.js",
        "trix": "/assets/trix-4b540cb5.js",
        "@rails/actiontext": "/assets/actiontext.esm-f1c04d34.js",
        "controllers/application": "/assets/controllers/application-3affb389.js",
        "controllers/hello_controller": "/assets/controllers/hello_controller-708796bd.js",
        "controllers": "/assets/controllers/index-ee64e1f1.js"
      }
    }</script>
    <link rel="modulepreload" href="/assets/application-3da76259.js">
    <link rel="modulepreload" href="/assets/turbo.min-3a2e143f.js">
    <link rel="modulepreload" href="/assets/stimulus.min-4b1e420e.js">
    <link rel="modulepreload" href="/assets/stimulus-loading-1fc53fe7.js">
    <link rel="modulepreload" href="/assets/trix-4b540cb5.js">
    <link rel="modulepreload" href="/assets/actiontext.esm-f1c04d34.js">
    <link rel="modulepreload" href="/assets/controllers/application-3affb389.js">
    <link rel="modulepreload" href="/assets/controllers/hello_controller-708796bd.js">
    <link rel="modulepreload" href="/assets/controllers/index-ee64e1f1.js">
    <script type="module">import "application"</script>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    

    3. Where Hotwire lives in design_studio

    Because Rails 8 scaffolded most of this for us, the integration is scattered across a few key spots:

    3.1 Gems & ES-modules are pinned

    # config/importmap.rb
    
    pin "@hotwired/turbo-rails",  to: "turbo.min.js"
    pin "@hotwired/stimulus",     to: "stimulus.min.js"
    pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
    pin_all_from "app/javascript/controllers", under: "controllers"
    

    The Gemfile pulls the Ruby wrappers:

    gem "turbo-rails"
    gem "stimulus-rails"
    

    3.2 Global JavaScript entry point

    # application.js 
    
    import "@hotwired/turbo-rails"
    import "controllers"   // <-- auto-registers everything in app/javascript/controllers
    

    As soon as that file is imported (it’s linked in application.html.erb via
    javascript_include_tag "application", "data-turbo-track": "reload"
    ), Turbo intercepts every link & form on the site.

    3.3 Stimulus controllers

    The framework-generated controller registry lives at app/javascript/controllers/index.js; the only custom controller so far is the hello-world example:

    connect() {
      this.element.textContent = "Hello World!"
    }
    

    You can drop new controllers into app/javascript/controllers/anything_controller.js and they will be auto-loaded thanks to the pin_all_from line above.

    pin_all_from "app/javascript/controllers", under: "controllers"
    

    3.4 Turbo Streams in practice – removing a product image

    The most concrete Hotwire interaction in design_studio today is the “Delete image” action in the products feature:

    1. Controller action responds to turbo_stream:
    respond_to do |format|
      ...
      format.turbo_stream   # <-- returns delete_image.turbo_stream.erb
    end
    
    1. Stream template sent back:
    # app/views/products/delete_image.turbo_stream.erb
    
    <turbo-stream action="remove" target="product-image-<%= @image_id %>"></turbo-stream>
    
    1. Turbo receives the <turbo-stream> tag, finds the element with that id, and removes it from the DOM—no page reload, no hand-written JS.
    # app/views/products/show.html.erb
    ....
    <%= link_to @product, 
        data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this product?" }, 
        class: "px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors duration-200" do %>
        <i class="fas fa-trash mr-2"></i>Delete Product
    <% end %>
    ....
    

    3.5 “Free” Turbo benefits you might not notice

    Because Turbo Drive is on globally:

    • Standard links look instantaneous (HTML diffing & cache).
    • Form submissions automatically request .turbo_stream when you ask for format.turbo_stream in a controller.
    • Redirects keep scroll position/head tags in sync.

    All of this happens without any code in the repo—Rails 8 + Turbo does the heavy lifting.


    4. Extending Hotwire in the future

    1. More Turbo Frames – Wrap parts of pages in <turbo-frame id="cart"> to make only the cart refresh on “Add to cart”.
    2. Broadcasting – Hook Product model changes to turbo_stream_from channels so that all users see live stock updates.
    3. Stimulus components – Replace jQuery snippets with small controllers (dropdowns, modals, copy-to-clipboard, etc.).

    Because everything is wired already (Importmap, controller autoloading, Cable), adding these features is mostly a matter of creating the HTML/ERB templates and a bit of Ruby.


    Questions

    1. Is Rails 8 still working with the real DOM?

    • Yes, the browser is always working with the real DOM—nothing is virtualized (unlike React’s virtual DOM).
    • Turbo intercepts navigation events (links, form submits). Instead of letting the browser perform a “hard” navigation, it fetches the HTML with fetch() in the background, parses the response into a hidden document fragment, then swaps specific pieces (usually the whole <body> or a <turbo-frame> target) into the live DOM.
    • Because Turbo only swaps the changed chunks, it keeps the rest of the page alive (JS state, scroll position, playing videos, etc.) and fires lifecycle events so Stimulus controllers disconnect/re-connect cleanly.

    “Stimulus itself is a tiny wrapper around MutationObserver. It attaches controller instances to DOM elements and tears them down automatically when Turbo replaces those elements—so both libraries cooperate rather than fighting the DOM.”


    2. How does the HTML from Turbo Drive get into the DOM without a full reload?

    Step-by-step for a normal link click:

    1. turbo-rails JS (loaded via import “@hotwired/turbo-rails”) cancels the browser’s default navigation.
    2. Turbo sends an AJAX request (actually fetch()) for the new URL, requesting full HTML.
    3. The response text is parsed into an off-screen DOMParser document.
    4. Turbo compares the <head> tags, updates <title> and any changed assets, then replaces the <body> of the current page with the new one (or, for <turbo-frame>, just that frame).
    5. It pushes a history.pushState entry so Back/Forward work, and fires events like turbo:load.

    Because no real navigation happened, the browser doesn’t clear JS state, WebSocket connections, or CSS; it just swaps some DOM nodes—visually it feels instantaneous.


    3. What does pin mean in config/importmap.rb?

    Rails 8 ships with Importmap—a way to use normal ES-module import statements without a bundler.pin is simply a mapping declaration:

    pin "@hotwired/turbo-rails", to: "turbo.min.js"
    pin "@hotwired/stimulus",    to: "stimulus.min.js"
    

    Meaning:

    • When the browser sees import "@hotwired/turbo-rails", fetch …/assets/turbo.min.js
    • When it sees import “controllers”, look at 
      pin_all_from "app/javascript/controllers" 
      which expands into individual mappings for every controller file.

    Think of pin as the importmap equivalent of a require statement in a bundler config—just declarative and handled at runtime by the browser. That’s all there is to it: real DOM, no page reloads, and a lightweight way to load JS modules without Webpack.

    Take-aways

    • Hotwire is not one big library; it is a philosophy (+ Turbo + Stimulus) that keeps most of your UI in Ruby & ERB but still feels snappy and modern.
    • Rails 8 scaffolds everything, so you may not even realize you’re using it—but you are!
    • design_studio already benefits from Hotwire’s defaults (fast navigation) and uses Turbo Streams for dynamic image deletion. The plumbing is in place to expand this pattern across the app with minimal effort.

    Happy hot-wiring! 🚀