Rails Architecture
Writing Modular Ruby Code: Lessons Learned from Rails 3
Summarized using AI

Writing Modular Ruby Code: Lessons Learned from Rails 3

by Yehuda Katz

The video, titled "Writing Modular Ruby Code: Lessons Learned from Rails 3," featuring speaker Yehuda Katz at the MountainWest RubyConf 2010, focuses on the concept of modularity in Ruby applications, particularly within the Rails framework. Katz emphasizes that modularity should not be an initial focus when developing applications. Instead, developers should adopt an iterative approach to gathering requirements based on user feedback rather than relying on predetermined specifications.

Key points discussed include:

- Modularity Timing: Modularity should be considered after initial development. Katz warns against spending excessive time on architecture upfront, suggesting that clarity around modularity often emerges during application evolution rather than a fixed plan.

- Two Approaches to Development: Katz contrasts the waterfall approach of predefined requirements with the agile methodology that prioritizes user feedback. The latter allows for flexibility in development and improvement based on actual use.

- Modularity Principles:

- Eliminate Global States: It is crucial to remove global states early in development to prevent complications and ensure that modules and components are easily testable and manageable. Rails 3's shift to class-level attributes exemplifies this principle by allowing instance-level manipulation without global state interference.

- Embrace Object-Oriented Principles: Katz discusses the advantages of leveraging object-oriented design in Ruby. This enables more efficient caching mechanisms and a simpler interaction model compared to procedural approaches prevalent in earlier Rails versions.

- Avoid Hard-Coding Dependencies: Encouraging the use of modules to encapsulate functionality can foster flexible and clean architecture. This practice supports modifications and enhancements as project needs change.

- Flexibility as Core Principle: The overarching message stresses the importance of adaptability in design to respond to evolving requirements. Katz encourages developers to not constrain themselves with rigid structures that may inhibit future development.

In conclusion, Katz's insights prompt developers to rethink their strategies towards modularity, advocating for an iterative and user-centered approach that evolves with the application’s lifecycle while promoting flexibility and reusability in code.

00:00:15.519 Okay, so what I'm here today to talk about is modularity. There are two possible talks I could have given here. One of them is sort of a general Ruby modularity talk, and that'll be awesome. But what I am giving today is a talk on some case studies in Rails around modularity.
00:00:20.560 I could go into more depth and give more useful techniques, but instead, I’m going to talk about some specific cases that hopefully will be representative of some things that you could do. I write a lot about this stuff on my blog, so if you want more in-depth information about specific techniques, pretty much everything I'm talking about today, I've written long blog posts about. Feel free to check it out.
00:00:32.880 The first thing I would say about modularity is: you ain't gonna need it. What do I mean by that? I mean that basically when you write an application, there are two ways to approach it. The first way to approach it is to start with a bunch of requirements and then build your application, which can take a year. Some people take a shorter amount of time, but they’re still fundamentally taking time to find those requirements.
00:00:57.920 The way that most of us build our apps is by making a basic plan or a basic idea and then, through short iterations, building our app. In this approach, you gather the requirements from feedback on the actual application, rather than upfront. So, again, there are two ways to do this: the waterfall approach, where you first gather a bunch of requirements, decide what your app is going to look like, and then build it, and the agile approach, where you gather your requirements from the app.
00:01:39.520 A lot of people look at agile and think it's just about doing things faster. It's actually different in that it flips where your requirements come from. Instead of gathering requirements from your brain, committee meetings, or grant letters, they're coming from real users telling you things about your application. Ultimately, it comes down to whether you start with requirements or a basic plan. Agile is also not about not planning; you need to know where you're going. You can't start writing something if you don't know what you're doing, so you do need a basic plan.
00:02:40.160 Let’s consider a real example: the Virtual Observatory architecture. This is a group of individuals who built a standards body to put together some standards for astronomical data. There's this whole system with 17 standards being worked on. Their document for creating the body states that the objective is to improve and unify access to astronomical data and services for primarily professional astronomers, but also for the public. That’s the basic plan they should start with.
00:04:12.640 But if we look at what they've actually achieved, it highlights a significant problem. Recent monitoring indicated that as few as seven percent of registered standard services are fully compliant, illustrating that when you start with a bunch of requirements and create huge architectures, you're not getting enough feedback rapidly enough, and your requirements are coming from the wrong place. Agile allows you to gather requirements iteratively based on user feedback.
00:05:04.479 The modularity should be approached later. If you leave this talk thinking modularity is awesome and decide to spend two weeks figuring out the architecture for every application you build, you will likely end up guessing wrong. It isn’t just about being a good enough programmer; it’s about knowing that it would be wrong to start your application by thinking about the architecture from the get-go. That’s something you figure out later.
00:05:51.680 For example, while Rails came to this party too late, it should not have done so in version 0.5 or even 1.0. Fundamentally, the way you build applications is to start with a plan and constantly iterate until you reach a point where the lack of modularity obviously starts to hinder you. Modularity is all about timing.
00:06:10.399 When we discuss modularity, we're really talking about two things: reducing assumptions in order to increase reuse. Many discussions about modularity focus primarily on reducing assumptions. The reason for reducing assumptions, however, is truly to enable reuse. It's easy to overdo it; modularity should not be pursued everywhere without clear reuse cases.
00:06:40.320 Before I explain further, let me provide an example. Consider a class in Ruby, 'Person', which has a bunch of attributes. If I set up attributes with 'hash.new' and decide later I want to switch from a hash to something else, Ruby allows that flexibility. This flexibility can lead to complicated animals over time, such as alias method chains, which can be effective but are not ideal for building large modular systems.
00:07:48.080 Monkey patching modularity in doesn't fundamentally work, yet many insist it does. This is often seen when developers encounter issues with scalability and performance as the application grows. A good approach is to focus on establishing a basic plan and then building your application iteratively.
00:09:11.440 The first rule regarding modularity is to eliminate global states early on. Tests can be a valuable indicator of where global state creates problems, as they highlight the difficulties in writing reliable reusable tests. For example, you often find that hard coding constants leads to situations where swapping them out becomes impossible.
00:09:54.000 In Rails 2.3, we had issues where everything relied on global state for things like routing. If you set up a system where global constants dictate behavior, you risk creating problematic states that are messy and difficult to manage later. By shifting that dependency to a class-based instance level approach in Rails 3, we can manipulate attributes without affecting the global state.
00:10:57.320 Rails 3 introduced class-level attributes that allow manipulation on an instance level. This means that if a class such as 'ActionController' has a 'perform_caching' attribute, each controller can override that setting without impacting the global state, leading to a cleaner, more manageable application codebase.
00:12:20.240 This method also improves testing since you do not have to worry about side effects of global state across multiple instances of controllers; you'll know exactly which instance's setting you're interacting with.
00:13:06.840 As additional instances retained their state, the rigid class attribute system allowed constructors to set specific values only when needed. Understanding this allows developers to create cleaner and more efficient code that respects modularity principles.
00:13:29.520 The second rule is to embrace object-oriented principles. Many assume allocating memory is expensive when creating objects, but in actuality, it’s quite the opposite in a dynamic language like Ruby. Get comfortable with the idea of objects, particularly when it comes to caching and performance. Objects simplify caching processes that would otherwise take a lot of time in procedural code.
00:14:48.240 For instance, in Rails 2.3, the rendering process relied heavily on procedural methods deeply intertwined with file I/O operations leading to inefficient caching mechanisms. By shifting to an object-based system in Rails 3, we allowed many caching processes to be reused through better design simplifying the stack operations.
00:15:26.720 By creating an object that could be responsible for caching throughout the entire stack, we simplified the interactions and made it easier to optimize performance. Allocating objects remains lightweight compared to the performance gains you can achieve by having a structured caching mechanism in place.
00:16:10.080 Third, resist the urge to hard-code dependencies directly in your classes. Instead, look to use modules that promote better structure and flexibility in your codebase. Encapsulating functionality as modules provides powerful tools for method overrides while keeping the central class implementations clean.
00:17:07.600 You can utilize Ruby’s native module features to create extensible designs where class behaviors can be modified without necessitating convoluted logic changes. This promotes a design-oriented approach that is friendly to modifications later on, which is crucial in a collaborative environment.
00:17:58.560 Using Rails Support Concerns is one effective way to manage inclusion of modules and ensure necessary dependencies are included as well. This can mitigate collisions between various modules by responding appropriately to method calls and avoiding unintended blocks.
00:19:00.080 In summary, everything I’ve shared today is about flexibility—don’t box yourself into hard-coded behavior that limits future functionality. Use objects and modules liberally to create a structure that can adapt and respond to changing requirements.
00:20:05.680 I want to emphasize that this approach doesn’t mean rushing into decisions or jumping into complex architectures without foundational knowledge. Each improvement incrementally restructures the foundation of your application.
00:20:40.960 Thank you for your attention. I hope this discussion has inspired you to rethink how you approach modularity in your own Ruby applications, particularly with Rails. Now, I’d love to open the floor to any questions you may have.
Explore all talks recorded at MountainWest RubyConf 2010
+18