RailsConf 2013

Building Extractable Libraries in Rails

Building Extractable Libraries in Rails

by Patrick Robertson

In the presentation titled "Building Extractable Libraries in Rails" at Rails Conf 2013, Patrick Robertson discusses the intersection of Ruby developers' creative instincts and the structured conventions of Rails. He emphasizes the importance of maintaining a well-organized /lib directory to prevent it from becoming a disorganized space filled with random code snippets. To achieve this, he introduces several conventions aimed at enhancing code quality and facilitating future gem extraction.

Key Points Discussed:

  • Philosophical Foundations:

    • Rubyists often work under the influence of both creative freedom and strict conventions imposed by Rails.
    • The /lib directory represents this tension, encouraging both flexibility and structure in coding practices.
  • Conventions for the /lib Directory:

    • Treat it as a Temple:
    • Keep the directory organized so that components can be easily extracted to a gem when needed.
    • Avoid Auto-loading Everything:
    • Ensure every piece of code in the /lib has explicit entry points, facilitating better management and future extraction.
    • Configuration Management:
    • Separate sensitive credential information from library code to improve security and maintainability.
    • Domain-Centric Architecture through DCI:
    • Utilize the DCI (Data, Context, Interaction) pattern to keep business logic separated from external dependencies, ensuring that core application logic remains clean and focused.
  • Examples and Case Studies:

    • Robertson uses a hypothetical feature request involving patient data updates and Twitter integration to illustrate DCI. He demonstrates how to effectively separate the core logic (patient BMI updates) from concerns related to external service (Twitter API handling).
  • Testing Methodology:

    • Emphasizes the use of method roles in testing library code effectively without being dependent on the database, enhancing the testing process for business logic added to plain data objects.

Conclusion and Takeaways:

Robertson concludes by urging developers to respect the structure of their /lib directory, implement effective configuration practices, and protect domain interactions to maintain a clean and manageable Rails application as they scale. He highlights that treating /lib as an organized space will facilitate easier extraction of gems and ensure efficient coding practices in Ruby on Rails development.

Overall, this talk provides practical insights for Ruby developers seeking to optimize their library management and maintain clean, efficient, and scalable codebases.

00:00:12.259 Thank you.
00:00:15.920 I'm Patrick Robertson. You can find me on Twitter. I'm glad that all of you made it to the last session of the first day. It's been a long day, but thank you for visiting me. I work at a healthcare company called Iowa Health. It's a little hard to explain; just think of it as a little bird native to Sri Lanka.
00:00:29.760 I've been working at a slightly different company than many of you. Instead of being a technology-focused company, we actually provide primary medical care, and I'm there to help support these people in what they do. It’s a fantastic role. I could talk to you for 40 minutes about changing healthcare, but you’re here for a technical talk, so I’m going to stop there. If you want to talk about it later, I’d be happy to do so.
00:00:46.260 In my spare time, I co-organize Boston Ruby, which meets on the second Tuesday of every month. We have a lot of fun, so come to Boston and see us! Also, in October, Boston will host its very first RubyConf, called Wicked Good Ruby. Tomorrow is the last day for early bird tickets, and I believe they’re almost sold out. The Call for Proposals is still open, and I promise that Wicked Good Ruby is going to be a good time. I’m not actually from Boston originally, so I can’t say that non-ironically.
00:01:07.740 Now, before I delve into helping you build better lib directories in Rails, I think I need to clarify a bit of philosophy. I apologize for starting off with philosophical musings at the end of the day. However, I think Ruby and Rails developers share a dual nature. One can even see it in our framework's name: Ruby on Rails. We have our inner Rubyist, driven by love and a sense of beauty, and our inner Railsist, driven by strong conventions and guided by the Rails Way.
00:01:39.360 The Rubyist within us loves the elegance of Ruby. We aim for what we consider beautiful solutions. Over time, our best practices and our views on beauty evolve. The Ruby code I wrote four years ago that I thought was beautiful now seems hideous to me; I’d probably throw it away and start anew. This diversity leads to a wonderful ecosystem with many solutions to the same problems, which is fantastic as it allows us to pursue our goals however we see fit. Yet, to be web developers and Rails developers, we need to get things done. Rails is driven by conventions, laying down a clear path to help us navigate the vast possibilities of Ruby. It helps us avoid tedious decisions during the request-response cycle.
00:02:35.800 Some argue that Rails frees us from unnecessary choices, while others claim there’s a benevolent dictator—Rails—making these decisions for us. The important takeaway here is that this freedom from trivial decisions allows us to be remarkably productive. Earlier today, DHH discussed how Struts died due to a lack of productivity. We can accomplish in 15 minutes what many Struts developers could only do in a day. The conventions and structure provided by Rails empower us to be efficient. Why do I find this significant? I believe the lib directory is where inner Rubyists and Rails developers clash. You essentially have the freedom to do anything within it, yet based on my nine years of Rails development experience, I’ve seen teams wander off track. Jumping from project to project can be challenging.
00:03:49.680 At the end of the day, I think we can establish some conventions for how we think about our lib directory. I won’t preach about object-oriented purity or any lofty architectural concepts. My goal in discussing conventions and ideas is straightforward: I want developers to ship more code, enhancing productivity and enabling the extraction of gems. This isn't just a theoretical standpoint; it’s about practical effectiveness. The first and simplest convention I suggest is namespacing. It’s essential to namespace everything you're going to extract into a library. Each application usually has a user library, and your external application may also need a user model, so avoiding naming collisions is crucial.
00:05:03.660 The second point I’d like to emphasize is avoiding the auto-load trap. I advocate for ensuring that every piece of code in your lib directory has an explicit entry point that is controlled and manageable, allowing for easier extraction later on. Lastly, separate your configuration from the implementation details of your credentials; keeping these intertwined is always messy. You want to isolate the interaction with your domain from the delivery mechanisms. Your users don’t care about these internal details—they just want their needs met.
00:05:38.700 Let’s delve into the first convention: namespacing. There are two critical reasons for this. First, collisions with your top-level namespace can lead to confusion and mistakes. We should be free to name things similarly, allowing for clear organization. Secondly, in an unstructured lib directory, you may see classes interacting with external services, such as Twitter. You might end up with a series of classes prefixed or suffixed with 'Twitter'—this is known as Smurf typing. Just like the Smurfs, where every character has "Smurf" in their name, this redundancy is unnecessary and cumbersome. Instead, by creating a Twitter module, you could have your Twitter-related classes without having to clutter your naming. Consider avoiding collisions by namespacing rather than prefixing or suffixing your models.
00:07:20.670 Next, please avoid the auto-load trap. In Rails 2.3, any file under the application root was auto-loaded. However, Rails 3 changed that, and applications were required to manage their own loading. This shift caused many applications to break as developers returned to Stack Overflow for solutions. One recommended fix involved configuring the application to explicitly require lib files, but I believe that’s not the right path. Instead, try utilizing the initializers directory. You can create a Ruby file in the initializers directory to require your lib directory and manually set up requires for all the files, subfolders, and namespaces.
00:08:17.820 When you start this process, it may take only 10 seconds to set up a proper requires path. However, as your application grows to include more classes, it becomes increasingly cumbersome to manage these requires manually. If you find yourself having problems with this as your directory scales, take a moment to re-evaluate how you manage your lib directory right from the start. Now let’s talk about keeping your credentials separate from your library code. It’s too easy to stake secrets, like your OAuth key for Twitter, directly into your codebase, which leads to potential security issues. The conventional advice is to use environment variables, but managing multiple environments can become chaotic.
00:09:07.980 Instead of hardcoding your credentials, leverage the entry point we discussed earlier. In your initializers directory, you can create a configuration class that enables you to inject credentials dynamically, keeping them separate from your core logic. This clear separation simplifies testing—allowing for easier credential management without compromising your application’s integrity. By doing so, your library code remains clean and maintainable, free from clutter.
00:10:23.140 Finally, let's focus on safeguarding interactions with your domain. Take the scenario of building an electronic medical records software; my primary focus is always on the patient. This means anything unrelated to patient care is of lesser importance. Let’s imagine a feature request from a product manager to tweet about a patient’s BMI whenever it’s updated. While a patient’s BMI may seem harmless, exposing patient health information can result in significant liabilities.
00:11:11.880 Building a platform to tweet updates may expose unnecessary details about a patient's health that should never be broadcasted. For each task your application performs, consider the impact on patient health data privacy. Focus on business logic: separate the action of tweeting from the core functionalities of your application. A good architectural pattern to implement here is DCI, which stands for Data, Context, Interaction.
00:12:56.040 DCI helps distinguish between your domain logic and your interaction logic. It separates the concerns of handling crucial business functions, like managing patient information, from the auxiliary tasks that might clutter your primary functionality. Implementing DCI can streamline your codebase while ensuring that critical business rules are upheld without interference from external concerns. This approach becomes particularly useful when building gem-based solutions, facilitating smoother maintenance and updates to your code as the project evolves.
00:15:02.160 When it comes to testing your code, you'll want clarity. Focus on creating impactful test doubles to mock the behavior of your domain objects, allowing you to isolate and verify functionality without the overhead of unwanted interactions with external systems. Opt for creating simple moq classes to return data specific to your tests without the complexity of an operational database.
00:16:25.560 DCI’s iterative process offloads operational logic into a separate context—this simplifies both the implementation and testing phases. Ensure your unit tests are designed to validate the interactions expected of your objects while keeping integration tests for those end-to-end scenarios, verifying that everything works in harmony across your application.
00:18:15.780 Let’s link this process back to your application via controller actions. Controllers should be designed to cleanly orchestrate operations without delving into the minutiae of external service calls. Send Twitter updates through a background job to avoid blocking your primary request-response cycles, providing smooth user experiences.
00:19:41.280 Moreover, emphasize on preserving user privacy and security while performing these operations. Ensure that sensitive patient information remains protected during interactions with third-party services. Effective validation and hygiene in your code structure, will keep unnecessary complications at bay while ensuring the integrity of your application. Research other patterns that might be suited for your application's needs, like presenters or decorators, to help you improve the data presentation and interaction layers.
00:21:08.940 To recap what I've discussed, treat your lib directory with respect, positioning it as a component ready for extraction rather than just a junk drawer. When working with Rails, think about how your design choices can enable easy extraction into gems, which ultimately enhances your application's maintainability. Prioritize configurational cleanliness, keeping hardcoded credentials away from your codebase and utilizing initializer setups for cleaner context management. Do protect your domain interactions, ensuring that your business logic aligns closely with the primary functions of your application, keeping our users’ sensitive information protected. Evaluate your design choices thoroughly and stay open to adopting effective strategies such as DCI to maintain clarity and focus within your application architecture.
00:24:51.840 Thank you! It has been an absolute pleasure discussing these concepts with you all. I hope you find them useful in your future endeavors with Rails.