A JavaScript developer's guide to Rails: Where Does Everything Come From?

As a senior JavaScript developer learning Rails, you’ve probably felt this frustration: you’re reading Rails code and suddenly come across a method like current_user, redirect_to, or belongs_to, and you have no idea where it originated. There’s no import statement. No require. It’s just… there.

Coming from JavaScript’s explicit world of imports and modules, Rails can feel like programming in a house of mirrors. Everything seems to appear out of thin air. This magical appearance was one of my biggest struggles too, so I want to demystify where everything actually comes from.

The Fundamental Difference: JavaScript vs Rails

In modern JavaScript, if you want to use something, you import it:

import { useState } from 'react';
import express from 'express';
import { getUserById } from './services/users';

It’s explicit. You can command-click on the import and jump to the source. Beautiful.

In Rails, you write:

class User < ApplicationRecord
  has_many :posts
  validates :email, presence: true
end

Wait… where did has_many come from? Where’s validates? There’s no import statement anywhere. Welcome to Rails “magic.”

Understanding Rails’ Philosophy: Convention Over Configuration

Rails operates on a principle called “Convention over Configuration.” The framework makes assumptions about what you need and automatically loads it for you. This automatic loading is the opposite of JavaScript’s explicit nature, but once you understand the patterns, it becomes predictable.

The Big Sources: Where Rails Methods Actually Come From

Inheritance

class ApplicationController < ActionController::Base
end

class UsersController < ApplicationController
  def index
    redirect_to root_path  # Where does redirect_to come from?
  end
end

redirect_to comes from ActionController::Base, which ApplicationController inherits from. This is similar to JavaScript class inheritance, but Rails takes it much further.

Mixins

Rails automatically includes specific modules based on what you inherit from. When you inherit from ApplicationRecord, Rails includes dozens of modules:

class User < ApplicationRecord
  belongs_to :organization  # From ActiveRecord::Associations
  has_many :posts          # From ActiveRecord::Associations
  validates :email, presence: true  # From ActiveRecord::Validations
  before_save :normalize_email      # From ActiveRecord::Callbacks
end

These methods come from modules mixed into ActiveRecord::Base (which ApplicationRecord inherits from). This means every model you create automatically gets these methods:

  • ActiveRecord::Associations gives you has_many, belongs_to, has_one, has_and_belongs_to_many
  • ActiveRecord::Validations gives you validates, validate, valid?, invalid?
  • ActiveRecord::Callbacks gives you before_save, after_create, around_update, and more

In JavaScript terms, it’s like if all your model classes automatically got Object.assign‘d with a bunch of utility methods.

Using Mixins Manually

You can also create and use your own mixins. Here’s an example showing the difference between include and extend:

module Taggable
  def add_tag(tag)
    # instance method
  end

  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def find_by_tag(tag)
      # class method
    end
  end
end

class Post < ApplicationRecord
  include Taggable  # Makes add_tag available as an instance method
                    # and find_by_tag available as a class method
end

# Now you can use:
post = Post.new
post.add_tag("ruby")        # instance method from include
Post.find_by_tag("rails")   # class method from extend (via ClassMethods)

include adds module methods as instance methods of the class, while extend adds them as class methods.

Helper Methods

In your controllers, you get methods like:

class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id])  # Where's params from?

    if current_user.admin?  # And current_user?
      render :admin_view
    end
  end
end

params and render both come from ActionController::Base. current_user probably comes from a gem like Devise, or you defined it in ApplicationController.

View Helpers

In your views, you have access to helper methods from ActionView::Helpers:

<%= form_with model: @post do |f| %>
  <%= f.text_field :title %>
  <%= link_to "Back", posts_path %>
  <%= f.submit %>
<% end %>

Rails automatically includes helper modules from ActionView::Helpers into ActionView::Base, which all your views inherit from. So form_with comes from ActionView::Helpers::FormHelper, link_to comes from ActionView::Helpers::UrlHelper, and dozens of other helpers are available without importing anything.

Custom Helpers

When you create helpers in app/helpers/, Rails automatically makes them available to views:

# app/helpers/posts_helper.rb
module PostsHelper
  def formatted_post_date(post)
    post.created_at.strftime("%B %d, %Y")
  end
end

Rails automatically includes all helper modules in your views. So in any view, you can call:

<%= formatted_post_date(@post) %>

The magic happens because Rails includes all helpers in the base view class. You can see this if you check ActionView::Base.included_modules - it includes all the helper modules from app/helpers/.

Dynamic Methods

Dynamic methods are where Rails goes full wizard mode. Some methods are created at runtime based on your database schema:

user = User.new
user.email = "dev@example.com"  # email= doesn't exist in your code!
user.save

If your users table has an email column, Rails automatically creates:

  • email (getter)
  • email= (setter)
  • email? (boolean check)
  • email_was (previous value)
  • email_changed?

These methods are generated dynamically when Rails reads your database schema. In JavaScript, this would be like using Proxy objects to intercept property access, but Rails does it with metaprogramming.

Route Helpers

When you define routes:

# config/routes.rb
resources :posts, only: [:index, :show, :create, :update, :destroy]

Rails automatically creates helper methods:

  • posts_path/posts
  • new_post_path/posts/new
  • edit_post_path(post)/posts/1/edit
  • post_path(post)/posts/1

These are generated based on your routes file. Coming from React Router or Express, this feels weird because there’s no central place you import these from.

How to Actually Find Where Things Come From

Use method and source_location

In a Rails console, you can inspect any method:

User.method(:has_many).source_location
# => ["/path/to/gems/activerecord-7.0.0/lib/active_record/associations.rb", 1234]
# Class methods point to where they're explicitly defined

user = User.first
user.method(:email).source_location
# => ["/path/to/gems/activemodel-7.0.0/lib/active_model/attribute_methods.rb", 273]
# Dynamic attribute methods point to Rails' metaprogramming machinery

Check the Inheritance Chain

User.ancestors
# => [User, ApplicationRecord, ActiveRecord::Base, ...]

This shows you all the classes and modules mixed in. Each one could provide methods.

Search the Codebase

Unlike JavaScript, searching for where something is defined can be tricky. You can search for:

  • def method_name (instance method)
  • def self.method_name (class method)
  • delegate :method_name (delegation)
  • attr_accessor :method_name (attribute accessor)
  • Method name in gems (check your Gemfile)

Search Your Code vs Gems

You’ll often need to search in different locations depending on where the method is defined:

# Search YOUR codebase (app/, lib/, config/)
rg "def current_user" app/

# Search inside GEMS using bundle show
rg "def redirect_to" $(bundle show actionpack)

Use Rails Documentation

When you encounter an unfamiliar method, Rails has two main documentation sources:

Rails Guides - Start here when you’re learning. They provide context, examples, and explain the “why” behind features. Great for understanding concepts.

Rails API - Use this when you know what you’re looking for and need precise method signatures, parameters, and return values. Better for reference once you’re more experienced.

Common “Where Did That Come From?” Examples

Controllers

class PostsController < ApplicationController
  before_action :authenticate_user!  # From Devise gem
  before_action :set_post, only: [:show, :edit]  # before_action from ActionController::Base

  def index
    @posts = Post.all  # Post constant is auto-loaded from app/models/post.rb
  end

  def show
    # @post set by before_action
    respond_to do |format|  # respond_to from ActionController::Base
      format.html
      format.json { render json: @post }  # render from ActionController::Base
    end
  end

  private

  def set_post
    @post = Post.find(params[:id])  # params from ActionController::Base
  end
end

Models

class User < ApplicationRecord
  # Where do these methods come from?

  belongs_to :organization    # From ActiveRecord::Associations
  has_many :posts            # From ActiveRecord::Associations

  validates :email, presence: true, uniqueness: true   # From ActiveRecord::Validations
  validates :username, presence: true                  # From ActiveRecord::Validations

  before_save :normalize_email  # From ActiveRecord::Callbacks

  def self.active
    where(active: true)  # From ActiveRecord::QueryMethods
  end

  def full_name
    # first_name and last_name are dynamic methods created from database columns
    "#{first_name} #{last_name}"
  end

  private

  def normalize_email
    # self.email= is a dynamic method created from the email database column
    self.email = email.downcase.strip
  end
end

All these methods come from modules that ActiveRecord::Base includes:

  • Associations: belongs_to, has_many, has_one, etc.
  • Validations: validates, validate, errors, etc.
  • Callbacks: before_save, after_create, around_update, etc.
  • Query methods: where, find, find_by, order, etc.
  • Dynamic attribute methods: Created from your database schema

Views

<%= form_with model: @post do |f| %>
  <%= f.text_field :title %>
  <%= f.submit %>
<% end %>

<%= link_to "All Posts", posts_path %>

form_with comes from ActionView::Helpers::FormHelper, link_to comes from ActionView::Helpers::UrlHelper (both automatically included), posts_path is a route helper generated from your routes file, and @post is an instance variable set in the controller that Rails passes to the view.

The JavaScript Mental Model Shift

In JavaScript/TypeScript, you think: “I need this function, so I’ll import it.”

In Rails, you think: “I’m in a [controller/model/view], so I probably have access to [these categories of methods].”

It’s less about tracking individual imports and more about understanding the context you’re in:

  • In a controller: You have access to params, session, redirect_to, render, etc.
  • In a model: You have access to validations, associations, callbacks, query methods
  • In a view: You have access to all view helpers, URL helpers, and instance variables from the controller

Pro Tips for JavaScript Developers

Keep the Rails API docs open: api.rubyonrails.org is your friend. When you see a mysterious method, search it there.

Use an IDE with Ruby/Rails support: VSCode with Ruby LSP can jump to method definitions, even in gems.

Read your Gemfile: Many mysterious methods come from gems. Devise gives you authenticate_user!, Pundit gives you authorize, etc.

Learn to love the magic, but prefer explicit code: Initially, Rails’ implicit nature feels uncomfortable. While Rails provides lots of implicit behavior, thoughtbot’s approach emphasizes being explicit when it improves clarity.

Think in layers: View → Controller → Model. Each layer has its own set of available methods.


The “where does this come from?” problem in Rails is real, and it’s jarring coming from JavaScript’s explicit world. Rails’ magic is predictable magic. Once you learn the conventions and common patterns, finding where things originate becomes straightforward.

Here’s the payoff: once you understand Rails conventions, you can jump into any Rails codebase and immediately feel at home. The controllers work the same way. The models use the same methods. The patterns are consistent. Compare that to JavaScript, where every project can have a completely different architecture, folder structure, and set of conventions. You’ve probably experienced the frustration of joining a new JavaScript project and spending days just figuring out where everything lives and how it’s organized. Rails eliminates that problem. Learn the conventions once, and they apply everywhere.

JavaScript makes you explicitly import everything, giving you total clarity but more boilerplate. Rails hides the imports, giving you less boilerplate but requiring you to learn the framework’s conventions. As a JavaScript developer, you’re used to being explicit. Rails asks you to trust the framework. Give it time, and the magic becomes second nature.

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.