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::Associationsgives youhas_many,belongs_to,has_one,has_and_belongs_to_manyActiveRecord::Validationsgives youvalidates,validate,valid?,invalid?ActiveRecord::Callbacksgives youbefore_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→/postsnew_post_path→/posts/newedit_post_path(post)→/posts/1/editpost_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.