9

I have an application in Ruby on Rails with mvc framework. As of now, I have API calls in the controller but don't think this is the right place for them. What kind of file should all my API calls go in? Thanks

def getDetails(id)
 api_response = HTTParty.get(base_uri, :query => {:DID => id, :DeveloperKey => devKey})
 @json_hash = api_response.parsed_response
 return @json_hash
end
JuliaC
  • 121
  • 1
  • 2
  • 11
  • What do you mean by "API calls"? – Dave Newton Mar 04 '13 at 16:03
  • Please, provide an example of your controller code which has you concerned. – thisfeller Mar 04 '13 at 16:05
  • If they're external API calls, my thought is that they should be treated as data and placed within a model. That way, you can easily add to the database (for throttling) and it keeps a SoC. Just my thought though, I've seen external calls as declared classes in lib as well. – Nic Mar 04 '13 at 16:05
  • I added the controller code. They are external API calls – JuliaC Mar 04 '13 at 16:16
  • Seeing as you seem to have methods defined already for making the calls, why not stick them all in a class and require it in your controller. – Mike Campbell Mar 04 '13 at 16:18
  • I'd use a model and controller as normal. Perhaps using a tableless model or something derived from ActiveResource (if applicable), but if the intent is to have a web-driven API then app/controllers is still the right place to start. – Jim Stewart Mar 04 '13 at 17:04

3 Answers3

14

API calls to external services (3rd party) are not specific to your app, as their service is available to everyone (in theory). It is my understanding that these sorts of features go in the lib/ directory because they are not app specific. Ideally you could then pull out the code from your lib in your project, and drop it into someone else's lib/ in another project and it would still work just fine.

Put the call in the lib/. If you want, you can create the a model from the returned data in your controller.

It would look something like this:

app/controller/

class YourController < ApplicationController

  def getDetails
   # keep in mind, api call may fail so you may want surround this with a begin/rescue
   api_response = YourApiCall.new.get_details(params[:id])
   # perhaps create a model
   @model = SomeModel.new(fname: api_response[:first_name], lname: api_response[:last_name])
    # etc...
  end
end

lib/

require 'HTTParty'

Class YourApiCall
  def get_details(id)
    HTTParty.get(base_uri, :query => {:DID => id, :DeveloperKey => devKey})
    @json_hash = api_response.parsed_response
    return @json_hash
  end
end
AdamT
  • 6,405
  • 10
  • 49
  • 75
  • Tried this but the class didn't get loaded. I ended up just putting it in models. The other issue (unless I'm mistaken) is that you've capitalized the C in `class` in your declaration. – Ben Saufley Mar 15 '14 at 16:22
  • To load the files in the /lib directory, add the following to `config/application.rb` : `config.autoload_paths += %W(#{config.root}/lib)`. Check http://edgeguides.rubyonrails.org/configuring.html#configuring-rails-components for more info. – Teisman Apr 23 '15 at 19:26
  • You can also load the class by putting `require 'your_api_call'` in the top of the controller where you want it loaded – Daniel Friis Oct 21 '15 at 16:11
9

Very late to this one, but thought I'd add my 2p/2c.

I like to try to keep my controllers clean of anything apart from controller code, which I loosely define as program flow code based on request type and parameters. For example, choosing the correct template for the request type or choosing the correct method to call based on whether a user is logged in or not.

When it comes to calculating the responses, I don't like to litter the controller with lots of code that manipulates models and sets instance parameters. This is hard to test and even harder to re-use. I prefer to defer to another object and return a single value object to the template.

Sometimes I can defer to a model: maybe it's a simple look-up and I'm just sending a single model to the template, or an array of models.

Maybe I've implemented a useful method in a model to return an appropriate value or value object.

However sometimes I'm doing something that doesn't use a model, or that uses a several models, or that doesn't feel like it should actually be cluttering up the model. In this case, neither the controller nor a model is an appropriate place for the code.

The lib directory doesn't feel right either. I tend to treat the lib directory as somewhere that contains code that I haven't been bothered to turn into gems yet. If the code I'm writing only makes sense in the context of the application, it doesn't sit well.

So I turn to service objects. Under the 'app' folder I have a 'services' folder, which contains small, functional classes that encapsulate single chunks of site behaviour. (Or sometimes, coordinate several other services to provide a simple interface for the controller.)

This allows me to slim down my controllers AND my models, and makes a perfect place to put code that needs to contact an API.

If you wanted to go one step further you could wrap the API itself in a wrapper class (or set of classes) and keep those in the lib directory (for conversion to a gem at a later date perhaps). Then the service object would perform the task of calling the API wrapper with the appropriate values (passed from the controller) and responding with something that a template can interrogate cleanly.

Of course, you can go further than this and add more layers. A presentation layer, for example, could sit between the service object (providing generic values) and format data for a specific view. (Maybe you want to provide both a web page and an RSS feed and they need different date formats for example.)

But you get the idea.

A Fader Darkly
  • 3,516
  • 1
  • 22
  • 28
5

By my coding style (and understanding of MVC), external calls would be placed in a "tableless" model. RailsCasts 193 talks a bit about this concept, and a less clunky syntax is supported in Rails 4. If you need to have any manipulation of the code, the model seems like the logical place to place these. Moving those methods into the controller would work, but could create problems as your app grows.

Another consideration with external API calls is actually storing those in a database, which would should definitely be in a model at that point, so (to me) it becomes clearer that these really should be in the model.

Nic
  • 13,287
  • 7
  • 40
  • 42
  • Rails 3 already has better support for tableless models. Yehuda Katz [wrote a good blog post on it](http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/). – Jim Stewart Mar 04 '13 at 17:01
  • "Tableless models" make it sound like you'll be lugging around a load of unnecessary code with your API wrapper. (Validations? Attributes? Etc.) Just use a PORO. (Plain Old Ruby Object). Why do you need any more than a class? – A Fader Darkly Nov 25 '16 at 16:41