Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
81: Rails MVC Architecture In Depth
Where exactly should the "heavy lifting" logic live?
You've probably heard the mantra "Skinny Controller, Fat Model." When I first started with Rails, I took that literally and shoved every single piece of business logic into my ActiveRecord models. I thought I was being a "good Rails developer," but I ended up with models that were 2,000 lines long and impossible to test without mocking the entire database.
Here is the reality: Models should handle data integrity and basic associations. Controllers should only handle request routing and response orchestration. If you have a process that involves multiple models, external APIs, or complex conditional flows—like enrolling a student in a course, charging their credit card via Stripe, and sending a welcome email—that doesn't belong in either.
In these cases, I use a Service Object. It's just a plain old Ruby class that does one thing. Look at this contrast:
# The "Fat Controller" way (Avoid this) def create @enrollment = Enrollment.new(enrollment_params) if @enrollment.save PaymentGateway.charge(current_user, @enrollment.price) UserMailer.welcome_email(@enrollment.user).deliver_later redirect_to @enrollment, notice: 'Enrolled successfully!' else render :new end end # The "Service Object" way (Do this) def create result = CourseEnrollmentService.call(user: current_user, params: enrollment_params) if result.success? redirect_to result.enrollment, notice: 'Enrolled successfully!' else @errors = result.errors render :new end endBy moving that logic into `CourseEnrollmentService`, your controller stays focused on HTTP concerns, and your service object can be tested in total isolation from the web request.
How does the Controller actually "talk" to the View?
It feels like magic, but it's actually just Ruby's instance variable scope. When a Rails controller action finishes executing, Rails implicitly renders the view template associated with that action. Any instance variable (the ones starting with
@) defined in the controller action is automatically available in the corresponding view.If you define
@courses = Course.allin yourindexaction, theindex.html.erbfile can just loop through@courses. But here is a tip I learned the hard way: don't use the view to "find" data. I've seen beginners do this in their HTML:# In a view file (Bad practice) <p>Instructor: <%= @course.instructor.user.full_name %></p>This creates an "N+1 query" nightmare. The view should be "dumb." It should simply display the data the controller has already prepared. If you need a specific string formatted for the UI, calculate it in the controller or a helper method, and pass the final result to the view.
Why can't I just put a database query inside the View?
Technically, you can. Ruby is Ruby, and the view is just executed in a context where the Model classes are available. But you absolutely shouldn't. If you start putting
Course.where(active: true)inside your.html.erbfiles, you've broken the MVC boundary.Why do I care so much about this? Two reasons: Testing and Maintenance. If your data fetching is buried in the HTML, you can't write a unit test for that logic without rendering the entire page. Plus, if you ever decide to change how an "active" course is defined (maybe now it needs a
published_atdate in the past), you'll have to hunt through a dozen HTML files to find every place you wrote that query.Keep the View as a presentation layer. Its only job is to take the variables the Controller gave it and turn them into HTML. If you find yourself writing complex logic in a view, it's a signal that you either need a better Controller action or a dedicated Helper method.
📋 Practical Task
Refactor the Bloated Enrollment Controller
You have a CourseEnrollmentsController where the create action is doing too much: it validates the user's subscription, creates the enrollment record, updates the course's student count, and sends a confirmation email.
Your Task:
Create a Service Object named EnrollmentProcessor in app/services/enrollment_processor.rb. Move all the business logic (subscription check, record creation, count update, and emailing) out of the controller and into a .call method in this service. Update the controller action to call this service and handle the success or failure response using a redirect or a render.
There are no comments for now.