Showing posts with label learning. Show all posts
Showing posts with label learning. Show all posts

Wednesday, April 17, 2013

Using ActiveSupport::TimeWithZone without Rails

My fast spec test suite does not load Rails (otherwise it's not so fast, right?), but I want to use the niceness of ActiveSupport::TimeWithZone to create a DateRange class like so:

class DateRange
  class DateRangeError < StandardError; end

  def initialize(range)
    @range = range
    raise_if_not_utc
  end

  def start_date
    range.first.beginning_of_day
  end

  def end_date
    range.last.end_of_day
  end

  private

  attr_reader :range

  def raise_if_not_utc
    if start_date.zone != 'UTC' || end_date.zone != 'UTC'
      raise DateRangeError.new('The date ranges must be in UTC.')
    end
  end
end

The corresponding tests didn't like Time.zone - they thought it was nil. Which it is.

The Fix

The tests need the proper ActiveSupport module loaded, and the time zone needs to be set.

require 'active_support/core_ext'
Time.zone = 'UTC'

Now the time zone is set and the tests will work as expected.

let(:range) { daterange.new(10.days.ago..time.zone.now) }

describe '#start_date' do
  it 'gets the beginning of the day for the earliest date in the range' do
    expect(range.start_date.to_i).to eq(Time.zone.parse('1999-12-31 00:00:00').to_i)
  end

  it 'is in UTC' do
    expect(range.start_date.zone).to eq('UTC')
  end
end

Sunday, June 10, 2012

Rails controller tests without Rails

In our continued path to faster tests, we've tried doing controller tests without loading Rails and using only what's minimally required to fully test the functionality. the idea is that not only will the tests be faster, but they will drive the controller's design to be thinner.

How did it work out? First the background.

We implemented a controller that received an OAuth callback from another site to allow their users to directly login to Crowdcast. The required code was minimal, so we tried to test minimally as well.

Since we're not loading Rails, we need to manually load some required libraries for the controller. We need enough to load the controller and its dependencies (in this case, a url generator class and the controller itself) successfully.

require 'uri'
require 'active_support'
require 'fast_spec/spec_helper'
require 'lib/crowdcast/url_generator'
require 'app/controllers/oauth_controller'

We also need to stub out calls to models, both ActiveRecord and plain old Ruby objects the controller uses. Here is where we realize how many of these we need and use the pain of stubbing yet another class to drive the design to remove that dependency.

stub_class 'UserSession'
stub_active_record_model 'Site'
stub_active_record_model 'SalesforceConnection'

Some are necessary, such as those for Authlogic and the relevant AR classes we're calling, but we were able to move calls to others out of the controller to make the tests easier to write and the code easier to read and maintain. All the usual good stuff we get from TDD and OOP.

Here's an example:

describe OauthController do
  let(:controller) { OauthController.new }

  describe "#callback" do
    let(:user_attributes) {{ :email => "email" }}
    let(:user) { stub("user", :single_access_token => "foo") }
    let(:site) { stub("site") }
    let(:redirect_to_url) { "https://foo.example.com:8080?foo=bar&baz=quux" }
    let(:state) { CGI.escape({:url => redirect_to_url, :subdomain => "foo" }.to_json) }

  before do
    controller.stub(:params) {{ :code => "foo", :state => state }}
    controller.stub(:current_user) { user }
    controller.stub(:session) {{ :return_to => "/return_path" }}
    Crowdcast::Config.stub(:salesforce) {{ :id => "bar" }}

    Salesforce::UserResolver.stub(:user_attributes_by_auth_code).with({ :id => "bar" }, "foo").and_return(user_attributes)
    SalesforceConnection.stub(:connect).and_return(user)
    Site.stub(:find_by_subdomain).with("foo").and_return(site)
  end

  it "creates a SalesforceConnection connection" do
    SalesforceConnection.should_receive(:connect).once.with(user, user_attributes)
    controller.callback
  end

  it "redirects to the return_to path with user's single access token" do
    controller.should_receive(:redirect_to).with(redirect_to_url + "&token=foo")
    controller.callback
  end
end

You can see that we need a fair amount of initial setup to get both the regular methods all controllers access and the specific ones we want to test or stub in the specific tests. It's still feels reasonable considering that we lose the Rails loading overhead and tests are incredibly fast.

Here is the controller:

def callback
  if params[:code]
    cc_user = connect_user_to_salesforce
    redirect_to Crowdcast::UrlGenerator.url(:url => return_to_url_from_callback, :params => { :token => cc_user.single_access_token })
  else
    flash[:error] = params[:error_description] if params[:error_description]
    redirect_to return_to_url_from_callback
  end
end

private

def connect_user_to_salesforce
  SalesforceConnection.connect(existing_or_autoregistered_user)
end

def existing_or_autoregistered_user
  current_user || Salesforce::UserAutoRegistrar.register(current_site)
end

def return_to_url_from_callback
  state_from_callback["url"]
end

def state_from_callback
  JSON.parse(CGI.unescape(params[:state]))
end

And now a problem.

We want to add some more Rails controller goodness in case there are exceptions (they're always where you least expect them). Check this out.

rescue_from Salesforce::UserResolver::UnsupportedAccountTypeError, :with => :account_error
rescue_from Salesforce::TokenResolver::AuthCodeExpiredError, :with => :expired_auth_code

def account_error
  render :status => :precondition_failed, :action => "account_error", :layout => "not_found"
end

Now we need to figure out how to get rescue_from or have more stubbing on the controller class. Before, when our controller was very lightweight, we could deal with the minimal amount of manual dependencies to get the speed increases. But at this point we decided to convert the controller to our "slow specs" folder, ie, our regular /spec folder, since the pain of managing the external dependencies reached a threshold we weren't willing to cross.

How did we decide this was the time? It wasn't anything specific but the overall feel of the code getting too complicated and stub-happy; we weren't getting pain from bad design but from using a framework.

Conclusions

Testing without Rails is still new, and we're still learning what works and what doesn't. Should we automatically test functionality that's coupled to the framework within the framework? I still say no, that we can get out of the framework if we use only a minimal subset that we can maintain manually. We decided to return to loading Rails when that subset was no longer minimal. That situation did not come up for some time, and it isn't a foregone conclusion that it always will. It's a developer's decision on the tradeoffs. Plus it was a great learning experience.

Sunday, February 12, 2012

Vimprovements

After the improvements I made to my Vim setup, I realized I could get that setup in a repeatable state without too many problems. Here's what I did.

The first decision was to not use Janus. I enjoyed my time with it, but I wanted to have full control over Vim and have a reason for each new piece of functionality. I went through the .vimrc and .gvimrc files and the list of plugins and took only the ones I used for day-to-day development. If I have a pain point and want more functionality, I'll find a solution organically, but I didn't want to have a kitchen sink setup where I didn't know if the functionality is Vim's or Janus'.

Don't feel that this means I dislike Janus, as I think it's a great environment, especially for those new to Vim, but it's time to move on.

I then installed pathogen for easy plugin management. I used submodules to install all the plugins I wanted so I'm able to easily update them through git. The two non-trivial installations were Command-T and javaScriptLint. I included instructions in the README for getting those installed. For the latter plugin, it's a nice thing to add the following line to your .vimrc file to get rid of the rather strong highlighting color:
let g:jslint_highlight_color=""
To get the .vimrc and .gvimrc files in the same folder, I renamed them without the dots and created symlinks with the original names in the root directory that pointed to them. Now everything was under version control and can go on whatever machine I want.

You can find my setup on github. Feel free to take what you like.

Thursday, December 22, 2011

Can We Have Too Many Tools?

Vim is a great editor. There are so many plugins that make it even better and increase my productivity. But can there be a saturation point where it's not worth finding the next plugin to shave a keystroke off of a command? I've been trying to find the sweet spot for the right amount of tools for the job.

Bigger Toolbox

Having more developers to talk to and work with, I have been exposed to different methods of development. Yan in particular, has been a tremendous Vim resource. He's made it his focus to optimize working with this editor as much as possible, and it's quite impressive. I've adopted some of his ideas, no longer content to be "good enough" with Vim.

Janus has been a tremendous help. It exposed me to command-T, which is now my favorite thing ever. I no longer use a buffer explorer because it's easier to just find it with command-T than search through the buffer list. I also rarely use NERD tree now, since I was using that mostly as a convenience to open project files. I'll still use it for looking in directories, but it's not open by default anymore.

From Yan, I've install git-grep and mapped K to search for the word under the cursor, and that's been such a pleasant, and faster experience, than using vimgrep. Yan is also cleaning up a plugin for rspec integration that provides some nice wins.

Too Big?

But how far do I take this path? I could continue to add plugins and map commonly used keystrokes to further increase efficiency, but when do I start to get diminishing returns? For example, I could map a letter to :GitGrep and save six keystrokes, but I haven't found the need to do that yet. Usually when I'm searching for something, I'm thinking about what I want to search for while typing the command, so I'm effectively multitasking and not wasting time with all that extra typing. Sometimes slowing down can be a good thing to allow that planning. Plus each new mapping or plugin is something new to learn, and it can occasionally become overwhelming with all the new options.

Just Right, For Now

I'll definitely continue to improve my Vim Fu, but I'm not in a hurry to continually add to my repertoire until I've mastered what I currently have available. I've come this far on a basic setup, and Vim has been around long enough that there have been many others who have gotten by with less, so I'm not going to stress out about not optimizing every single key I type.

Saturday, August 6, 2011

My name is Danny...and I make mistakes.

Yes, it's true. Here's what I did, and here's the reaction.



At work a few weeks ago, I was going through some callback code that sets some meta data on a model's associations. I noticed that a related flag wasn't getting set as well, that it was only set in one other specific instance with the meta data. Hmm...let's fix that, shall we? Flag added, moving on.

Star wipe to this week.

We found a bug when displaying historical data, and I quickly realized that the display was wrong because it was skipping over objects it shouldn't, objects that were flagged when they shouldn't be. Cue pants pooping.

There was a fix, and I would just need to run a script that would update the flag for all the associated models affected after the callback happens and ignore the other ones because those were the ones that explicitly have the flag set at the other, correct, time. But should I tell anyone or do I just run the script and say that I fixed the display bug? Well, WWJD (what would Jack Nicholson do)? He'd tell everyone, damn it, because he's like that. Keeping it real. Not like Chuck Norris.



Anyway, I sent out an email admitting what happened, and I included a high-level explanation of what happened along with a technical one. I explained that there is a fix and we won't lose any data, and that it would fix the current display bug but that we need to do some tests to make sure it didn't affect any other parts of the application.

My manager's response? "Hey, man, shit happens. Glad you fixed it." That's why I like working here.

Monday, May 2, 2011

I Am Become Genius

As follow-up to my previous post, I would like to share an article that's made the rounds on the internet recently. "How to get to Genius," an excellent synthesis of key ideas for success, correlates with themes from Pragmatic Thinking and Learning.

The working definition for "genius" is "the extreme form of insight...in terms of perspective," or similarly, the intuition an expert has gained from many years of deliberate study (Malcolm Gladwell's 10,000 hour rule). A genius has internalized a system so much that he is effectively part of that system. A genius/expert's perspective separates the novice because the latter does not understand either the rules of the system or the broad implications of why the system exists. It's the "ability to notice these vague connections amongst all the noise, amongst all the internal chatter going on inside your head, [that] separates the insightful from the unaware, the unobservant."

With evidence from neuroscience about brain plasticity, and using the computer/brain metaphor from Pragmatic Thinking, each read is a write, meaning that the act of remembering changes the memories of the brain and that constantly recalling information will write this information more permanently to memory, we bring together the necessity of much deliberate practice and seeing patterns within a system. The long hours thinking and practicing something will ingrain what's learned deeply in the mind, creating greater understanding and recall of the rules. This in turn grants the ability to understand more of what we study, and thus we can gain a broader perspective. With these tools, it becomes possible to see something different than if we were to only focus our attention on small details. Thus, becoming a genius, while certainly not easy, is, conceptually, simple and, more importantly, possible.

Tuesday, April 26, 2011

Book Review: Pragmatic Thinking and Learning: Refactor Your Wetware

The Pragmatic Programmers have consistently put out good material for years, and whether their own or other authors', the quality is almost always high. I just finished Pragmatic Thinking and Learning and, reading numerous other reviews, they are all very positive. The community liked it, and that's usually a good sign (we'll see what happens when not everyone is happy).

Important Relationships


The first major concept introduced, and repeatedly referenced, is that the relationships between objects are more interesting than the objects themselves. Discrete "things" - whether facts, concepts, or people, exist not in a vacuum but with other similar and different "things." Emergent behaviors and ideas spring up with the interactions between everything in their specific system. These specific contexts generate yet more powerful versions of the what is interacting in them.

A non-programming example is opening a locked door. The situation of why one wants to open the door matters in how one will attempt to open the door. Whether there is a baby in a burning house with a locked front door or there are documents we want to steal in a hotel room under security will influence the method used - an axe or lock picking tools. The why strongly influences the how and context matters.

Got Skills?


We then turn to the Dreyfus Model for Skill Acquisition and see how people learn new topics. Starting as a novice, one who needs context-free rules, like recipes, because he doesn't know enough to establish any big picture of the problem domain, and ending as an expert who understands the system he works in so well he works by intuition and pattern matching, there is a trend from stricter rules to practically none. The greater the expertise, the more we understand what is possible and what we should consider, both giving us faster access to what is possible and a smaller, and more easily understood, problem.

Impr-you-vement


The methods for improving skills is simple but not easy. Only through deliberate practice can we get to the highest levels of understanding, and this involves a lot of well-structured, hard work. We need a system to work on well-defined tasks that are appropriately challenging, continuous feedback from those tasks to keep us working on relevant ideas, and lots of repetition to strengthen the ideas and make them part of our long-term memories.

It also helps to find the relationships between what we're learning, looking at the big picture to understand the overall meaning and not be bothered with the minute details (at least not at the beginning). Analogies to previous knowledge further create relationships and help establish the context of the new ideas. For example, when reading a non-fiction book, we should first scan the table of contents and chapter summaries to get an overview of what we're going to read to establish the most ideas to focus on. While reading we should summarize the concepts, create metaphors with the material, and, when finished, expand the notes with a reread and discuss the ideas with colleagues. These tactics will cement the knowledge in the brain better than just a cursory skimming of the book.

Nothing is Perfect, However


One weakness of the book is the reliance on the left/right brain dichotomy, an idea that isn't totally backed by science. This criticism goes as deep as one wants to rely on the truth of this assertion, but if we think of it as a metaphor, like the main brain metaphor of a computer (another incomplete metaphor), and always remember that it's just a method to point in the general direction of understanding, and that we shouldn't understand these concepts to be almost literally accurate, we can get away with the comparisons. Regardless, I'm sure there is value in the takeaways such as creating analogies between disparate topics, drawing out ideas, talking out loud, and using other mixed media.

So What?


Pragmatic Learning is an excellent book, faults and all, and I did take away some concrete plans.

  • I've created a wiki for knowledge dumps and connecting ideas
  • I'm researching mind mapping software (either this one or this one)
  • I'm writing down my ideas in a notebook, along with notes on books I'm reading, which I then transfer to an online medium

There are dozens of other specific tasks to do, and I plan to come back to the book and implement more as needed. It's certainly not necessary to try every single one, but it is reasonable, if the motivation is there (and who wouldn't want to learn how to learn better?), the results will follow.