Customizing Rake:spec

rails

For Quottly, we have a lot of code in our rails app. Most of it is a regular web application, but we also have a significant amount of code that is used for importing data from different data providers.

We write tests for our data import code, like all our code. But the tests for that part of the code are pretty slow. We use a lot of machine learning code, and training classifiers is time intensive.

When we’re working on the web app, we want to be able to run our specs quickly. What to do?

We decided to make rake:spec just run our web app specs, ignoring our import specs. And we created a new task, rake spec:import, that runs the import specs.

How to do this?

First, a gotcha: you can’t just override a rake task definition. If you want to re-define a rake task, you have to call Rake::Task["task_name"].clear before re-defining it.

Second, how do we redefine rake:spec? This isn’t quite straightforward - the documentation for customizing the rspec rake task isn’t great.

This will do, in one of our rake task files:

1
2
3
RSpec::Core::RakeTask.new(:spec) do |t|
  t.rspec_opts = '--tag ~type:import'
end

Instead of instantiating our new spec using the standard rake task definition, we use rspec’s custom rake task. Then, we just pass an option - the equivalent of passing command line options. The ~type declaration indicates that we’ll exclude the passed type, so --tag ~type:import indicates that we’ll exclude all specs tagged with type: import.

Now we just need a new rake spec:import task. To do this, just copy the above, but change :spec, to :import, and remove the tilde.

Finally, we need to make sure all our import specs are labeled with type: import, and wrap our task definitions in unless Rails.env.production?. Since rspec isn’t loaded in production, it’ll throw an error if we try to define these tasks in production.

This should get you going. Don’t forget to add rake spec:import to the appropriate spots (e.g. in your CI build configuration) where you should be running all your specs.

Happy hacking!

Six Months With Betterment

betterment, investing

I started using Betterment about six months ago to manage my investments. Shortly after joining, I wrote a blog post giving a brief overview of what it is and how I had started using it.

Six months later, I’m happy to report that I’m still enthusiastic with Betterment’s service. I’d recommend it to anyone, especially anyone who is just getting started with investing.

A few notes: First, I haven’t used auto-deposits nearly as much intended. I’ve spent most of my time consulting, so my paycheck varied significantly pay period to pay period. As such, I’ve kept a single $100/pay period auto deposit into my Roth IRA to ensure that I meet Betterment’s contribution requirements 1, but have otherwise managed my deposits by hand.

Second, I wish there were an auto-deposit-to-rebalance feature. As far as I can tell, Betterment removed the option to prevent your portfolio from automatically rebalancing. You can still avoid the tax implications of rebalancing by making deposits to rebalance, but I’d really like to put that on autopilot. It’s rather annoying to feel the need to check in every time the market makes a major move to make sure your portfolio isn’t going to rebalance.

Third, I wish that dividend reinvestment was more frequent. I understand that most of the funds only pay out their dividends once per month, but the feeling of getting that alert email - even if it is only for eleven cents - is fantastic. Perhaps when my accounts are larger, they’ll be more frequent.

Finally, I really wish Betterment offered 401(k) plans. For Quottly, we’re offering 401(k) plans through ForUsAll, and we’re more than happy with them - but this seems like a great place for Betterment to possibly expand to.

The net: I’d still recommend Betterment to anyone with less than a million or so in assets, without hesitation. I am looking forward to seeing what new features come out over the next six months.


  1. If you have less than $10,000 between all your accounts, you have to auto-deposit - not just deposit - $100/m to be eligible for 0.33% per year pricing, as opposed to $3/month. It’s a great motivator.

Using CloudFront With Heroku Pipeline Review Apps and Rails

heroku, rails

Most of the applications I deploy follow a common pattern. Rails for the framework, deployed to Heroku, with a standard set of dependencies and integrations. One of those is Amazon CloudFront’s content distribution network for hosting static assets.

Heroku, CloudFront, and Rails usually work beautifully together in production to make applications load lightning quick.

We recently started using Heroku Pipelines to manage deployment and iterating faster. The killer feature for us is automatically-generated review applications. Every time someone opens a Pull Request on GitHub, that version of the code is automatically turned into a brand new, live application on its own subdomain. These domains are usually of the from “app-name-pr-n”, where n is the number of the pull request.

Then we can send product managers, investors, beta users, or whoever needs to see a new feature to the pull request application to get their feedback. It’s as easy as sending them a link.

One downside: this setup breaks our CloudFront configuration. When the application is compiled on the review app, the asset links point to CloudFront… which then goes to our master server, not the review app. Since the origin domain is set on a per-distribution basis, to make CloudFront work with each of our review apps would require creating a new distribution every time we create a pull request.

We probably could do that using the Amazon AWS API, but CloudFront really isn’t designed to be used that way, and creating a distribution can easily take five to ten minutes. Instead, we decided to configure our application so as to skip using the CDN entirely if it is on a review app deployment.

How to skip CloudFront on review apps

This isn’t as easy as you might think. Heroku dynos, by design, don’t know which domain they’re hooked up to - they just serve an application. The only way to really configure them on a per-application basis is using ENV vars.

We created a new ENV var called “PR_ENVIRONMENT” (though “REVIEW_ENVIRONMENT” would probably have been a better name). Then, we added “PR_ENVIRONMENT”: “true” to the “env” section of our application’s app.json, which is used to initialize the new review applications.

Then, we added a tiny bit of logic to our config/environments/production.rb to skip CloudFront if “PR_ENVIRONMENT” is set to true:

1
2
3
unless ENV["PR_ENVIRONMENT"] == "true"
  config.action_controller.asset_host = "<url>"
end

Finally, we need to remove the PR_ENVIRONMENT variable from the environments where we want CloudFront to be enabled. Just run heroku config:set PR_ENVIRONMENT="" --app <app_name> for the appropriate app names.

This way, your review apps will serve assets directly from the application, while your production apps keep the whiz-bang speed of CloudFront.

Getting the Penultimate Element in Clojure

clojure

I recently signed up for Code Wars in order to get more practice using Clojure - although I’ve really enjoyed doing Project Euler problems in Clojure, they eventually become more mathematics-focused than any sort of programming focused, and don’t provide much practice working with real world application datasets and the like.

The first kata that I completed is penultimate: define a function that returns the second to last element of a Clojure ISeq.

Here’s my gut reaction:

1
2
3
(defn penultimate [lst]
  "Returns the second to last element of an ISeq"
  (first (rest (reverse lst))))

Note that doing reverse breaks the laziness of lst, though this is necessary as there is no way to determine what the ‘second to last’ element of a list is without knowing that the list is in fact finite.

First, I could have cleaned up that parentheses nest using the -> threading operator:

1
2
(defn penultimate [lst]
  (-> lst reverse rest first))

Much prettier.

Here are the most interesting alternative implementations, and what I learned from them:

1
2
(def penultimate
  (comp second reverse))

The comp macro, which I was not previously familiar with, takes functions as their argument and returns their function composition as a new function. This is a very powerful macro - here, we sidestep having to explicitly defn a function at all, and just define penultimate to be the function that is the result of composing some functions! Beautiful. Thanks to all the code warriors who posted this solution (zoldar, importsoul, apage43, Odomontois, denisw).

Finally, several of the other solutions used butlast. butlast returns a sequence of all but the last item in a collection, allowing the following solution:

1
2
(defn penultimate [lst]
  (-> lst butlast last))

Or, equivalently,

1
2
(defn penultimate [lst]
  (last (butlast lst)))

Though not as pretty of a solution as the comp solution, this is still a really handy function to have in the toolbox - thanks to everyone who submitted answers using butlast!

Dealing With Ember Content Security Policy Errors

ember

If you build Ember.js applications and have been keeping your ember-cli installation up to date, you will have run into or will soon run into Ember’s content-security-policy feature. You will likely have run into it if you start seeing errors in your javascript console to the effect of

1
Refused to load the script 'script' because it violates the following Content Security Policy Directive:

Content Security Policy is a really powerful way to help keep your users safe. It is also has a tiny bit of a learning curve, and is totally unnecessary for early-stage applications that are just being shown to friends and family.

Unfortunately, I know of no way to ‘whitelist all’ when working wiht content-security-policy. The only way to get the approximate effect of a ‘whitelist all’ command is to remove the ember-cli-content-security-policy package from your application’s package.json, and re-run npm install. This will remove the content-security-policy checking from your application - don’t forget to re-start ember serve!