Samuel Mullen

What could possibly go wrong?

Blogging for O’Reilly Media

The last post I wrote, The Costs of Keeping Your Rails App Up to Date…Or Not, went over very well. It remained on the front page of Hacker News for the better part of a day, got tweeted by a number of people, and is ranking well in search.

This isn’t the first time I’ve seen this sort of response, but this time I decided to find a way I could leverage my writing for my business. After some time considering possibilities, speaking with friends, and just ruminating on things, I decided to reach out to a friend who worked for O’Reilly Media. My intent for reaching out wasn’t to find an opportunity, but just to get his opinion. I knew he was a writer, and thought he might be able to provide some good perspective. As the conversation progressed, he asked if I would have an interest in writing for O’Reilly’s Programming Blog on the “Ruby Beat”. I said, “Yes”, and he made the connections. It really was about that simple

I’ve already published two articles there and ny future Ruby articles will go there as well. I’ll still be posting other things here when I get the opportunity, but it will be less frequent. You can find all my O’Reilly articles under my author’s page.

The Costs of Keeping Your Rails App Up to Date…Or Not

Clients are always asking interesting questions. They’re interesting, not always because of the depth of the question, but because sometimes they force us to reevaluate our assumptions.

Recently I had a call with a company seeking a new maintainer for an existing (i.e. legacy) Rails application. As I spoke with them and looked over the application, it became evident that it was several versions out of date. As part of the work they wanted, I recommended they also upgrade the application to a more current version of Rails, to which they asked, “Why do we need to upgrade?”.

“Why do we need to upgrade?” is an interesting question. As a developer, I take for granted the need to keep applications current, but for business people, especially less technical ones, the need is less apparent. “After all”, they argue, “the application still works and does what we need it to do.”

There are three big reasons to upgrade: security, performance, and maintainability. Let’s address these reasons and also look at the potential costs of upgrading.

Security

"I'm in my happy place"

The number one reason - and this is a big number one - you need to keep your web application up to date is for security. There are a lot of very clever, but malicious, people out there looking for new and interesting ways to take advantage of even the smallest chink in a system’s armor. If your application isn’t kept up to date, it’s vulnerable to their attacks.

“But my site’s small, no one even knows about us.” This would be a great argument if the bad guys cared about what site they hit, but they don’t. They have programs which scan the Internet looking for any site which might be vulnerable. When they find one, like a tick, they dig in. Things can get really bad at this point.

If this happens to you, these are a just a few things you can look forward to:

  • Compromised data: You won’t have any idea what from your database or files have been modified, maybe nothing, maybe lots of things, but you can be sure it’s all been copied.
  • Control: Your system is under their control. This means they can be using your system to crack other systems.
  • Lawsuits: You may be on the receiving end of lawsuits for endangering your customer’s data.
  • Insurance: Chances are, your insurance policy doesn’t cover “cyber” crimes.

The cheapest solution is to be proactive and make sure your application is kept up to date with all the latest patches. If it’s not, the bad guys’ll use the vulnerabilities you leave open. As they say, “It’s not a matter of ‘if’, it’s a matter of ‘when’.”

Most security patches are super easy to install and may take as little as 15 minutes to download, install, and deploy. There’s absolutely no reason not to do this.

Performance

If “security” is the number one reason to upgrade, then the second big reason is “performance”. Of course, this is a little tough to argue since there is evidence to show upgrading from Rails 2.x to 3.0 incurred a significant performance hit. Since then, however, effort has been put forth to improve performance, and we’ve seen that with the Asset Pipeline, the Arel SQL engine, “Russian Doll” caching, and more.

"Squirrel power!"

But performance isn’t merely limited to the applicaiton. As improvements are made to the framework , developers reap the rewards with expanded and improved features; features which reduce our development time (I can’t even guess how much time I’ve saved by using Arel, CoffeeScript, SASS, and the Asset Pipeline), expand what we can do, and simplify our workflow.

Some time ago, Google showed us that speed matters and directly affects your bottom line. It also been shown that a site’s performance directly affects its SEO. If that’s not compelling enough, then what about the savings you’ll reap in increased developer productivity? Is getting a few more hours of development time a month worth it?

Maintainability

When looking for reasons why you should upgrade, it is sometimes argued that you should upgrade your application so that it’s easier to upgrade later. While this argument isn’t wrong, it is a tautology.

Unfortunately, the simple truth is that as an app’s Rails version gets further away from what is current, there are fewer and fewer developers who are able or even interested in maintaining it. Furthermore, libraries and plugins used by an application and which add functionality and features to the application also fall out of maintenance - and do so much faster - as the creators focus instead on newer versions of the framework.

By keeping your application up to date, you ensure that you’ll have a larger pool of developers who can maintenance your application. You also reduce the numbef of hurdles needed keep the site running.

Cost to Upgrade

So here’s the big question: How much is this going to cost?

The cost to upgrading your application from its current state to the current stable release depends on a number of factors:

  • Current version: If your application has not been kept up to date, then it stands to reason that it’s going to take more time than one which has.
  • Application size: In the same way that a larger building or vehicle requires more maintenance than a smaller version of the same, so too a larger, more complex application is going to require more effort to maintain.
  • Usage of external libraries: Most Rails applications use external libraries to simplify the addition of features. As an application is upgraded, the libraries it utilizes must also be upgraded. Many libraries fall by the wayside and so alternative methods of duplicating functionality must be sought out.
  • Test coverage: This may be the biggest factor in determining the level of effort required to update a Rails application. Tests in an application serve a similar function to a body’s nervous system; they help identify areas which aren’t working correctly. When there is a comprehensive test suite, it is much easier to find what breaks with each incremental upgrade. If you don’t have that “nervous system” in place, finding what breaks becomes a much longer and arduous process.

"This really has nothing to do with the post"

Most upgrades I’ve run across average between 6 and 10 hours of effort per “minor” version jump. Of course, that time is affected by the factors mentioned above, but these are general numbers.

With the added security, improved performance and faster development times, and simplified maintenance, the real cost is not in the upgrade, but in remaining with the status quo.

By putting off upgrades you run the risk of a security breach, you allow your application to be less performant, you hinder the ability of your developers to work quickly and effectively, and you add to your application’s future maintenance cost.

So how much does an upgrade cost? Not nearly as much as not upgrading.

Validating Presence of Associations and Foreign Keys in Rails

The Problem

When working with ActiveRecord models, there are occasions where using either a foreign key or an object to denote an association is desirable. It may be the only available information about the foreign record is the key, or it may be that only a “new” (i.e. not persisted) object has been created. To ensure the association is present, you need to validate the presence of both the association (i.e. the Object) and the foreign key.

Assuming we have a User model with a has_many association to a Post model, the solution might look something like this:

1
2
3
4
5
6
class Post < ActiveRecord::Base
  belongs_to :user, :inverse_of => :posts

  validates :user, :presence => {:if => proc{|o| o.user_id.blank? }}
  validates :user_id, :presence => {:if => proc{|o| o.user.blank? }}
end

Here, we’re validating the existence of the User association: first by checking the presence of the object if the foreign key is blank; second by checking the presence of the foreign key if the User association is blank.

This isn’t a great solution, because it results in two error messages if the validation fails.

A New Validation

A better solution is to create a validator. To do that in Rails, the validator file must be created under lib/validators, and the filename must be the snake case version of the class name (i.e. ExistenceValidator => existence_validator.rb).

1
2
3
4
5
6
7
class ExistenceValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.blank? && record.send("#{attribute}_id".to_sym).blank?
      record.errors[attribute] << I18n.t("errors.messages.existence")
    end
  end
end

lib/validators/existence_validator.rb

As you can see, we subclass ActiveModel::EachValidator and override the validate_each method.

Inside the method, we add to the errors list if either the value of what we are testing is #blank? or if the foreign key is #blank? Just add the desired error message to your config/locales/en.yml file (adjust for language).

1
2
3
4
en:
  errors:
    messages:
      existence: "must exist as an object or foreign key"

config/locales/en.yml

Aside: #send

The above solution may be a little tricky if you’re not too familiar with metaprogramming in Ruby, so we’ll look at it a little more closely here.

1
record.send("#{attribute}_id".to_sym).blank?

In Ruby, the #send method takes a symbol as its first argument, and it “sends” that symbol as a message to an object, (i.e. it calls a method of the same name as the symbol).

We are defining the message to be sent with the "#{attribute}_id".to_sym bit – in our use case, it will end up being user_id. That message gets sent to the model instance the validation is defined within and determined if it is blank?.

When fully translated, it looks like this:

1
record.user_id.blank?

Usage

Now that we’ve defined our new validation, let’s use it. We can remove the two presence validators we had before and replace them with the more succinct existence validator.

1
2
3
4
5
class Post < ActiveRecord::Base
  belongs_to :user, :inverse_of => :posts

  validates :user, :existence => true
end

Post model using new validator

Where It Doesn’t Work

The solution is pretty good, but it assumes the foreign key is the same name as the association, only with an appended _id (e.g. user and user_id).

For the occasion when you need to get around this, you’ll want to create a sort of “in class” validator for your odd association.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Post < ActiveRecord::Base
  belongs_to :author, :class_name => User, :inverse_of => :posts

  validate :must_have_author

  private

  def must_have_author
    if author.blank? && user_id.blank?
      self.errors.add(:author, I18n.t("errors.messages.existence"))
    end
  end
end

Post model using specific validator

At this point you should have a solution for 90% of what you’re going to run into with regard to validating associations. If you see something I missed, or if there’s a way I can improve anything above, please leave a comment and I’ll update the post accordingly.

Startup Journal: Motivation

We’ve all heard stories about, or maybe even know the stereotypical entrepreneur. These are the men and women whose obsession to launch a company drive them 24 hours a day at a frenetic pace. They seem like they can do it all: making new contacts, hustling the product, winning sales, hacking out code, managing the project, and charismatic enough to keep their team on target. And they seem to be able to do it all on nothing more than three hours of sleep and massive quantities of coffee.

I’m not like that.

I started working on Mesa about three months ago, and while I’m still actively working on it, I was really only obsessive about it for the first month or so. Some days I struggle with motivation and “inspiration”; I don’t always know what direction to go; I’ve been bored with building out parts of the application; I procrastinate even when I know the time I have is limited; It’s difficult to see where the finish line is.

How does one find passion or motivation? It’s one thing when you are working for a company or freelancing; it’s quite another when you don’t even know if what you’re building will ever see the light of day. To help with this, I’ve been employing a few tactics, two of which are from a chapter on motivation from “59 seconds”.

Creating a Plan

As the old adage says, “If you fail to plan, you plan to fail.” Based on this, I’ve created a five-point plan for achieving my goal – which, by the way, isn’t creating a product, but rather gaining independence. Each step is further broken into these components:

  • I believe I can achieve this goal because…
  • To achieve this, I will…
  • This will be achieved by the following date…
  • My reward for this will be …

I’ve completed the first step – launching the RedCoyote website. If there was any reward, it was learning how to create a static site with Jekyll.

The second step is to launch Mesa as a closed beta. This will be complete March 1.

Visualization

This tactic sounds kinda cheesy and it doesn’t help that it originated with Oprah in a roundabout sort of way. Before I sit down to start to work in the mornings, I’ve started taking a few minutes to visualize what I’m working for; not in a way that assumes I’ve already achieved my goals, but in a way that emphasizes the positives of achieving them. It’s a fine line, but it makes a difference.

Combating Procrastination

There are thousands of articles written about how to avoid procrastination, but it oftentimes just boils down to momentum; once you get started, the momentum can carry you through.

To get that initial movement, I’ve found that just sitting down and setting my pomodoro timer is enough to get going. Once I’m into the pomodoro, focus comes more naturally. If I don’t set the timer, it may be another 15 to thirty minutes before I actually settle in.

Nightly Goal Setting

This is the big one for me: each night I write down the goals I want to accomplish for the next day. I have an alarm which goes off late in the evening to remind me to set goals for the next day. At that point, I look over what I’ve completed that day, and determine what needs to get accomplished the following day.

I can’t stress how much of a difference this practice makes in my level of motivation, and the more thought I put into it, the more focused I’ll be the next day.

Success Journalling

During the time I use to set goals, I also list the successes I had for the day. This is a less traditional sort of journalling, and really just a bulleted list of successes.

When I first began this practice, I was more inclined to write down the tasks I accomplished, but as I’ve continued, I’ve begun listing more abstract successes. So rather than listing, “Finished the Appointments feature on Mesa”, I might write, “Appointments are finished! Time to tackle estimates”.

While I’m listing successes, I also try to think back over the day and list the things for which I’m grateful. This has less to do with motivation, and more with general happiness.

So, is all of this working? I would say, “yes”. Creating an overarching plan definitely helps with the vision I’m trying to realize, and the nightly goal settings have a huge influence in the following day. The visualization component seems to be more influenced by my nightly goal setting, than anything. Lastly, journalling is still a work in progress, but I think it will make a difference if I just keep doing it.

Two more major components remain before I can release the beta. I can’t wait.

Extending Minitest 5: Custom Reporters

In the previous two posts, we looked at extending Minitest with new Assertions and Expectations, and modifying the progress reporter output. In this post, we will look at what can be done with the data collected from the test suite once it’s run.

To be clear, Minitest has a class called SummaryReporter which has the purpose of summarizing the run, providing information on the arguments used, displaying failures and errors, and reporting the statistics from the run. The only way to alter this output is to override the SummaryReporter.

In this post, rather than altering Minitest itself, we’ll look at what can be done with the data collected from the test suite. We’ll use a plugin called VocalReporter as our example.

must_be_kind_of AbstractReporter

All custom reporters will be a subclass of AbstractReporter, either by subclassing it directly or by inheritance through another reporter such as StatisticsReporter. In so doing, your class will be able to override, the following four methods:

  • start - Called when the run has started
  • record - Called for each result, passed or otherwise
  • report - Called at the end of the run
  • passed? - Called to see if you detected any problems

must_include :example

For our example, we will be subclassing the StatisticsReporter and only use the report method. We’ll also take advantage of the Mac’s say command to announce our test results.

vocal_reporter_plugin.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Minitest
  def self.plugin_vocal_reporter_init(options)
    self.reporter << VocalReporter.new(options[:io], options)
  end

  class VocalReporter < StatisticsReporter
    def report
      super

      pct = self.failures / self.count.to_f * 100.0

      if pct > 50.0
        message = "#{self.count} tests run with #{self.failures} failures. Are
you even trying?"
      elsif self.failures > 0
        message = "#{self.count} tests run with #{self.failures} failures. That
kinda sucks"
      else
        message = "%d tests run with no failures. You rock!" % self.count
      end
      `say #{message}`
    end
  end
end

As explained in the previous post under “must_respond_to :conventions”, the file’s name and init methods must be in alignment.

Let’s break this down:

  • Line 1: Like all Minitest plugins, we’re adding to the Minitest module
  • Lines 2-4: The init method which has been discussed previously
  • Line 3: We append an instance of our VocalReporter to Minitest’s CompositeReporter which handles execution of the four methods (#start, #record, #report, and #passed?)across all reporters
  • Line 6: Naming the VocalReporter class and inheriting from the StatisticsReporter
  • Lines 7-20: Our report method which will output our results
  • Line 8: calls StatisticReporter’s #report method in order to get access to attributes set therein, such as #failures and #count
  • Line 10: calculate the percentage of fail
  • Lines 12-18: a little logic to define the message
  • Line 19: output the message audibly

If you were to include this plugin in your test suite, you would hear – assuming all tests passed – ”n tests run with no failures. You rock!”

It’s a silly example, but it does make the point that you can do interesting – and unexpected – things with your test results.

@ideas.wont_be_empty

Other ideas for customer reporters:

  • Output results to a USB LED device using blinky
  • Send emails to everyone on the team announcing your success or failure (Public shaming always leads to increased motivation. Just ask anyone who’s tried dieting.)
  • Output statistics to a database to track progress
  • Send results to a Continuous Integration server
  • Make a game out of the results
  • Other ideas? List them in the comments below

must_be_kind_of Conclusion

We’ve really only scratched the surface with what you can do with custom reporters for Minitest. Remember, it’s just Ruby, get in and play. At this point in the series, you should be able to implement any customization you want, be it new assertions or expectations, progress reporters, or customized reporters.

I hope you’ve enjoyed reading this series as much as I’ve enjoyed writing it. I’ve learned a lot from getting into Minitest’s code and trying to understand it , and I would encourage you to do the same; it’s small, it’s very approachable, and it’s pretty funny. Just start with autorun.rb and follow the code.

Extending Minitest 5: Progress Reporters

Minitest has a number of strengths: small size, speed, simplicity, and inclusion in the Ruby standard library. Arguably, its small size and simplicity are the greatest, not because they enhance testing, but rather because they encourage modifying Minitest’s behavior.

In the last post (Extending Minitest 5: Assertions), we saw how easy it is to add new assertions and expectations to our tests. In this post, we’ll look at extending Minitest by modifying the output of the ProgressReporter.

must_include :explanations

If you’ve done any amount of testing, you’ve seen the results of progress reporters: the string of dots punctuated by the occasional “F”, “S”, or “E” (depending on your test suite). Out of the box, Minitest’s progress reporter is rather dull; it’s your terminal’s foreground color.

We’re going to change that.

must_respond_to :conventions

In order to get Minitest to automatically read our new progress reporter – and this will be the same for any “plugin” you create – we’ll need to adhere to a few conventions.

$LOAD_PATH.must_include “/path/to/plugin”

In the ::load_plugins method in the minitest.rb file of Minitest, it uses Gem::find_files to search for plugins. The ::find_files method uses the $LOAD_PATH global variable to determine which directories in which to look. So, in order for Minitest to find your plugins, you’ll either need to create it as a Gem, or push your directory onto the $LOAD_PATH array.

$LOAD_PATH.push
1
$LOAD_PATH.push "/home/fooman/projects/rearden"

Furthermore, you will need a “minitest” directory immediately under the directory you push onto $LOAD_PATH. Based on our example, our plugin file would reside under /home/fooman/projects/galt/minitest.

filename.must_match /_plugin.rb/

As the heading implies, all plugins must end with _plugin.rb. Therefore, if your plugin is named “whizbang”, it would reside in the file whizbang_plugin.rb.

initializers.must_match /plugin_name_(init|options)/

Continuing with the filename example above, you will also need to create initializer methods in your plugin using the following format:

initializer methods
1
2
3
4
5
6
7
def plugin_whizbang_init
...
end

def plugin_whizbang_options # This one is optional
...
end

The first method (i.e. the “init” method) is required for every Minitest plugin, and we’ll see how that works shortly. The latter method, as mentioned in the code comment, is optional.

examples.must_include “The Hard Way”

What follows is an example plugin which adds Unix escape characters to Minitest’s default output to make the results green (success), yellow (skips), or red (fails and errors). An explanation of the code is below.

stoplight_plugin.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  module Minitest
    def self.plugin_stoplight_init(options)
      io = StoplightReporter.new(options[:io])

      self.reporter.reporters.grep(Minitest::Reporter).each do |rep|
        rep.io = io
      end
    end

    class StoplightReporter
      attr_reader :io

      def initialize(io)
        @io = io
      end

      def print(o)
        case o
        when "." then
          opening = "\e[32m" # green
        when "E", "F" then
          opening = "\e[31m" # red
        when "S" then
          opening = "\e[33m" #yellow
        end

        io.print opening
        io.print o
        io.print "\e[m"
      end

      # Just here so you can see it can be overridden
      def puts(*o)
        io.puts(*o)
      end

      def method_missing(msg, *args) # :nodoc:
        io.send(msg, *args)
      end
    end
  end

As you can see, we are adding the StoplightReporter class to the Minitest module, and we are initializing that class in the plugin_stoplight_init method.

This method does a couple things:

  1. It retrieves a “modified” io object from the StoplightReporter class
  2. It replaces the io object used by every Minitest::Reporter with that newly created io object.

We use this new io object to override the print method, and as you can see it’s just wrapping a Unix escape sequence to what is sent. The puts method is merely there to show that it can be overridden as well. And the method_missing catches everything else which might be sent to the original io object.

Now, anytime Minitest’s reporters output a “.”, “S”, “E”, or “F”, it gets colored.

examples.must_include “The Easy Way”

Of course there’s an easier way to do all of this, but to do it, you have to ignore the plugin system Ryan Davis set up.

StoplightReporter v2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Minitest
  class ProgressReporter < Reporter
    def record(result)
      case result.result_code
      when "." then
        opening = "\e[32m"
      when "E", "F" then
        opening = "\e[31m"
      when "S" then
        opening = "\e[33m"
      end

      io.print opening
      io.print result.result_code
      io.print "\e[m"
    end
  end
end

This is pretty straightforward. All we’re doing here is monkey patching Minitest’s ProgressReporter class with our own version, and so avoiding the io component altogether. To use it, you’ll need to require it rather than load it through Minitest’s plugin system.

Of course, there are risks involved with doing things this way, but we’re just playing with the output of a test framework. What could possibly go wrong? :)

must_be_kind_of Conclusion

Minitest, by design, wants to be extended, and in this post we’ve seen how easy it is to do just that by modifying the behavior of the ProgressReporter class.

In the next post, we’ll see what we can do with the results of our test runs.

Extending Minitest 5: Assertions

Minitest, as a testing framework, is quickly gaining popularity in the Ruby community. Some of this is due to it replacing the old Test::Unit library in the Ruby standard library, but certainly much is due to the simplicity of the framework. This simplicity lends itself to easily extending the framework, which I believe is exactly what its creator (Ryan Davis) wanted.

In this, the first of three posts on extending Minitest, we’ll start off slow and look only at extending Minitest with new assertions and expectations. In the posts to follow, we’ll look more closely at naming files and directory structures, tweaking the progress output, and then doing something with the results once everything has run.

assert_background

Assertions were originally developed in programming languages, not for the specific purpose of testing, but rather ensuring a sort of contract between caller and receiver was maintained. If a caller sent the wrong message to the receiver, the assertion would fail (i.e. the contract was broken), and an exception would be raised.

Tests, then, are just contracts stating what we expect from our software. And in the end, all assertions and expectations boil down to the humble assert method which asks, “does your output match the expectation?”

must_include “reasons”

Minitest contains a comprehensive list of both assertions and expectations, and as was already mentioned, assertions and expectations all fall back to the assert method, so why would you want or need to add something new?

assert_readable

One reason to create new assertions, is similar to the reason we might create new methods: it makes our code more readable. For example, which snippet of code below is more readable?

readable code
1
2
3
4
5
6
assert_includes users, user

# -- OR --

assert_respond_to users, :include?
assert users.include? user

Furthermore, by making the assertions and expectations more readable, they can show specifically what is being tested.

rounding expectation
1
2
3
4
5
3.14.must_round_to 3

# -- OR --

3.14.round.must_equal 3

In this example, by creating the must_round_to expectation, it is clear the test is on the value 3.14. In contrast, the second example implies the round method is what is being tested.

Hat tip: Jared Ning for his Gist on creating expectations

assert_dry

Creating new assertions also helps to DRY your code, which in turn makes your tests less error prone. You know from experience that duplicated code is problematic, and so you extract logic into other classes and methods. Why would it be different in tests?

note: This section could use more support and possibly code, but the post is getting long, and as a Rubyist, it’s easier to swing “because it’s DRYer” around like a club to end any disagreement.

assert_new_assertion

It is not difficult to add new assertions and expectations. It’s just a matter of creating a positive assertion, a negative assertion (refute), and inserting them into Minitest. Expectations are almost laughably simple.

In the following code, we’ll add assertions to ensure all values within a collection match – or don’t match, in the case of refute – the value being tested.

assert_all_equal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Minitest::Assertions
  def assert_all_equal(collection, value, msg=nil)
    msg = message(msg, "") {
      "Expected #{mu_pp(collection)} to contain only #{mu_pp(true)} values"
    }
    assert collection.all? {|item| item == value} == true, msg
  end

  def refute_all_equal(collection, value, message=nil)
    msg = message(msg, "") {
      "Expected #{mu_pp(collection)} not to only contain #{mu_pp(true)} values"
    }
    assert collection.all? {|item| item == value} == false, msg
  end
end

As you can see, we are adding assert and refute methods to the Minitest::Assertions module. In each method, we accept a collection to test against, a value we expect the collection to only contain (or not only contain), and an optional message.

The mu_pp method used in the message call is a Minitest-local method which basically prints the object name nicely.

Lastly, we assert that all objects in the collection match (or don’t) the value passed in.

assert_loaded

Unlike Minitest plugins, assertions and expectations don’t need to reside under a minitest directory – plugins don’t either, really, but that’s a different post – they just need to get loaded. You can store all your assertions and expectations in separate files and require or load them through a helper, or you can store them in the helper file itself.

There really isn’t a “correct” way of doing this, which, in a lot of ways, is what I love about Minitest. As you use the framework more and more, you’re continually reminded that it’s not some sort of sacred cow, but rather it’s still just Ruby, and as such, you are at liberty to do whatever you damn well please.

assert_usage

This is how we might use our newly created assertion

example assertion usage
1
2
assert_all_equal([true, true, true], true)
refute_all_equal([true, false, true], true)

assert_expectations

If you run Minitest::Spec you’ll want add some expectations. Nothing could be simpler.

must_all_equal
1
2
3
4
module Minitest::Expectations
  infect_an_assertion :assert_includes, :must_all_equal, :reverse
  infect_an_assertion :refute_includes, :wont_all_equal, :reverse
end

That really is all you have to do. Just open up the module and add the “infections”. Did I mention Ryan Davis has a wicked sense of humor?

A note about :reverse. It doesn’t have to be :reverse, it’s just a “flag”. It could be :unary, :reverse, :foo, true, etc. As long as it evaluates to a “truthy” value.

All this flag does is reverse the order of the arguments passed into the assertion. You’ll generally use this flag when dealing with collections or boolean methods (e.g. #nil?, #empty?, etc.)

must_have_usage

Usage for the new expectations:

must_all_equal usage
1
2
[true, true, true].must_all_equal true
[true, false, true].wont_all_equal true

assert_end

In the next posts, we’ll cover creating plugins for Minitest, but at this point, you should have enough knowledge to start creating your own assertions and expectations. If you have questions, or would like to see more examples, I encourage you to look at the code itself. The Minitest library is remarkable small and very approachable; it’s also really funny. For what we’ve covered today, you’ll only need to look at assertions.rb and expectations.rb.

Startup Journal: Procrastination

Several weeks ago, I met a friend for lunch to discuss what we’ve each been working on. I was excited about my product and began relating to him how much progress I was making on the registration and authentication pieces, completing the accounts pages, and how I was turning my attention to defining “plans”.

Without missing a beat, Kenny just looked at me and asked very pointedly, “Why are you wasting time on registration, when you should be working on the product itself?”

That stung a lot, but he was right. I should have been focusing on the product itself, but there I was working on registration. At the time, I was convinced registration had to be in place before I could even start on the main application. Now I know I was just putting it off.

Unlike registration, the rest of the application is uncharted territory. I don’t know how things are done in the industry, what the workflows are like, or what the user experience should be. (I’m basically trying to duplicate a real world process on the computer without ever having actually seen the process being performed.) Not having a clear idea of what to do is highly demotivating. So, like most people, I gravitate toward activities I know until more is revealed about the problem at hand. In this case, however, no more information was forthcoming. I just needed to push through.

Getting that initial momentum is everything. Once that’s acquired, building upon it is easy. What seems to work well for me is staring blankly at the ceiling for a little while – but not too long – trying to figure out how things will work, and then drawing some basic wireframes on paper.

I don’t know if it’s the act of laying things out at that high of a level, of if drawing opens up some part of my brain which knows what needs to be done, but that’s the pattern which seems to have emerged. Think, draw, do.

Since that lunch with Kenny, I’ve completed the first of the three major components of the application, and have started working on the second. Procrastination’s still a problem – it always will be – but having friends to keep you on track can sometimes be all the momentum you need.

Transparency

It’s not uncommon for clients to send me emails asking for the current hours worked on a project for a given month. Sometimes it’s to help them allocate resource, while other times it’s due to budget constraints, and I’m sure there are other reasons as well.

It’s never a problem to provide this information, of course, but I also recognize it as a pain point for them. In that light, I thought it might be better to give my clients the ability to check on their projects themselves. To that end, I’ve created a web application called Transparency.

All my clients have access to the application, and through it, can see the amount of time spent on each of their projects.

They can also zoom in on any given project to see the task breakdown.

There are more features I’d like to add - listing non-billable work and subcontractors’ hours come to mind - but as the project stands, it does what it’s supposed to do: it provides some Transparency to my customers.

The Two Most Productivity Enhancing Scripts Ever Written in the History of UNIX

You’re not a slacker. In fact, you’re a pretty good developer who gets things done on time and done well. But sometimes you find yourself wasting time on that site again. You weren’t planning to go there, but the code takes a minute to compile and the tests take a bit longer to run and so you just flip over there while you wait.

Ten minutes later you’re still there, not thinking, just consuming.

And you berate yourself again for wasting time.

What you don’t need is some overlord application telling you you can’t access the internet for the next three hours. You just need a gentle reminder that you want to be productive.

Here it is. Two simple scripts. (go to the GitHub Gist)

One to block the sites you don’t want to visit:

worktime bash function
1
2
3
4
5
6
function worktime {
  echo "# WORKTIME" | sudo tee -a /etc/hosts > /dev/null
  while read -r line; do
    echo "127.0.0.1 ${line}"
  done < $HOME/.blocked_sites | sudo tee -a /etc/hosts > /dev/null
}

And one to remove the block:

slacktime bash function
1
2
3
4
5
6
7
8
9
10
function slacktime {
  flag=0
  while read -r line; do
    [[ $line =~ "# WORKTIME" ]] && flag=1
    [[ $flag -eq 1 ]] && continue
    echo $line
  done < /etc/hosts > /tmp/hosts

  sudo cp /tmp/hosts /etc/hosts
}

List the sites you don’t want to be visiting in .blocked_sites in your $HOME directory. Like this:

.blocked_sites
1
2
3
4
5
twitter.com
www.twitter.com # you may need to include the www subdomain
feedbin.me
alpha.app.net
app.net

When you want to be productive, run worktime. When you’re ready to slack off, run slacktime.

worktime adds those sites to your /etc/hosts file redirecting you back to localhost. If you’re feeling creative, make a landing page with a picture or text reminding you to keep been awesome. I use a picture of R. Lee Ermey to gently remind me what I’m supposed to be doing.

When you’re done being productive, run slacktime. slacktime just removes the entries which were added to the /etc/hosts file.

So are these really the two most productivity enhancing scripts ever written in the history of UNIX, or am I just using a bit of hyperbole to increase traffic? Probably the latter, but I still hope I help you become more productive.

Now quit slacking off and get back to work!