Introduction to Software Development Methodologies ๐Ÿ“Š: Part 1

Software development is not just about writing code; it’s about building high-quality, maintainable, and scalable systems that deliver value to users. To achieve this consistently, teams follow structured approaches known as software development methodologies. These methodologies provide a roadmap for planning, designing, developing, testing, and delivering software.

In this three-part blog series, we’ll explore key methodologies and best practices in software development, using Ruby and Ruby on Rails examples wherever appropriate.

๐ŸŒ What Are Software Development Methodologies?

Software development methodologies are structured processes or frameworks that guide the planning and execution of software projects. They help teams manage complexity, collaborate effectively, reduce risk, and deliver projects on time.

Common Goals of Any Methodology:

  • Define clear project scope and goals
  • Break down work into manageable tasks
  • Encourage communication among team members
  • Track progress and measure success
  • Deliver working software iteratively or incrementally

๐Ÿ’ผ Why Methodologies Matter

Without a methodology, software projects often suffer from unclear requirements, missed deadlines, buggy releases, or scope creep. A good methodology:

  • Increases team productivity
  • Ensures better quality and maintainability
  • Reduces time-to-market
  • Improves customer satisfaction

In Ruby and Rails projects, where rapid development is a key feature, following a methodology keeps things under control and makes collaboration more effective.

๐Ÿ“– A Brief Overview of Popular Software Development Methodologies

We’ll explore these in detail in Part 2, but here are the major ones:

1. Waterfall

A linear approach where each phase (Requirements โ†’ Design โ†’ Implementation โ†’ Testing โ†’ Deployment) happens one after another.

2. Agile

An iterative and incremental model that encourages adaptive planning, early delivery, and continuous improvement.

3. Scrum

A popular Agile framework that structures work in sprints and emphasizes team roles, ceremonies, and artifacts.

4. Kanban

A flow-based Agile method that uses visual boards to manage work and improve process efficiency.

5. Extreme Programming (XP)

Focuses on engineering excellence, including practices like pair programming, TDD, and continuous integration. Ruby and Rails communities highly embrace XP.

6. DevOps

Combines software development and IT operations to shorten the development lifecycle and deliver high-quality software continuously.

โœจ Ruby On Rails: A Natural Fit for Agile and XP

Ruby and Rails were built with developer happiness and productivity in mind. They naturally complement Agile and XP methodologies.

Example:

  • Ruby on Rails encourages convention over configuration, which speeds up development.
  • It supports Test-Driven Development (TDD) out of the box via tools like RSpec and Minitest.
  • Features like scaffolding align well with the iterative nature of Agile.

๐Ÿ”น Coming Up in Part 2

In the next part of this series, I’ll do a deep dive into each of the above methodologies, with pros, cons, and real-world use cases. I’ll also show examples of how Rails developers apply these in practice.


Stay tuned! ๐Ÿš€

Rails 8 + ActiveAdmin: The Propshaft vs Sprockets Dilemma ๐Ÿ˜ตโ€๐Ÿ’ซ

ActiveAdmin traditionally requires Sprockets, but there are actually several alternatives and modern approaches. Let me break down your options:

ActiveAdmin Asset Pipeline Options:

Current Status (2025):

  • ActiveAdmin 3.x still primarily designed for Sprockets
  • It has SCSS files with imports, mixins, and variables that need preprocessing
  • The gem’s CSS compilation expects Sprockets’ asset compilation pipeline

Alternative Approaches:

1. Keep Sprockets (Current Choice)

Pros:

  • โœ… Fully supported, zero configuration issues
  • โœ… All ActiveAdmin features work out-of-the-box
  • โœ… Stable and well-tested

Cons:

  • โŒ Older, slower asset pipeline
  • โŒ More complex than Propshaft
  • โŒ Goes against Rails 8 defaults

2. Hybrid: Propshaft + Manual CSS for ActiveAdmin

We could try keeping Propshaft for our main app and handling ActiveAdmin CSS manually:

ActiveAdmin traditionally requires Sprockets, but the landscape is evolving. Here are your realistic options in 2025:

Current Situation:

ActiveAdmin 3.x: Requires Sprockets (stable)
ActiveAdmin 4.0 beta: Propshaft support is experimental/problematic
Rails 8 default: ๐Ÿ†• Propshaft (faster, simpler)

https://github.com/activeadmin/activeadmin/releases

Our Options:

Option 1: Stay with Sprockets โญ (Recommended for now)

Pros:

  • โœ… Zero configuration issues
  • โœ… All ActiveAdmin features work perfectly
  • โœ… Battle-tested and stable

Cons:

  • โŒ Slower than Propshaft
  • โŒ More complex asset pipeline
  • โŒ Goes against Rails 8 defaults

Option 2: ActiveAdmin Assets Gem (Simplest alternative)

The activeadmin_assets gem was mentioned as a simple solution! Let me add this option:

# Add to Gemfile
gem 'activeadmin_assets'

Pros:

  • โœ… Zero Node.js dependency
  • โœ… Works with Propshaft
  • โœ… Pre-compiled ActiveAdmin assets
  • โœ… Simple one-line installation

Cons:

  • โŒ Can’t customize ActiveAdmin styles
  • โŒ Community-maintained (not official)

Option 3: ActiveAdmin 4.0 Beta + Propshaft โš ๏ธ (Experimental)

Based on the GitHub discussions, this is possible but complex:

# Rails 8 setup with Propshaft + ActiveAdmin v4 beta
rails new myapp -a propshaft
bundle add activeadmin -v=4.0.0beta15
bundle add cssbundling-rails -v "1.4.1"
bundle add tailwindcss-ruby -v "~> 3.4.17"
yarn add tailwindcss@^3.4.17  # ActiveAdmin uses TailwindCSS v3
rails generate active_admin:install
rails css:install:tailwind
yarn add @activeadmin/activeadmin@4.0.0-beta15
npm pkg set scripts.build:css="tailwindcss -i ./app/assets/stylesheets/active_admin.css -o ./app/assets/builds/active_admin.css --minify -c tailwind-active_admin.config.js"
./bin/dev  # Instead of rails server

Pros:

  • โœ… Modern asset pipeline (Propshaft)
  • โœ… ActiveAdmin v4 features

Cons:

  • โŒ Complex setup with many steps
  • โŒ Requires Node.js/Yarn (defeats the purpose)
  • โŒ Beta software (unstable)
  • โŒ TailwindCSS v3/v4 compatibility issues

Option 4: ๐Ÿ”„ Alternative Admin Gems

If you want to avoid the ActiveAdmin complexity entirely:

RailsAdmin:

gem 'rails_admin'
  • โœ… No asset pipeline issues
  • โœ… Works with Propshaft out of the box
  • โŒ Less customizable than ActiveAdmin

Trestle:

gem 'trestle'
  • โœ… Modern Bootstrap-based admin
  • โœ… Better Rails 8 compatibility
  • โŒ Smaller community

Adminterface (Deprecated):

  • ๐Ÿšซ No longer maintained (as shown in search results)

๐Ÿ† My Recommendation:

For our production app: Stick with Sprockets + ActiveAdmin 3.x

Why?

  1. Stability – Battle-tested combination
  2. Zero configuration issues
  3. Full feature set available
  4. Easy maintenance

The performance difference between Propshaft and Sprockets won’t significantly impact your admin interface, which is typically used by a small number of internal users.

๐Ÿ”ฎ Future Outlook:

  • ActiveAdmin v4 will eventually have better Propshaft support
  • Rails community is working on solutions
  • Node.js dependency trend for complex CSS is becoming standard anyway

Bottom Line: Don’t let the asset pipeline take over the application preferences. Our current Sprockets setup is perfectly fine for an admin interface! ๐ŸŽฏ

The Reality Check:

We completely replaced Propshaft with Sprockets for the entire application.

Current State:

  • โŒ Propshaft: Completely removed
  • โœ… Sprockets: Handles ALL assets (main app + admin)
  • โœ… Importmaps: Still working (unaffected)
  • โœ… Hotwire/Turbo: Still working (unaffected)

๐Ÿ—๏ธ How ActiveAdmin Works:

ActiveAdmin is NOT a Rails Engine. It integrates directly into your app:

  1. Routes: ActiveAdmin.routes(self) adds /admin routes to your app
  2. Assets: ActiveAdmin’s CSS/JS becomes part of your asset pipeline
  3. Controllers: ActiveAdmin generates controllers in your app’s namespace

It’s tightly coupled to your application, not isolated.

๐Ÿคทโ€โ™‚๏ธ So What Are Our Real Options?

Option 1: Accept Current Setup

What we have:

  • Modern Rails 8 with Importmaps + Hotwire โœ…
  • Sprockets instead of Propshaft โŒ (but works fine)
  • Full-featured admin interface โœ…

Option 2: Use activeadmin_assets Gem

# Replace current ActiveAdmin setup
gem 'activeadmin_assets'  # Pre-compiled assets
gem 'propshaft'           # Back to Rails 8 default
  • โœ… Get Propshaft back
  • โœ… Keep Rails 8 defaults
  • โŒ Can’t customize admin styles

Option 3: Build Custom Admin

Keep your Rails 8 purity but build admin from scratch:

  • โœ… Pure Rails 8
  • โŒ Weeks of development time

๐Ÿ” What activeadmin_assets Actually Does:

The activeadmin_assets gem doesn’t replace ActiveAdmin – it provides pre-compiled static assets for ActiveAdmin to work with Propshaft.

But here’s the critical issue for you:

๐Ÿšจ activeadmin_assets = NO Customization

If you use activeadmin_assets, you get:

  • โœ… ActiveAdmin functionality
  • โœ… Propshaft compatibility
  • โŒ ZERO ability to customize styles/interface

The assets are pre-compiled and frozen – you can’t modify colors, layouts, or styling.

Since We Want Customization:

We’re actually in the BEST setup possible right now!

Our Current Setup Gives You:

  • โœ… Full ActiveAdmin customization (styles, layouts, themes)
  • โœ… Modern Rails 8 (Importmaps, Hotwire, Turbo)
  • โœ… All ActiveAdmin features (filters, actions, forms)
  • โŒ Sprockets instead of Propshaft (minor trade-off)

Customization Examples We Can Do:

  • Custom CSS themes and colors
  • Layout modifications
  • Custom JavaScript behaviors
  • Brand styling and logos
  • Advanced form customizations

๐ŸŽฏ Reality Check:

There’s no solution that gives us:

  1. Full ActiveAdmin customization AND
  2. Propshaft AND
  3. Zero complexity

We have to pick 2 out of 3.

๐Ÿ’ก My Honest Last Decision:

Keep our current setup! We’re already using:

  • Modern Rails 8 patterns (95% of your codebase)
  • Importmaps and Hotwire (exactly as intended)
  • Sprockets only affects asset serving (invisible to users)

The Propshaft vs Sprockets difference won’t affect your product’s success, but losing admin customization will limit our UX.


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 App โ†’ import 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! ๐Ÿš€

    Guide: Creating React Native โš›๏ธ App For Our Design Studio Application โ€“ Part 2

    Let’s now check what is the default code added to our App.tsx file and move on to developing it further.

    Check our mobile app here: https://github.com/MIRA-Designs/DesignStudioMobileApp

    What We’ve Accomplished:

    ๐Ÿ“ Current Status:

    Location: ~/react/DesignStudioMobileApp
    Git Branch: main
    Project: React Native app with default template

    1. โœ… Created React Native app with default template
    2. โœ… Renamed app from “DesignStudioApp” to “DesignStudioMobileApp”
    3. โœ… Fixed registration errors in iOS AppDelegate
    4. โœ… Renamed project folder to match app name
    5. โœ… Clean git history with 2 meaningful commits
    6. โœ… App running successfully on iOS simulator

    Ready for Next Steps:

    Our DesignStudioMobileApp is ready for development!

    Pending work options:

    • ๐Ÿงญ Add navigation between screens (React Navigation)
    • ๐ŸŽจ Create custom UI components for design studio features
    • ๐Ÿ“ธ Add camera/image functionality for capturing designs
    • ๐Ÿ’พ Set up state management (Redux/Zustand)
    • ๐ŸŽฏ Build specific features like project gallery, design tools, etc.
    • ๐Ÿ“ฑ Customize the welcome screen and app styling

    ๐Ÿ”ฅย Why My MacBook Heatsย Up when do react native development:

    Our laptop heating up when running npx react-native run-ios is actually very common in React Native development. Here’s why this happens and how to reduce it:

    1. Multiple Heavy Processes Running Simultaneously:

    • Metro Bundler – JavaScript bundling and hot reloading
    • Xcode Build System – Compiling iOS native code
    • iOS Simulator – Running a virtual iPhone with full OS
    • Node.js processes – Package management and tooling
    • CocoaPods – iOS dependency management

    2. Resource-Intensive Operations:

    • JavaScript compilation and transformation
    • iOS native code compilation (Objective-C/Swift)
    • File watching for hot reload (monitoring thousands of files)
    • Real-time bundling of JavaScript modules

    ๐Ÿ› ๏ธ Solutions to Reduce Heat & CPU Usage:

    Option 1: Optimize Metro Bundler ๐ŸŽฏ

    Quick Heat Reduction Tips: ๐Ÿ’ก

    1. Use Release Build for Testing (Cooler):

    # Instead of debug build (hot)
    npx react-native run-ios --configuration Release
    

    2. Reduce Metro File Watching:

    # Start Metro with fewer watchers
    npx react-native start --max-workers=2
    

    3. Close Unnecessary Apps:

    • Close other heavy applications (browsers with many tabs, etc.)
    • Quit unused developer tools

    Option 2: Optimize Metro Configuration โš™๏ธ

    Let’s update your Metro config to be more efficient:

    Option 3: Alternative Development Strategies ๐ŸŽฏ

    A. Use Physical Device Instead of Simulator:

    # Connect iPhone via USB, then:
    npx react-native run-ios --device
    

    Physical device uses less CPU than simulator

    B. Split Development Process:

    # Terminal 1: Start Metro (keep running)
    npx react-native start
    
    # Terminal 2: Build only when needed
    npx react-native run-ios --no-packager
    

    C. Use Flipper Alternative (Lighter):

    # Disable Flipper in development
    npx react-native run-ios --no-flipper
    

    Option 4: System-Level Optimizations ๐Ÿ–ฅ๏ธ

    1. Monitor CPU Usage:

    # Check what's using CPU
    top -o cpu
    
    # Or use Activity Monitor app
    

    2. Reduce Background Processes:

    • Close unnecessary browser tabs
    • Quit Slack, Discord, etc. during development
    • Close other IDEs/editors

    3. Improve Cooling:

    • Use laptop cooling pad
    • Work in cooler environment
    • Ensure laptop vents aren’t blocked

    Option 5: Alternative Tools ๐Ÿ› ๏ธ

    A. Expo Development Build (Lighter):

    # If you want to switch to Expo (much lighter)
    npx create-expo-app --template
    

    B. React Native Web for Quick Testing:

    # Test on web browser (much lighter)
    npm install react-native-web
    npx react-native run-web
    

    ๐Ÿ“Š Expected CPU Reduction:

    MethodCPU ReductionTrade-off
    Release Build40-60%Slower debugging
    Reduced Workers20-30%Slower builds
    Physical Device30-50%Need iPhone/cable
    Disable Flipper15-25%Less debugging tools
    Web Testing70-80%Not native experience

    ๐ŸŽฏ Recommended Approach:

    For your DesignStudioMobileApp:

    1. Use the optimized Metro config I just created โœ…
    2. Start with release builds for general testing
    3. Switch to debug only when you need debugging
    4. Use physical device if you have an iPhone

    Test the optimized setup:

    npx react-native start --reset-cache --max-workers=2
    

    Started Metro with the optimized settings.

    ๐ŸŒก๏ธ This Should Reduce Heat Significantly:

    What we just optimized:

    • โœ… Reduced workers from default (~8) to 2
    • โœ… Limited file watching
    • โœ… Optimized bundling process
    • โœ… Reduced concurrent operations

    Try running your app now:

    npx react-native run-ios --configuration Release
    

    This combination should reduce CPU usage by 30-50% and make your laptop run much cooler! ๐ŸŽ‰

    Quick tip: The heating is normal for React Native development, but these optimizations should make it much more manageable. Monitor Activity Monitor to see the CPU reduction! ๐Ÿ“Š


    Explain the App.tsx file content

    /**
     * Sample React Native App
     * https://github.com/facebook/react-native
     *
     * @format
     */
    
    import { NewAppScreen } from '@react-native/new-app-screen';
    import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
    
    function App() {
      const isDarkMode = useColorScheme() === 'dark';
    
      return (
        <View style={styles.container}>
          <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
          <NewAppScreen templateFileName="App.tsx" />
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
      },
    });
    
    export default App;
    

    Lines 1-6: Comments and Documentation ๐Ÿ“š

    /**
     * Sample React Native App
     * https://github.com/facebook/react-native
     *
     * @format
     */
    
    • Lines 1-5: JSDoc comment block describing the file
    • Line 4: @format tells Prettier to auto-format this file
    • Purpose: Documentation and tooling instructions

    Lines 8-9: Import Statements ๐Ÿ“ฆ

    import { NewAppScreen } from '@react-native/new-app-screen';
    import { StatusBar, StyleSheet, useColorScheme, View } from 'react-native';
    

    Line 8:

    • import { NewAppScreen } – Imports the default welcome screen component
    • from '@react-native/new-app-screen' – From React Native’s built-in new app package
    • Purpose: Gets the pre-built welcome screen with React Native logo and links

    Line 9:

    • StatusBar – Controls the phone’s status bar (battery, time, signal)
    • StyleSheet – Creates optimized styles (like CSS)
    • useColorScheme – Hook to detect if device is in dark/light mode
    • View – Basic container component (like <div> in HTML)
    • from 'react-native' – All from the core React Native library

    Lines 11-19: Main App Component โš›๏ธ

    function App() {
      const isDarkMode = useColorScheme() === 'dark';
    
      return (
        <View style={styles.container}>
          <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
          <NewAppScreen templateFileName="App.tsx" />
        </View>
      );
    }
    

    Line 11:

    function App() {
    
    • Declares the main App component as a function
    • This is the root component of your entire app
    • Alternative: Could also write as const App = () => {

    Line 12:

    const isDarkMode = useColorScheme() === 'dark';
    
    • useColorScheme() – React Hook that returns ‘dark’, ‘light’, or null

    Line 15:

    <View style={styles.container}>
    
    • <View> – Container component (like <div> in web)
    • style={styles.container} – Applies CSS-like styles defined below
    • Purpose: Wraps all app content in a styled container

    Line 16:

    <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
    
    • <StatusBar> – Controls the phone’s top status bar appearance
    • barStyle – Sets color of status bar text/icons
    • isDarkMode ? – Ternary operator (if/else in one line)
    • 'light-content' – White text (for dark backgrounds)
    • 'dark-content' – Dark text (for light backgrounds)
    • Purpose: Makes status bar readable in both dark/light modes

    Line 17:

    <NewAppScreen templateFileName="App.tsx" />
    
    • <NewAppScreen> – React Native’s default welcome screen component
    • templateFileName="App.tsx" – Tells the component which file it’s in
    • Purpose: Shows the welcome screen with React Native logo, docs links, etc.

    Lines 21-26: Styling ๐ŸŽจ

    const styles = StyleSheet.create({
      container: {
        flex: 1,
      },
    });
    

    Line 21:

    const styles = StyleSheet.create({
    
    • StyleSheet.create() – Creates optimized style objects
    • Benefits: Performance optimization, validation, auto-completion
    • Alternative: Could use plain objects, but StyleSheet is better

    Lines 22-24:

    container: {
      flex: 1,
    },
    
    • container – Style name (can be anything you want)
    • flex: 1 – Takes up all available space
    • CSS equivalent: flex: 1 makes the View expand to fill the screen
    • Purpose: Ensures the app content fills the entire screen

    Line 28: Export Statement ๐Ÿ“ค

    export default App;
    
    • export default – Makes the App component available to other files
    • Purpose: Allows index.js to import and register this component
    • Required: Without this, React Native can’t find your app component

    ๐Ÿ”„ How It All Works Together:

    Flow of Execution:

    1. Import dependencies โ†’ Get required React Native components
    2. Define App component โ†’ Create the main component function
    3. Detect color scheme โ†’ Check if user prefers dark/light mode
    4. Render UI โ†’ Return JSX that describes the screen
    5. Apply styles โ†’ Style the container to fill screen
    6. Export component โ†’ Make it available to React Native

    Visual Structure:

    ๐Ÿ“ฑ Phone Screen
    โ”œโ”€โ”€ ๐Ÿ“Š StatusBar (adapts to dark/light mode)
    โ””โ”€โ”€ ๐Ÿ“ฆ View (container that fills screen)
        โ””โ”€โ”€ ๐ŸŽ‰ NewAppScreen (welcome screen with React Native content)
    

    Key Concepts:

    ConceptExplanationExample
    JSXHTML-like syntax in JavaScript<View> instead of <div>
    ComponentsReusable UI pieces<StatusBar>, <View>
    PropsData passed to componentsbarStyle="dark-content"
    HooksFunctions that add React featuresuseColorScheme()
    StylesCSS-like styling for React NativeStyleSheet.create()

    React Native vs Web Differences:

    Web (HTML/CSS)React NativePurpose
    <div><View>Container
    CSS filesStyleSheetStyling
    Media queriesuseColorScheme()Responsive design
    Manual status bar<StatusBar>System UI control

    ๐ŸŽฏ What You’re Seeing on Screen:

    When you run this app, you see:

    • โœ… React Native logo
    • โœ… “Welcome to React Native” text
    • โœ… Links to documentation
    • โœ… Step-by-step instructions
    • โœ… Status bar that adapts to your phone’s theme

    This is the default template – a starting point for building our DesignStudioMobileApp! ๐Ÿš€


    โš›๏ธ Functional vs Class Components in React

    TypeIntroduced InSimpler SyntaxSupports HooksUses this?
    Functional ComponentReact since beginning, became more powerful in React 16.8โœ… Yesโœ… YesโŒ No
    Class ComponentReact 0.13+โŒ VerboseโŒ Old patternโœ… Yes

    ๐Ÿงฉ 1. Functional Component (Modern)

    โœ… What is it?

    A JavaScript function that returns JSX.
    As of React 16.8, functional components can manage state and side effects using Hooks (useState, useEffect, etc.).

    ๐Ÿ“ฆ Example:

    import React, { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // hook
    
      return (
        <div>
          <h2>Count: {count}</h2>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      );
    }
    
    

    โœ… Clean, short, and modern โ€” preferred in most cases today.

    ๐Ÿงฑ 2. Class Component (Old-style)

    โœ… What is it?

    A JavaScript class that extends React.Component, and uses this.state, this.props, and lifecycle methods like componentDidMount.

    ๐Ÿ“ฆ Example:

    import React, { Component } from 'react';
    
    class Counter extends Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
      }
    
      increment = () => this.setState({ count: this.state.count + 1 });
    
      render() {
        return (
          <div>
            <h2>Count: {this.state.count}</h2>
            <button onClick={this.increment}>Increment</button>
          </div>
        );
      }
    }
    
    

    โš ๏ธ More boilerplate, needs this, not as concise.

    ๐Ÿค” Why Two Types?

    ๐Ÿงช Historical Reason:

    • Before React 16.8, only class components could have state or lifecycle methods.
    • Functional components were stateless-only.

    ๐Ÿ’ก Then came Hooks (React 16.8):

    • Gave functional components full power: state, side effects, refs, context.
    • Made class components mostly obsolete for new code.

    ๐Ÿง  Which One Should You Use?

    โœ… Use Functional Components unless:

    • You’re working with legacy code
    • You’re learning how React evolved
    • A library forces you into class-style APIs (rare now)

    โœ… TL;DR

    FeatureFunctional ComponentClass Component
    SyntaxSimple functionClass with extends Component
    State supportโœ… Yes (via Hooks)โœ… Yes
    Lifecycle methodsโœ… Yes (via Hooks like useEffect)โœ… Yes (componentDidMount, etc)
    this keywordโŒ Noโœ… Yes
    Modern React choiceโญ PreferredDeprecated for new apps

    Absolutely! Letโ€™s go over the most important React Hooks that turned functional components into full-featured building blocks, just like class components (and even more powerful!).


    โš›๏ธ React Hooks โ€” Power Tools for Functional Components

    Introduced in React 16.8, Hooks let you:

    • Use state, side effects, context, refs, etc.
    • Without writing a class component

    ๐Ÿงฐ Essential React Hooks (with Examples)

    1๏ธโƒฃ useState โ€” Manage Local Component State ๐Ÿง 

    ๐Ÿ“Œ When to Use:

    To store and update component-level state (like a counter, input field value, toggle, etc.)

    โœ… Example:

    import { useState } from 'react';
    
    function Counter() {
      const [count, setCount] = useState(0); // initial value = 0
    
      return (
        <div>
          <h3>Count: {count}</h3>
          <button onClick={() => setCount(count + 1)}>+1</button>
        </div>
      );
    }
    
    

    2๏ธโƒฃ useEffect โ€” Handle Side Effects โณ

    ๐Ÿ“Œ When to Use:

    To run code after render: API calls, timers, subscriptions, etc.
    (Just like componentDidMount, componentDidUpdate, componentWillUnmount)

    โœ… Example:

    import { useEffect, useState } from 'react';
    
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
    
      useEffect(() => {
        fetch(`/api/users/${userId}`)
          .then(res => res.json())
          .then(data => setUser(data));
      }, [userId]); // runs again if userId changes
    
      return <div>{user ? user.name : 'Loading...'}</div>;
    }
    
    

    The useEffect hook in React takes two parameters:

    ๐Ÿง  useEffect Syntax

    useEffect(effectFunction, dependencyArray)
    
    ParameterTypeRequiredPurpose
    effectFunction() => {}โœ… YesCode to run after render (can include async logic)
    dependencyArray[] (array)โœ… YesList of values to watch โ€” effect re-runs only if these change

    ๐Ÿ” Our Example Breakdown

    useEffect(() => {
      fetch(`/api/users/${userId}`)
        .then(res => res.json())
        .then(data => setUser(data));
    }, [userId]);
    
    

    1. First Argument: Arrow Function () => { ... }

    • This function runs after the component renders.
    • It performs a side effect (API call).
    • You can also return a function for cleanup (like removing event listeners).

    2. Second Argument: Dependency Array [userId]

    • This tells React:
      “Only re-run this effect if userId changes.
    • If userId stays the same between renders โ†’ the effect won’t run again.
    • If omitted โ†’ the effect runs on every render.
    useEffect(() => {
      console.log("Runs after every render!");
    });
    
    

    โš ๏ธ This runs after every single re-render, which can be expensive.


    3๏ธโƒฃ useContext โ€” Access Context (Global Data) ๐ŸŒ

    ๐Ÿ“Œ When to Use:

    To consume global values like theme, language, authentication, etc., without prop drilling.

    โœ… Example:

    import { useContext } from 'react';
    import { ThemeContext } from './ThemeProvider';
    
    function ThemedButton() {
      const theme = useContext(ThemeContext); // 'dark' or 'light'
    
      return <button style={{ background: theme === 'dark' ? '#333' : '#eee' }}>Click</button>;
    }
    
    

    4๏ธโƒฃ useRef โ€” Store a Mutable Reference ๐Ÿชž

    ๐Ÿ“Œ When to Use:

    To reference DOM elements or store values without causing re-renders.

    โœ… Example:

    import { useRef } from 'react';
    
    function InputFocus() {
      const inputRef = useRef();
    
      return (
        <div>
          <input ref={inputRef} />
          <button onClick={() => inputRef.current.focus()}>Focus</button>
        </div>
      );
    }
    
    

    5๏ธโƒฃ useMemo โ€” Memoize Expensive Computations ๐Ÿงฎ

    ๐Ÿ“Œ When to Use:

    To cache the result of heavy functions, only recomputing when dependencies change.

    โœ… Example:

    import { useMemo } from 'react';
    
    function ExpensiveList({ items }) {
      const sortedItems = useMemo(() => {
        return [...items].sort(); // costly operation
      }, [items]);
    
      return <ul>{sortedItems.map(i => <li key={i}>{i}</li>)}</ul>;
    }
    
    

    6๏ธโƒฃ useCallback โ€” Memoize Functions to Avoid Re-Creation ๐ŸŽฏ

    ๐Ÿ“Œ When to Use:

    To prevent unnecessary re-renders when passing callbacks to child components.

    โœ… Example:

    import { useState, useCallback } from 'react';
    
    function Parent() {
      const [count, setCount] = useState(0);
    
      const increment = useCallback(() => setCount(c => c + 1), []);
    
      return <Child onClick={increment} />;
    }
    
    

    (Bonus) useReducer โ€” Complex State Logic โš™๏ธ

    ๐Ÿ“Œ When to Use:

    To manage complex state transitions or when youโ€™d use redux-like reducers.

    โœ… Example:

    import { useReducer } from 'react';
    
    function reducer(state, action) {
      switch (action.type) {
        case 'inc': return { count: state.count + 1 };
        case 'dec': return { count: state.count - 1 };
        default: return state;
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(reducer, { count: 0 });
    
      return (
        <>
          <p>Count: {state.count}</p>
          <button onClick={() => dispatch({ type: 'inc' })}>+1</button>
        </>
      );
    }
    
    

    โœ… TL;DR โ€“ Hook Summary Table

    HookPurposeReplaces
    useStateState inside function componentthis.state / setState
    useEffectSide effects (API, timers, etc.)componentDidMount etc.
    useContextUse global context valuescontextType, props drilling
    useRefDOM ref or persistent valuescreateRef
    useMemoCache a computed valueManual memoization
    useCallbackCache a functionInline anonymous functions
    useReducerComplex state logic (like Redux)Multiple useState calls

    Handy booklet:


    Let’s see in Part 3.. Happy React Native Development ๐Ÿš€

    Guide: Creating React Native โš›๏ธ App For Our Design Studio Application โ€“ Part 1

    The guide is for creating a React Native app for our design studio web application made with Ruby On Rails 8. Let me start by explaining the fundamentals and then walk you through the entire setup process.

    What is React Native? ๐Ÿค”

    React Native is a framework developed by Facebook (now Meta) that allows you to build mobile applications using JavaScript and React. It enables you to write code once and deploy it on both iOS and Android platforms.

    Why Use React Native? ๐ŸŽฏ

    Key Benefits:

    • Cross-Platform Development: Write once, run on both iOS and Android
    • Code Reusability: Share up to 90% of code between platforms
    • Native Performance: Uses native components, not webviews
    • Hot Reloading: See changes instantly during development
    • Large Community: Extensive libraries and community support
    • Cost-Effective: Faster development with smaller teams
    • Facebook Backing: Strong ecosystem and continuous updates

    React Native vs React: Key Differences ๐Ÿ“Š

    AspectReactReact Native
    Target PlatformWeb browsersMobile devices (iOS/Android)
    DOMVirtual DOMNative components
    StylingCSSStyleSheet API (CSS-like)
    ComponentsHTML elements (<div>, <p>, etc.)Native components (<View>, <Text>, etc.)
    NavigationReact RouterReact Navigation
    DeploymentWeb serversApp stores
    DevelopmentBrowser dev toolsMobile simulators/devices

    Setting Up Your React Native Development Environment ๐Ÿ› ๏ธ

    Let me guide you through creating your React Native app step by step:

    Step 1: Install Prerequisites

    First, let’s check your current setup and install the necessary tools:
    Ran tool

    We’ll need to install several tools. Let me guide you through each one:

    1. Node.js & npm/yarn

    # Check if Node.js is installed
    node --version
    npm --version
    
    # If not installed, download from https://nodejs.org/
    # Or install via Homebrew (recommended for macOS):
    brew install node
    

    2. React Native CLI

    npm install -g @react-native-community/cli
    

    3. For iOS Development (macOS only):

    # Install Xcode from Mac App Store
    
    # Install CocoaPods
    # Homebrew
    brew install cocoapods
    
    # Later
    # Update with Homebrew
    brew upgrade cocoapods
    
    # OR use ruby gem
    sudo gem install cocoapods
    

    4. For Android Development:

    • Install Android Studio
    • Set up Android SDK
    • Configure environment variables

    Step 2: Create Your React Native App

    Let’s navigate to our react folder and create the app. Let’s create our React Native app for the design studio.

    Run these commands in our terminal:

    # Navigate to your react directory
    cd ~/react
    
    # Create a new React Native app
    npx react-native init DesignStudioApp
    
    # Navigate to the new app directory
    cd DesignStudioApp
    

    ๐ŸŽ‰ Our DesignStudioApp has been created successfully!

    What Happened

    The React Native CLI successfully:

    • โœ… Downloaded the template
    • โœ… Copied template files
    • โœ… Processed the template
    • โœ… Installed dependencies
    • โœ… Initialized Git repository

    โœ…  CocoaPods is Already Installed!

    CocoaPods version 1.16.2_1 is properly installed via Homebrew in our system.

    CocoaPods manages dependencies for your Xcode projects.

    You specify the dependencies for your project in a simple text file: your Podfile. CocoaPods recursively resolves dependencies between libraries, fetches source code for all dependencies, and creates and maintains an Xcode workspace to build your project. The latest released Xcode versions and the prior version are supported.

    What This Means ๐Ÿ“‹

    CocoaPods Installation Details:

    • โœ… Version: 1.16.2_1 (latest stable)
    • โœ… Location: /opt/homebrew/Cellar/cocoapods/1.16.2_1/
    • โœ… Main Command: pod is available
    • โœ… Complete Installation: All necessary files are present
    # Let's Verify CocoaPods is Working 
    pod --version
    1.16.2
    

    Now Let’s Try Installing iOS Dependencies ๐Ÿ“ฑ

    Since CocoaPods is working, let’s try to install the iOS dependencies for our DesignStudioApp:

    cd DesignStudioApp/ios && pod install
    ....
    [Hermes] Using release tarball from URL: https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.80.1/react-native-artifacts-0.80.1-hermes-ios-debug.tar.gz
    

    Step 3: Understanding the Project Structure ๐Ÿ“

    After creating the app, you’ll see this structure:

    DesignStudioApp/
    โ”œโ”€โ”€ android/          # Android-specific code
    โ”œโ”€โ”€ ios/              # iOS-specific code
    โ”œโ”€โ”€ node_modules/     # Dependencies
    โ”œโ”€โ”€ src/              # Your app source code (you'll create this)
    โ”œโ”€โ”€ App.tsx           # Main app component
    โ”œโ”€โ”€ index.js          # Entry point
    โ”œโ”€โ”€ package.json      # Dependencies and scripts
    โ”œโ”€โ”€ metro.config.js   # Metro bundler configuration
    โ””โ”€โ”€ ...
    

    Step 4: Key Files Explained ๐Ÿ“

    App.tsx – Main component (like App.js in React web)
    index.js – Entry point that registers your app
    package.json – Dependencies and scripts
    android/ & ios/ – Platform-specific native code

    Step 5: Running Your App (No XCode installed) ๐Ÿš€

    For iOS Simulator:

    # Make sure you're in the DesignStudioApp directory
    npx react-native run-ios
    

    Install Xcode: Xcode Component Selection for React Native Development ๐Ÿ› ๏ธ

    For React Native development, you should select BOTH:

    โœ… Required Components:

    • macOS15.5 (built-in) – โœ… Select this
    • iOS 18.5 – โœ… Select this (Essential for React Native)

    Why You Need Both ๐Ÿ“ฑ

    ComponentWhy You Need ItWhat It Provides
    macOS15.5Basic development toolsXcode IDE, build tools, debugger
    iOS 18.5iOS app developmentiOS Simulator, iOS SDK, device support

    Select BOTH components because:

    1. macOS15.5 (built-in) โœ…
    • Core Xcode functionality
    • Build tools and compiler
    • Essential for any development
    1. iOS 18.5 โœ…
    • iOS Simulator – Test your React Native app
    • iOS SDK – Required for building iOS apps
    • Device support – Deploy to real iPhones

    Step-by-Step Instructions ๐Ÿ“‹

    Point System to Full Xcode Installation
    sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
    
    xcode-select --print-path
    
    xcodebuild -version
    Xcode 16.4
    

    Now Try:

    # Install iOS Dependencies Again
    cd DesignStudioApp && cd ios && pod install
    

    ๐ŸŽ‰ CocoaPods Installation Complete!

    All iOS dependencies have been successfully installed!

    What Just Happened โœ…

    • โœ… 74 dependencies installed from Podfile
    • โœ… 73 total pods installed successfully
    • โœ… React Native modules all configured
    • โœ… Hermes engine installed (JavaScript engine)
    • โœ… Xcode workspace created

    Hermes is an open-source JavaScript engine optimized for React Native. For many apps, using Hermes will result in improved start-up time, decreased memory usage, and smaller app size when compared to JavaScriptCore. Hermes is used by default by React Native and no additional configuration is required to enable it.

    Important Note ๐Ÿ“

    The system created a .xcworkspace file – this is what you’ll use instead of .xcodeproj from now on.

    # Step 5: Now Let's Run Your App on iOS! 
    
    โœ— npx react-native run-ios
    info Found Xcode workspace "DesignStudioApp.xcworkspace"
    info No booted devices or simulators found. Launching first available simulator...
    info Launching iPhone 16 Pro (iOS 18.5)
    info Building (using "xcodebuild -workspace DesignStudioApp.xcworkspace -configuration Debug -scheme DesignStudioApp -destination id=XXXX-XXXXX-XXXX-XXXX
    
    

    Step 1: Install iOS Dependencies ๐Ÿ“ฆ

    Open your terminal and run these commands:

    # Navigate to your app directory
    cd ~/react/DesignStudioApp
    
    # Install iOS dependencies
    cd ios && pod install && cd ..
    

    This will install all the necessary iOS libraries and dependencies.

    Step 2: Run the iOS App ๐Ÿ“ฑ

    Option A: Use React Native CLI (Recommended)

    Open a new terminal window (keep Metro running in the first one) and run:

    cd ~/react/DesignStudioApp
    npx react-native run-ios
    

    Option B: Use Xcode Directly

    # Open the iOS project in Xcode
    cd ~/react/DesignStudioApp
    xed ios/DesignStudioApp.xcworkspace
    

    Then press the “Run” button (โ–ถ๏ธ) in Xcode.

    What Should Happen ๐ŸŽฏ

    1. iOS Simulator opensA virtual iPhone will appear
    2. App builds – You’ll see build progress in terminal
    3. App launches – Your DesignStudioApp will open in the simulator
    4. Metro connects – The app connects to the development server

    Expected Output ๐Ÿ“ฑ

    You should see the default React Native welcome screen with:

    • React Native logo
    • “Welcome to React Native” message
    • Various getting started links
    • Debug information

    Common Issues & Solutions ๐Ÿ”ง

    Issue 1: “No iOS Simulator Available”

    # List available simulators
    xcrun simctl list devices
    
    # If no simulators, create one in Xcode:
    # Xcode > Window > Devices and Simulators > Create Simulator
    

    Issue 2: “Build Failed”

    # Clean and rebuild
    cd ~/react/DesignStudioApp
    npx react-native run-ios --clean
    

    Issue 3: “Metro not responding” – not used metro

    # Reset Metro cache
    npx react-native start --reset-cache
    

    Quick Commands Summary ๐Ÿ“

    # Terminal 1: Start Metro
    cd ~/react/DesignStudioApp
    npx react-native start
    
    # Terminal 2: Run iOS app
    cd ~/react/DesignStudioApp
    npx react-native run-ios
    

    What You’ll See ๐Ÿ‘€

    Your app should display:

    • React Native welcome screen
    • “Welcome to React Native” text
    • Various learning resources
    • Debug information

    Testing Your App ๐Ÿงช

    Once running, you can:

    • โŒ˜+D – Open developer menu
    • โŒ˜+R – Reload the app
    • โŒ˜+T – Open new simulator
    • Shake gesture – Open dev menu on device

    Next Steps After Success ๐Ÿš€

    Once your app is running:

    1. Edit App.tsx – Make changes to see hot reload
    2. Add navigation – React Navigation
    3. Add UI components – For your design studio
    4. Add features – Camera, image picker, etc.

    Run These Commands Now! ๐Ÿ’ป

    Open your terminal and execute:

    cd ~/react/DesignStudioApp
    cd ios && pod install && cd ..
    npx react-native run-ios
    

    This should launch your DesignStudioApp in the iOS simulator! ๐ŸŽ‰

    For Android Emulator:

    # Start Android emulator first, then:
    npx react-native run-android
    

    Step 6: Understanding Core Components ๐Ÿ“ฑ

    Let me show you the main React Native components we’ll use:

    Basic Components:

    • <View> – Like <div> in React web
    • <Text> – Like <p> or <span> in React web
    • <Image> – For displaying images
    • <ScrollView> – Scrollable container
    • <TouchableOpacity> – Touchable button
    • <TextInput> – Input field
    • <FlatList> – Efficient list rendering

    Example App.tsx:

    import React from 'react';
    import {
      SafeAreaView,
      ScrollView,
      StatusBar,
      StyleSheet,
      Text,
      View,
      TouchableOpacity,
      Image,
    } from 'react-native';
    
    const App = () => {
      return (
        <SafeAreaView style={styles.container}>
          <StatusBar barStyle="dark-content" />
          <ScrollView contentInsetAdjustmentBehavior="automatic">
            <View style={styles.header}>
              <Text style={styles.title}>Design Studio App</Text>
              <Text style={styles.subtitle}>Welcome to your creative space</Text>
            </View>
    
            <View style={styles.content}>
              <TouchableOpacity style={styles.button}>
                <Text style={styles.buttonText}>Get Started</Text>
              </TouchableOpacity>
            </View>
          </ScrollView>
        </SafeAreaView>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: '#f8f9fa',
      },
      header: {
        padding: 24,
        alignItems: 'center',
      },
      title: {
        fontSize: 24,
        fontWeight: 'bold',
        color: '#333',
      },
      subtitle: {
        fontSize: 16,
        color: '#666',
        marginTop: 8,
      },
      content: {
        padding: 24,
      },
      button: {
        backgroundColor: '#007AFF',
        padding: 16,
        borderRadius: 8,
        alignItems: 'center',
      },
      buttonText: {
        color: 'white',
        fontSize: 18,
        fontWeight: '600',
      },
    });
    
    export default App;
    

    Step 7: Styling in React Native ๐ŸŽจ

    React Native uses a StyleSheet API similar to CSS:

    import { StyleSheet } from 'react-native';
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,              // Like flex: 1 in CSS
        backgroundColor: '#fff',
        padding: 20,
      },
      text: {
        fontSize: 16,
        color: '#333',
        textAlign: 'center',
      },
    });
    

    Key Styling Differences:

    • Use backgroundColor instead of background-color
    • Use flexDirection instead of flex-direction
    • All style properties are camelCase
    • No cascade – styles are component-specific

    Step 8: Development Tools ๐Ÿ”ง

    Metro Bundler – Automatically starts when you run the app
    React Native Debugger – For debugging
    Flipper – Advanced debugging tool

    Step 9: Common Commands ๐Ÿ’ป

    # Start Metro bundler
    npx react-native start
    
    # Run on iOS
    npx react-native run-ios
    
    # Run on Android
    npx react-native run-android
    
    # Clear cache
    npx react-native start --reset-cache
    
    # Install dependencies
    npm install
    # or
    yarn install
    

    Step 10: Next Steps for Our Design Studio App ๐ŸŽจ

    For the design studio app, you might want to add:

    1. Navigation – React Navigation
    2. UI Components – NativeBase, React Native Elements
    3. Image Handling – React Native Image Picker
    4. Animations – React Native Reanimated
    5. State Management – Redux Toolkit or Zustand
    6. Network Requests – Axios or fetch
    7. Storage – AsyncStorage

    To get started right now, run these commands in your terminal:

    cd ~/react/DesignStudioApp
    
    npx react-native run-ios  # or run-android
    

    This will create and run our first React Native app! ๐ŸŽ‰

    Check our App in Github: https://github.com/MIRA-Designs/DesignStudioMobileApp

    Issues: https://github.com/MIRA-Designs/DesignStudioMobileApp/issues


    What is NPX? ๐Ÿค”

    NPX is a package runner tool that comes with npm (Node Package Manager). It was introduced in npm version 5.2.0 and is now a standard part of the npm ecosystem.

    NPX Full Name:

    • NPX = Node Package eXecute
    • It’s a command-line tool for executing npm packages

    How NPX Works ๐Ÿ”ง

    Basic Concept:

    Instead of installing packages globally, npx allows you to:

    • Run packages directly without installing them permanently
    • Execute the latest version of a package
    • Avoid global installation clutter

    NPX vs NPM Installation:

    MethodCommandWhat Happens
    Traditionalnpm install -g create-react-app
    create-react-app my-app
    Installs globally, then runs
    NPXnpx create-react-app my-appDownloads, runs, then removes

    Why Use NPX? ๐ŸŽฏ

    1. No Global Installation Required

    # Old way - install globally first
    npm install -g create-react-app
    create-react-app my-app
    
    # NPX way - run directly
    npx create-react-app my-app
    

    2. Always Latest Version

    # NPX ensures you get the latest version
    npx create-react-app my-app
    
    # vs global install might be outdated
    npm install -g create-react-app  # Might be old version
    create-react-app my-app
    

    3. Saves Disk Space

    • No need to store packages globally
    • Temporary downloads are cleaned up automatically

    4. Avoids Version Conflicts

    • Different projects can use different versions
    • No global version conflicts

    Common NPX Use Cases ๐Ÿ“ฑ

    1. Creating Projects

    # React Native
    npx react-native init MyApp
    
    # React Web App
    npx create-react-app my-web-app
    
    # Next.js
    npx create-next-app my-next-app
    
    # Vue.js
    npx create-vue my-vue-app
    
    # Expo (React Native)
    npx create-expo-app my-expo-app
    

    2. Running Development Tools

    # TypeScript compiler
    npx tsc --init
    
    # Linting
    npx eslint .
    
    # Testing
    npx jest
    
    # Package auditing
    npx npm-check-updates
    

    3. One-time Scripts

    # Check bundle size
    npx bundlesize
    
    # Generate documentation
    npx jsdoc
    
    # Code formatting
    npx prettier --write .
    

    How NPX Works Behind the Scenes ๐Ÿ”

    When you run npx some-package:

    1. Checks locally – Is the package in node_modules/.bin?
    2. Checks globally – Is it installed globally?
    3. Downloads temporarily – If not found, downloads to temp folder
    4. Executes – Runs the package
    5. Cleans up – Removes temporary files

    Example Flow:

    npx create-react-app my-app
    
    1. โœ… Check: Is create-react-app in local node_modules? No.
    2. โœ… Check: Is it installed globally? No.
    3. โฌ‡๏ธ Download: Downloads latest create-react-app to temp folder
    4. โ–ถ๏ธ Execute: Runs create-react-app my-app
    5. ๐Ÿ—‘๏ธ Cleanup: Removes temporary files

    NPX Command Options ๐Ÿ› ๏ธ

    Basic Syntax:

    npx [options] <command>[@version] [command-args...]
    

    Useful Options:

    # Force download (ignore local/global)
    npx --ignore-existing create-react-app my-app
    
    # Use specific version
    npx create-react-app@4.0.0 my-app
    
    # Check what would be executed
    npx --version create-react-app
    
    # Run from specific package
    npx -p typescript tsc --init
    

    NPX vs NPM: Key Differences ๐Ÿ“Š

    AspectNPMNPX
    PurposePackage managerPackage executor
    Installationnpm installNo installation needed
    StoragePermanent (node_modules)Temporary
    UsageInstall, then runRun directly
    UpdatesManual (npm update)Always latest

    Real-World Examples ๐ŸŒŸ

    For Our React Native Project:

    # Create the app
    npx react-native init DesignStudioApp
    
    # Instead of:
    npm install -g @react-native-community/cli
    react-native init DesignStudioApp
    

    Development Workflow:

    # Start Metro bundler
    npx react-native start
    
    # Run on iOS
    npx react-native run-ios
    
    # Run on Android
    npx react-native run-android
    

    Other Useful Commands:

    # Check React Native version
    npx react-native --version
    
    # Upgrade React Native
    npx react-native upgrade
    
    # Doctor (check environment)
    npx react-native doctor
    

    Benefits Summary ๐ŸŽ‰

    For Developers:

    • โœ… Cleaner system – No global package clutter
    • โœ… Always updated – Latest versions automatically
    • โœ… Faster setup – No pre-installation needed
    • โœ… Less conflicts – No version conflicts

    For Projects:

    • โœ… Consistent environments – Everyone uses same versions
    • โœ… Easier setup – New team members can start quickly
    • โœ… Better CI/CD – Consistent builds

    Pro Tips ๐Ÿ’ก

    1. Use NPX for project creation – Always use npx for scaffolding tools
    2. Check before installing globally – Ask yourself: “Do I need this globally?”
    3. Use specific versions when needednpx package@version
    4. Great for CI/CD – Ensures consistent tool versions

    NPX is essentially a way to “try before you buy” – run packages without the commitment of installing them permanently! It’s become an essential tool in modern JavaScript development. ๐Ÿš€


    Let’s see in Part 2.. Happy React Native 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 ๐Ÿš€…

    Rails 8 App: Create an Academic software app using SQL without using ActiveRecord- Part 3

    In this episode we move on from creating the tables with constraints, foreign keys, proper indexes, enums, reversing each migrations, seeded data etc. Now let’s check our seeded data with some ui tool that help us smell and modify our db data.

    Setup a UI tool for analysing SQL and data

    W’re using PostgreSQL. Here are the best SQL GUI tools for pure SQL data analysis and manipulation on macOS:

    ๐Ÿ† Top Recommendations for SQL Analysis

    1. TablePlus โญ๏ธ (Highly Recommended)

    • Best for: Fast SQL queries, data filtering, before/after comparisons
    • Strengths:
      Lightning-fast query execution
      Excellent data filtering UI with SQL query builder
      Beautiful native macOS interface
      Export to CSV/JSON/SQL
      Query history and favorites
      Cost: Free tier (2 tabs), Pro $89
      Perfect for: Your use case of checking seeded data

    2. Postico (macOS Native)

    • Best for: PostgreSQL-specific features and analysis
    • Strengths:
      Built specifically for PostgreSQL
      Excellent for large dataset analysis
      Advanced filtering and sorting
      Beautiful data visualization
      Cost: $49 (one-time)
      PostgreSQL-optimized: Uses all PostgreSQL features

    3. pgAdmin (Free, Official)

    • Best for: Advanced PostgreSQL administration and complex queries
    • Strengths:
      Official PostgreSQL tool
      Advanced query planner visualization
      Excellent for performance analysis
      Complete database management
      Cost: Free
      Learning curve: Steeper but very powerful

    4. DBeaver (Free, Cross-platform)

    • Best for: Advanced SQL analysis and scripting
    • Strengths:
      Powerful SQL editor with autocomplete
      Data export in multiple formats
      Query execution plans
      Visual query builder
      Cost: Free (Community), Pro $10/month
      Great for: Complex data analysis workflows

    Quick Setup Commands

    For TablePlus (easiest to get started):

    # Install via Homebrew
    brew install --cask tableplus
    

    For pgAdmin:

    # Install via Homebrew
    brew install --cask pgadmin4
    

    For Postico:

    # Install via Homebrew
    brew install --cask postico
    

    ๐Ÿ”ง Connection Details You’ll Need

    Your PostgreSQL connection details:

    • Host: localhost (default)
    • Port: 5432 (default)
    • Database: academic_sql_software_development
    • Username: Your macOS username (default)
    • Password: None (default for local development)

    ๐Ÿ’ก Pro Tips for Data Analysis

    Once connected, you can:

    1. Check seeded data:
       SELECT COUNT(*) FROM users;
       SELECT COUNT(*) FROM orders;
       SELECT COUNT(*) FROM products;
    
    1. Analyze relationships:
       SELECT 
         u.first_name, u.last_name, 
         COUNT(o.id) as order_count
       FROM users u 
       LEFT JOIN orders o ON u.id = o.user_id 
       GROUP BY u.id, u.first_name, u.last_name
       ORDER BY order_count DESC;
    
    1. Filter and export specific datasets for before/after comparisons

    My Recommendation: Start with TablePlus – it’s the most intuitive for our workflow of checking and filtering seeded data, with excellent performance for the data volumes we’re working with (10k users, 5k orders, etc.).

    Let’s Go with TablePlus ๐Ÿฅณ

    ๐Ÿš€ Stepย 1: Install TablePlus

    brew install --cask tableplus
    

    ๐Ÿ“Š Stepย 2: Check Our Database Schema

    ย Weย have a greatย setup forย learning SQL with realisticย relationships. Let’s create aย progressive SQL learning path usingย our actualย data.

    ๐Ÿ”— Stepย 3: Connectย to Your Database

    TablePlus Connection Details:

    • Host:ย localhost
    • Port:ย 5432
    • Database:ย academic_sql_software_development
    • User:ย (yourย macOS username)
    • Password: (leaveย blank)

    ๐Ÿ“š SQLย Learning Path: Basic to Advanced

    Change Font size, colour, theme etc:

    Level 1: Basic SELECT Queries

    -- 1. View all users
    SELECT * FROM users LIMIT 10;
    
    -- 2. Count total records
    SELECT COUNT(*) FROM users;
    SELECT COUNT(*) FROM orders;
    SELECT COUNT(*) FROM products;
    
    -- 3. Filter data
    SELECT first_name, last_name, email 
    FROM users 
    WHERE gender = 'female' 
    LIMIT 10;
    
    -- 4. Sort data
    SELECT first_name, last_name, date_of_birth 
    FROM users 
    ORDER BY date_of_birth DESC 
    LIMIT 10;
    
    -- 5. Filter with conditions
    SELECT title, price, category 
    FROM products 
    WHERE price > 50 AND category = 'men' 
    ORDER BY price DESC;
    

    Level 2: Basic Aggregations

    -- 1. Count by category
    SELECT category, COUNT(*) as product_count 
    FROM products 
    GROUP BY category;
    
    -- 2. Average prices by category
    SELECT category, 
           AVG(price) as avg_price,
           MIN(price) as min_price,
           MAX(price) as max_price
    FROM products 
    GROUP BY category;
    
    -- 3. Users by gender
    SELECT gender, COUNT(*) as user_count 
    FROM users 
    WHERE gender IS NOT NULL
    GROUP BY gender;
    
    -- 4. Products with low stock
    SELECT COUNT(*) as low_stock_products 
    FROM products 
    WHERE stock_quantity < 10;
    

    Level 3: Inner Joins

    -- 1. Users with their orders
    SELECT u.first_name, u.last_name, u.email, o.id as order_id, o.created_at
    FROM users u
    INNER JOIN orders o ON u.id = o.user_id
    ORDER BY o.created_at DESC
    LIMIT 20;
    
    -- 2. Orders with product details
    SELECT o.id as order_id, 
           p.title as product_name, 
           p.price, 
           p.category,
           o.created_at
    FROM orders o
    INNER JOIN products p ON o.product_id = p.id
    ORDER BY o.created_at DESC
    LIMIT 20;
    
    -- 3. Complete order information (3-table join)
    SELECT u.first_name, u.last_name,
           p.title as product_name,
           p.price,
           p.category,
           o.created_at as order_date
    FROM orders o
    INNER JOIN users u ON o.user_id = u.id
    INNER JOIN products p ON o.product_id = p.id
    ORDER BY o.created_at DESC
    LIMIT 20;
    

    Level 4: Left Joins (Show Missing Data)

    -- 1. All users and their order count (including users with no orders)
    SELECT u.first_name, u.last_name, u.email,
           COUNT(o.id) as order_count
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.first_name, u.last_name, u.email
    ORDER BY order_count DESC;
    
    -- 2. Users who haven't placed any orders
    SELECT u.first_name, u.last_name, u.email, u.created_at
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE o.id IS NULL
    ORDER BY u.created_at DESC;
    
    -- 3. Products that have never been ordered
    SELECT p.title, p.price, p.category, p.stock_quantity
    FROM products p
    LEFT JOIN orders o ON p.id = o.product_id
    WHERE o.id IS NULL
    ORDER BY p.price DESC;
    

    Level 5: Advanced Aggregations & Grouping

    -- 1. Top customers by order count
    SELECT u.first_name, u.last_name,
           COUNT(o.id) as total_orders,
           SUM(p.price) as total_spent
    FROM users u
    INNER JOIN orders o ON u.id = o.user_id
    INNER JOIN products p ON o.product_id = p.id
    GROUP BY u.id, u.first_name, u.last_name
    HAVING COUNT(o.id) > 1
    ORDER BY total_spent DESC
    LIMIT 10;
    
    -- 2. Most popular products
    SELECT p.title, p.category, p.price,
           COUNT(o.id) as times_ordered,
           SUM(p.price) as total_revenue
    FROM products p
    INNER JOIN orders o ON p.id = o.product_id
    GROUP BY p.id, p.title, p.category, p.price
    ORDER BY times_ordered DESC
    LIMIT 10;
    
    -- 3. Monthly order analysis
    SELECT DATE_TRUNC('month', o.created_at) as month,
           COUNT(o.id) as order_count,
           COUNT(DISTINCT o.user_id) as unique_customers,
           SUM(p.price) as total_revenue
    FROM orders o
    INNER JOIN products p ON o.product_id = p.id
    GROUP BY DATE_TRUNC('month', o.created_at)
    ORDER BY month;
    

    Level 6: Student Enrollment Analysis (Complex Joins)

    -- 1. Students with their course and school info
    SELECT u.first_name, u.last_name,
           c.title as course_name,
           s.title as school_name,
           st.enrolment_date
    FROM students st
    INNER JOIN users u ON st.user_id = u.id
    INNER JOIN courses c ON st.course_id = c.id
    INNER JOIN schools s ON st.school_id = s.id
    ORDER BY st.enrolment_date DESC
    LIMIT 20;
    
    -- 2. Course popularity by school
    SELECT s.title as school_name,
           c.title as course_name,
           COUNT(st.id) as student_count
    FROM students st
    INNER JOIN courses c ON st.course_id = c.id
    INNER JOIN schools s ON st.school_id = s.id
    GROUP BY s.id, s.title, c.id, c.title
    ORDER BY student_count DESC;
    
    -- 3. Schools with enrollment stats
    SELECT s.title as school_name,
           COUNT(st.id) as total_students,
           COUNT(DISTINCT st.course_id) as courses_offered,
           MIN(st.enrolment_date) as first_enrollment,
           MAX(st.enrolment_date) as latest_enrollment
    FROM schools s
    LEFT JOIN students st ON s.id = st.school_id
    GROUP BY s.id, s.title
    ORDER BY total_students DESC;
    

    Level 7: Advanced Concepts

    -- 1. Subqueries: Users who spent more than average
    WITH user_spending AS (
      SELECT u.id, u.first_name, u.last_name,
             SUM(p.price) as total_spent
      FROM users u
      INNER JOIN orders o ON u.id = o.user_id
      INNER JOIN products p ON o.product_id = p.id
      GROUP BY u.id, u.first_name, u.last_name
    )
    SELECT first_name, last_name, total_spent
    FROM user_spending
    WHERE total_spent > (SELECT AVG(total_spent) FROM user_spending)
    ORDER BY total_spent DESC;
    
    -- 2. Window functions: Ranking customers
    SELECT u.first_name, u.last_name,
           COUNT(o.id) as order_count,
           SUM(p.price) as total_spent,
           RANK() OVER (ORDER BY SUM(p.price) DESC) as spending_rank
    FROM users u
    INNER JOIN orders o ON u.id = o.user_id
    INNER JOIN products p ON o.product_id = p.id
    GROUP BY u.id, u.first_name, u.last_name
    ORDER BY spending_rank
    LIMIT 20;
    
    -- 3. Case statements for categorization
    SELECT u.first_name, u.last_name,
           COUNT(o.id) as order_count,
           CASE 
             WHEN COUNT(o.id) >= 5 THEN 'VIP Customer'
             WHEN COUNT(o.id) >= 2 THEN 'Regular Customer'
             ELSE 'New Customer'
           END as customer_type
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    GROUP BY u.id, u.first_name, u.last_name
    ORDER BY order_count DESC;
    

    Level 8: Self-Joins & Advanced Analysis

    -- 1. Find users enrolled in the same course (pseudo self-join)
    SELECT DISTINCT 
           u1.first_name || ' ' || u1.last_name as student1,
           u2.first_name || ' ' || u2.last_name as student2,
           c.title as course_name
    FROM students s1
    INNER JOIN students s2 ON s1.course_id = s2.course_id AND s1.user_id < s2.user_id
    INNER JOIN users u1 ON s1.user_id = u1.id
    INNER JOIN users u2 ON s2.user_id = u2.id
    INNER JOIN courses c ON s1.course_id = c.id
    ORDER BY c.title, student1
    LIMIT 20;
    
    -- 2. Complex business question: Multi-role users
    SELECT u.first_name, u.last_name, u.email,
           COUNT(DISTINCT o.id) as orders_placed,
           COUNT(DISTINCT st.id) as courses_enrolled,
           CASE 
             WHEN COUNT(DISTINCT o.id) > 0 AND COUNT(DISTINCT st.id) > 0 THEN 'Customer & Student'
             WHEN COUNT(DISTINCT o.id) > 0 THEN 'Customer Only'
             WHEN COUNT(DISTINCT st.id) > 0 THEN 'Student Only'
             ELSE 'No Activity'
           END as user_type
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    LEFT JOIN students st ON u.id = st.user_id
    GROUP BY u.id, u.first_name, u.last_name, u.email
    ORDER BY orders_placed DESC, courses_enrolled DESC;
    

    ๐ŸŽฏย Our Learning Strategy:

    1. Start with Level 1-2ย in TablePlus toย get comfortable
    2. Progressย through each levelย – try to understand eachย query before moving on
    3. Modify theย queriesย – change filters, add fields, etc.
    4. Create your own variationsย based on businessย questions

    to be continued โ€ฆ ๐Ÿš€

    Rails 8 App: Create an Academic software app using SQL without using ActiveRecord- Part 2 | students | courses | schools

    Design: Our Students Table -> course -> school

    We need a UNIQUE constraint on user_id because:

    • โœ… One student per user (user_id should be unique)
    • โœ… Multiple students per course (course_id can be repeated)

    Check Migration Files:

    Key Changes:

    1. โœ… Added UNIQUE constraint: CONSTRAINT uk_students_user_id UNIQUE (user_id)
    2. ๐Ÿ”ง Fixed typos:
    • TIMSTAMP โ†’ TIMESTAMP
    • stidents โ†’ students

    ๐Ÿ“ˆ Optimized indexes: No need for user_id index since UNIQUE creates one automatically

    Business Logic Validation:

    • user_id: One student per user โœ…
    • course_id: Multiple students per course โœ…
    • school_id: Multiple students per school โœ…

    This ensures referential integrity and business rules are enforced at the database level!


    ๐Ÿ“ Schema Storage Options:

    Rails allows you to store the schema in SQL format instead of the default Ruby format. Let me explain the options and why you’d choose each:

    1. Ruby Format (Default)

    # db/schema.rb
    ActiveRecord::Schema[8.0].define(version: 2025_07_09_074552) do
      enable_extension "pg_catalog.plpgsql"
    
      create_table "users", force: :cascade do |t|
        t.string "first_name", limit: 100, null: false
        t.string "email", limit: 150, null: false
        t.datetime "created_at", null: false
        t.index ["email"], name: "idx_users_email"
      end
    end
    

    2. SQL Format

    -- db/structure.sql
    CREATE EXTENSION IF NOT EXISTS pg_catalog.plpgsql;
    
    CREATE TYPE gender_enum AS ENUM ('male', 'female', 'not-specified');
    
    CREATE TABLE users (
        id bigserial PRIMARY KEY,
        first_name varchar(100) NOT NULL,
        email varchar(150) NOT NULL,
        created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    
    CREATE UNIQUE INDEX idx_users_email ON users(email);
    

    Check structure.sql File: Github: db/structure.sql

    ๐Ÿ”ง How to Switch to SQL Format:

    Add this to config/application.rb:

    module AcademicSqlSoftware
      class Application < Rails::Application
        # ... existing config ...
    
        # Use SQL format for schema
        config.active_record.schema_format = :sql
      end
    end
    

    Then regenerate the schema:

    # Generate SQL schema file
    rails db:schema:dump
    

    This will create db/structure.sql instead of db/schema.rb.

    Comparison Table:

    FeatureRuby Format (schema.rb)SQL Format (structure.sql)
    Defaultโœ… YesโŒ No
    Database Agnosticโœ… YesโŒ No (DB-specific)
    Custom SQL FeaturesโŒ Limitedโœ… Full support
    ViewsโŒ Noโœ… Yes
    Stored ProceduresโŒ Noโœ… Yes
    TriggersโŒ Noโœ… Yes
    Custom Types/ENUMsโŒ Limitedโœ… Full support
    Partial IndexesโŒ Limitedโœ… Yes
    Check ConstraintsโŒ Limitedโœ… Yes
    Version Controlโœ… Easy to diffโš ๏ธ Harder to diff
    Readabilityโœ… Ruby-likeโš ๏ธ Raw SQL

    When to Use Each:

    Choose SQL Format When:

    • โœ… Using PostgreSQL-specific features (ENUMs, arrays, JSONB operators)
    • โœ… Need database views, triggers, or stored procedures
    • โœ… Using complex constraints (CHECK, partial indexes)
    • โœ… Working with custom data types
    • โœ… Team is SQL-proficient

    Choose Ruby Format When:

    • โœ… Want database portability (switching between PostgreSQL/MySQL/SQLite)
    • โœ… Team prefers Ruby syntax
    • โœ… Using standard Rails features only
    • โœ… Need easier version control diffs

    For Our Project:

    I recommend SQL format because you’re using:

    • โœ… PostgreSQL ENUMs (gender_enum, category_enum) – Rails 8 supports this
    • โœ… Custom constraints (CHECK (price > 0)) – Rails 8 supports this
    • โœ… Complex foreign key relationships
    • โœ… Raw SQL migrations

    Our config/application.rb should include:

    config.active_record.schema_format = :sql
    

    Then run:

    rails db:schema:dump
    

    This will give us full control over PostgreSQL features while maintaining schema consistency! ๐ŸŽฏ

    ๐ŸŒฑ Seed Data

    1. Let’s refactor the seed file into modular components. I’ll create separate seeder classes for each model type and organize them properly.

    2. Let’s me create individual seeder classes for each model, the seeders for the dependent models (orders and students), refactor the main seeds.rb file to use the new modular structure, create a documentation file for the new modular structure.

    3. I’ve successfully refactored our seed file into a modular, maintainable architecture. Here’s what we’ve created:

    ๐ŸŽฏ Modular Seed System Overview

    ๐Ÿ“ File Structure:

    db/
    โ”œโ”€โ”€ seeds.rb                    # Main orchestrator (now 20 lines vs 246!)
    โ””โ”€โ”€ seeds/
        โ”œโ”€โ”€ README.md              # Complete documentation
        โ”œโ”€โ”€ base_seeder.rb         # Shared functionality & config
        โ”œโ”€โ”€ users_seeder.rb        # User creation logic
        โ”œโ”€โ”€ products_seeder.rb     # Product creation logic  
        โ”œโ”€โ”€ courses_seeder.rb      # Course creation logic
        โ”œโ”€โ”€ schools_seeder.rb      # School creation logic
        โ”œโ”€โ”€ orders_seeder.rb       # Order creation (with dependencies)
        โ””โ”€โ”€ students_seeder.rb     # Student creation (with constraints)
    

    Key Benefits:

    โœ… Separation of Concerns:
    • Each model has its own dedicated seeder class
    • Clear responsibility boundaries
    • Easy to modify individual model generation
    โœ… Maintainability:
    • Single file changes vs monolithic seed file
    • Inheritance from BaseSeeder for shared functionality
    • Comprehensive documentation
    โœ… Flexibility:
    • Easy to add new models
    • Environment-specific configuration
    • Configurable record counts
    โœ… Robustness:
    • Proper dependency handling
    • Constraint compliance (stock, unique keys)
    • Error handling and validation

    ๐Ÿ“‹ Usage Examples:

    # Basic seeding
    rails db:seed
    
    # Clear data first
    CLEAR_DATA=true rails db:seed
    
    # Scale up for testing
    # Edit USERS_COUNT in base_seeder.rb then:
    rails db:seed
    

    Check my Github Seed File: Seed File – Academic SQL App

    ๐Ÿ› ๏ธ Commit the Refactored System:

    # Stage all the new modular files
    git add db/seeds.rb \
            db/seeds/
    
    # Commit with comprehensive message
    git commit -m "Refactor seed system into modular architecture" -m "
    
    Modular Structure:
    - Split monolithic seeds.rb into focused seeder classes
    - Create BaseSeeder with shared functionality and configuration
    - Separate concerns: each model has dedicated seeder
    - Add comprehensive documentation in seeds/README.md
    
    Benefits:
    - Maintainability: Easy to modify individual model generation
    - Flexibility: Simple to add new models or adjust data
    - Robustness: Proper dependency handling and constraint compliance
    - Testability: Individual seeders can be tested in isolation
    
    Features:
    - Environment-specific behavior (auto-clear in development)
    - Smart constraint handling (stock validation, unique keys)
    - Progress indicators and comprehensive reporting
    - Configurable record counts via constants
    
    Files:
    - db/seeds.rb: Main orchestrator (20 lines vs 246)
    - db/seeds/base_seeder.rb: Shared functionality & config
    - db/seeds/*_seeder.rb: Individual model seeders
    - db/seeds/README.md: Complete documentation
    
    The system is now ready for production use and easy to extend."
    

    ๐ŸŽ‰ What We Can Do Now:

    Scale Easily:

    # In base_seeder.rb - change these and run rails db:seed
    USERS_COUNT = 10_000
    PRODUCTS_COUNT = 1_000  
    ORDERS_COUNT = 50_000
    

    Add New Models: Create new_model_seeder.rb, inherit from BaseSeeder, add to orchestration.

    Customize Data: Edit individual seeder constants and attribute methods.

    Test Components:

    users = UsersSeeder.create_users
    products = ProductsSeeder.create_products
    

    Our seed system is now enterprise-ready with clean architecture, comprehensive documentation, and infinite scalability! ๐Ÿš€

    Possible Refactoring

    Now Let’s move the helper methods to a dedicated helper folder for better organization. I’ll create a helpers directory and move the shared utility methods there.

    Here’s how to refactor your seed helper methods into a new helpers folder under db/seeds:

    1. Create a directory:
      db/seeds/helpers/
    2. Move all shared/random data methods (like random_gender, random_category, etc.) into a module, e.g. db/seeds/helpers/data_generators.rb:
    # db/seeds/helpers/data_generators.rb
    module DataGenerators
      def random_gender
        ['male', 'female', 'not-specified'].sample
      end
    
      def random_category
        ['men', 'women', 'kids', 'infants'].sample
      end
    
      def random_date_of_birth
        Date.today - rand(18..80).years - rand(365).days
      end
    
      def random_phone
        "+1#{rand(100..999)}-#{rand(100..999)}-#{rand(1000..9999)}"
      end
    
      def random_price
        [9.99, 19.99, 29.99, 49.99, 99.99, 199.99, 299.99, 499.99].sample
      end
    
      def random_stock
        [0, 5, 10, 25, 50, 100, 500].sample
      end
    
      def random_past_date(days_ago_max = 365)
        Date.today - rand(1..days_ago_max).days
      end
    end
    
    1. In your seeders and base seeder, require and include the helper:
    # At the top of each seeder or in base_seeder.rb
    require_relative 'helpers/data_generators'
    
    class UsersSeeder < BaseSeeder
      extend DataGenerators
      # ... now you can call random_gender, random_category, etc.
    end
    
    1. Remove the helper methods from BaseSeeder and use the module instead.

    Summary:

    • Move all shared/random data methods to db/seeds/helpers/data_generators.rb as a module.
    • require_relative and extend or include the module in your seeders.
    • This keeps your seed logic clean and DRY!

    Our BaseSeeder still contains all the random data and progress helper methods, which are now in db/seeds/helpers/data_generators.rb and db/seeds/helpers/ui_helpers.rb.

    Here’s what we should do:

    • Remove all the methods at the bottom of BaseSeeder that are now in the helpers.
    • At the top of base_seeder.rb, require the helper modules.
    • If you want to use the helpers as class methods, use extend for each module.

    Here’s the refactor plan:

    1. Remove:
    • random_gender
    • random_category
    • random_date_of_birth
    • random_phone
    • random_price
    • random_stock
    • random_past_date
    • progress_indicator
    1. Add at the top:
       require_relative 'helpers/data_generators'
       require_relative 'helpers/ui_helpers'
       extend DataGenerators
       extend UiHelpers
    

    Changes Made:

    1. In UiHelpers:
      • Removed the hardcoded print_summary and print_verification methods.
      • Ensured print_summary_box and print_verification_stats are ready to accept data.
    2. In BaseSeeder:
      • Now dynamically creates a summary_data hash with the final record counts.
      • Creates a verification_stats hash with key metrics.
      • Calls UiHelpers.print_summary_box and UiHelpers.print_verification_stats, passing in the data.

    Benefits of this Refactor:

    • More Flexible: You can now easily add or remove items from the summary and verification sections just by changing the hashes in BaseSeeder.
    • Decoupled: The UiHelpers module is now purely for presentation and has no knowledge of your specific models.
    • Cleaner Code: The logic is now clearly separatedโ€”BaseSeeder gathers the data, and UiHelpers displays it.

    Now the BaseSeeder is clean and only contains orchestration logic and configuration. All random data and UI/progress helpers are now properly delegated to the new helper modules.

    We can now use these helpers in any seeder by calling random_gender,  progress_indicator, etc., thanks to the extend statements. This makes our seed system much more modular, maintainable, and DRY!

    That’s a great suggestion for further refactoring. It makes sense to keep all data-related logic, including aggregation for summaries, within the DataGenerators helper.

    Let’s proceed. I’ll move the hash construction into new methods inside db/seeds/helpers/data_generators.rb and then update BaseSeeder to call them.
    Now that the data generation has been moved, I’ll update the BaseSeeder to call the new methods.

    Issues Fixed:

    1. UiHelpers method calls: All seeder files were calling progress_indicator directly, but since the methods in UiHelpers are defined as class methods, they needed to be prefixed with UiHelpers.. Fixed in:
    • UsersSeeder
    • ProductsSeeder
    • CoursesSeeder
    • SchoolsSeeder
    • OrdersSeeder
    • StudentsSeeder
    1. DataGenerators references: The BaseSeeder was trying to call DataGenerators.summary_data and DataGenerators.verification_stats, but that file was deleted. I updated it to use the local summary_data and verification_stats methods that were already defined in the BaseSeeder class.

    Seeding Results:

    • โœ… 10,000 users created
    • โœ… 1,000 products created
    • โœ… 50 courses created
    • โœ… 25 schools created
    • โœ… 5,000 orders created
    • โœ… 5,000 students created

    The verification stats show proper data relationships:

    • 4,004 users have orders
    • 647 products have been ordered
    • 756 products are active
    • 200 students per school (average)
    • 100 students per course (average)

    โœ… Final Architecture:

    1. DataGenerators: Is now responsible for all data-related tasks, including generating random primitive data (random_phone) and creating aggregated summary data (summary_data, verification_stats).
    2. UiHelpers: Is responsible for all presentation logic, taking data as input and printing it to the console in a formatted way.
    3. Individual Seeders (UsersSeeder, etc.): Responsible for the business logic of creating a specific type of record, using helpers for data and UI.
    4. BaseSeeder: The main orchestrator. It knows the correct order to call the individual seeders and delegates all data and UI tasks to the appropriate helpers.
    5. seeds.rb: The single entry point that kicks off the entire process.

    to be continued … ๐Ÿš€