#rails #test

TL;DR

# in config/initializers/delayed_job_config.rb
Delayed::Worker.delay_jobs = true

# in your spec
Delayed::Worker.new.work_off

Common Setup of DelayedJob

Assume you follow DelayedJob readme example to configure it like this: Delayed::Worker.delay_jobs = !Rails.env.test?, what it does is in test env it doesn’t delay the job, meaning DelayedJob is being transparent, the job you put will be executed in “real time”. In most cases you don’t even need to worry about it, your test should be just fine, but recently it caught me…

It Fails When…

To give some background, I’m working on a API centric rails project. In order to authenticate with API we pass in access token for every request, and that’s done in the middleware layer. Since access token is stored in cookie, and in middleware we can’t access browser cookie directly, so another tool called RequestStore is used. If in the same request, what you stored in RequestStore you can access it later no matter the context, a unrealistic example would be you store a cookie value to RequestStore then use it in model later. Don’t do that :).

The code below is a simplified version to illustrate the flow.

class ApplicationController < ActionController::Base
  before_action :set_api_access_token

  def set_api_access_token
    RequestStore.store[:access_token] = cookies.signed[:access_token]
  end
end

class Authentication < Faraday::Middleware
  def call(env)
    env[:request_headers]['Authorization'] = RequestStore[:access_token] if RequestStore[:access_token]
    @app.call(env)
  end
end

Every api request happened inside the rails ApplicationController stack should have the access token being set, but what would happen in a different context like rake task or DelayedJob where you need to send request to the API? The before_action is not gonna be executed there so RequestStore[:access_token] would be nil. This is an easy-to-spot issue if you try it once, but if you follow the TDD work flow and write test for it first, then it’ll fail you.

With Delayed::Worker.delay_jobs set to false in test env, the job will be executed immediately in the same request, so the RequestStore[:access_token] still contains the value and will pass to the Authorization header in the middleware, spec passed but but in real world env it failed. Typical false positive result.

To Run It Manually

# in config/initializers/delayed_job_config.rb
Delayed::Worker.delay_jobs = true

# in your spec
# here is the code to enqueue a job to DelayedJob queue
visit post_path(post)
# run it manually
Delayed::Worker.new.work_off
# expectation
expect(api_endpoint).to have_been_requested
end

Delayed::Worker.new.work_off returns an Array like [1, 0] indicating succeeded job counts and failed job counts. I’ve also seen some people testing against that like expect(Delayed::Worker.new.work_off).to eq([1, 0]), personally I don’t think it’s necessary.

  1. You have your own expectation right after that and that should be the main concern of the spec. If the job failed, your spec should be failed too.
  2. What if multiple jobs are enqueued while you’re only focusing one of them in the spec? Update the value to [2, 0]? That’s just noise.

I guess what I encountered is a rare case, but definitely an interesting case. I kinda prefer this way to mimic real world environment to prevent any possible regressions.

Commercial time: If you’re about to build a API centric rails app, be sure to check out the awesome gem called spyke made by @balvig, the slogan is “Interact with remote REST services in an ActiveRecord-like manner.”

#book

“Have you ever found yourself stretched too thin? Have you ever felt both overworked and underutilized? Have you ever found yourself majoring in minor activities? Do you ever feel busy but not productive?”

This book is about answers to these questions. The key messages and takeaways for me are:

  • Life is a tradeoff. You can’t have it all, therefore you have to choose, choose what matters the most. Less, but better.
  • You have to give yourself permission to say No to others, so you can say Yes to your goal and make highest contribution.
  • It’s not about getting more things done, it’s about getting the right things done. Effective over productive. I love this so much.

If You Don’t Have Time to Read it…

Here is a great video reviewing this book, and actually I started reading it because of this video. To be honest I think it might be arguably better than the book itself. If you don’t have enough time, just check out the video, should take you less than 25 minutes.

Chapters I Would Like to Read Twice

Part I: Essence: What is the core mind-set of an Essentialist?

  • CHOOSE: The Invincible Power of Choice
  • DISCERN: The Unimportance of Practically Everything
  • TRADE-OFF: Which Problems Do I want?

Part II: Explore: How can we discern the trivial many from the vital few?

  • SLEEP: Protect the Asset
  • SELECT: The Power of Extreme Criteria

Part III: Eliminate: How can we cut out the trial many?

  • DARE: The Power of a Graceful “No”

Part IV: Execute: How can we make doing the vital few things almost effortless?

  • PROGRESS: The Power of Small Wins

Overview

The first part, the “What” part, is really great. All the problems introduced there are so real, I could map literary everything to my real life. Very inspirational.

But sadly the following chapters, the “how” part, are less compelling to me. I agree with the general idea, it’s just the examples and evidences are not convincing enough.

Overall it’s a good book, I’d rate it 4 out of 5. It does give me positive energies and courages to improve my life, makes me pause to think about the past, which choices were made by “default”(others) and which were made by “design”(me), makes me think twice before purchase any non-essential items, makes me observe my bad habits & routines and fix them, makes me pursue less but better.

p.s. I didn’t title this post as “Book Review”, as I realized the word “review” is too big for me, makes me want to cover every aspect of the book, pros and corns, scan every note and highlight I made during the reading, that is overwhelming and will simplify stops me from writing. “Done is better than perfect”, indeed, the essential thing for me is to write this post and share my findings!

#rails

ActiveJob is the headline feature in Rails 4.2, the Active Job Basics on RailsGuides explains the philosophy and usage very well, make sure you’ve checked that first. However there’re some gotchas if you want to use it right now in your Rails 4.1 app. Here I’m gonna show you how to install it in 4.1, and things you need to take extra care of.

Install ActiveJob in Rails 4.1

Add activejob to your Gemfile then bundle install.

Create a active_job.rb file under config/initializers and paste code below.

require 'active_job'
# or any other supported backend such as :sidekiq or :delayed_job
ActiveJob::Base.queue_adapter = :inline

Now you should be abel to load ActiveJob in your rails app without error.

Note that the one you installed is not the one inside the rails repository, that has a version of 4.2.0.beta2 same as Rails at the time of writing, the one you installed is version 0. You can find the archived source code from its original repository.

Creating a Job

To create a job, you have to manually create the app/jobs folder first, then follow the same naming convention to create your job class file like app/jobs/guests_cleanup_job.rb.

class GuestsCleanupJob < ActiveJob::Base
  queue_as :default

  def perform(*args)
    # Do something later
  end
end

Enqueuing the Job

GuestsCleanupJob.enqueue(record)
GuestsCleanupJob.enqueue(record, options)

Differences between latest ActiveJob

  • no rails generator for jobs.
  • no callback mechanism like before_enqueue, before_perform etc.
  • doesn’t load itself to rails app by default, that’s why you need a initializer to load it manually.
  • enqueue syntax is slightly different, in Rails 4.2.beta2 enqueue has changed to perform_later.
  • internally it’s using activemodel-globalid instead of GlobalID(GlobalID is renamed from activemodel-globalid).
  • setting backend syntax is slightly different
# Rails 4.2.beta2
Rails.application.config.active_job.queue_adapter = :delayed_job

# Rails 4.1
ActiveJob::Base.queue_adapter = :delayed_job

p.s. I haven’t checked ActionMailer. I’m currently using it with DelayedJob and so far so good.

Summary

ActiveJob is very convenient, it provides a unified interface for the job infrastructure that allows you to switch the backend easily.

But as you can see there’re big diffs between the latest developed version and the one now we’re able to install in Rails 4.1.

Is it worth it to make the effort to try it now, and push these small upcoming changes when you upgrade to Rails 4.2 to your mental stack? My suggestion is if you’re just right about to implement a queue system and willing to adapt to it, then it’s OK, otherwise maybe better just leave the current app running as is and wait for a more mature timing.

#event

I went to Rubykaigi 2014 last week, there were lots of great talks about ruby itself, rails tips and more. Here I’ll share 3 things I feel worth to share for rails developers.

Synvert

Speaker page on Rubykaigi2014

Synvert = syntax + convert, makes it easy to convert ruby code automatically

Synvert is a gem made by the same author of bullet gem. Using it you can convert your rails 3 before_filter to rails 4 syntax before_action, rspec should have to expect(…).to have or ruby 1.8 hash to 1.9 hash syntax with just one command, and it does support more. It’s well tested so no worry for human error.

You could define your own rules to convert code, or use those built-in very common used snippets for rails, rspec and factory_girl.

Installation and usage

gem install synvert

# fetch snippets to ~/.synvert directory
synvert --sync

synvert -r factory_girl/use_short_syntax

Run git diff I got

- user = FactoryGirl.create(:user)
+ user = create(:user)

How does it work

AST(Abstract Syntax Tree) is used internally, it parses your source code to meaningful structures. It’s like grammar in English, one phrase can be broken down to words and some are noun, some are verb, it’s like saying “let’s replace all the verb ‘walk’ to ‘run’”. I highly recommend you to check out the slide if you want to learn more.

Example of attributes for AST node

For instance, it breaks a method call to receiver, message and arguments. A typical method call is a type of “send node”, class definition is “class node” and there is “block node”.

Links

Going the distance

Speaker page on Rubykaigi2014

To be honest I’m not sure I fully understand the algorithm of calculating distance between 2 words, but the great part is @ schneems used it to improve rails generator command to suggest possible commands when you had a typo.

Please see the pull request for details. Emit suggested generator names when not found #15497

If you ever make a cli command, you could follow the same pattern, using the algorithm to suggest candidates instead of just showing plain error message.

Good to see how how he adapted scientific algorithm to solve real world problem.

Speeding up Rails 4.2

Speaker page on Rubykaigi2014

Interesting to see how @tenderlove find room for optimization and how to measure it.

Tools mentioned:

  • benchmark-ips, benchmarks a blocks iterations/second. For short snippits of code, ips automatically figures out how many times to run the code to get interesting data. No more guessing at random iteration counts!
  • allocation_tracer, allows to trace object allocation.

Slide on speakerdeck

#octopress

Here I’ll show you how to recover or restore your local octopress repository, for instance you’re on a new machine, or just lost your local copy of it.

It requries you to have a repository on github that you’ve deployed your octopress posts.

First I recommend that you read this post: Clone Your Octopress to Blog From Two Places, the Recreating a local Octopress repository section.

And here is what worked for me, please replace kinopyo with your user name.

git clone -b source [email protected]:kinopyo/kinopyo.github.com.git octopress
cd octopress

gem install bundler
bundle install

rake preview
open http://localhost:4000

Tips on Converting HTML to Markdown

Somehow the latest 3 posts are still missing after I git cloned my remote repository. I checked in advance and saved those copies as html. And after I restored my octopress repository I used Pandoc to convert those html to markdown.

It works like:

pandoc -f html -t markdown your_html_file.html

Thoughts on Disaster

p.s. I stored my blog octopress repository on Dropbox and somehow it just all gone, it really shocked me. Probably it was my fault not theirs, but still I always thought Dropbox is a safe place, but I don’t even have a clue why it happended and somehow could’t see the history and restore from their website. After I noticed it everytime I came up with something to write I’ve had used it as an excuse, or maybe I was not in the mood to deal with it(also I didn’t know how…).

So I need a fallback solution about Dropbox, every disaster changes you to rethink about current seems comfortable situation. And hope I’ll write more often :)

#work

Lots of big changes happened in my life recently, one of them is my workplace. Lucky enough, now I got a chance to embrace the modern style of work, that is working remotely.

I suppose you know more or less about remote. 37signals has published their new book, REMOTE: Office Not Required, where all the benefits you can get from remote is described. I’m half way done of that book, and probably will write another post once I finished it and compared it to my real experience. So here I’m gonna talk about only one thing, a key thing, trust.

I’m a true believer of remote work. As in the book says,

The bottom line is that you shouldn’t hire people you don’t trust, or work for bosses who don’t trust you. If you’re not trusted to work remotely, why are you trusted to do anything at all?

I totally agree with that. But in reality is, well at least the environment around me is even every single manager or boss is good and kind, we still fall into the traditional working style, 9:00 - 18:00+ in the office, that’s the norm, so dominant, I didn’t even try to convince anyone to change that.

Fortunately I got a very unique opportunity to do remote work. After the first day, I should say the biggest and most important “get” is not about the productivity, freedom or flexibility, although they’re truly there, it’s about the trust I’ve never experienced at work.

“I trust you. So no matter what, when and where you do the work, I don’t care, as long as we’re moving forward.” That was pretty much the key conversation I had. It was a really warm feeling. I don’t know how to explain it in words exactly, to be honest I did see that remote work coming before I joined. But when it really happened, I was flattered! I’ve never got this kind of tremendous trust, it means a lot to me as a person. I’m sure I’m not gonna waste it.

Trust is the first step to remote work. It’s personal and fundamental. If you trust your teammate, with proper tools and communications I really think remote work will work!

#capybara

I got a brand new Mac Air recently, when I tried to install capybara-webkit I encountered some problems.

The Error

Got this error when bundle install one rails app.

Installing capybara-webkit (0.12.1)
Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

    /Users/qihuan-piao/.rvm/rubies/ruby-2.0.0-p247/bin/ruby extconf.rb


Gem files will remain installed in /Users/qihuan-piao/.rvm/gems/ruby-2.0.0-p247/gems/capybara-webkit-0.12.1 for inspection.
Results logged to /Users/qihuan-piao/.rvm/gems/ruby-2.0.0-p247/gems/capybara-webkit-0.12.1/./gem_make.out

An error occurred while installing capybara-webkit (0.12.1), and Bundler cannot continue.
Make sure that `gem install capybara-webkit -v '0.12.1'` succeeds before bundling.

Tried brew install qt didn’t get luck, raised this error:

brew install qt4 --build-from-source
==> Downloading http://download.qt-project.org/official_releases/qt/4.8/4.8.5/qt-everywhere-opensource-src-4.8.5.tar.gz
Already downloaded: /Library/Caches/Homebrew/qt-4.8.5.tar.gz
==> ./configure -prefix /usr/local/Cellar/qt/4.8.5 -system-zlib -confirm-license -opensource -nomake demos -nomake examples -cocoa -fast -release -no-3dnow -L/opt/X11/lib -I/opt/X11/include -pl
==> make
                                   ^
7 errors generated.
make[2]: *** [.obj/release-shared/qdrawhelper_ssse3.o] Error 1
make[1]: *** [release] Error 2
make: *** [sub-gui-make_default-ordered] Error 2

READ THIS: https://github.com/mxcl/homebrew/wiki/troubleshooting

These open issues may also help:
    https://github.com/mxcl/homebrew/pull/22283
    https://github.com/mxcl/homebrew/issues/23480

Solution

Checked all those links and tried several things, eventually downloading qt installer from the qt-project website solved my problem.

Download link

Just install the debug libraries, qt libraries then bundle install again.

Note

If you already got qt installed and capybara-webkit worked before upgrade to Mavericks, then it’s probably gonna work as well.