when working on any non-hello-world rails application you invariably need teeny ajax helpers to communicate with the server - in order to perform client side logic. i'm not talking an api per-se, but ad-hoc utilities like this:
markdown.change(function(){
generate_html_on_the_server( markdown.val(), function(html){
preview.html(html);
});
});
or
reservation.change(function(){
check_availibity_on_the_server( reservation.val(), function(data){
if(!data['available']){
alert('that time is not available - pick another!');
};
})
});
etc.
these types of functions aren't part of a server-to-server public api, they are just cooperating js/backend endpoints that are needed to make the view function.
most rails' applications will accumulate many of these and the question arises:
"Where do you put them?"
in most teams you'll have three or four developers naming and organizing these functions differently, ensuring that the code base turns into a cowboy spaghetti mess in short order.
@dojo4 we've abstracted this with a teeny rpc design pattern: first we have a teeny controller dsl that's included into our ApplicationController that allows declarative definitions of 'rpc' js helpers on a per-controller basis.
class ApplicationController < ActionController::Base
include(RPC)
end
check out the implementation here: https://gist.github.com/ahoward/7320900
in plain english this dsl just defined a single action on the controller that multi-plexes which method to employ based on params, and an easy way to define them. it expects that all rpc actions will return a hash and give back json.
it's implementation boils down to
def rpc
which = params['method']
action = @rpc[which]
result = action.call(params)
render :json => result.to_json
end
it's usage should be obvious from the code
class GeoLocationController < ApplicationController
rpc(:geo_location) do |params|
geo_location = GeoLocation.geo_locate( params['address'] )
geo_location.attributes
end
rpc(:lat_lng) do |params|
geo_location = GeoLocation.geo_locate( params['address'] )
{ 'lat' => geo_location.lat, 'lng' => geo_location.lng }
end
end
usage from js requires two things: a route, and a teeny js class. first the route:
match ':controller#rpc', :action => 'rpc'
next, the js, also here -> https://gist.github.com/ahoward/7320900
reading over that you can see that using the backend rpc actions from js is as simpile as
var rpc = new RPC(url);
rpc.call('geo_location', params, callback);
normally, at the bottom of a view, we're just intantiating an rpc object for the page with a relative url
<script>
jQuery(function(){
var rpc = new RPC(<%= raw url_for(:action => :rpc).to_json %>);
});
</script>
and we're off and running.
so, there you have it - a simple way to keep your rpc helper js from polluting your controllers and views with varied strategies and another example about how having brutally consistent interfaces makes abstraction possible and simple.
update: the entire approach can be summarized like this
class BaseController
RPC = Hash.new
def self.rpc(method_name, &block)
RPC[method_name] = block
end
def rpc
method_name = params['method_name']
block = RPC[method_name]
result = block.call(params)
render :json => result
end
end
class Controller
rpc :teh_method do
{'K' => params['k'].upcase}
end
end
# curl http://0.0.0.0:3000/foo/rpc?method_name=teh_method&k=v #=> {'K' : 'V'}