Showing posts with label ruby on rails. Show all posts
Showing posts with label ruby on rails. Show all posts

Friday, January 17, 2014

Cleaner Rails JSON API Controller Specs with OpenStruct


As many of us know, Ruby on Rails makes it really easy to write RESTful APIs. Paired with a rich client-side framework, we can create applications with slick user interfaces. A common approach for this is to write JSON APIs on the server for consumption by a Javascript front-end framework. To test my APIs, I like to use RSpec and build specs for the actions on each of the controllers. The goal of the tests is to make sure each JSON response returns the correct information in the proper structure.

TL;DR Example code on Github

How about an example? Let's say we have a model named Article:
class Article < ActiveRecord::Base
  attr_accessible :title, :body
end
Following standard TDD practices, we begin by writing a test. Here's a first crack at a controller spec describing the create action on our ArticlesController:
describe ArticlesController do
  describe '#create' do
    let(:title)  { 'New Title' }
    let(:body)   { 'New Body' }
    let(:attrs)  {{ title: title, body: body }} # our new Article
    let(:params) {{ format: :json, article: attrs }}

    before { post :create, params } # make the request

    it 'creates a new Article' do
      Articles.all.count.should == 1
    end

    it 'returns the title' do
      response.body.should include(title)
    end

    it 'returns the body' do
      response.body.should include(body)
    end
  end
end
The first assertion makes sure an Article was actually created. The next two assertions check the response. A successful creation should return the new Article's attributes in a JSON object. With the spec written, we can implement our controller:
class ArticlesController < ApplicationController
  respond_to :json
  
  def create
    @article = Article.new(params[:article])

    if @article.save
      render json: @article # { title: 'New Title', body: 'New Body' }
    end
  end
end
When we run our test, it passes. However, our test is incorrect! What if, for some odd reason, the title and body were actually swapped in the response (title has body, body has title)? The test would still pass! While our spec ensures the correct values are in the response, it doesn't ensure the proper structure. Let's try again:
describe ArticlesController do
  describe '#create' do
    let(:title)  { 'New Title' }
    let(:body)   { 'New Body' }
    let(:attrs)  {{ title: title, body: body }}
    let(:params) {{ format: :json, article: attrs }}

    before { post :create, params }

    # removed Article count for brevity

    subject { JSON.parse(response.body) }

    it 'returns the title' do
      subject['title'].should == title
    end

    it 'returns the body' do
      subject['body'].should == body
    end
  end
end
In this version, we make our test more descriptive by declaring our response object as the subject. We also parse our response into a Hash by using JSON.parse() in the standard Ruby JSON library. Now, our assertions make sure we get the correct attributes from the proper keys in the response object. We also get the added bonus of ensuring a valid JSON response by parsing it in our subject.

Our test is now correct, but it can still be improved. When I'm testing JSON responses, I like to use OpenStruct to clean up my assertions. In simple terms, an OpenStruct takes a Hash and returns an object with methods named according to each key in the Hash. So, we can make the keys in our JSON response behave like methods on an object! Using OpenStruct, here is a prettier version of our test:
describe ArticlesController do
  describe '#create' do
    let(:title)  { 'New Title' }
    let(:body)   { 'New Body' }
    let(:attrs)  {{ title: title, body: body }}
    let(:params) {{ format: :json, article: attrs }}

    before { post :create, params }

    subject { OpenStruct.new(JSON.parse(response.body)) }

    its(:title) { should == title }

    its(:body) { should == body }
  end
end
Compared to our first draft, this spec is more concise, correct, and straightforward.

How do you like to use OpenStructs? Any thoughts, tips, and tricks are appreciated. Let me know in the comments!

Friday, May 25, 2012

Rails: Updating Association Models w/ Form Checkboxes

Disclaimer: I am a Rails nublet, so please correct my speculation on behavior if needed!

I wanted to create a web form allowing a user to add/remove an object's association with another object via a third object. What?! In the world of databases, this is called an association table. Hopefully, I can make this more clear below.

The Model

Let's say we are modeling a clinic/pharmacy and we want patients to be able to request refills on their medications:

app/models/patient.rb
class Patient < ActiveRecord::Base
  has_many :medications, dependent: :destroy
  has_many :refill_medications, through: :refills
end

app/models/medication.rb
class Medication < ActiveRecord::Base
  belongs_to :patient
end

app/models/refill.rb
class Refill < ActiveRecord::Base
  belongs_to :patient
  belongs_to :medication
end

The Interface

With our model defined, we want an interface that will list each medication for the patient along with a checkbox. If the checkbox is checked, a refill will be created, linking the patient to his/her medication. This interface will look like the following:

Please check the medications you want refilled:





The html.haml for the form, using Rails, will look something like this:
= form_for @patient do |f|
  = hidden_field_tag "patient[refill_medication_ids][]"
  - @patient.medications.each do |medication|
    = check_box_tag "patient[refill_medication_ids][]", medication.id, @patient.refill_medications.include?(medication)
    = medication.name

The Rails Magic

Taking advantage of Rails conventions, the attribute refill_medication_ids will be made available for use. Rails will automagically create this for you! This will be the ids for all medications with a refill (since it uses a has_many :through).

So that means on form submit, we are actually updating refill by modifying refill_medications. By passing a list of medication_ids, Rails knows to create a refill with that medication_id for the patient.

Why the Hidden Field?

We add a hidden field to the form because we want the form to pass an empty list when none of the checkboxes are selected. This will completely remove all refills for any medications, which is the intended behavior.