Upgrading Rails 5 Controller Tests

Posted by Brian in News (July 3rd, 2016)

If you're embarking on a Rails 5 upgrade, or just trying to figure out what's involved, you'll find lots of pieces of information spread around. But I'm summarizing this here for my own benefit and for others.

No more unit tests for controllers

Rails 5's controller tests are now integration tests. This means you lose the ability to interact with many of the internals. And if you've built apps of any reasonable size with controller tests, you'll find a lot of the hacks you've been using are no longer there.

URLS, not actions, are required.

Instead of

get :index

you'll need to do

get root_path

In other words, you'll use full URLs in your tests, or use the URL helpers for your routes.

And when sending params for looking up records, instead of

get :index, {id: 1}

You'll want to use

@post = posts(:one) # if using a fixture
get posts_path(@post)

You must use named parameters for params

So if you are doing a create, instead of doing this:

post posts_path, {post: { title: "First post", body: "This is the body"}

you'll need to make it less ambiguous, as these helpers now require named parameters:

post posts_path, params: {post: { title: "First post", body: "This is the body"} }

No more access to request

The request object has been removed. If you've been using it to set headers, that's no longer possible.

So instead of trying to use

request.env["HTTP_AUTHORIZATION"] = something....

You'll need to send it along on each request:

get admin_path, headers: {'HTTP_AUTHORIZATION' => something }

Mo more access to session

If you're used to sending along session data with your requests, like for authorization, know that you can't do that either. Instead, you'll have to actually hit the login page of your site. DHH explains this in his response to this Github issue.

  def sign_in_as(name)
    post login_url, params: { sig: users(name).perishable_signature )
  end
  setup { sign_in_as 'david' }

This is exactly how it's done in integration tests, so this isn't a surprise, but no doubt your existing apps have tests that directly pass the session on each request. You'll need to change those.

No more access to assigns and assert_template

If you’ve used `assigns` to access instance variables, or used `assert_template` to ensure a specific template was rendered, those tests will no longer work. There is a gem that brings this behavior back, but I won’t link it because I don’t trust that it’ll be maintained, and the way forward is clear – use `assert_select` to look for things on the page or in the response.

The days of controller tests as units are over, and like me, you may be now questioning the value of controller tests at all going forward, since they are just integration tests.

Basic Authentication in Ruby on Rails – In Case You Forgot

Posted by Brian in News (March 11th, 2010)

There are so many authentication choices in Rails these days, but sometimes the simplest approach is the best approach. I’m working with a client right now building an application that has a mostly-public interface. Only a handful of people need to log in to the site, and they only need to modify content occasionally. It’s not a complicated project at all, and while my first instinct was to reach for my starter project that I usually use for these kinds of things, I thought again and realized the following:

  1. This project doesn’t need to let people sign up.
  2. There’s no need to send password recovery instructions
  3. Much of the data entry will be done with a mobile device connecting to the app’s REST-style XML API.

Something like Authlogic, or even Restful Authentication seems like overkill for something this simple. Many Rails developers are probably used to their solutions because many Rails projects are more complex than this. In the spirit of keeping things simple, I’m going to show you how to do authentication of users without any plugins, the way we used to do it in 2005. (For those of you that have been working with Rails as long as me, this will be a good refresher for you. When was the last time you hand-rolled your authentication solution?)

Back To Basic (Authentication, that is)

Since Rails 2.0, Basic Authentication has been an option, as Ryan Bates explains in Railscast #82. We’ll use that and a basic user model to authenticate our users.

First, generate a user mode with login and a hashed password fields. I’m not going to do any salting here, as it’s not necessary. If you want it, you should be able to add it easily.

ruby script/generate model User login:string hashed_password:string

Now we need to modify the User model to do the password encryption.

First, set up the validations and the attribute accessors for the password and password_confirmation fields.

  validates_presence_of :login
  validates_confirmation_of :password
  attr_accessor :password

Next, be a good developer and write a unit test for hashing the password.

  test "should create a user with a hashed password" do
    u = User.create(:login => "homer",
                 :email =>"homer",
                 :password => "1234",
                 :password_confirmation => "1234")
    u.reload # (make sure it saved!)            
    assert_not_nil u.hashed_password
  end

Prepare your test database

rake db:test:clone

and run your test

rake test:units

With the test in place, write the code to encrypt the password on save. Add the SHA1 digest library at the top of your class:

require 'digest/sha1'

Then add the before filter and an encryption method:

  before_save :encrypt_password
 
  def encrypt_password
    unless self.password.blank?
      self.hashed_password = Digest::SHA1.hexdigest(self.password.to_s)
      self.password = nil
    end
    return true 
  end

Run your test again and everything should pass.

rake test:units

Authenticating Users

Now we need to write a class method that we can use to grab a user from the database by looking up their username and hashed password. We’ll use a simple pattern for this. First, let’s write a quick test:

  test "given a user named homer with a password of 1234, he should be authenticated with 'homer' and '1234' " do
    User.create(:login => "homer",
                 :email =>"homer",
                 :password => "1234",
                 :password_confirmation => "1234")
    assert User.authenticated?("homer", "1234")
  end

We create a user and then call User.authenticated?. If its return value evaluates to True, we’ve got a good set of credentials. Add this class method to your User model to make your test pass:

  def self.authenticated?(login, password)
    pwd = Digest::SHA1.hexdigest(password.to_s)
    User.find_by_login_and_hashed_password(login, pwd)
  end

Notice that here, I’m actually returning the user object, rather than a boolean. If no user is found, nil is returned which evaluates to false. Remember, in Ruby, everything except nil and false evaluates to True.

Our entire user model looks like this:

require 'digest/sha1'
class User < ActiveRecord::Base
  
  validates_presence_of :login
  validates_confirmation_of :password
  attr_accessor :password
  
  before_save :encrypt_password
  
  def self.authenticated?(login, password)
    pwd = Digest::SHA1.hexdigest(password.to_s)
    User.find_by_login_and_hashed_password(login, pwd)
  end
  
  private
  
  def encrypt_password
    unless self.password.blank?
      self.hashed_password = Digest::SHA1.hexdigest(self.password.to_s)
      self.password = nil
    end
    return true 
  end
end

Creating the Filter

Let's create a simple Projects scaffold. We'll use our authentication to protect this scaffolded interface.

ruby script/generate scaffold Project name:string description:text completed:boolean

Now, open app/controllers/application_controller.rb and this code:

  def authenticate_with_basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      @current_user = User.authenticated?(username, password)
    end
  end

This tiny bit of code will pop up a Basic Authentication credentials box and look the user up in our database using the supplied credentials. If we find our user, we put it in the @current_user instance variable. It's common practice in Rails apps to have a current_user helper method, so we can add that to application_controller.rb as well.

  helper_method :current_user

  def current_user
    @current_user
  end

With that, we simply need to invoke the filter. Open your projects_controller.rb file and add this to the top:

before_filter :authenticate_with_basic_auth

And that's it! You've protected the application. Create a user via the Rails runner, fire up script/server and test it out!

ruby script/runner 'User.create(:login => "homer", :password => "1234", :password_confirmation => "1234")
ruby script/server

You should also be able to use cURL to play with the XML REST-style API provided by the scaffold generator.

Get projects:

curl http://localhost:3000/projects.xml -u homer:1234

Create project via XML

curl http://localhost:3000/projects.xml \
-u homer:1234 \
-X POST \
-d "Test" \
-H "Content-Type: text/xml"

Simple is Good

This simple solution is easy to write, easy to maintain, and easy to extend. It's also something that anyone with any practical experience with Rails should be able to write in as much time as it would take to configure Authlogic.

So what's left to do with this? First, SHA1 isn't great encryption - it's just hashing. BCrypt might be better, Adding a salt to this hash might be another good idea too. Some more tests would be great. So, go write them, make this fit your security needs, and have fun! As always, I'd love to hear your comments.

Why Rails?

Posted by Brian in News, Rails (February 16th, 2010)

NAPCS was a proud sponsor of the first Chippewa Valley Ruby Camp, a day-long Ruby training camp where 23 students learned how to build and deploy their first Rails application. I taught two of the three sessions and had a great time helping other developers get their hands on what I believe to be the best way to develop scalable, maintainable, and stable web applications today. That’s a pretty bold statement, but I believe in it, and it’s why NAPCS uses Rails on all new client projects. (In fact, every project since 2006 has been a Rails project.)

Rails projects are quick to launch

With Rails, we can build and launch a prototype application in an extremely short time. On average, we can have something simple in front of the client in less than a couple of days, which is much faster than our previous projects where we used ASP or PHP. And that project isn’t usually a throwaway project; we can tweak it and move forward, from prototype to production.

Rails applications are easily testable

Professionals write tests that prove the code works as it should, and since testing is built right in to the Rails framework. testing is an easy natural part of the process. Testing has always been possible regardless of the language used, but with Rails, it’s so easy to produce well-tested code that you’d be foolish not to test. For my customers, that means much better products, and less support calls.

It’s a standard framework

I occasionally pick up projects from other developers, and while I can’t always ensure that the quality of the code will be good, I at least already know my way around the project because, in a Rails application, conventions dictate where things go. This means the learning curve is lower when we transition an application, and the customer doesn’t get billed extra time for me to figure out what’s going on.

The community is incredible

We rely heavily on open-source projects to get stuff done, and Rails has an amazing community that is always pushing the limits of what Rails applications can do. There is a new solution to a new problem almost every day, and that keeps us all on our toes. Plus, we’re very proud to be sponsoring the Rails Mentors project, which helps other developers get better at Rails development. We’re always giving back to open source, too.

It gets out of the way.

This is the most important point of all; Rails lets me deliver features. Instead of spending hours wiring up database tables to web pages, I can do that in five minutes and spend more time focusing on user experience and new features. And since it isn’t difficult to build things incrementally, I don’t get boxed in. I can make changes without feeling that I’ll lose days of work. It allows me to respond flexibly to new feature requests.

Rails gives us a competitive advantage. We cannot always compete on price alone, but we can provide better-quality solutions than others because we embrace an open, agile framework that lets us deliver stable, scalable, well-tested, and maintainable web applications.

Want to learn how you can take advantage of Ruby on Rails?

Contact us for information on customized training and mentoring services. We offer affordable hourly rates for remote mentoring, as well as custom training classes upon request.

Chippewa Valley Code Camp 2009

Posted by Brian in News (November 15th, 2009)

I had the honor of presenting two talks this year at the second annual Chippewa Valley Code Camp. Code camps are free events where programmers of any skill level come together for a day of sessions and talks that are centered around code. I’ve only recently become involved with these events and am extremely happy I have. The opportunity to learn from so many experienced developers is incredible.

We held the event at UW-Stout this year, and had a great turnout. I gave two talks, the first on Ruby, and the second on Cucumber. I had wonderful audiences both times, and met some great people that I hope I get to work with in the future. Here are the slides for my talks.

See the end of the slide decks for links to the demo source code.

“Introduction to Ruby” talk from Twin Cities Code Camp 7

Posted by Brian in News, Projects (October 25th, 2009)

On Saturday, I had the honor of giving a talk on the Ruby programming language to an exceptional audience. I talked about the simple syntax and powerful libraries available, and then showed how we can use Ruby to maintain static web sites, build web applications, and test web sites.

During the talk, I showed how to use Sinatra to create a very simple wiki. I wrote two versions. During the talk, I used SQLite3 and ActiveRecord, but I wrote a version that uses MongoDB. You can grab the source for those at Github.

Here are the slides from my talk. You can view the notes too by viewing the slides on Slideshare.

Participating at code camps is something I really enjoy. It gives me a chance to talk about what I love, but it also gives me a chance to learn from other speakers. These events are usually free, and an awful lot of fun.

lazy_developer gets some TLC

Posted by Brian in News, Projects, Rails (June 10th, 2009)

lazy_developer is a Ruby on Rails plugin I use on a lot of my projects to make my life easier as a developer. It’s a collection of Rake tasks that automate some common operations. Some of the more interesting features it provides are

  • the ability to dump a database to YAML format and then pull it back in again
  • a simple way to obliterate models, controllers, views, helpers, and related files easily (in the case of a refactor or a fat-fingered typo
  • a method to compact your migrations into a single migration file
  • and of course, automatically clone your test database whenever you migrate

Today, Kevin Gisi and I gave this plugin some much-needed love and attention after we discovered a few problems. Here’s what’s new:

Data exporting works with Rails versions prior to 2.0

Got an old database you’d like to pull in? This now works in Rails 1.2.3!

Data dumping works much better now!

We noticed some duplicate records sneaking into the output files, and it was due to a mistake I made when I implemented my own version of record pagination. It wasn’t limiting correctly, and Kevin quickly spotted the reason why. It also wasn’t storing records in the YAML file properly either, which I also resolved.

This is tested on Microsoft SQL Server, MySQL, and SQLite3.

Migration compacting works now

This was patched a few days ago and merged in, but I flip-flopped a couple of lines during a merge and it made it into the master branch that way. Kevin decided he’d like support for Subversion for this, so he added it. I’ll add in Git support very soon.

Interested in using this on your projects? Go get lazy_developer right now!

Slides from “Learning To Walk In Shoes” presentation

Posted by Brian in Howto, News (May 14th, 2009)

As promised, here’s the slide deck from my talk “Learning To Walk In Shoes” from April’s Twin Cities Code Camp. After the slides, we went over some demo applications, and you can get those from Github so you can play around with them yourself.

Learning To Walk In Shoes

Rails and Legacy Databases

Posted by Brian in News, Rails (May 7th, 2009)

Here are the slides from my RailsConf 2009 talk. You can watch the slides below, download the PDF.

Rails and Legacy Databases (PDF slides)

RailsConf – Don’t Mock Yourself Out.

Posted by Brian in News, Rails (May 5th, 2009)

David Chelimsky gave a great presentation on mocking and stubbing. I liked the fact that he talked about a lot of the fears that people have when they do rely on mocks and stubs. It was also nice to see someone clearly state that one of the problems we have in the Rails community when it comes to testing frameworks (and most other libraries) is that people tend to promote their own projects while trashing other libraries. He challenged an audience member to create a site where we could write up comparisons.

Highlights of the talk:

  • Stubs vs. mocks
  • “Ravioli” code where everything is clumped up in nice separate concerns
  • “Calzone” code, like Rails, which is harder to test in isolation.
  • Stubble, a wonderful-sounding library that can completely stub out an ActiveRecord object, which will be usable in every testing framework.

RailsConfigModel updated

Posted by Brian in News, Projects, Rails (April 22nd, 2009)

The Rails Config Model gem makes it extremely easy to create a “settings” table in your application. It creates a configuration controller and model that can be used to quickly create configuration table for your system so you can store system-wide variables that you’d like the site administrator to be able to set.

You can see instructions on usage or contribute to the project.

Next Page »