Last active
August 29, 2015 13:56
-
-
Save justinko/8871303 to your computer and use it in GitHub Desktop.
Inheritance based API versioning in Rails
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ApiVersioning | |
class Middleware | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
if version_number = extract_version_number(env) | |
ApiVersioning.current_version_number = version_number.to_i | |
Rails.application.eager_load! unless Rails.application.config.cache_classes | |
ClassDescendantsBuilder.build ApiBeta::ApplicationController, | |
ApiVersioning.max_version_number | |
end | |
@app.call env | |
end | |
private | |
def extract_version_number(env) | |
env['HTTP_ACCEPT'] && env['HTTP_ACCEPT'][/v([0-9\.]+)\+json/, 1] | |
end | |
end | |
class_attribute :current_version_number | |
def self.instance | |
@instance || raise('call `.setup` first') | |
end | |
def self.setup(context, &block) | |
@instance = new(context, &block) | |
end | |
def self.min_version_number | |
instance.min_version_number | |
end | |
def self.max_version_number | |
instance.max_version_number | |
end | |
def initialize(context, &block) | |
@context, @definitions = context, {} | |
instance_eval &block | |
end | |
def version(number, &block) | |
@definitions[number] = block || Proc.new {} | |
@context.scope module: "v#{number}", constraints: ->(*) { ApiVersioning.current_version_number == number } do | |
number.downto(min_version_number) do |i| | |
@context.instance_eval &@definitions[i] | |
end | |
end | |
end | |
def min_version_number | |
@definitions.keys.min | |
end | |
def max_version_number | |
@definitions.keys.max | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module MyApp | |
class Application < Rails::Application | |
config.middleware.use 'ApiVersioning::Middleware' | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ApplicationController | |
respond_to :json | |
def self.remove_action(*action_names) | |
action_names.each do |action_name| | |
define_method(action_name) { head :not_found } | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ClassDescendantsBuilder | |
LEVEL_REGEX = /\d+/ | |
def self.build(base_class, level) | |
base_class.descendants.each do |descendant| | |
new(descendant, level).build | |
end | |
end | |
def initialize(descendant, level) | |
@descendant, @level = descendant, level | |
end | |
def build | |
initial_level.upto(@level - 1) do |level| | |
build_descendant(level) unless descendant_defined?(level) | |
end | |
end | |
private | |
def descendant_defined?(level) | |
!!swap_level(level.next).safe_constantize | |
end | |
def build_descendant(level) | |
namespace(level.next).constantize.const_set( | |
swap_level(level.next).demodulize, | |
Class.new(swap_level(level).constantize) | |
) | |
end | |
def namespace(level) | |
swap_level(level).deconstantize.presence || 'Object' | |
end | |
def swap_level(level) | |
@descendant.name.sub LEVEL_REGEX, level.to_s | |
end | |
def initial_level | |
@descendant.name[LEVEL_REGEX].to_i | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MyApp::Application.routes.draw do | |
ApiVersioning.setup(self) do | |
version 1 do | |
resources :accounts | |
end | |
version 2 do | |
# will inherit `resources :accounts` | |
resources :users | |
end | |
version 3 # will inherit routes in versions 1 & 2 | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment