{"id":4508,"date":"2013-04-08T12:02:55","date_gmt":"2013-04-08T09:02:55","guid":{"rendered":"http:\/\/railsware.com\/blog\/?p=4508"},"modified":"2025-02-14T15:23:04","modified_gmt":"2025-02-14T12:23:04","slug":"api-with-ruby-on-rails-useful-tricks","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/api-with-ruby-on-rails-useful-tricks\/","title":{"rendered":"API with Ruby on Rails: useful tricks"},"content":{"rendered":"\n<p>This article is about gotchas you should be aware of while building your API <a href=\"https:\/\/railsware.com\/blog\/ruby-on-rails-guide\/\">with Ruby on Rails<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Controller tricks: API on Metal<\/h2>\n\n\n\n<p>Sooner or later each <a href=\"https:\/\/railsware.com\/blog\/how-to-hire-good-ruby-on-rails-developer\/\">Rails developer<\/a> come to a point when he wants to build his first API.<br>Among the first things you have to take care of are your controllers.<br>If you want your API to be fast (and I bet you do) then you should consider using <a href=\"http:\/\/weblog.rubyonrails.org\/2008\/12\/17\/introducing-rails-metal\/\">ActionController::Metal<\/a>.<br>The trick is that <code>ActionController::Base<\/code> has many Middlewares that are not necessary for the API, by using Metal controller with minimum modules included, the one can achieve <a href=\"https:\/\/gist.github.com\/738168\">up to 40% speedup<\/a>.<br>Lets see what your basic metal controller may looks like:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class Api::V1::BaseController &lt; ActionController::Metal\n  include ActionController::Rendering        # enables rendering\n  include ActionController::MimeResponds     # enables serving different content types like :xml or :json\n  include AbstractController::Callbacks      # callbacks for your authentication logic\n\n  append_view_path \"#{Rails.root}\/app\/views\" # you have to specify your views location as well\nend<\/pre>\n\n\n\n<p>Unfortunately NewRelic doesn&#8217;t support Metal by default, so you have to <a href=\"https:\/\/newrelic.com\/docs\/ruby\/adding-instrumentation-to-actioncontroller-metal\">add monitoring manually<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Routing: use versioning<\/h2>\n\n\n\n<p>Nobody&#8217;s perfect. So are we.<br>Your API will definitely be changed and extended multiple times in the future so you better take care of your versioning at the beginning.<br>As you noticed, <code>BaseController<\/code> wrapped in <code>V1<\/code> namespace. Use something like this in your routes:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">namespace :api do\n  namespace :v1 do\n    # put your routes here\n  end\nend<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Views tricks: RABL &#8217;em all<\/h2>\n\n\n\n<p>You don&#8217;t want to burden your code with logic of exposing different model fields for different API actions, right?<br>In this case you should use some template engine. <a href=\"https:\/\/github.com\/nesquena\/rabl\">RABL<\/a> is at your service. Here&#8217;s an example of your view:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">object @object\n\nattribute :public_id => :id\nattributes :title, :created_at, :source\n\nchild :contacts do\n  attributes :title\nend\n\nchild :files do\n  attributes :filename, :content_type, :size\nend<\/pre>\n\n\n\n<p>Also that will save you time by getting rid of ugly <code>respond_to<\/code> blocks. Instead of<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def show\n  @object = Object.find(params[:id])\n  respond_to do |f|\n    f.json { render json: @object.to_json }\n    f.xml  { render xml: @object.to_xml }\n  end\nend<\/pre>\n\n\n\n<p>You can simple do literally Nothing<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def show\n  @object = Object.find(params[:id])\nend<\/pre>\n\n\n\n<p>Just make sure you have a RABL view in a corresponding directory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Security<\/h2>\n\n\n\n<p>There&#8217;re plenty of articles about API security best practices with OAuth and <a href=\"https:\/\/fusionauth.io\/articles\/identity-basics\/what-is-oidc\" target=\"_blank\" rel=\"noopener\" title=\"OIDC\">OIDC<\/a>. Another convenient way is to simply use token passed in the query string. If you ended up with token keep in mind that you can easily generate it by calling <code>SecureRandom.urlsafe_base64<\/code>. To make sure token is unique you can use something like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def build_token\n  begin\n    token = SecureRandom.urlsafe_base64\n  end while User.exists?(api_token: token)\n  token\nend<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Hiding your IDs with GUIDs<\/h2>\n\n\n\n<p>By default Rails uses incremental integer for primary key.<br>Common practice is not to expose these kind of IDs to the public via your API because users can guess other IDs in your database and that might be a potential risk.<br>To solve this you can come up with a simple algorithm that will convert your IDs into some &#8220;safe&#8221; form and back.<br>But still it&#8217;s not super safe because someone can find out what the algorithm is.<br>Another possible solution is to expose <a href=\"http:\/\/en.wikipedia.org\/wiki\/Globally_unique_identifier\">GUIDs<\/a> to the public.<br>It&#8217;s a 128 entity that typically looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11<\/pre>\n\n\n\n<p>To generate it in Ruby use <code>SecureRandom.uuid<\/code> (generates V4 GUID).<br>You can store it as a simple string column but if you are using Postgresql then you can utilize it&#8217;s built in <code>uuid<\/code> type &#8211; this can <a href=\"http:\/\/simononsoftware.com\/how-to-store-uuids-in-postgresql\/\">save you a lot of space<\/a>.<br>Rails, however falls back to <code>:string<\/code> for the <code>uuid<\/code> Postgresql native type.<br>To workaround this you can create column manually in your migration (index should be added as well):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">execute \"ALTER TABLE objects add COLUMN public_id uuid NOT NULL DEFAULT uuid_generate_v4()\"\nadd_index :objects, [:public_id], name: \"index_objects_on_public_id\", unique: true<\/pre>\n\n\n\n<p>To use <code>uuid_generate_v4<\/code> function you have to add Postgresql extension:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">create extension \"uuid-ossp\"<\/pre>\n\n\n\n<p>And don&#8217;t forget to change your schema format to <code>:sql<\/code> in <code>config\/application.rb<\/code><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">config.active_record.schema_format = :sql<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Testing your API<\/h2>\n\n\n\n<p>You definitely want to cover up your new shiny API with some sort of tests. See example below that uses <code>Rack::Test::Methods<\/code><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># spec_helper\nmodule ApiHelper\n  include Rack::Test::Methods\n\n  def app\n    Rails.application\n  end\nend\n\nRSpec.configure do |config|\n  config.include ApiHelper, api: true\nend\n\n# spec\nrequire 'spec_helper'\n\ndescribe \"api\/v1\/objects\", api: true do\n  let(:url) { \"api\/v1\/objects\" }\n  let(:object) { Factory.create(:object) }\n  let(:token) { \"YOUR_SECRET_TOKEN\" }\n  let(:data) { JSON.parse(last_response.body) }\n\n  before(:each) { get url, token: token }       # here's where API call made\n\n  subject { data }\n\n  it { should have(1).object }\nend<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>We&#8217;ve just gone through the following tricks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Minimum usable Metal controller<\/li>\n\n\n\n<li>Simple versioning<\/li>\n\n\n\n<li>Easy API views with RABL<\/li>\n\n\n\n<li>Postresql native <code>uuid<\/code> type<\/li>\n\n\n\n<li>API spec sample<\/li>\n<\/ul>\n\n\n\n<p>Sure, you&#8217;ll face a lot more issues in the wild but these basics intended to help you start up quickly.<br>The last, but not the least &#8211; don&#8217;t forget about good documentation. Your API users will definitely appreciate it.<\/p>\n\n\n\n<p>If you want to try all these tricks in the wild see recently released API for our <a href=\"https:\/\/mailtrap.io\">dummy SMTP server &#8211; Mailtrap.io<\/a><\/p>\n\n\n\n<p><b>UPDATE<\/b><br>If you want to use <code>render json: your_json<\/code> you should <code>include ActionController::Renderers::All<\/code> in your base API controller. Thanks <b>Divya Kunnath<\/b> for pointing that out.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article is about gotchas you should be aware of while building your API with Ruby on Rails. Controller tricks: API on Metal Sooner or later each Rails developer come to a point when he wants to build his first API.Among the first things you have to take care of are your controllers.If you want&#8230;<\/p>\n","protected":false},"author":34,"featured_media":9484,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"coauthors":["Innokenty Mihailov"],"class_list":["post-4508","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Engineering","link":"https:\/\/railsware.com\/blog?category=development"}],"post_thumbnails":"https:\/\/railsware.com\/blog\/wp-content\/themes\/railsware\/vendors\/images\/article-thumbnail-default.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/4508","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/34"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=4508"}],"version-history":[{"count":35,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/4508\/revisions"}],"predecessor-version":[{"id":18142,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/4508\/revisions\/18142"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/9484"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=4508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=4508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=4508"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=4508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}