00:00:13.799
Cool, thank you, and thank you all for being here! It's so fun to meet new people within the community and learn new things, or perhaps talk about the things that we all do and love so much.
00:00:20.560
Hopefully, my talk continues to embody that spirit as well. This talk, "Go Pro with POROs," is based on one of the big motivations or emphases I have.
00:00:27.519
I have a penchant for words; I love learning new vocabulary and finding just the right word for a given situation. I genuinely believe in the power of naming things. Being able to communicate clearly ensures that everyone is on the same page, which leads to greater creativity and problem-solving.
00:00:41.239
So, I wanted to put together a talk where I could start providing some names and vocabulary to illustrate how I organize my code and discuss some design patterns I've learned along the way from other developers or have simply explored.
00:00:54.039
I hope that some of these patterns may be familiar to you. Perhaps you have different names for them or even different patterns that I didn’t touch on, and I'd really love to hear about those later if you'd like to come up and chat with me.
00:01:11.799
A little bit more about me: my name is Ifat, and I work at a digital product agency called Launchpad Lab. We are headquartered in Chicago, but we have developers from all over North and South America.
00:01:26.799
This has been a tremendous benefit, as I have learned from many different developers while working in a project-oriented environment, collaborating with diverse teams, learning different styles, and exploring various patterns together.
00:01:40.880
We've had the chance to solve novel problems and observe how some challenges arise repeatedly, allowing us to gain mastery over solutions or even take chances on new explorations.
00:01:54.079
One of the big themes I want to emphasize throughout this talk is the idea of encapsulation. We heard a bit about it in the last talk by Mark, which resonated with me. This concept involves defining boundaries in your code and determining what domain models or aspects of business logic you want to encapsulate and keep in a specific place, making them easier to reason about.
00:02:18.280
This approach allows you to follow the SOLID principles we talked about earlier. You'll see that I will likely bring this up repeatedly in connection to different patterns because, at their core, they are all different ways to encapsulate your code.
00:02:32.080
The second big theme I want to focus on is clarity. There are so many different ways to write Ruby code, which is part of its beauty, but that flexibility can also be a double-edged sword. The guiding principle I often use in my code is: Is this clear for another developer?
00:02:49.879
This consideration includes your future self, be it in six months, twelve months, or five years from now. Can someone looking at this code, whether onboarding or trying to address an unexpected bug, easily find it, understand it, and continue working with it?
00:03:07.479
The final theme I'll keep in mind during this talk is simplicity. It can be enjoyable to devise clever solutions, often resulting in beautifully complex code. However, when working with others—or even your future self—keeping things simple reinforces the first two themes, making life easier for both you and anyone else you might collaborate with.
00:03:43.799
With that said, I'll now touch on several categories of design patterns. I understand the first two might not even fall under the PORO category, but I ask for your patience as they provide foundational concepts that we will build upon—starting from the most familiar to many in this room and advancing toward possibly novel or different ideas.
00:04:03.319
We'll explore database wrappers, modules, services, API wrappers, virtual domain models, and request and presentation objects.
00:04:24.440
Let’s get started with database wrappers. This should be the most familiar concept for everyone here. Database wrappers are classes typically part of an Object-Relational Mapping (ORM) framework, like Rails. Other frameworks may use similar concepts, or you might roll your own.
00:04:54.280
These wrappers provide a user-friendly interface for working with your database tables. Rails specifically offers a nice Domain-Specific Language (DSL) for constructing queries without needing extensive SQL knowledge, as well as for defining relationships, associations, and validations.
00:05:20.400
One pattern I've converged on as I've gained experience as a developer, which was also evident in the last talk, focuses on maintaining a single responsibility for your Active Record models. I've aimed to keep the model classes closely tied to what's relevant to the database.
00:05:43.680
This approach ensures that we avoid the common pattern of cluttering model classes with unrelated logic, which can lead to messy, spaghetti-like code. Instead, I strive to be intentional about where I place that logic, leveraging other patterns we’ll discuss to find more appropriate homes for it.
00:06:05.720
An important point I want to highlight, which may be obvious to many in the room but was an 'aha' moment for me early in my Ruby on Rails journey, is that ORM can indeed be used for any database table across any schema.
00:06:29.159
So while it's a common understanding, recognizing how powerful ORM is was enlightening. Like all tools, we must learn them and use them wisely.
00:06:49.760
As an example, here you can see a standard class for a workout plan table in our database. I've limited it to associations, constraints, and maybe a few defined enum types and some queries with scopes. It also features a simple instance method wrapping a more complex query.
00:07:11.720
One of the key things I want to stress is to be intentional in keeping your model classes focused on database-related tasks rather than drowning them in business logic.
00:07:34.400
Another comparable example is how we can work with various schemas. At Launchpad Lab, we frequently integrate with clients using Salesforce, which often entails utilizing a tool called Heroku Connect. This tool allows us to build Active Record wrappers around Salesforce schemas, treating them just like any other database table.
00:07:51.600
Next up are modules, which are fascinating, and I've been experimenting with them increasingly. In simple terms, a module is simply a collection of code. Namespacing is a common use case for modules, as it bundles code together, making it easier to manage.
00:08:09.360
That being said, a major benefit is the opportunity to assign meaningful names to code segments, which improves readability. I often encourage teams to extract functionality into modules that can be easily named.
00:08:23.239
One thing to note about modules is that while they can't be instantiated, they are incredibly versatile. They can be utilized directly by defining public methods or indirectly by including them in classes. As someone beginning their Ruby journey, you'll likely use modules regularly, particularly for model concerns.
00:08:41.359
When different models share logic, extracting that logic into a module (commonly referred to as a concern in Rails) becomes practical. This also applies to controller concerns that help keep your controllers lean. Helpers for views allow you to separate presentation code from business logic seamlessly.
00:09:03.000
An insightful perspective that my mentor at Launchpad introduced me to is that modules are a fantastic way to apply functional programming principles in Ruby. For instance, when writing a calculator for basic arithmetic operations, it’s a great example of how modules can effectively encapsulate input and output functionalities.
00:09:25.840
Despite their flexibility, it is essential to maintain clarity and consistency in your codebases. A pattern I have leaned into is encouraging consistent naming for modules, which allows developers to quickly recognize their purpose.
00:09:50.960
So, a few examples of modules might begin with a model concern for shared fields across models, a controller concern to handle error notifications or request management, or view helpers to format data before presentation.
00:10:13.680
One instance might involve a workout metrics module that takes in specific attributes and calculates outputs like pace. These methods can be tested independently while providing clarity and organization within your application.
00:10:42.760
Now, I've made a significant oversight in this presentation by repeatedly referring to POROs without defining it. I sincerely apologize; let's clarify that now: a PORO, or Plain Old Ruby Object, is a term used to distinguish any Ruby class not tied to an Active Record class backed by a database.
00:11:00.720
As I previously mentioned, the rest of the categories we'll discuss fall under the PORO umbrella. We'll start with Services, which have generated substantial discussion and diverse opinions recently.
00:11:23.200
My perspective on services which might lean towards a hot take is that while they often become a catch-all term for any PORO performing business logic, I believe we can come up with clearer names and definitions for the logic types we work with.
00:11:41.920
To me, services encapsulate business logic to perform calculations or a series of steps, following a procedural approach. Admittedly, instantiating a class may not leverage the advantages of object-oriented programming fully, but utilizing classes provides convenience in methods and public attributes.
00:12:06.760
In our agency, we tend to create services that expose a single public method, often labeled 'run' or 'call', and we name the class after its intended function. While there’s no right or wrong way to do this, it's a pattern I've adopted for consistency.
00:12:30.480
An example of a service could be a factory that executes a sequence of steps to create objects. For instance, I might create a School Factory that accepts parameters to generate additional objects or trigger notifications as needed.
00:13:03.440
Similarly, another practical example involves interacting with APIs. I once worked on an app where I tweeted my progress during my first half-marathon so I would fetch my logged workout every few miles.
00:13:22.079
This API wrapper service handled the intricacies of fetching data, which could be tailored to facilitate further processing. I ensured to expose an errors variable to inform other components when something went awry.
00:13:46.799
Transitioning to API wrappers, these are reusable classes that help interface with external APIs. They generate a cleaner abstraction for your interactions, which is useful whether the APIs are owned or third-party.
00:14:08.319
When we write API wrappers, we often implement interfaces for making requests, abstracting away some details to provide a more straightforward implementation for API endpoints.
00:14:27.360
These wrappers allow us to handle variations in requests, such as overriding headers or payloads for specific needs. This way, if details need to change, we can handle those adjustments in one place.
00:14:51.840
Next, we can discuss virtual domain models, which can help keep our model class focused on database-centered operations while still representing domain objects that aren't backed by a database.
00:15:12.480
They're often transient, with a focus on immediate use cases without persisting them in a database. This is particularly useful for APIs where we can capture incoming data into virtual domain models for processing.
00:15:34.799
A practical example could involve financial models where we address complex calculations based on prior line items and current values, utilizing POROs to facilitate this pluralism.
00:15:54.240
Finally, we'll cover request and presentation objects that help clarify requests and responses. These POROs allow us to separate concerns, reducing complexity in controllers, models, and views.
00:16:15.520
By adopting this approach, we can document, test, and maintain our code more easily, aligning with established patterns and libraries like presenters, decorators, and form objects.
00:16:38.560
For instance, presenters simply encapsulate presentation logic, while decorators enhance existing objects with presentation-focused methods. Form objects, on the other hand, streamline complex forms by abstracting away interdependencies for ease of use.
00:17:11.199
We can also utilize serializers to format API data as needed for clients, ensuring the right shape is returned. Moreover, transformers can help with incoming parameters to normalize and prepare data ahead of persistence.
00:17:33.680
An example might involve using a workout view component to encapsulate how we present workout data without needing to intertwine that with business logic. This separation promotes clarity and maintainability.
00:18:01.039
With that in mind, I hope these patterns resonate with you or inspire new approaches in your work. I'd be delighted to hear your perspectives or engage in discussions afterward.