An alternate approach to Avdi’s method of slimming down Rails controllers

I recently watched/read Avdi’s fantastic article Slimming down hefty Rails controllers AND models using domain model events on Ruby Tapas. PS – you should probably subscribe to Ruby Tapas.

While being amazed by Avdi’s refactoring skills and watching him plug in proven software development patterns, I couldn’t help but think about how I would have approached this problem.

Recently, I’ve been trying to be extremely adherent to “The Rails Way.” I’ve been using as few gems as possible, and just working with what Rails gives you out of the box. It’s been an eye-opening experience to say the least.

If you haven’t yet watched the episode, or read the accompanying article, essentially the problem is a long update method in the controller for a project management app. It has a bunch of nested conditionals with methods to send out updates via email or Websockets depending on what has been updated. Because so much of this depends on the logged-in user and other session-specific variables, breaking the functionality out into a “domain object” didn’t make a ton of sense.

What Avdi observed (pun intended) is that these are actually life-cycle events for a task and associated project.

Looking over this action again, we realize something: hidden in all these conditionals is a series of domain-model lifecycle events, each with different concrete actions which trigger on those events.

  1. There are some actions to perform anytime the task is successfully updated.
  2. There are actions to take when the task has moved from one project to another.
  3. There are actions to perform when the task is newly created.
  4. There are actions that happen when the task’s status has changed.
  5. There are actions for when the task has been reassigned.

He then proceeded to add observers to the Task model and subsequently send out notifications to listeners whenever a life-cycle event occurred.

Immediately, I see a lot more objects here. I see a Move, a StatusChange, and a Reassignment; all resources related to a Task. Only a traditional updating of attributes belongs in this method and controller.

As an aside, I’m not exactly sure why handling the case of a newly created task is in the update action of the controller instead of create, but I’ll assume there is something here that we don’t know. For simplicity, I’m just going to skip it.

Let’s start with some routes:


resources :tasks do
  resource :move, only: [:create]
  resource :status_change, only: [:create]
  resource :reassignment, only: [:create]
end

Now, instead of just hitting the update action, anytime something happens with a Task, we’ve got a very distinct route for each special case.

Because the controller in the example is just returning JSON, I’m going to assume that this is a Javascript front-end communicating with the Rails app. This would require a change in the front-end logic to POST to a subresource url instead of a PUT to the task url.

Here’s our first controller to handle the case of when a Task moves from one project to another.


# app/controllers/tasks/moves_controller.rb

class Tasks::MovesController < ApplicationController
  def create
    old_project_id = @task.project_id

    if @task.update_attributes params.require(:task).permit(:project_id)
      push_project_update old_project_id
      push_project_update @task.project_id

      respond_to do |format|
        format.json { render "show", status: :accepted }
      end
    else
      respond_with @task do |format|
        format.json { render @task.errors.messages, status: :unprocessable_entity }
      end
    end
  end
end

Now, let’s examine what needs to happen when a status change occurs.


# app/controllers/tasks/status_changes_controller.rb

class Tasks::StatusChangesController < ApplicationController
  def create
    old_status = @task.status

    if @task.update_attributes params.require(:task).permit(:status)
      notifee = @task.readers(false) - current_user
      if notifee
        mail_completion_notice(notifee) if @task.status == Status::COMPLETED
        mail_uncomplete_notice(notifee) if old_status == Status::COMPLETED
      end

      respond_to do |format|
        format.json { render "show", status: :accepted }
      end
    else
      respond_with @task do |format|
        format.json { render @task.errors.messages, status: :unprocessable_entity }
      end
    end
  end
end

I’ll leave it to the reader to come up with their own implementation of a Tasks::ReassignmentsController but I think you can see how this method works.

I’d also challenge you to start thinking very strictly in terms of REST and “The Rails Way.” This codebase is the perfect example that rarely does an app just have basic CRUD needs. More often, there are special cases, and paths that require a different response despite technically falling under one of the basic CRUD actions.

Think about updating a User in your own codebase. Does the same thing happen when they update their password as when they update their name? Or, are you sending a email in once case and not in the other?

Instead of nesting conditionals inside of your update action in your UsersController could their be a Users::PasswordsController? Start flushing out more and more sub-resources with small controllers for these special cases.

It may not follow the classic patterns, but I’ve enjoyed this method of thinking recently.

I’d love to hear what you think as well!

Why Webhooks Are the Future of Web Integration

Anyone who has ever built a web application has surely felt the need to add features. You need to user management, email, payments, the list goes on and on. It isn’t too long before you realize that you’re re-inventing the wheel time and time again. Even worse, you’re running into problems that have already been solved!

For the sake of this post, I’ll use email as the example. You start with the simple transactional emails to confirm user registration or reset a password, but it isn’t too long before you think, “hey I’d like to allow my users to email all of their customers from time to time.”

Now you need to worry about staying out of SPAM filters, unsubscribes, DKIM signatures, the list goes on and on.

Obviously there is a lot more to this than you realized. Good thing this problem has already been solved multiple times over.

Next, you say, “I’ll just let my users integrate their Mailchimp account.”

So you spend a few days slinging some code, and fully integrate the Mailchimp API. Your users now can send email newsletters to their customers with pretty templates and confidence that they’ll arrive. Life is good.

Then a support request comes in saying:

“Hey, I saw that you now enable integration with Mailchimp. This is so great! I’ve been sending customers emails since I signed up, but have to manually export them. Only problem is that I use Drip. How long before you offer an integration with them? Also, a good friend of mine is a user as well but uses Campaign Monitor, I know she’d love to be able to do the same. Hope to hear from you soon!”

You’re now stuck with dozens of support requests for integration with their favorite email app, or CRM. Plus, you have to worry about deprecations or changes to every API you’ve integrated with. 😱

If only you could force them to integrate with your API. Those selfish developers should offer the ability to poll for changes and automatically update their records. After all, they’ve got millions in VC funding and a team of developers!

Cooler heads prevail, and you realize that it isn’t viable for anyone to keep up with every other app’s API who could potentially want to integrate.

🤔 Then you remember webhooks!

Any time a change occurs in your own app, you can send an update via a webhook to any URL a user wants! Ingenious – let your users decide where and when to send data they want!

A few days go by and what once felt so overwhelming has now simplified into:


# customers_controller.rb

def update
  if @customer.update_attributes customer_params
    current_user.webhooks.run "customer.updated", @customer
    redirect_to customer_path @customer
  else
    render :edit
  end
end

webhook-flow

Now anytime your user’s customers update a profile, they have instant push notifications to their favorite email app, CRM, and analytics tools!

There are even amazing services like Zapier who can take your webhooks and put data into a Google spreadsheet, post a Tweet, and update your Slack room without any programming by your users!

Now you understand why I believe webhooks are the one feature you must add to your API.

Announcing SMS Hooks

I’m going to keep this one short, but I’ve got 2 important things to announce today.

1. Announcing SMSHooks

It’s up, SMSHooks has a landing page! I’ve got a basic pitch, a quick screenshot, and some features spelling out how the service can help businesses.

It’s purposely very basic, but just enough to see what kind of interest there is for businesses who use Chargify, Stripe, and Shopify and want to connect with their customers via SMS.

2. Start Small, Stay Small eBook – A Giveaway

As I mentioned in the previous post, I bought, and quickly read Start Small, Stay Small, the fantastic book by serial entreprenuer Rob Walling.

This book is so chock full of insight, and actionable advice, I want to be able to share it. As such, I will be giving away several extra copies that I bought.

How do I get my copy?!

I offer 2 ways to get yourself a copy of this fantastic resource:

  1. Throw down $19-25 and buy it. Seriously. More than worth the money.
  2. Email me, and give me honest, insightful feedback on my business idea, SMSHooks. I’ll enter everyone who emails me into a drawing and give out a few copies to the winners.
    Note: the feedback does not have to be positive; just honest.

What’s next?

That’s it for this week. I’m happy to have a landing page up, and I’ve already solicited some fantastic feedback. I’m going to continue working to drive traffic to the site, and see how I can help people with their eCommerce businesses.

I’m looking forward to hearing from you, and excited to continue working and sharing my progress.

Realizing Your Value

I want to share a quick story, and how it really smacked me across the face with a realization of how often we leave money on the table as developers. I hope that by the end of this post, we’ll all feel less weird/guilty/embarassed about charging an appropriate amount of money for the value we provide.

When thinking about charging money for my skills, there are too many instances I completely disregard the value of what I’m able to do.

What I can do in an hour, may take someone years to accomplish.

How many hours have we all spent learning to code, reading books and blogs, watching screencasts, pouring over APIs?

Don’t forget these! We have invested the time, energy, and money in ourselves. And why do we make investments? For future payoff!

With that, on to my story.

A Call List

A guy I know helps run events, and like most events, people wait until the very last minute to confirm whether or not they’re coming. Despite countless emails, cough, cough, some people just don’t act.

So, a few days before, he ends up having make phone calls to everyone to ensure that they received the message that the deadline is very quickly approaching. He doesn’t like dropping people from an event without providing every opportunity for them to confirm. Nothing like having an angry participant yelling about how they didn’t receive any notices of a deadline because they don’t check that email address anymore.

So, anyway, he’s telling me how there’s a huge list and how he’s going to be paying a few people to sit and make phone calls all weekend, and into the next week.

He’s not exactly great with computers, and as I’m sitting there listening, he also asks me for some help formatting an Excel spreadsheet so that the callers can be more efficient with calling and marking whether or not the call was answered.

“Sure. Email it over, and I’ll clean it up before I leave today.”

A Wizard Appears

So, I’m sitting there looking at this list of just under 1,000 phone numbers and thinking about how long these people are going to have to be sitting there calling each person, and repeating a script.

As a fellow developer, I know what’s burning you up inside right now… This is the ideal case for automation.

I have a Twilio account and a few minutes. I pull out my iPhone, record a voice memo of the information each person on the call list needs, and within no more than 20 minutes total, I shoot off an automated call.

I also included my colleague’s number…

A few minutes later, he walks in with a confused look on his face. He received the phone call.

“How did you do that…?”

“Oh, well… Everyone on that list received the same message. All of the calls are done.”

I may as well have made a 737 jet disappear; the magic was the same in his eyes. I had achieved wizard status.

The Fail

The next question is, what should we charge this person? The total Twilio fees ended up at about $11.00 or so, so I figured $20.00 or $25.00 is good. It didn’t take me much time or effort, and that more than covered what I’m actually paying in fees.

Fair right?

WRONG

This person was ready, willing, and happy to spend hundreds of dollars to pay a few people to make all of these calls for him. I just saved him countless hours, and quite a bit of money. As quick as I said $25.00, I could have said $250.00 and he wouldn’t have flinched for a second.

If I spent some time and really broke down how much time and money I would save him, I could probably convince him to pay much more than even $250.00.

Understanding Our Value

So, what’s the true value of our work? The mistake I continually make is to sit and start thinking of an hourly rate, how long something will take, what’s the “fair” price to them, will they think I’m asking too much, etc. There’s a flood of questions I ask, when the answer is very plain and clear.

My work is worth whatever someone is willing to pay.

Most people I know aren’t willing to just give their money away. I know I’m not. We all calculate what something is worth to us, and if it’s worth it, we pay it. We make our decision based on the value a product or service provides us.

My guess is that I could charge $100.00 a month to automate these calls. I could write the script once, spend $11.00 in Twilio fees every now and then, and end up well ahead at the end of the year.

If I came across a website offering call services at $100.00 a month, I’m passing without even considering it. The value isn’t there for me. I could do it myself for much less, with very little time invested.

But, I know of at least one person already who would be entering his credit card this second at $100.00 a month for this. The value of automating these calls is well worth $100.00 a month to him. By paying for this service, he ends up ahead in money and time, a great value.

Wizards Don’t Pay for Magic!

The next time you’re ready to undercharge, or do something for free because it’s “easy,” remember this. Just because this magic flows through your hands typing on a keyboard, not everyone has been trained in these spells. What you would be utterly unwilling to pay, may be a great deal for someone else.

Anytime you can alleviate a pain point, save time, and save money for someone, the value you provide is whatever they’re willing to pay. If you can gain someone one more customer who provides a lifetime value of $1,000, then charging $500 is a great proposition no matter how much time or effort it takes you to accomplish the task.

No need to feel weird charging a fair amount when both parties benefit.

What About You?

Have you ever left money on the table because you failed to charge based on the value you provide?

Please share your story in the comments, or shoot me an email.

Patching Ruby 1.9.3 for Faster Rails Tests

I admit it, I haven’t fully gotten around to stubbing out all Rails classes in my tests. So, while I was writing tests for some older code, I decided to patch Ruby 1.9.3 to see how much I could gain just from that (I also modified a few env variables).

Results below:

13.907secs – 4.733secs = 400+% speed-up!

Not bad for 10mins of install time!

Note: I just added the env variables in my .rvmrc so they’re not loaded anytime I open the shell.

Why I Am Learning to Love TDD

Wait, What?

I know that this might be ahead of my post progression (last time I was still using PHP), but I had this thought go through my head yesterday while working, and thought it would be better to get it out of my system.

Isn’t It Just Writing More?

Yeah, that’s what I used to think too. I thought it was a waste of time, and just one more thing to do. I write a piece of code, fire up the browser, it works or it doesn’t, and go from there.

I went along this path for quite some time, and I’m willing to bet that you already know what would CONSTANTLY happen.

  1. Make a change.
  2. Test it in the browser.
  3. Works 🙂
  4. Commit
  5. Deploy
  6. OH %$&$*, I FORGOT ABOUT THAT OTHER CLASS!
  7. Make a change….

I knew that this is why I was supposed to test, but I still didn’t really get it. I tried Cucumber, and it seemed like I was spending so much time writing features for trivial tasks like signing-in.

I didn’t know how to write a good test. It was just more work that was slowing me down, and it was actually easier to just deal with the headaches of unexpected consequences.

So, What Changed?

I started to finally grasp RSpec. I still wrote “bad” tests, but they did eliminate some of problems.

I started to see why this was important, and how it would help me avoid those errors where I just forgot that there a method was being used in multiple places.

The problem I still had were the “easy things.”

Do I really need to test adding a full_name method on a class? I’m just making a small change, and I know it’s fine, it’s no big deal to just change it.

95% of the time, this was probably true. I did know what I was doing with a small change, it it wouldn’t hurt anything to just do it. Was it worth going through the whole process just for that last 5%?

What turned me was another habit that this led into: too many times I found myself saying, “I can just change it on the server, and update my code locally later.”

I don’t have to tell you why this is a horrible idea.

Where I Am Today

I thought to write this post today, because yesterday I started to think that I’m starting to “get it,” with TDD. I was writing some simple methods that would send confirmation emails, and I was disciplined in writing a failing test, making it green, then moving forward.

Then came the time for the second method that just sent a different type of email… “Well, I’m awfully close to just copying the first… just write the method and you can test it later.”

“NOOO! Just take the 2 minutes and write a failing test first, you know this!”

And I did. And it took less than 2 minutes. And I felt more confident that everything worked.

I had my tests passing and was ready to deploy, when I had almost forgot to run the entire suite. OK, OK, I’ll run it first.

“Wait, a failing test? Why? Oh, it’s because I that other controller spec calls it too.”

I go in to update it and remember that I wrote the spec a long time ago and it’s very tightly coupled. This is going to need more than a quick change on 1 or 2 lines… “I know where it is, I can just deploy and refactor the spec tomorrow when I have more time.”

“NOOO!”

I think I am finally learning to love TDD

All nationalists have the power of not seeing resemblances between similar sets of facts. A British Tory will defend self-determination in Europe and oppose it in India with no feeling of inconsistency. Actions are held to be good or bad, not on their own merits, but according to who does them, and there is almost no kind of outrage — torture, the use of hostages, forced labour, mass deportations, imprisonment without trial, forgery, assassination, the bombing of civilians — which does not change its moral colour when it is committed by ‘our’ side. The nationalist not only does not disapprove of atrocities committed by his own side, but he has a remarkable capacity for not even hearing about them.

George Orwell

How I Started to Learn Ruby – Part II


<?php
  session_start();
?>

Where we left off… I had just started to learn PHP/MySQL to run my own online registration system for a couple of camps that I was working at.

I had just enough tools to do some real damage, and, it, was, awesome!

I really couldn’t believe that I used to spend my time building tables of data by hand! Reusable data, real templates, dynamic interaction; with a little time I could do it all.

Keep in mind, if I wanted to insert a row into a campers table in my database, I had to do this:


$query = sprintf("INSERT INTO campers (account_username, account_password, camper_email, camper_lastname, camper_firstname, overnightday, camper_address_street, camper_address_city, camper_address_state, camper_address_zip, camper_primaryphone, camper_secondaryphone, camper_mothername, camper_fathername, camper_highschool, camper_highschool_address_city, camper_highschool_address_state, camper_height, camper_weight, camper_age, camper_grade, camper_position, camper_insuranceco, camper_insuranceco_address, camper_insuranceco_policy, camper_insuranceco_group, camper_insuranceco_id, campfee, deposit1, deposit1date, totalpaid, totaldue, onlinecamper, shuttle, camper_canceled) VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')",
        mysql_real_escape_string($account_username),
        $account_password,
        mysql_real_escape_string($camper_email),
        mysql_real_escape_string($camper_lastname),
        mysql_real_escape_string($camper_firstname),
        $overnightday,
        mysql_real_escape_string($camper_address_street),
        mysql_real_escape_string($camper_address_city),
        mysql_real_escape_string($camper_address_state),
        mysql_real_escape_string($camper_address_zip),
        mysql_real_escape_string($camper_primaryphone),
        mysql_real_escape_string($camper_secondaryphone),
        mysql_real_escape_string($camper_mothername),
        mysql_real_escape_string($camper_fathername),
        mysql_real_escape_string($camper_highschool),
        mysql_real_escape_string($camper_highschool_address_city),
        mysql_real_escape_string($camper_highschool_address_state),
        mysql_real_escape_string($camper_height),
        mysql_real_escape_string($camper_weight),
        mysql_real_escape_string($camper_age),
        mysql_real_escape_string($camper_grade),
        mysql_real_escape_string($camper_position),
        mysql_real_escape_string($camper_insuranceco),
        mysql_real_escape_string($camper_insuranceco_address),
        mysql_real_escape_string($camper_insuranceco_policy),
        mysql_real_escape_string($camper_insuranceco_group),
        mysql_real_escape_string($camper_insuranceco_id),
        $campfee,
        $deposit1,
        $deposit1date,
        $totalpaid,
        $totaldue,
        $onlinecamper,
        $shuttle,
        $camper_canceled);
$result = mysql_query($query);

As you can imagine, managing data like this without stored procedures, without an ORM can be incredibly time consuming, monotonous and error prone. Spread this among even a very small application, and I was right back ready to call it quits.

Again, this wasn’t fun any more. I went from solving real problems to pulling my hair out for an hour until I noticed that I misspelled “camper,” forgot a comma, or some stupid mistake.

A Light From Above

By this point, I had bought a second book trying to make sure I was processing payments properly. In the book there were a couple of pages mentioning “frameworks.” This sounded like JUST what I needed!

I had messed around with PEAR a little bit, but never found it to be much help. I didn’t really care for the packaging system you had to install, and to be honest really had no idea how to use it properly. These mystical “frameworks,” however, seemed like they could be a big help.

After reading up a little bit more, I had narrowed the choices down to 2: CakePHP and the Zend Framework.

I went through the basic tutorials for both to see which would work best for me, and it basically came down to 2 things:

  1. The basic CakePHP tutorials all seemed to be about building a blog. As I went through, it felt almost like doing a WordPress installation.
  2. Zend was heavily involved in the development of PHP.

If you couldn’t guess, I went with the Zend Framework. (By the way, I do now know that CakePHP is MUCH more than a blogging machine).

Adapters, Authorization, ACL, ORMS and….. MVC

I have to say, the Zend Framework really taught me a ton about the web. I started to REALLY learn about public directories, permissions, access control, mapping objects to database rows, and on, and on, and on.

Once again, I was able to shake myself from a rut by learning something new and becoming much more productive (you may notice a pattern here).

On I went, building multiple apps with the Zend Framework that were much more efficient, and I felt much more confident in their ability to perform tasks properly.

Along the way, I had also started to pick up this decades old patter, MVC. I still didn’t really “get” it, but it did help me separate my data, and have some form of organization in my projects.

Life was good.

And One Video Changed Me Forever

One odd day, I realized that I may have counted out CakePHP too soon, so I went to read a couple of other peoples’ reviews. Along the way, I saw that CakePHP was often being compared to something I hadn’t heard of before, Ruby on Rails.

Of course, I had to see this for myself, so off I went to their website. There I saw the original, Build a Blog Engine in 15 Minutes video.

After watching this video, I HAD NO IDEA WHAT WAS GOING ON.

Matrix Terminal

I saw this guy, DHH yell, “whoops” as he whipped together an application. I was watching text fly by like the Matrix, and felt completely overwhelmed.

That day I went back to my safe home of PHP and the very helpful Zend Framework. Work continued for some time, but I never forgot about that video…