Recent Posts

Getting Rails respond_with to recognize a custom media type

Posted on 23 Aug 2012

I am writing a sample hypermedia API in Rails, here, and recently came across some trouble trying to get Rails to recognize “application/hal+json” as a JSON format.

I ended up having to override respond_with to make it understand the HAL format. Not exactly what I hoped for, but it works.

config/initializers/mime_types.rb
Mime::Type.register "application/hal+json", :hal

ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup('application/hal+json')] = 
  lambda do |body|
    JSON.parse(body)
  end
app/controllers/application_controller.rb<
class ApplicationController < ActionController::Base
  ...

  private
  def respond_with(resource, options = {})
    super(resource, options) do |format|
      format.hal do
        render options.merge(:json => resource)
      end
    end
  end
end
app/controllers/home_controller.rb
HomeController < ApplicationController
  respond_to :hal

  ...
end

This method doesn’t give the benefit of auto status codes based on the HTTP verb. I did however get a monkey patch to work:

config/initializers/monkey_patching.rb
module ActionController
  class Responder
    def to_hal
      @format = :json
      api_behavior(nil)
    end
  end
end

The overload of respond_with is now no longer necessary, but we’re accessing a protected method.

Resources:

  1. http://stackoverflow.com/questions/8700332/rails-3-and-json-default-renderer-but-custom-mime-type
  2. http://http://api.rubyonrails.org/classes/Mime/Type.html
  3. https://github.com/oestrich/hypermedia_rails/commit/1fc2d721aa80c384867d7d374baf3eada6e166e6
  4. http://api.rubyonrails.org/classes/ActionController/Responder.html

cURLin' for Docs

Posted on 20 Aug 2012

This post was originally published on the SmartLogic Blog.

You may have docs for your API, but do you have an API for your docs? With RspecApiDocumentation and Raddocs, you can cURL for your documentation. Try out the following cURL command to see for yourself:

$ curl -H "Accept: text/docs+plain" http://rad-example.herokuapp.com/orders

This cURL trick might save you so much time you can go curling! (Sorry.)

  1. Install the gem
  2. Configure
  3. Write tests
  4. Host via public or Raddocs

Also available as a presentation.

What is the rspec_api_documentation gem?

The rspec_api_documentation gem (RAD for short) is a gem that lets you easily create documentation for your API based on tests for the API. You write acceptance tests and it will output docs.

Assumptions

  • rspec is installed and setup

Installing the gem

Installing RAD is as simple as adding it to your Gemfile. You might want to add it to a test and development group because it has a handy rake task for generating docs.

Gemfile
gem "rspec_api_documentation"
Install gem
$ bundle

Configure

Once RAD is installed there are several options you probably want to configure. Listed below are all of the default options that RAD ships with. The most common ones you will want to change are “api_name”, “docs_dir”, “format”, and “url_prefix”.

spec/spec_helper.rb
RspecApiDocumentation.configure do |config|
  # All settings shown are the default values
  config.app = Rails.application # if it’s a rails app
  config.api_name = API Documentation
  config.docs_dir = Rails.root.join(docs)
  config.format = :html # also: :json, :wurl, :combined_text, :combined_json
  config.url_prefix = “”

  # If you want cURL commands to be included in your docs,
  # set to not nil
  config.curl_host = nil # “http://myapp.example.com”

  # Filtering
  # If you set the :document key on an example to a particular group
  # you can only output those examples
  config.filter = :all

  # You can also exclude by keys
  config.exclusion_filter = nil

  # To use your own templates
  config.template_path = “” # The default template path is inside of RAD

  # Instead of sorting alphabetically, keep the order in the spec file
  config.keep_source_order = false

  config.define_group :public do |config|
    # When you define a sub group these defaults are set
    # Along with all of the parents settings
    config.docs_dir = Rails.root.join(docs, public)
    config.filter = :public
    config.url_prefix = /public
  end
end

Write tests

Tests for RAD are written in a DSL which helps assist in getting the metadata correct for properly formatting the outputted docs. Tests go in spec/acceptance.

spec/acceptance/orders_spec.rb
require 'acceptance_helper'

resource "Orders" do
  header "Accept", "application/json"
  header "Content-Type", "application/json"

  let(:order) { Order.create(:name => "Old Name", :paid => true, :email => "email@example.com") }

  get "/orders" do
    parameter :page, "Current page of orders"

    let(:page) { 1 }

    before do
      2.times do |i|
        Order.create(:name => "Order #{i}", :email => "email#{i}@example.com", :paid => true)
      end
    end

    example_request "Getting a list of orders" do
      expect(response_body).to eq(Order.all.to_json)
      expect(status).to eq(200)
    end
  end

  head "/orders" do
    example_request "Getting the headers" do
      expect(response_headers["Cache-Control"]).to eq("no-cache")
    end
  end

  post "/orders" do
    parameter :name, "Name of order", :required => true, :scope => :order
    parameter :paid, "If the order has been paid for", :required => true, :scope => :order
    parameter :email, "Email of user that placed the order", :scope => :order

    response_field :name, "Name of order", :scope => :order, "Type" => "String"
    response_field :paid, "If the order has been paid for", :scope => :order, "Type" => "Boolean"
    response_field :email, "Email of user that placed the order", :scope => :order, "Type" => "String"

    let(:name) { "Order 1" }
    let(:paid) { true }
    let(:email) { "email@example.com" }

    let(:raw_post) { params.to_json }

    example_request "Creating an order" do
      explanation "First, create an order, then make a later request to get it back"

      order = JSON.parse(response_body)
      expect(order.except("id", "created_at", "updated_at")).to eq({
        "name" => name,
        "paid" => paid,
        "email" => email,
      })
      expect(status).to eq(201)

      client.get(URI.parse(response_headers["location"]).path, {}, headers)
      expect(status).to eq(200)
    end
  end

  get "/orders/:id" do
    let(:id) { order.id }

    example_request "Getting a specific order" do
      expect(response_body).to eq(order.to_json)
      expect(status).to eq(200)
    end
  end

  put "/orders/:id" do
    parameter :name, "Name of order", :scope => :order
    parameter :paid, "If the order has been paid for", :scope => :order
    parameter :email, "Email of user that placed the order", :scope => :order

    let(:id) { order.id }
    let(:name) { "Updated Name" }

    let(:raw_post) { params.to_json }

    example_request "Updating an order" do
      expect(status).to eq(204)
    end
  end

  delete "/orders/:id" do
    let(:id) { order.id }

    example_request "Deleting an order" do
      expect(status).to eq(204)
    end
  end
end

DSL Methods of Interest

See https://github.com/zipmark/rspec_api_documentation/wiki/DSL

Host via public or Raddocs

Public

This is the easiest method. If you generate HTML or wURL HTML output then you can simply place the generated docs inside of the publicly accessible folder in your application when you deploy.

Raddocs

Raddocs is a simple Sinatra app that will take the JSON output from RAD and serve it up as HTML pages. The output is very similar to the HTML generated pages, but Raddocs allows us to have better asset handling than straight HTML.

  1. Generate :json and :combined_text output from RAD
  2. Configure Raddocs
  3. Mount Raddocs
spec/spec_helper.rb
RspecApiDocumentation.configure do |config|
  config.formats = [:json, :combined_text]
end
config/initializers/raddocs.rb
Raddocs.configure do |config|
  # output dir from RAD
  config.docs_dir = "docs"

  # Should be in the form of text/vnd.com.example.docs+plain
  config.docs_mime_type = /text\/docs\+plain/
end
config/routes.rb
match "/docs" => Raddocs::App, :anchor => false

For middleware

config/application.rb
config.middleware.use "Raddocs::Middleware"

Conclusion

You now have docs that won’t generate if your tests fail, making sure that they are correct. And you can view them in a console as well as the browser.

Image Source

Android ImageView

Posted on 19 Nov 2011

Android’s ImageView does a strange thing when you have it stretch to fit the width of the view but not the height. It creates a big square for the view instead of conforming to the image size.

This:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/red" />
</RelativeLayout>

Gives you:

Android ImageView no bounds

In order to fix this you can set the property “android:adjustViewBounds” to true, and the view will conform to the size of the image.

This:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/red" 
        android:adjustViewBounds="true"/>
</RelativeLayout>

Gives you:

Android ImageView with bounds

Much better.

Found from: http://stackoverflow.com/questions/5355130/android-imageview-size-not-scaling-with-source-image

Devise Authentication Token

Posted on 07 Oct 2011

I started using the devise authentication token for a project recently and was confounded on how to get the token from the client’s perspective. After fiddling with different ways, I finally found that adding :authentication_token to attr_accessible let’s it slide through the post to sign_in.

class User
  attr_accessible :authentication_token
end


    curl -d "user[email]=eric@example.com&amp;user[password]=password" 
    http://example.com/users/sign_in.json


    {"authentication_token":"RxYA7tzybe8E3H4q1zvb",
    "email":"eric@example.com"}

RVM and Cron

Posted on 23 Aug 2011

Today I set up my first real rails production server and in the process ran across an issue with cron and rvm. I have a rake task that I want to run every 15 minutes. I made a script that cd’d into the directory, loaded the correct ruby and gemset then did the rake task.

Every time cron ran though, it kept giving me the following error:

rvm: command not found
rake: command not found

After some quick googling I came across two posts that let me do the rake task:

Sourcing the rvm script was the part I was missing.

This was the final script I came up with:

#!/bin/bash

source ~/.rvm/scripts/rvm
cd /home/deploy/apps/my_app
rvm use 1.9.2@my_app
RAILS_ENV=production rake email:reminder
Creative Commons License
This site's content is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License unless otherwise specified. Code on this site is licensed under the MIT License unless otherwise specified.