00:00:24.480
So starting off this morning is Bryan Helmkamp. Did I pronounce that right?
00:00:30.000
All right, so Bryan. A couple of high points. Bryan currently runs a company called Code Climate. How many people are familiar with Code Climate? Okay, if you're not, check it out.
00:00:36.760
It's a great product. I started using it after RubyConf, after he showed me the demo at RubyConf. We started using it for our teams for the projects that we have.
00:00:43.000
In addition to Code Climate, Bryan is also heavily involved and is one of the lead organizers for Goru. I got clearance from him before using his Wi-Fi.
00:00:51.000
For those of you who aren't aware, Goru is a conference that takes place in New York City. It's a similar single-day, single-track conference. It's well worth the trip and a great excuse to go to New York.
00:01:00.000
Oh, one more thing I wanted to mention is that Bryan is also a Ruby Hero as of 2009. If you're not familiar with the Ruby Heroes program, Google it and check it out.
00:01:08.000
It's a community effort where we recognize about half a dozen members of the community each year for their extensive contributions to what we do and to help grow the community.
00:01:16.000
With that, I'm going to turn it over to Bryan for a discussion that is not about Enterprise Rails, if you saw that on the schedule.
00:01:23.000
Refresh your schedule; this morning we're talking about refactoring ActiveRecord.
00:01:29.000
Is this mic on? Can people hear me? Okay, cool. Thank you, Kobe. That was a very nice introduction.
00:01:35.000
I'd like to begin with a little bit of group therapy. So think back to the last time you were working on a completely greenfield application.
00:01:43.000
There was no legacy code, there was no technical debt. You had a test suite that was comprehensive but ran really quickly.
00:01:50.000
You had a backlog of features, and you were just in your editor, tearing through them. Can everyone think back to the last time they had that feeling?
00:01:56.000
Give me an adjective for how you felt when you were working like that. Just toss one out there.
00:02:02.000
Anybody got one? Fun? Productive? Anything else? What's that? Enabled? Yes, great. Sane.
00:02:09.000
So, the greenfield feeling is great, right? The problem is that as your requirements increase and you start adding developers to the project, and the requirements start changing, it's kind of like having cows enter your greenfield.
00:02:15.000
The problem with letting cows in your greenfield is that after a while they have a tendency to turn a greenfield into a brownfield.
00:02:23.000
Then you're sort of in that position where you feel like you're fighting your codebase. Have you ever had that feeling?
00:02:30.000
Where it feels like you have to change everything just to get one thing done, and your app is your adversary?
00:02:37.000
Then you have to beat it into submission. That's a brownfield.
00:02:44.000
So my name is Bryan Helmkamp. I'm the founder of Code Climate. It's a hosted automated service that reviews the Ruby code in your applications.
00:02:50.000
Today, I'd like to talk about one particular aspect of this problem as it relates to Rails applications.
00:02:57.000
We're going to look at ways to deal with complexity in our model layer and how we might manage that complexity more effectively.
00:03:04.000
This is to prevent our greenfields from turning brown.
00:03:10.000
As a quick warning, this talk is going to move really fast. I'm going to try to cover seven patterns in one anti-pattern.
00:03:17.000
With an intro and an outro in time for questions in about 40 minutes, so if you haven't had coffee yet, God help you.
00:03:24.000
I'd like to begin with a quote: Rails makes it natural and easy to build large, well-designed object-oriented systems.
00:03:31.000
Do you know who said that? I don't think anybody has ever said that, at least not to me.
00:03:38.000
But why is that? I think in that context when someone refers to Rails, they're really referring to the Active Record pattern.
00:03:45.000
So we have to go back to what the Active Record pattern is to understand this feeling.
00:03:52.000
Active Record was documented in Martin Fowler's book, Patterns of Enterprise Application Architecture.
00:04:00.000
If you go back to the source text, he clearly lays out some implications of using the pattern.
00:04:06.000
The biggest advantage is simplicity, and we feel this every day when we create a new model and layer on some quick and dirty CRUD sort of behavior.
00:04:14.000
We can plug it into forms and perform reads and writes from our database.
00:04:21.000
However, Active Records are, by definition, directly coupled to your database schema.
00:04:28.000
The problem with this is if you have a small database schema that isn't expanding all the time, and you restrict yourself strictly to the Active Record pattern,
00:04:34.000
the tools that you end up with to manage complexity in your domain and in your model layer are very limited.
00:04:40.000
If you only have five tables, but you have all sorts of behaviors layered on top of them and only five classes,
00:04:46.000
those five classes are going to become pretty problematic pretty quickly.
00:04:55.000
Sometimes when people talk about object-oriented systems, they use the term 'ravioli code'.
00:05:00.000
This can have a positive or negative connotation depending on who’s speaking or the context.
00:05:06.000
However, I'm going to use the positive connotation here: you have these little individual units of code that are loosely coupled.
00:05:11.000
They're all sort of sliding around in the same bowl, coordinating together to feed you dinner.
00:05:17.000
Rails is calzone code. I believe David Chelimsky coined this phrase.
00:05:24.000
If you look at the picture, you can get the feeling for why this fits.
00:05:31.000
Your Active Records tend to be these big chunks. If you eat more than two calzones, you're just going to feel really sick.
00:05:41.000
Today, we're going to look at ways to turn calzones into ravioli.
00:05:48.000
The problem with calzones is that they can grow into God objects.
00:05:54.000
If I were to look at your applications today, I would be fairly confident that there are at least two God objects in your application.
00:06:05.000
These are probably the user class or account class, whatever you want to call it.
00:06:11.000
If you're in any commerce application, I would bet that the user class and the order class are probably the worst code that you've got to deal with.
00:06:18.000
You probably dread going in there and making changes because who knows what's going to fly out the other side.
00:06:24.000
So how do we solve this? In the past, people have talked about the idea of skinny controllers and fat models.
00:06:30.000
They want their controllers to be limited in responsibility to do one thing and one thing well.
00:06:37.000
Then offload that responsibility to someone else so the controllers are very easy to understand and you don't feel like you have to fight with them.
00:06:44.000
Unfortunately, I think 'skinny controller' as a phrase has done perhaps as much damage as it has done good.
00:06:50.000
There's a clear alternative: skinny controllers and skinny models.
00:06:57.000
Notice the pluralization on models. There’s no reason we can't have the benefits of well-encapsulated objects with limited responsibilities in our model layer.
00:07:04.000
Just like we can have them in our controller layer. On to the patterns.
00:07:11.000
There’s going to be a lot of code in the remaining slides. If you can see if you can read the text on the bottom.
00:07:18.000
If you can't read that, you should pull up the GitHub repository which will have all the code samples in it.
00:07:26.000
It's github.com/codeclimate/refactoring_fat_models. If you don't have a laptop, just maybe you can share over someone's shoulder.
00:07:34.000
I want to begin with an anti-pattern. Whenever the topic of refactoring Active Records comes up, one of the first things people want to understand is this idea of extracting mixins.
00:07:40.000
When I say mixin, I mean Ruby modules.
00:07:47.000
So this would be like if you have a user class that's in a social networking context, and it has a couple of associations and some behaviors around knowing whether it's friends or not.
00:07:54.000
In this case, you might include a friending module or a blocking module.
00:08:01.000
Here's the problem: I don't really like this. I think that the behavior you get when you include modules is akin to inheritance.
00:08:08.000
You're essentially creating complex multiple inheritance hierarchies in your application.
00:08:14.000
What's worse is that you've created a junk drawer system.
00:08:20.000
If your user class has a thousand lines in it, it's kind of like sweeping those lines under five separate rugs.
00:08:27.000
You can't really see it now; it's all split up, but what have you really improved?
00:08:33.000
The worst part is, by having your complexity swept under five different rugs, it becomes harder to identify concepts that can be given first-class names.
00:08:40.000
I've been saying this for a while: any application with an 'app' concerns directory is concerning.
00:08:46.000
Come to find out, Rails 4 generates an 'app concerns' directory by default. I think I'm being trolled.
00:08:54.000
So let's go to the actual patterns. The first pattern is value objects.
00:09:01.000
Value objects are simple, plain Ruby objects whose equality is dependent on their value and not their identity.
00:09:08.000
As such, they're often, but not always, immutable.
00:09:14.000
We can look at an example in the Ruby standard library. You've got, in the Kernel, things like Fixnum and Float.
00:09:21.000
In the standard library, you have classes like URI and Pathname. Those are all great examples of value objects.
00:09:27.000
They layer on nice behaviors.
00:09:33.000
In this case, we have a fat ActiveRecord model.
00:09:39.000
This one's from Code Climate, and it’s Code Climate's representation of a class in your system.
00:09:45.000
We understand information about the classes that you have.
00:09:51.000
We might write a road to the database called a constant, which is for your class definition.
00:09:57.000
Every class gets a rating from A to F based on a cost that we associate with the class.
00:10:03.000
The cost is sort of our estimation of how difficult it would be to fix the smells that we've identified.
00:10:09.000
Based on the cost, the rating could be anything from A to F.
00:10:15.000
Now, the problem with this code is that the first thing I would notice is that there are three methods that all have a repeated word in the name or arguments.
00:10:23.000
That's almost always a telltale sign that you're missing an object.
00:10:29.000
When you start having repeated prefixes, suffixes, or words that travel together in method names,
00:10:36.000
a rating can't exist on its own.
00:10:42.000
The only way to do anything with a rating, like figure out if one rating is higher than another, is to have a constant that has that rating.
00:10:49.000
Then ask if the rating is higher than another constant's rating.
00:10:57.000
A refactoring that you could take here is to introduce a value object.
00:11:03.000
We'll introduce a class which is a Rating and give it a little bit of behavior to start.
00:11:10.000
So here, we've added a simple factory method that gives us a nice place to map between costs and the appropriate rating.
00:11:17.000
We've also defined the 'to_s' method so that if you put it into a string interpolation, it'll come back out just like you want.
00:11:23.000
By using Comparable and the spaceship operator, we now have ratings that can be sorted or compared against one another.
00:11:29.000
We can ask a rating what the next worst rating is.
00:11:35.000
So if you have a B, you want to get back C, C to D, and on.
00:11:41.000
We also have a convenience method for asking if one rating is higher than another.
00:11:49.000
By defining the hash and equals methods, we can use ratings as keys in Ruby hashes, and they work just like you would expect.
00:11:56.000
This is really useful for Code Climate because we do a lot of operations where we need to group different data by rating.
00:12:03.000
We want to show you all of the classes that have a D rating.
00:12:10.000
To initialize that, it's very simple.
00:12:16.000
You just define a method called 'rating' in your constant and use the factory method to produce the correct rating object.
00:12:24.000
If you've seen the ActiveRecord feature called composed_of, it's a macro to basically do the same thing.
00:12:31.000
It layers value objects into your models. I think it might be deprecated in part just because it's so simple to do it yourself.
00:12:37.000
Never be afraid to add a simple method like this that just returns an object.
00:12:43.000
It doesn't even need to return the same instance every time because we've set it up so that if you have two instances of the rating B, they'll always be equal.
00:12:51.000
You just create the object, and if the object passes out of scope, it gets garbage collected and everything's fine.
00:12:58.000
The implications are we now have value objects that can be made comparable.
00:13:05.000
They can be used as hash keys with the 'to_s' method, we can make it easy to interpolate them into strings.
00:13:11.000
They also provide a convenient place for factory methods.
00:13:18.000
I like to use them whenever you've got an attribute that tends to have logic that hangs around it.
00:13:25.000
URL, for example, often need to split out the host from the port.
00:13:31.000
Data clumps are a good example if you ever have pieces of data that travel together in your system.
00:13:36.000
It doesn't make sense to have one piece without the other, like a street number without a street name.
00:13:44.000
That's a good place for a value object.
00:13:50.000
If you find yourself using primitives in lots of places and contorting those objects into doing more complex behaviors, that's a good time to look at value objects.
00:13:57.000
The second pattern is fairly broad.
00:14:03.000
This is the idea of service objects, which are defined as objects that represent standalone operations within your code base.
00:14:09.000
They tend to have a short lifecycle, so I like to instantiate new ones and then use them once and throw them away.
00:14:16.000
Sometimes they are stateless, but if they're not stateless, that state will go away pretty quickly.
00:14:22.000
Let's look at an example. We have a user class that has two different ways that authentication can take place.
00:14:29.000
We can authenticate with a password using the BCrypt library. That might be the algorithm that our sessions controller uses.
00:14:37.000
We also have a method for token-based authentication, perhaps from an API.
00:14:43.000
This method, at the bottom, is necessary, as we need to do a secure compare function when validating the API tokens.
00:14:51.000
This ensures we avoid making ourselves vulnerable to timing attacks.
00:14:56.000
The problem is that the user class already has to include n different strategies for doing authentication.
00:15:03.000
If we wanted to introduce a third way, perhaps with a mobile phone, we're just continuing to expand the responsibility of the user object.
00:15:10.000
This secure compare method is right under the token authenticate method in this example.
00:15:16.000
However, in real life, that's likely buried at the bottom of your private method definitions, maybe 100 or 200 lines from the public interface.
00:15:23.000
It doesn't make sense to say what secure compare means in the context of a user.
00:15:30.000
There's a mismatch there. So how could we do it better? We could introduce service objects for the two different authentication strategies.
00:15:38.000
A password authenticator could use BCrypt to authenticate users that come in with sessions.
00:15:44.000
A token authenticator could securely compare the API tokens with the provided tokens.
00:15:51.000
Now, the secure compare function is still a utility function hanging out at the bottom.
00:15:58.000
At least the correlation between token authentication and secure string comparison is made much more direct.
00:16:04.000
There are no 200 lines of code sitting in between those two things.
00:16:10.000
Of course, they're easy to use in the controller.
00:16:17.000
We can simply initialize a password authenticator with the required user and then call 'authenticate' on the authenticator.
00:16:23.000
Instead of calling a method on the user, what do we get out of this?
00:16:29.000
We achieve simpler models—that's what we want. We avoid callback hell, where we have layers of callbacks coordinating across different objects.
00:16:36.000
It's hard to know what will happen with them.
00:16:43.000
We can also clean up duplication in the controller layer.
00:16:50.000
If you've ever had to add an API to an application, you might end up repeating the logic across the API and regular controller versions.
00:16:57.000
For example, if there are side effects after someone places an order, you can extract those into a service object.
00:17:04.000
That way, you can use it in both controllers.
00:17:10.000
When you do so, it has the advantage of making behavior opt-in instead of opt-out.
00:17:17.000
Your ActiveRecord model maintains its simplicity for interacting with the database.
00:17:24.000
You know what's going to happen, but when you need higher-level behaviors, you can reach for the service objects.
00:17:30.000
I like using service objects in this case.
00:17:37.000
That was an example of having multiple strategies for doing the same thing.
00:17:44.000
If you only have one way to do it and it's particularly complex, you might still want to get it out of the Active Record layer.
00:17:51.000
Similarly, coordinate between multiple models which can be a great reason to use service objects.
00:17:58.000
Perhaps placing an order involves saving credit card information and managing shipping addresses.
00:18:05.000
If you're dealing with external services, you might need to query your payment gateway before allowing an order to go through.
00:18:12.000
If you have ancillary concerns, like logic that runs once a month on a cron job, you wouldn't want to give that logic top real estate.
00:18:19.000
You can push it over to the side.
00:18:25.000
The third pattern is form objects.
00:18:32.000
Form objects deal with coordinating multiple models through one form.
00:18:39.000
If you've done Rails programming for a while, you know this feeling of dread.
00:18:46.000
You get the mockup from your designer, and there are four models on this form. How can we do that?
00:18:52.000
Rails has powerful tools for putting applications together very easily, but it doesn't have good solutions for this.
00:19:00.000
If you've got a form object, you're probably going to be using that in a create or update flow.
00:19:07.000
That's when you're most often dealing with multiple models in a single operation.
00:19:15.000
I like to make my form objects quack like Active Record objects. We'll see how that works.
00:19:22.000
Here's an example: I've seen this sort of thing before.
00:19:28.000
This user class has been extended to support sign-ups that require a username, email address, and company name—all on the same form.
00:19:34.000
When the form is submitted, we create both a company record and a user record.
00:19:41.000
The person added an accessor to the user for the company name and a before_create callback to create the company.
00:19:48.000
This is done before the user is created and assigns the association.
00:19:55.000
Have you guys ever seen code like this? It's a little bit hairy and has a few problems.
00:20:03.000
If the user save fails after creating the company, it's unclear what state your database will be left in.
00:20:09.000
You have to start thinking about when, like, is there a transaction here? What's going to be sent when and when's it going to be rolled back?
00:20:17.000
The company method is what I call a shape-shifter method.
00:20:24.000
Sometimes it returns a company, and sometimes it returns a string.
00:20:30.000
If you just want to create a user but not a company, it's not clear how you would do that.
00:20:36.000
Let's refactor it. We're going to introduce a form object called a signup.
00:20:44.000
When I'm doing form objects, I like to use the Virtus library; it's lightweight.
00:20:51.000
It extends a plain Ruby object with attribute-like behavior.
00:20:58.000
So we can declare attributes such as name, email, and company name.
00:21:05.000
We can also expose an accessor for the user and the company, which will come in handy.
00:21:12.000
Next, we make it quack like an Active Record.
00:21:19.000
We define a save method, just like Active Record. If validation succeeds, we proceed to persist.
00:21:27.000
The answer is very clearly laid out in the signup class.
00:21:32.000
First, we create the company, then create the user hanging off that association.
00:21:40.000
We populate instance variables so any coordinating code can access the created user or company.
00:21:48.000
Because it's similar to Active Record, the signup controller becomes simple.
00:21:56.000
We just instantiate the signup and save it as if it were a model.
00:22:02.000
What do we gain? We're layering the concept of aggregation into a single form.
00:22:09.000
We've limited the responsibility of our Active Records, which is always a good thing.
00:22:16.000
We also have a place to style contextual validation.
00:22:23.000
Most of the time, validations on a signup form might change over the life cycle of your business.
00:22:29.000
When you have validations that are only relevant to signup, now you have a natural place to put them.
00:22:36.000
When to use form objects? Whenever you're aggregating multiple models into a form.
00:22:44.000
If you ever see 'accepts_nested_attributes_for,' I hate this method. This is probably my least favorite method in all of Rails.
00:22:51.000
It's had all sorts of bugs because it's so complex.
00:22:58.000
But you can replace uses of 'accepts_nested_attributes_for' with form objects, and you'll be a lot happier.
00:23:05.000
The fourth pattern is query objects.
00:23:12.000
Query objects encapsulate a single way to query the database.
00:23:19.000
Let's look at an example. This might not be unusual in and of itself, but we have an account imported from an external system.
00:23:26.000
We have to run queries to figure out which accounts are ready to be imported.
00:23:35.000
Additionally, which accounts have attempted to be imported but the process has failed?
00:23:42.000
This requires joins against an imports table.
00:23:47.000
The issue here is you're gumming up your ActiveRecord class with a bunch of SQL.
00:23:54.000
If you have a bunch of these, you end up pushing down the important behavior of the object.
00:24:00.000
What do its instances do? That's the key for objects.
00:24:06.000
This gets pushed way down the file, and I understand the benefit of hopping down to SQL for certain types of queries.
00:24:13.000
But if I'm scanning through a Ruby file and see big blocks of SQL, it throws me off.
00:24:20.000
It begs the question of what is this doing, and why is that there?
00:24:26.000
What are better ways to manage that complexity? If you introduce a query object,
00:24:34.000
you can define a class that is responsible for executing one query.
00:24:40.000
You can initialize it with a relation to gain some benefits.
00:24:46.000
Then you move that query out from the top of your ActiveRecord model into its own object.
00:24:52.000
You provide a find_each method. If you have a background job that needs to page through accounts to import, you just call that with a block.
00:24:59.000
What does this get us? We focus the ActiveRecord on core behavior.
00:25:06.000
We get composable objects, which encourages refactoring.
00:25:12.000
This theme will recur, but I love first-class objects.
00:25:18.000
I don't like class methods; they are difficult to refactor.
00:25:23.000
When you get things into an object, your entire OOP toolbox becomes readily available.
00:25:29.000
You can do composition, inheritance, and introduce state naturally.
00:25:36.000
When would I use this? If I have lots of scopes with subtle variations at the top of my ActiveRecord.
00:25:44.000
If I have complicated scopes that take eight lines to run a query, that's a good case for factoring it into a separate object.
00:25:50.000
If I have rarely used scopes, something that is a simple scope but is only relevant for an ancillary operation in the domain,
00:25:56.000
then maybe it should be in an ancillary file in the codebase.
00:26:04.000
The fifth pattern is view objects.
00:26:10.000
View objects are what I like to use to refer to objects that back up a template or a partial.
00:26:17.000
They might depend on zero, one, or multiple models.
00:26:25.000
I've seen all three cases.
00:26:32.000
Picture LinkedIn, which likes to give you a percentage completion of your onboarding process.
00:26:40.000
LinkedIn also provides a next step; a nice touch, but you don't want to stick that in your user model.
00:26:48.000
You might end up with code like this. The problem here? We've got repeated method prefixes.
00:26:55.000
The onboarding prefix is evident in two methods. More importantly, you have this non-critical concern in the user class.
00:27:01.000
Things you tell users to do are usually hints, not critical logic.
00:27:08.000
Instead of putting that logic into the user, we might introduce a view object called onboarding steps.
00:27:14.000
You can initialize the onboarding steps object with the user and provide it two methods.
00:27:22.000
These methods would return a message for what we should tell the user to do next, and indicate their progress.
00:27:29.000
This allows us to remove the prefix and creates a clear mapping between templates and classes.
00:27:36.000
We now have a clear place to put any logic that might otherwise mix into templates or helpers.
00:27:43.000
What did we accomplish? We achieve simpler templates.
00:27:50.000
If you've read about the two-step view pattern, this is kind of an implementation of that.
00:27:58.000
If you're not familiar with this pattern, it’s one that is often underused.
00:28:05.000
You can avoid excessive use of helpers.
00:28:12.000
And again, you can have first-class objects that encourage composition and refactoring.
00:28:20.000
You can inherit different view objects from one another, and use one inside of another.
00:28:27.000
All of this is difficult if that logic is stuck in an ActiveRecord model.
00:28:34.000
Use view objects whenever you have display logic in the model.
00:28:42.000
If something is clearly only related to presentation, consider moving it to a view object.
00:28:49.000
If it's delivery mechanism-specific, you should do the same.
00:28:55.000
For instance, if you can conceptualize the application differently, consider how relevant it still is.
00:29:02.000
Whenever you see a partial, if it's doing anything non-trivial and doesn't map one-to-one to an object that can encapsulate that logic,
00:29:09.000
consider reaching for a view object.
00:29:15.000
The sixth pattern is policy objects.
00:29:22.000
Policy objects encapsulate a single business rule, focusing on reads.
00:29:29.000
They're about making determinations based on data in your system, not updating that information.
00:29:36.000
Everybody has dealt with this problem at one point or another—deciding which email notifications to send.
00:29:43.000
In this case, we have about four conditions to consider.
00:29:50.000
We need to ensure that emails to the user have not hard bounced.
00:29:57.000
We also need to check that the user's preferences allow for them to receive notifications of a given type.
00:30:03.000
If the notification is project-specific, we make sure the user has opted into those notifications.
00:30:09.000
It’s a complex conditional statement.
00:30:15.000
From my perspective, this important domain concept is disguised as a method, floating in a sea of other user object methods.
00:30:23.000
Knowing which emails are sent to which users is critically important in web applications.
00:30:30.000
So, we can promote this into a first-class object, making it easier to answer questions.
00:30:37.000
This way, when a product owner asks what emails we're sending, you now have an object that provides the answer.
00:30:43.000
In this case, we have an email notification policy initialized with a user, notification type, and project.
00:30:50.000
It determines whether an email should be delivered based on Boolean logic.
00:30:57.000
This provides a clean place to extract methods.
00:31:04.000
We could have extracted a method in the user class, but it creates a public interface far removed from the private methods that compose it.
00:31:10.000
This leads to a hodgepodge of private methods at the bottom of your ActiveRecord.
00:31:17.000
Encapsulating a business rule into one object is nice.
00:31:24.000
First-class objects make it easier to build aggregates.
00:31:32.000
Sometimes you have business rules that layer on top of other rules.
00:31:41.000
Authorization is a common case, where you have fine-grained authorization rules built on top of others.
00:31:49.000
Policy objects are great for complicated reads, like our four-branched conditional.
00:31:56.000
For ancillary reads or rules, consider using them.
00:32:03.000
However, if the read is simple and core to the domain, you might not want to extract a policy object.
00:32:10.000
The ActiveRecord pattern shines with simple behaviors.
00:32:15.000
The seventh pattern is decorators.
00:32:23.000
Decorators are about wrapping another object and layering additional behavior.
00:32:30.000
You create an object that responds to either the entire wrapped object's interface or just a segment.
00:32:36.000
For example, we have an order that needs to send email receipts.
00:32:43.000
However, we only want to send email receipts if the order was placed online by a customer.
00:32:50.000
If the order came in over the phone or through fax, we don’t want to send an email.
00:32:57.000
Here, the programmer has added an accessor to the order record called 'placed_online.'
00:33:04.000
They're using this to determine whether to fire the email receipt callback.
00:33:11.000
The problem is 'placed_online' is a hack—it doesn't really make sense as a value to determine sending the email.
00:33:18.000
This callback only fires sometimes, not a core behavior of the object's lifecycle.
00:33:25.000
To resolve this, we can introduce an OrderEmailNotifier, which is a decorator.
00:33:32.000
It's going to wrap the order; we only add logic to the save method.
00:33:39.000
If the order saves successfully, we want to send an email.
00:33:47.000
In the orders controller, which processes orders from customers, we can separate logic for building orders.
00:33:54.000
If we do need to send emails, we layer on relevant logic.
00:34:01.000
We build up the order and its decorators, which means that from the perspective of the create method, it's still an order.
00:34:07.000
We have a nice separation between arrangement and work.
00:34:13.000
This allows us to wire together objects, while maintaining the core behaviors.
00:34:22.000
We've promoted the concept of emailing receipts into a first-class domain object.
00:34:29.000
Additionally, it makes sending email opt-in instead of opt-out.
00:34:36.000
You're never going to accidentally send an email receipt when writing a cron job that just needs to make a small change.
00:34:43.000
I like using this pattern if you have contextual behavior that is only relevant for some orders, depending on how they were placed.
00:34:50.000
If you have behavior that interacts with external web services, you wouldn't want to place that into a callback.
00:34:57.000
If you've put an external service call into an Active Record callback, you know the pain it leads to.
00:35:04.000
Decorators allow you to deal with that concern.
00:35:11.000
Some people prefer to use decorators in the view layer, but I'm focusing on models today.
00:35:18.000
There are gems like Draper designed to make layering additional behavior in the view layer easier.
00:35:25.000
That covers the seven patterns. I want to leave you with one more concept before we move to questions.
00:35:34.000
You have to use your judgment whenever you're applying patterns like this.
00:35:41.000
If you wait too long to introduce good design in your code base, you'll end up in a brownfield.
00:35:48.000
You’ll feel stuck in mud, finding it difficult to get things done.
00:35:55.000
However, you also don't want to over-engineer from the start.
00:36:02.000
Rails allows us to accomplish a lot quickly, but there is conceptual overhead when introducing new patterns.
00:36:10.000
Ideally, your design and architecture will grow incrementally as the complexity of your application increases.
00:36:17.000
It's not a huge step function where you go from no design to perfectly done design.
00:36:25.000
You want to gradually increase as they correlate together.
00:36:32.000
Think about that when applying these patterns to your application. Thank you.
00:36:39.000
Thank you.