Talks

Summarized using AI

The Command Pattern in Ruby

Annie Kiley • October 31, 2023 • online

In this video, Annie Kiley discusses the Command Pattern in Ruby, emphasizing its importance in object-oriented programming and showcasing its practical applications through real-world examples.

Key Points:

  • Introduction to the Speaker and Context: Annie Kiley begins by sharing her background in software development and her current role at Depbook, which develops financial software primarily in Ruby on Rails. She mentions her consulting experience and interest in Ruby, leading to her choice of the Command Pattern as a topic for discussion.

  • What is the Command Pattern? The Command Pattern is introduced as a means to turn a method into a class while maintaining a predictable and reliable structure. This pattern enables developers to manage inputs, perform validations, and standardize error handling more effectively than traditional methods.

  • Basic Example Using Ruby: Kiley outlines a simple Ruby code example where a House and a Kid are instantiated. She demonstrates the trick-or-treat method that allows a kid to take candy from a house. The method is then enhanced by converting it into a class (TrickOrTreat), illustrating how the Command Pattern helps maintain structure and organize code.

  • Advantages of the Command Pattern: Upon demonstrating the basic example, Kiley discusses the advantages of using the Command Pattern:

    • Improved management of inputs
    • Enhanced input validation
    • Clear output management
    • Consistent error handling
  • Integration with Ruby on Rails: Kiley emphasizes her favorite use of the Command Pattern in Rails controllers, particularly when using the ActiveInteraction gem. She highlights how this gem can manage inputs, run validations, and return standardized error messages in a Rails application. An example of a Candy controller is presented, showcasing the transition from a standard controller to one that utilizes the Command Pattern with input filtering and validation.

  • Real-World Use: In her example, when filtering candies based on created dates, Kiley demonstrates how the Command Pattern simplifies adding new features without complicated changes to the existing controller structures.

  • Conclusion and Recommendations: Kiley concludes by recommending the ActiveInteraction gem for anyone interested in implementing the Command Pattern in their projects. She underlines that while this pattern may add complexity upfront, it provides long-term benefits in managing increasingly complex Rails applications.

The video effectively illustrates how adopting the Command Pattern can significantly enhance the maintainability and clarity of Ruby on Rails applications.

The Command Pattern in Ruby
Annie Kiley • October 31, 2023 • online

Let's talk about the command pattern and how it can be useful in object-oriented programming. We'll briefly go over what it is and using it with Ruby. Then we'll look at some real-world examples using the ActiveInteraction gem in a Ruby on Rails application.
https://www.wnb-rb.dev/meetups/2023/10/31

WNB.rb Meetup

00:00:00.840 uh my name is Annie Kylie and I'm going
00:00:03.040 to talk about the command pattern in
00:00:07.120 Ruby all right uh Happy Halloween if you
00:00:11.400 celebrate it or live somewhere that
00:00:13.040 celebrates it um this is my family last
00:00:16.440 year at Halloween um because went went
00:00:20.560 with the theme today um but my name is
00:00:23.240 Annie uh I work at dep book um we are we
00:00:26.160 make Financial software and it's a big
00:00:28.279 old rails app with uh act too so I'm
00:00:31.560 doing this stuff every day um full
00:00:34.320 disclosure I've been working there
00:00:36.680 for one week and two days um so this is
00:00:40.399 actually not most of my experience um
00:00:42.760 for the last seven years I've been
00:00:45.039 working at viot which is a digital
00:00:46.879 agency um so I did lots and lots of
00:00:50.160 different kinds of projects for lots and
00:00:51.520 lots of different kinds of
00:00:53.800 clients um before that I actually worked
00:00:56.000 at two other agencies so I've been a
00:00:57.600 consultant for almost a decade um this
00:01:00.039 is my first like in-house product job um
00:01:04.119 but the nice thing about Consulting is
00:01:05.680 you get to do a lot of a lot of
00:01:07.119 different things and see what kind of
00:01:09.240 patterns end up being useful in a lot of
00:01:11.200 different places um and so that's why
00:01:14.799 I'm talking about the command pattern
00:01:16.520 today um it's one of my favorites and
00:01:18.280 it's come in useful for me um in a lot
00:01:20.479 of different
00:01:21.720 scenarios um also I love Ruby um I've
00:01:25.040 been I've done work in a bunch of
00:01:26.200 different languages and I I just feel
00:01:29.479 like is the most fun as a developer um
00:01:32.240 so I'm happy to be here talking to you
00:01:34.920 all all right what is the command
00:01:37.920 pattern um it's
00:01:40.399 a technical pattern and if you Google it
00:01:43.799 there's a lot of blog posts and a lot of
00:01:45.520 wik there's a Wikipedia article and a
00:01:47.000 whole bunch of information and I am not
00:01:49.119 going to talk about any of that today uh
00:01:51.159 we only have 15 minutes so I'm just
00:01:52.640 going to talk about it from a kind of
00:01:54.880 practical perspective um the idea here
00:01:57.880 is to introduce you to the concept
00:02:00.520 and if you want to kind of mess around
00:02:01.960 with it um that that's something that
00:02:04.640 hopefully you'll want to do on your own
00:02:06.360 after this so for our purposes today the
00:02:10.280 command pattern is a little bit of magic
00:02:13.080 that turns a method into a
00:02:16.239 class and we'll take a look at what that
00:02:19.000 means in just a
00:02:20.680 second but not just any class a class
00:02:23.599 with a predictable structure um and that
00:02:26.560 part's really important um you know if
00:02:29.360 we all do object-oriented programming
00:02:31.599 and you know you can make a class or a
00:02:33.560 method do basically anything you want
00:02:35.319 especially with Ruby with all its meta
00:02:36.800 programming you can you know the there's
00:02:39.519 infinite uh infinite options here but
00:02:42.280 the idea here is to really provide a
00:02:45.840 reliable structure um that lets you do
00:02:49.120 more um with the things you're trying to
00:02:51.959 do with your
00:02:53.640 app I realize that's all a little
00:02:55.519 abstract that's why I skipped all the
00:02:56.920 details we're just going to dive right
00:02:58.519 into to talking about it then hopefully
00:03:00.159 have some time for
00:03:01.480 questions all right here we
00:03:05.599 go um this is just a little bit of Ruby
00:03:08.000 code and we don't need to go in into it
00:03:12.000 too far but all we're doing is creating
00:03:13.720 an object called a house and a house has
00:03:16.200 a certain amount of candy for Halloween
00:03:19.599 um and then I'm just instantiating uh an
00:03:21.840 instance of a house so my neighbor's
00:03:23.840 house has 500 pieces of
00:03:26.040 candy um similarly we're just making a
00:03:28.799 class called kid kids also have a
00:03:30.879 certain amount of candy on Halloween and
00:03:32.879 we're going to talk about a kid called
00:03:34.439 Adam and he has zero candies to start so
00:03:37.760 all you really need to know is that this
00:03:40.280 this house instance exists Neighbor
00:03:42.200 House they' got 500 candy and Adam
00:03:44.959 exists and he has no
00:03:46.720 candy and down at the bottom we're just
00:03:49.080 kind of printing it so we can see what's
00:03:50.360 going to happen and when we run this
00:03:53.640 code it'll print
00:03:55.959 this uh my neighbor has 500 treats left
00:03:59.360 and has zero pieces of
00:04:02.040 candy uh let's add a method um so all of
00:04:06.519 this was just sort of set up let's add a
00:04:08.360 method so this is the same code we saw
00:04:10.560 before except down kind of in the middle
00:04:12.439 you've got a trick-or treat method where
00:04:14.840 a kid goes to a house and the house
00:04:17.959 loses a piece of candy and the kid gains
00:04:19.959 a piece of candy and then I'm actually
00:04:22.079 running the so I'm just defining the
00:04:23.479 method first and then I'm actually
00:04:24.600 running it and then again I'm just
00:04:26.000 printing out the results so if I run
00:04:29.360 this file
00:04:32.400 now because I ran the trick-or treat
00:04:35.759 method The Neighbor House lost a piece
00:04:39.240 of candy and Adam gained a piece of
00:04:40.560 candy
00:04:43.720 great so we're not going to worry about
00:04:45.880 the setup stuff anymore um I'm just
00:04:47.880 going to talk about the method from here
00:04:49.240 on out um so that that that code is
00:04:51.560 still there it's still running but uh
00:04:53.840 it's it's not super important so we're
00:04:55.800 just going to focus on the method and
00:04:58.000 we're going to move it over here so uh
00:05:00.440 some pretty standard Ruby code right
00:05:02.639 here um but let's adjust
00:05:06.080 it again
00:05:08.199 remember uh this is what we get when we
00:05:10.199 run
00:05:12.080 it let's try this so instead of a method
00:05:15.639 trick-or treat let's make a class called
00:05:17.960 trick-or treat we'll initialize it with
00:05:20.240 a kid in a house and then it has an
00:05:22.600 execute method which does the exact same
00:05:25.240 thing that um that our method did before
00:05:29.560 then if you look down at the bottom I'm
00:05:30.759 just running this code so I
00:05:34.520 instantiate uh Make an instance of this
00:05:36.680 class so trick-or treat. new pass in
00:05:39.199 adom pass in the neighbor house and then
00:05:40.919 run execute when I run this code you
00:05:44.199 will not be
00:05:45.400 surprised again the exact same thing
00:05:48.319 happens so you're probably
00:05:51.160 thinking great we went from you know
00:05:54.360 five lines of code to 10 lines of code
00:05:57.400 and we're doing the exact same thing why
00:05:59.520 why would that be useful and that's what
00:06:01.880 we're going to talk
00:06:03.160 about
00:06:05.080 so I'm actually going to make this even
00:06:07.000 more
00:06:07.759 complicated uh I'm going to add a class
00:06:11.000 method down at the bottom here that does
00:06:13.720 the work of instantiating the new one
00:06:15.919 and the reason I like that is if you
00:06:17.240 look all the way down at the bottom here
00:06:19.240 when I'm running the trick-or treat
00:06:20.800 method trick-or treat. run and then I
00:06:23.599 pass in Adam in the neighbor house
00:06:26.160 so this looks a lot like running the
00:06:29.639 method over here on the left but we're
00:06:32.280 doing it with a class and again you're
00:06:34.919 probably thinking great we have five
00:06:37.479 more lines of code why why would anybody
00:06:39.960 do this which is a good question and um
00:06:44.000 I'll show you in a little bit so again
00:06:48.120 more code does the same
00:06:50.680 thing why is this
00:06:53.319 useful let's find out this lets you
00:06:56.919 actually do a few things once it's it
00:06:59.479 starts getting when things start getting
00:07:00.759 more complicated this becomes really
00:07:02.639 powerful it lets you manage the inputs
00:07:06.680 in a much more robust way than you can
00:07:08.759 do with a method on its own it lets you
00:07:11.800 run validations that a usual part of the
00:07:14.599 command pattern is to kind of add in a a
00:07:16.520 validation step so you can actually
00:07:18.560 validate the inputs coming into your
00:07:20.360 method it lets you do the thing which is
00:07:23.120 the same thing your method was doing
00:07:24.759 before so that's the same it lets you
00:07:27.919 manage the output of your method in a
00:07:29.599 very explicit way and it lets you
00:07:32.520 standardize errors um so that I'm sure
00:07:36.759 all sounds great and this has all been
00:07:38.479 very abstract um so let's let's dive in
00:07:42.120 a little bit to how this might actually
00:07:43.400 be useful in your day-to-day life my
00:07:45.800 favorite place to use the command
00:07:47.240 pattern is in rails
00:07:50.000 controllers so let's look at some code
00:07:52.400 that actually hopefully is a little more
00:07:55.159 familiar um for this I'm going to be
00:07:57.080 using the active interaction gem um I
00:07:59.440 highly suggest you check it out the read
00:08:01.599 me of this gem has really really good
00:08:04.280 examples of of how to use this and how
00:08:06.080 to make it useful um way more than I can
00:08:08.280 fit into a 15minute talk so um if you
00:08:10.879 are at all interested in this after I
00:08:12.280 talk about it I highly recommend that
00:08:14.159 you um just take a look at this Gem and
00:08:16.919 just reading the read me will do a
00:08:18.599 better job explaining it than than I can
00:08:21.560 today so using this
00:08:23.960 gem let's talk
00:08:26.639 rails um this is all going to be a
00:08:28.960 little hand wavy because if I did all
00:08:30.520 the details it um it just was like
00:08:33.599 looked like a lot so I'm just saying
00:08:37.039 candy. all here right but if you were
00:08:38.760 doing a traditional application you do
00:08:40.640 you know at Candy equals candy. all and
00:08:42.519 use that in your um or at you know
00:08:45.320 candies equal candy do and use that in
00:08:47.200 your view or maybe if you were running
00:08:49.040 an API you'd render Jason or something
00:08:51.760 um I'm kind of just skipping that but it
00:08:55.480 just for like readability purposes but
00:08:58.240 other than that consider this a pretty
00:09:00.320 typical controller where part of what's
00:09:01.959 happening in the index action is that we
00:09:03.440 need to fetch all the
00:09:05.240 candy so let's see how the command
00:09:08.360 pattern can help us make sense of our
00:09:11.640 controllers all right all right so over
00:09:13.880 here on the right we have uh the
00:09:17.279 same candy
00:09:19.200 controller except instead of straight
00:09:22.120 calling candy doall I'm calling another
00:09:25.200 class called candy index that's going to
00:09:27.079 be our Command um and I'm saying run
00:09:30.320 which is do the thing um and then I've
00:09:33.360 just moved the candy doall into into
00:09:36.399 that class so that class is right below
00:09:38.640 so again you're probably thinking great
00:09:40.399 we went from one class and one method to
00:09:42.680 two classes and two methods and why why
00:09:45.000 would we do this um let's let's take a
00:09:50.440 look before we show you that let me let
00:09:52.800 me say pretty often you start with
00:09:54.640 something like this and somebody says oh
00:09:56.240 shoot we need to be able to filter our
00:09:58.560 candies right maybe by the date they
00:10:01.240 were created right if cand is too old we
00:10:03.240 don't want to we don't want to get it um
00:10:06.079 so now I'm going to change both of these
00:10:08.200 things to manage that change so over
00:10:11.120 here on the left I've updated the the
00:10:12.720 non-command pattern controller um to
00:10:15.480 take in a Pam start date and a Pam's end
00:10:17.519 date and we'll filter based on that over
00:10:21.600 here on the right I've done the exact
00:10:24.200 same thing but with the the command
00:10:27.000 pattern so I'm now passing in pams
00:10:31.240 to to my command candy index and then
00:10:35.200 here's where it starts to get
00:10:36.120 interesting so down at the bottom on the
00:10:38.680 right candy
00:10:40.440 index notice what I have up here it says
00:10:42.959 date time start date date time end date
00:10:46.040 I'm managing my inputs really really
00:10:48.720 clearly here and what active interaction
00:10:50.880 will do for you here is actually a bunch
00:10:52.360 of things so first it's only going to
00:10:55.079 accept those inputs if you try to pass
00:10:57.120 anything else in here it just ignores
00:10:58.680 them you can also make it strict and
00:10:59.959 make it throw an error if you want to do
00:11:01.399 that too but so you're already once
00:11:04.560 you're once you're kind of running this
00:11:05.920 class you're only going to deal with the
00:11:08.320 inputs that you've explicitly said are
00:11:10.120 okay and that's that's really nice so if
00:11:12.200 you use this pattern you actually don't
00:11:13.560 need to use strong pams um because it's
00:11:16.000 going to do all that same stuff for you
00:11:17.519 and then actually a little bit more um
00:11:19.600 you can still use strong prams if you
00:11:21.079 want to do that you just don't have to
00:11:23.720 it's also going to cast whatever comes
00:11:25.600 in here so start date and end date as
00:11:27.880 inputs it's going to cast them to date
00:11:30.240 times so if somebody tries to pass in a
00:11:33.160 start date that's not a valid date time
00:11:35.720 or a string that like can't be cast to a
00:11:39.240 valid date time this is gonna throw an
00:11:41.760 error which means it's not going to try
00:11:43.880 to do anything else because the inputs
00:11:46.000 can't be what they're supposed to be so
00:11:48.040 I've actually already gained a lot here
00:11:50.560 um if some nonsense prams are coming in
00:11:54.440 this this this command is going to throw
00:11:56.760 an error and it's not going to try to do
00:11:59.160 anything else which is really really
00:12:01.040 valuable this is how you can kind of
00:12:03.079 catch any problems with your inputs very
00:12:05.240 first thing instead of getting like an
00:12:07.160 error way down the line where it's like
00:12:09.040 you know string nonsense can't be cast
00:12:11.279 to a date time or whatever um and it's
00:12:14.120 not just going to throw any error it's
00:12:15.680 going to throw an error that says start
00:12:17.320 date is not a valid date time that's a
00:12:19.399 human readable error that you can return
00:12:21.079 back to your users in your controller so
00:12:23.959 while this looks really similar the fact
00:12:26.639 that you can specify what the inputs are
00:12:29.000 really gives you a lot of control over
00:12:30.880 what's going to kind of move move down
00:12:32.839 through your um through your code all
00:12:36.760 right so you've added this you get start
00:12:39.279 date and end date and then next up
00:12:42.480 somebody some weird stuff starts
00:12:44.240 happening when people try to pass in
00:12:45.760 pams where end date is actually before
00:12:48.279 the start date so you end up wanting to
00:12:50.000 validate it' be really nice if you could
00:12:52.160 just control the fact that end date has
00:12:53.760 to be after start date well the command
00:12:55.800 pattern and specifically active
00:12:57.440 interaction will let you do do that too
00:13:00.680 um so keep in mind that that's the new
00:13:03.000 feature that we want and then I'm just
00:13:04.320 going to move some code around all right
00:13:07.639 I'm not going to talk about the
00:13:08.519 non-command pattern controller anymore
00:13:10.639 um we don't really need the comparison
00:13:13.240 um but I am going to
00:13:14.880 separate these two classes so over on
00:13:17.600 the left we have our controller that's
00:13:19.040 running the command and over on the
00:13:21.040 right we have the the actual command
00:13:22.839 class so the same thing we were just
00:13:24.760 looking at I just moved it so it's no
00:13:26.440 longer a comparison this is all all code
00:13:29.199 that would be an outw application right
00:13:31.920 now we also need to handle errors so
00:13:35.199 this is actually one of the really cool
00:13:36.959 Parts about this pattern and I kept
00:13:40.000 saying that the candy Index this command
00:13:42.399 is going to throw errors if the inputs
00:13:44.440 aren't valid that's pretty great but you
00:13:46.720 got to do something with those errors
00:13:48.839 active interactions errors look a lot
00:13:51.160 like active model errors and you can do
00:13:53.120 all the same things with them um so you
00:13:58.000 like you'll say outcome. valid this is
00:14:00.279 going to look really similar to how
00:14:01.440 active model does things um so that
00:14:04.120 means if everything went great in the
00:14:06.320 command you can do whatever you want
00:14:08.279 right like outcome. results is going to
00:14:10.240 be whatever is returned in the command
00:14:11.839 in this case all the candies and
00:14:13.440 outcome. errors is going to be an error
00:14:14.959 as object that looks a lot like what
00:14:17.160 you're used to with active
00:14:18.920 model that's really powerful um this
00:14:22.920 kind of if else statement over here in
00:14:24.480 the controller that's going to be the
00:14:26.720 same in all of your controller actions
00:14:29.720 if if you're using this pattern and so
00:14:31.440 you can abstract it out and your errors
00:14:33.600 are always going to look exactly the
00:14:35.279 same which means it's really easy to
00:14:37.600 kind of manage what your errors look
00:14:40.040 like when they when they get back to
00:14:41.399 your user um I didn't go into all the
00:14:45.360 details of how you would do something
00:14:47.040 with these error objects because of time
00:14:49.800 and um like I said just kind of the
00:14:51.440 complexity it's a little much for this
00:14:53.639 this size of a talk but um this is what
00:14:57.440 your controller end up looking like
00:14:59.959 though the the bottom part can just be
00:15:03.120 abstracted out right
00:15:05.079 away so let's so let's go back to that
00:15:08.000 feature we wanted to add where we really
00:15:10.440 don't want people passing in end dates
00:15:12.600 that are after start dates you can
00:15:15.839 validate your commands exactly the same
00:15:18.519 way you're used to validating your um
00:15:21.639 your models so over on the right I've
00:15:24.519 added a validate and date must be after
00:15:26.519 start date and then I've you know add a
00:15:29.319 little private method that does exactly
00:15:31.360 that so I can if the end date isn't
00:15:36.120 after the start date I'll explicitly add
00:15:37.880 an error I can add it to the end date
00:15:41.079 input you can also add errors to the
00:15:42.519 base which are just general errors um
00:15:44.800 and it'll say must day after start date
00:15:47.680 so what this does is we've now added
00:15:49.720 kind of another step to our life cycle
00:15:51.319 first we're going to process our inputs
00:15:53.319 then we're even going to validate our
00:15:55.079 inputs and you can get more and more
00:15:56.680 complicated validations the nice thing
00:15:59.040 about this is if at any step something
00:16:01.959 goes wrong active interaction stops what
00:16:05.199 it's doing and returns errors it's not
00:16:07.360 going to go try to get all the candies
00:16:09.319 if the in inputs are aren't valid um and
00:16:13.199 then your controller even though you've
00:16:14.880 added this validation step doesn't have
00:16:16.920 to change at all you're already handling
00:16:19.399 errors um and most in all of these
00:16:22.560 errors are going to come back in in a
00:16:24.000 human readable way if you're smart about
00:16:25.639 it um and you can just you know throw
00:16:28.880 those you know straight into your error
00:16:30.959 message um there's a there's a lot of
00:16:33.720 power in this especially as things get
00:16:35.920 more complicated right maybe you want to
00:16:38.199 be able to filter by a like type of
00:16:44.279 candy right you only want to get
00:16:46.040 chocolate um but you want to restrict
00:16:50.120 the filters you you could even so you
00:16:52.839 can
00:16:53.680 validate that the filter prams that are
00:16:56.240 coming in are are exact ly the way you
00:16:59.079 want them to be um over time this
00:17:02.920 pattern so your controllers hardly ever
00:17:04.600 change they all just kind of look like
00:17:06.120 this you just pass things straight
00:17:07.480 through to the commands but over time I
00:17:10.240 found this really really really helps
00:17:12.000 clean up a rails application um it gives
00:17:15.360 you a clear place to put um all the
00:17:17.720 logic that's that's happening in a
00:17:19.679 controller and um it lets you really
00:17:23.480 cleanly manage errors and make sure that
00:17:26.880 you're always returning nice human
00:17:28.439 readable errors um back to the user so
00:17:32.559 if nothing else I highly recommend you
00:17:34.280 check out active interaction it's just a
00:17:37.280 fun it's a really good gem I've used it
00:17:39.520 a lot um it's wellmaintained
00:17:41.840 and
00:17:43.440 um it it can help you do these things
00:17:46.400 right so manage your inputs run
00:17:48.160 validations do the thing you want to do
00:17:50.200 manage your output and standardize your
00:17:51.960 error handling um so it's more code than
00:17:55.600 just a method but if you think about
00:17:57.440 sometimes how complicated at your
00:17:58.760 controller methods can get this kind of
00:18:01.440 abstraction this this service layer has
00:18:04.280 been has been the best one that I've
00:18:05.840 tried um I've also used it in a bunch of
00:18:07.760 other places but I think the controller
00:18:09.960 example is is probably the best
00:18:12.520 one and that's all I've got
00:18:20.159 thanks yeah sorry I might have
00:18:24.080 um yeah and again I highly recommend
00:18:26.200 checking out the active interaction gem
00:18:28.080 as
00:18:28.840 uh just like an like just a just an
00:18:31.000 exploration I think putting together
00:18:33.080 this talk made me realize that 15
00:18:34.480 minutes is not really long enough for
00:18:35.960 this talk but the idea was like show you
00:18:39.240 what we got and then we can go from
00:18:40.640 there uh yeah any thank you for your
00:18:44.360 time and thank you um to to wnb uh we
00:18:48.120 were talking in the breakout room this
00:18:49.320 is a great community and really
00:18:51.280 appreciate everything all you're doing
Explore all talks recorded at WNB.rb Meetup
+20