00:00:05.240
So you guys can hear me, right? Do you know what this is? It's a camera. Not just a camera, but a shockproof and water-resistant camera. However, it’s also a weapon—a weapon against prejudice. What is prejudice? It’s categorizing or classifying something without having sufficient information about it. That's a somewhat ridiculous definition I made up. But here's the thing: you all have prejudices. I know it, and I can tell you what they are; it’s pretty obvious.
00:00:24.480
It's natural for us to categorize or filter out information. Our brains are designed to analyze things and look for specific indicators, which leads us to assign judgments without knowing much about a person. For example, while you watch me walking around here, your brain tells you, there are people to your right and people to your left. But if I asked you who is right next to you, it might be your mother, but your brain just categorizes them as 'nerds' right? This is because our brains filter out and abstract information.
00:01:12.960
Let’s take an example. You knew there were going to be Germans at this conference, and I know you thought, 'Okay, it’s going to be a fetish, sunburned guy with a beard and a belly, maybe wearing lederhosen, and of course, sandals with white socks.' Do we have any Germans in the audience? I think maybe... well, Constantine isn't here, but if you see him, check out his feet; he's wearing sandals, but he's not wearing white socks, which proves that your prejudice about Germans is wrong! I can put it another way. I was living in Munich for about half a year, and I went out with friends one beautiful summer night. The wind was in my hair, and I was wearing flip-flops, or as you call them, thongs, with short pants. My friends asked me, 'Aren’t you going to wear something warm?' I replied, 'Why? It’s a warm, beautiful night!' So, we went to a bar.
00:02:13.440
Interestingly, when I walked into that bar wearing thongs, two or three people, I can't remember because I was drunk, came up to me and started a conversation in English. I thought, 'What the hell?' in German. It turned out those guys thought I was from Australia. So, this camera you see pointing at you, if you see me taking pictures, it's not to use for pictures all over the world at Ruby conferences. No, it is to bring the truth back to Europe and to fight prejudice!
00:02:37.480
Now, I want you guys to take a look at this picture. It’s a train, terribly loaded with people. By the way, this picture was taken in Australia; it’s traveling from Melbourne to Perth. The main reason for this train's existence is simple: it takes people from point A to point B. However, it might be slightly slow since there are a lot of people. Not to mention, if there's a problem, the air conditioning might be great for those sitting outside, but the maintenance guys will have trouble getting to the problem inside the train because they have to fight through all the people.
00:02:58.600
Also, if there’s a kangaroo on the track, the guy in the front might break his leg. This might not be a cool experience! Whatever. Let’s move on. Are you ready for some coding examples? What does this class have to do with this train? I have no clue, whatever. Let's consider an ActiveRecord-based class where we do different things in it. We also have an after-save hook to send out SMS notifications after saving this very record.
00:04:11.280
All of you have done this before, right? Everyone is looking at the floor right now. So, I used this API; I used this record in my controller, and I called save. Of course, the controller, or maybe the model, would send out SMS notifications to 500,000 users saying they were saved, which was not my intention! The cool thing about Rails is it provides us with all these tools, like testing, Sprockets, ActiveRecord, and best practices. However, a lot of these so-called best practices can lead us to problems once a project becomes complex.
00:04:57.440
We often end up with huge controllers, huge models, and fat views—this is what Google image search looks like. I’m not giving this talk to criticize Rails, just to point out that there are two alternatives: we either disregard Rails and use something else, like Sinatra, or we learn to coexist with Rails and use appropriate tools to modify its default behavior. This talk will discuss that coexistence.
00:05:44.240
Let’s consider our train as our ActiveRecord-based class. I know it’s a stupid analogy, but every person on this train is a feature—like logging, saving, notifications, and so on. So here we return to this class, and now it begins to make sense, right? To manage complexity in your application, we can shift from this monolithic train to an approach where you have hundreds, even millions, of independent units that work autonomously.
00:06:52.160
Imagine if a problem arises on the street. The bicycle can take shortcuts—eliminating the hidden cows or kangaroos. If you experience complexity similar to this train and can no longer manage it, you have to reduce the complexity. This usually works by applying object-oriented techniques. Over the last year, applying object-oriented patterns to Rails has become a hot topic. We need to choose between one monolithic ActiveRecord model or many small, independent classes.
00:07:53.840
The issue is that many people suffer from a sickness I invented: fear of the class. We all know this person; they're from the Rails core team, and they’re fantastic. The patterns I’m going to show you are meant to alleviate that fear of using more classes and more instances in your applications.
00:09:07.280
Let’s start by checking out some patterns for the model layer. I want to split my domain logic from the persistence layer. For illustration, let’s take the scenario of two cowboys sitting around a campfire.
00:09:38.360
Do you have cowboys in Australia? What do you call them? Jack? Whatever they’re called, we have two of them: one named Jim and the other named Steve. All I want to model in my application is a chat with these two cowboys exchanging messages. To achieve this, I’ll use the PORO pattern, which stands for Plain Old Ruby Object.
00:10:13.280
Here’s how this application is supposed to work. I instantiate two cowboys, then I call the same method on Jim, passing the message, and then Steve can respond by calling the respond method with the response content and the question object, effectively modeling the conversation. This all needs to happen without ActiveRecord and database thinking. I’ll use OpenStruct, which automatically provides accessors for the properties of objects.
00:12:21.600
To implement the method on the Cowboy class—responsible for receiving content in the form of a message—I encapsulate that message in a Message object and push it to a chat array, nothing more. To respond, I also need to implement the response method within the Cowboy class, which receives the message and the question. I instantiate a Response object to handle the message.
00:13:41.120
You might wonder, 'Okay, that’s great, but what about persistence?' In our application, we need users to browse their chat history. We can utilize several approaches for persistence, and I will introduce Data Mapper 2. This upcoming object-relational mapper is expected to be available soon. It completely changes how we use persistence in our models.
00:14:39.920
DataMapper allows you to include a resource model module and define persistent fields in your objects, which can be mapped to a database table. Unlike ActiveRecord, it isn’t connected to any specific database or table layout. The cool thing is you can manage your model separately, so your domain logic remains unobstructed.
00:15:32.560
Now, let’s talk about views. Let’s say we have a sidebar in our application. Who’s running a Rails app? Wow, that’s awesome! Who has sidebar elements or dashboards? Are you happy?
00:16:12.640
So, say we have a sidebar with two functional boxes. The top box shows a list of all recent messages from our chat, while the lower box displays chat activity. This works well for simple scenarios, allowing for partial renderings and simple controllers.
00:17:23.120
Now I want to create two different sidebars—a top box displaying recent messages and lower boxes showing the happiness of our chat. I want the pie chart sidebar on three controllers, and the happiness sidebar on one. The traditional approach involves adding logic to decide which sidebar to render. Should we have logic in our views? The answer is no!
00:18:37.520
To eliminate this decision logic, I would use a View Component. There’s a gem available for Ruby developers called Cells. It allows you to call render cells instead of render partial. I’ll handle the sidebar as a separate class, passing necessary values like the chat object. This encapsulates the partial into a cell.
00:20:07.920
We can further improve our views by applying object-oriented techniques to avoid conditional logic. This encapsulation leads to cleaner, more maintainable code. By utilizing inherited views, we can create a base view class and derive new classes from it, overriding only what needs to change in our sidebar functionality.
00:21:31.280
This makes the architecture easier to follow. For instance, if I wanted to represent the activities of chats, I can define a new cell; adjusting it is as simple as overriding what we need from the parent class. This approach helps manage complexity in the application.
00:23:11.280
Typically, a Rails controller can become a big monster. It often works well for smaller applications, but controllers can become overly complicated. Instead of aggregating all data in one controller, we can split logic using the Objectify gem. This gem allows us to break down the complexity of controllers into separate classes, following the single responsibility principle.
00:24:25.920
By defining a policy class for validating input URLs, a service class for processing data, and a responder class for rendering, we can handle complexity more gracefully. This leads to better organized and more testable code. While I haven’t implemented this solution in a project yet, the concept is intriguing.
00:26:10.780
Next, we'll discuss how to implement API endpoints. The campfire example could require an API for listing messages or sending new ones. Typically, APIs are designed to render documents through endpoints, and Rails provides several techniques to facilitate this.
00:28:16.720
By using the SJson for rendering, you can produce documents from models, although it often requires complex configurations. So Active Model Serializer was introduced, offering a more object-oriented style for rendering. Parsing can be done with parameter hashes that Rails automatically parses regard less of the format, simplifying handling of incoming documents.
00:30:41.800
However, there are limitations to separating rendering and parsing. The Representer pattern unifies this by encapsulating all the logic for handling documents. By creating a representer to define the document properties, a single interface can be used for both rendering and parsing.
00:32:14.840
Utilizing a Representer not only simplifies handling of data in both directions but also allows you to easily switch between media formats like JSON or XML without significant restructuring of the code base. This reduces the amount of configuration needed and integrates your APIs as a cohesive unit.
00:34:53.780
In conclusion, the key takeaways from this talk highlight the importance of separating domain logic from persistence through methods like PORO and Data Mapper, using object-oriented techniques in views with Cells, and simplifying controller logic with gems like Objectify. The Representer pattern is an effective way to unify rendering and parsing within APIs. Rails does not strictly force conventional approaches; instead, it allows for incremental changes to improve code architecture.
00:36:55.640
You shouldn't fear using more classes. Your code will improve as a result. Let's embrace the use of object-oriented principles in our Rails applications. Thank you!