apish 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ gemdev
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p392
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in apish.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 C. Jason Harrelson (midas)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Apish
2
+
3
+ Apish provides a set of tools to aid in API creation and management. These tools include but are not limited
4
+ to version maangement, responders and generators.
5
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'apish'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install apish
20
+
21
+
22
+ ## Usage
23
+
24
+ ### Generators
25
+
26
+ Generate endpoint with controller, routing, and request specs (default view engine is Rabl):
27
+
28
+ rails g apish:endpoint api/v1/things index show create update
29
+
30
+ Generate endpoint with controller, routing, request and view specs (specifying view engine):
31
+
32
+ rails g apish:endpoint api/v1/things index show create update -t rabl --view_specs
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new('spec')
6
+ task :default => :spec
data/apish.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'apish/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.authors = ["C. Jason Harrelson (midas)"]
8
+ gem.email = ["jason@lookforwardenterprises.com"]
9
+ gem.description = %q{Apish provides a set of tools to aid in API creation and management.}
10
+ gem.summary = %q{Apish provides a set of tools to aid in API creation and management. These tools include but are not limited to version maangement and responders.}
11
+ gem.homepage = ""
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "apish"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Apish::VERSION
19
+
20
+ gem.add_development_dependency(%q<rspec>, ["~> 2"])
21
+
22
+ gem.add_dependency(%q<activesupport>, [">= 2"])
23
+ gem.add_dependency(%q<actionpack>, [">= 2"])
24
+ gem.add_dependency(%q<mime-types>, [">= 1"])
25
+ end
data/lib/apish.rb ADDED
@@ -0,0 +1,37 @@
1
+ #require 'apish/railtie' if defined?( Rails )
2
+
3
+ module Apish
4
+
5
+ autoload :ApiVersion, "apish/api_version"
6
+ autoload :Configuration, "apish/configuration"
7
+ autoload :Controller, "apish/controller"
8
+ autoload :Error, "apish/error"
9
+ autoload :FormatResolver, "apish/format_resolver"
10
+ autoload :Responder, "apish/responder"
11
+ autoload :VERSION, "apish/version"
12
+
13
+ def self.configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def self.configure
18
+ yield(configuration) if block_given?
19
+ end
20
+
21
+ def self.register_custom_mime_types
22
+ dir = File.join( Rails.root, 'app', 'controllers', 'api' )
23
+ Dir.glob( "#{dir}/v*" ) do |d|
24
+ version = d[/(v\d+)$/, 1]
25
+ types = %w( json xml ).collect do |wire_format|
26
+ "api_#{version}_#{wire_format}"
27
+ end
28
+ types.each do |type|
29
+ mime_type = ["application/vnd.#{Apish.configuration.mime_types_base}",
30
+ type.split( '_' )[1..2].join( '+' )].join( '-' )
31
+ next if Mime.const_defined?( type.upcase )
32
+ Mime::Type.register mime_type, type.to_sym
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,42 @@
1
+ require 'active_support/core_ext/object/try'
2
+
3
+ class Apish::ApiVersion
4
+
5
+ attr_reader :accepts
6
+
7
+ def initialize( accepts )
8
+ @accepts = accepts
9
+ end
10
+
11
+ def to_s
12
+ parse
13
+ end
14
+
15
+ def to_i
16
+ parse.try( :to_i ) || 0
17
+ end
18
+
19
+ def max
20
+ 1
21
+ end
22
+
23
+ def exists?
24
+ to_i <= max
25
+ end
26
+
27
+ def most_recent?
28
+ to_i == max
29
+ end
30
+
31
+ def self.version_regex
32
+ /application\/vnd.#{Apish.configuration.mime_types_base}-v(\d+)\+\w*/
33
+ end
34
+
35
+ private
36
+
37
+ def parse
38
+ matches = accepts.match( self.class.version_regex )
39
+ matches.try :[], 1
40
+ end
41
+
42
+ end
@@ -0,0 +1,10 @@
1
+ module Apish
2
+
3
+ class Configuration
4
+
5
+ attr_accessor :mime_types_base,
6
+ :representation_framework
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_support/concern'
2
+
3
+ module Apish::Controller
4
+
5
+ autoload :CheckFormat, 'apish/controller/check_format'
6
+ autoload :Rescue, 'apish/controller/rescue'
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ # Adds common API controller functionality.
13
+ #
14
+ def acts_as_api_controller
15
+ base = self.name.split( '::' )[0..1].join( '_' ).downcase
16
+ json = "#{base}_json"
17
+ xml = "#{base}_xml"
18
+ types = [json, xml]
19
+
20
+ formats = { Mime::const_get( json.upcase ) => :json,
21
+ Mime::const_get( xml.upcase ) => :xml }
22
+
23
+ class_eval do
24
+ def self.supported_formats
25
+ Hash[*(self.mimes_for_respond_to.keys.map { |f| [Mime::Type.lookup_by_extension(f), f.to_s[/^api_v\d+_(.+)$/, 1]] }.flatten)]
26
+ end
27
+ end
28
+
29
+ # Set up custom mime types that this controller will respond to
30
+ respond_to( *formats.keys.map( &:to_sym ) )
31
+
32
+ # Define renderers for each mime type
33
+ formats.each do |mime_type, format|
34
+ if Apish.configuration.representation_framework == :rabl
35
+ ActionController.add_renderer mime_type.to_sym do |object, options|
36
+ templates = options[:prefixes].map { |path| File.join( path, options[:template] ) }.select { |path| File.exists?( File.join( Rails.root, 'app', 'views', "#{path}.rabl" )) }
37
+ # Do not try to render if no templates were found
38
+ unless templates.empty?
39
+ self.content_type ||= mime_type
40
+ self.response_body = Rabl::Renderer.send( format, object, templates.first, :view_path => 'app/views', :scope => self )
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Define #to_* methods for each custom mime type (in order to get free
47
+ # format.custom_mime_type { render :custom_mime_type => @object } functionality)
48
+ types.each do |type|
49
+ method_name = "to_#{type}"
50
+
51
+ unless ActionController::Responder.method_defined?( method_name )
52
+ ActionController::Responder.class_eval do
53
+ define_method method_name do
54
+ # if the default_response Proc is from the current controller, then it is an override
55
+ #controller_provides_response = @default_response.binding.eval( "self.class" ) == controller.class
56
+ controller_provides_response = @default_response.file_colon_line.split( ':' ).first.gsub( /app\/controllers\/|.rb/, '' ).classify == controller.class.name
57
+ if controller_provides_response
58
+ # allow overridden responses in respond_with blocks
59
+ @default_response.call( options )
60
+ else
61
+ collection_name = controller.class.name.gsub( /Controller/, '' ).gsub( /Api::V\d+::/, '' ).tableize
62
+ object_name = collection_name.singularize
63
+ object = controller.instance_variable_get( "@#{collection_name}" ) || controller.instance_variable_get( "@#{object_name}" )
64
+ controller.render type.to_sym => object
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,27 @@
1
+ module Apish::Controller::CheckFormat
2
+
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_filter :check_format
7
+ end
8
+
9
+ protected
10
+
11
+ def check_format
12
+ unless self.class.supported_formats.key?( request.format )
13
+ respond_with_unsupported_format
14
+ end
15
+ end
16
+
17
+ def respond_with_unsupported_format
18
+ render :status => :not_found,
19
+ :text => unsupported_text
20
+ end
21
+
22
+ def unsupported_text
23
+ return "You must provide a format for #{request.path.split( '.' ).first.inspect}" if params[:format].blank?
24
+ "Format #{params[:format].inspect} not supported for #{request.path.split( '.' ).first.inspect}"
25
+ end
26
+
27
+ end
@@ -0,0 +1,64 @@
1
+ module Apish::Controller::Rescue
2
+
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ rescue_from Exception, :with => :handle_exception
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def respond_with_detail_exceptions
12
+ @@respond_with_detail_exceptions ||= {}
13
+ end
14
+
15
+ def rescue_from_with_details( exception_class, status_code_or_symbol )
16
+ respond_with_detail_exceptions[exception_class] = status_code_or_symbol
17
+ end
18
+
19
+ end
20
+
21
+ protected
22
+
23
+ def handle_exception( ex )
24
+ respond_to do |format|
25
+ format.html do
26
+ rescue_action_without_handler( ex )
27
+ end
28
+ format.any do
29
+ log_error( ex )
30
+ erase_results if performed?
31
+ if ex.respond_to?( :handle_response! )
32
+ ex.handle_response!( response )
33
+ end
34
+
35
+ if self.class.respond_with_detail_exceptions.key?( ex.class )
36
+ render :status => self.class.respond_with_detail_exceptions[ex.class],
37
+ :text => Api::Error.new( ex ).send( to_method_name )
38
+ else
39
+ render :status => :internal_server_error,
40
+ :text => Api::Error.new( 'Internal server error' ).send( to_method_name )
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def log_error( ex )
47
+ return unless Rails.logger
48
+ Rails.logger.error "\n#{ex.class} (#{ex.message}):\n #{Rails.backtrace_cleaner.clean(ex.backtrace).join("\n ")}"
49
+ end
50
+
51
+ def to_method_name
52
+ return 'to_json' if json_variant?
53
+ return 'to_xml' if xml_variant?
54
+ end
55
+
56
+ def json_variant?
57
+ request.format.to_s.include? 'json'
58
+ end
59
+
60
+ def xml_variant?
61
+ request.format.to_s.include? 'xml'
62
+ end
63
+
64
+ end
@@ -0,0 +1,36 @@
1
+ module Apish::Error
2
+
3
+ attr_reader :message, :type
4
+
5
+ def initialize( message_or_error )
6
+ if message_or_error.is_a?( Exception )
7
+ @message = message_or_error.message
8
+ @type = message_or_error.class.name
9
+ else
10
+ @message = message_or_error
11
+ @type = 'Exception'
12
+ end
13
+ end
14
+
15
+ def to_json
16
+ { root => body }.to_json
17
+ end
18
+
19
+ def to_xml
20
+ body.to_xml :root => root
21
+ end
22
+
23
+ protected
24
+
25
+ def body
26
+ {
27
+ :message => message,
28
+ :type => type
29
+ }
30
+ end
31
+
32
+ def root
33
+ :error
34
+ end
35
+
36
+ end
@@ -0,0 +1,35 @@
1
+ require 'active_support/core_ext/object/try'
2
+ require 'action_dispatch/http/mime_type'
3
+ require 'action_dispatch/http/mime_types'
4
+
5
+ class Apish::FormatResolver
6
+
7
+ attr_reader :accept,
8
+ :current_format
9
+
10
+ def initialize( accept, current_format=nil )
11
+ @accept = accept || ''
12
+ @current_format = current_format
13
+ end
14
+
15
+ def format
16
+ if current_format == Mime::ALL || current_format.to_s.include?( "application/vnd.#{Apish.configuration.mime_types_base}-v" )
17
+ matches = accept.match( self.class.format_regex )
18
+ mime_type_str = matches.nil? ? current_format : matches[1]
19
+ mime_type = Mime[mime_type_str]
20
+ return mime_type.symbol.to_s unless mime_type.blank?
21
+ elsif current_format.nil?
22
+ matches = accept.match( self.class.format_regex )
23
+ mime_type_str = matches.nil? ? current_format : matches[1]
24
+ mime_type = Mime[mime_type_str]
25
+ return mime_type.symbol.to_s unless mime_type.blank?
26
+ end
27
+
28
+ current_format.try( :symbol ).try :to_s
29
+ end
30
+
31
+ def self.format_regex
32
+ /application\/vnd.#{Apish.configuration.mime_types_base}-v\d+\+(\w*)/
33
+ end
34
+
35
+ end
@@ -0,0 +1,6 @@
1
+ module Apish::Responder
2
+
3
+ autoload :Pagination, "apish/responder/pagination"
4
+ autoload :Version, "apish/responder/version"
5
+
6
+ end
@@ -0,0 +1,24 @@
1
+ module Apish::Responder::Pagination
2
+
3
+ def respond(*args)
4
+ if paginated?
5
+ controller.headers['X-Total-Entries'] = resource.total_entries.to_s
6
+ controller.headers['X-Total-Pages'] = resource.total_pages.to_s
7
+ controller.headers['X-Current-Page'] = resource.current_page.to_s
8
+ controller.headers['X-Next-Page'] = resource.next_page.to_s
9
+ controller.headers['X-Previous-Page'] = resource.previous_page.to_s
10
+ controller.headers['X-Per-Page'] = resource.per_page.to_s
11
+ end
12
+
13
+ super( *args )
14
+ end
15
+
16
+ private
17
+
18
+ def paginated?
19
+ resource.respond_to?(:total_entries) &&
20
+ resource.respond_to?(:total_pages) &&
21
+ resource.respond_to?(:per_page)
22
+ end
23
+
24
+ end
@@ -0,0 +1,15 @@
1
+ module Apish::Responder::Version
2
+
3
+ def respond( *args )
4
+ controller.headers['X-Api-Version'] = controller.class.name.match( /^Api::V(\d*)::.*$/ ).try( :[], 1 )
5
+
6
+ super( *args )
7
+ end
8
+
9
+ protected
10
+
11
+ def accept
12
+ request.headers['Accept']
13
+ end
14
+
15
+ end
@@ -0,0 +1,3 @@
1
+ module Apish
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,131 @@
1
+ require 'rails/generators'
2
+
3
+ module Apish
4
+
5
+ class EndpointGenerator < Rails::Generators::Base
6
+
7
+ attr_reader :action_name,
8
+ :controller_super_class_name
9
+
10
+ desc "Description:\n Creates the specified API endpoint with all spec files, etc."
11
+
12
+ argument :endpoint_name, :type => :string, :required => true
13
+ argument :action_names, :type => :array, :required => false
14
+
15
+ class_option :view_engine, :type => :string, :aliases => "-t", :desc => "Template engine for the views. Available options are 'rabl'.", :default => "rabl"
16
+ class_option :view_specs, :type => :boolean, :default => false
17
+
18
+ def self.source_root
19
+ File.join File.dirname(__FILE__),
20
+ 'templates'
21
+ end
22
+
23
+ def install_controller
24
+ @controller_super_class_name = ask( 'What is the class name (without namespace) of the versioned super class for this API endpoints controller (press enter for ApiController)?' )
25
+ @controller_super_class_name = default_controller_super_class_name if @controller_super_class_name.blank?
26
+ template "controller.erb", File.join( 'app', 'controllers', "#{controller_file_name}.rb" )
27
+ end
28
+
29
+ def install_controller_spec
30
+ template "controller_spec.erb", File.join( 'spec', 'controllers', "#{controller_spec_file_name}.rb" )
31
+ end
32
+
33
+ def install_routing_spec
34
+ template "routing_spec.erb", File.join( 'spec', 'routing', "#{routing_spec_file_name}.rb" )
35
+ end
36
+
37
+ def install_request_specs
38
+ return if action_names.blank?
39
+
40
+ basename = ask( 'What base name would you like for the request_spec(s) (press enter for authenticated_request)?' )
41
+ basename = default_request_spec_base_name if basename.blank?
42
+
43
+ action_names.each do |action_name|
44
+ @action_name = action_name
45
+ template "request_spec.erb", File.join( 'spec', 'requests', "#{request_spec_file_name( action_name, "#{basename}_spec" )}.rb" )
46
+ end
47
+ end
48
+
49
+ def install_views
50
+ return if action_names.blank?
51
+
52
+ action_names.each do |action_name|
53
+ create_file File.join( 'app', 'views', view_file_name( action_name ))
54
+ end
55
+ end
56
+
57
+ def install_view_specs
58
+ return if action_names.blank? || options[:view_specs] == false
59
+
60
+ action_names.each do |action_name|
61
+ template "view_spec.erb", File.join( 'spec', 'views', "#{view_spec_file_name( action_name )}.rb" )
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def default_controller_super_class_name
68
+ 'ApiController'
69
+ end
70
+
71
+ def default_request_spec_base_name
72
+ 'authenticated_request'
73
+ end
74
+
75
+ def view_file_name( action_name )
76
+ [versioned_endpoint_human_name, "#{action_name}.#{view_file_extension}"].join( '/' )
77
+ end
78
+
79
+ def view_spec_file_name( action_name )
80
+ [versioned_endpoint_human_name, "#{action_name}.#{view_file_extension}_spec"].join( '/' )
81
+ end
82
+
83
+ def view_file_extension
84
+ options[:view_engine]
85
+ end
86
+
87
+ # api/v1/blocks
88
+ def versioned_endpoint_human_name
89
+ endpoint_name.underscore
90
+ end
91
+
92
+ # blocks
93
+ def endpoint_human_name
94
+ human_name.split( '/' ).last
95
+ end
96
+
97
+ # api/v1/blocks_controller
98
+ def controller_file_name
99
+ [endpoint_name.underscore, '_controller'].join
100
+ end
101
+
102
+ # api/v1/blocks_controller_spec
103
+ def controller_spec_file_name
104
+ [controller_file_name, '_spec'].join
105
+ end
106
+
107
+ # api/v1/blocks_routing_spec
108
+ def routing_spec_file_name
109
+ [endpoint_name.underscore, '_routing_spec'].join
110
+ end
111
+
112
+ # api/v1/blocks/create/something_spec
113
+ def request_spec_file_name( action_name, filename )
114
+ [endpoint_name.underscore, action_name, filename].join( '/' )
115
+ end
116
+
117
+ def endpoint_controller_class
118
+ [endpoint_name.camelcase, 'Controller'].join
119
+ end
120
+
121
+ def endpoint_namespace
122
+ endpoint_controller_class.split( '::' )[0...-1].join( '::' )
123
+ end
124
+
125
+ def endpoint_versioned_base_controller_name( class_name='ApiController' )
126
+ [endpoint_namespace, class_name].join( '::' )
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,3 @@
1
+ class <%= endpoint_controller_class %> < <%= endpoint_versioned_base_controller_name( controller_super_class_name ) %>
2
+
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe <%= endpoint_controller_class %> do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe "<%= versioned_endpoint_human_name %> API <%= action_name %>", :type =>:api do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe '<%= versioned_endpoint_human_name %> routing' do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe '<%= view_file_name( action_name )%>' do
4
+
5
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apish::ApiVersion do
4
+
5
+ before :each do
6
+ configure_apish
7
+ end
8
+
9
+ let :v1_accepts do
10
+ 'application/vnd.some-app-v1+json'
11
+ end
12
+
13
+ let :v2_accepts do
14
+ 'application/vnd.some-app-v2+json'
15
+ end
16
+
17
+ describe "#max" do
18
+
19
+ subject do
20
+ described_class.new( v1_accepts ).max
21
+ end
22
+
23
+ it { should == 1 }
24
+
25
+ end
26
+
27
+ describe "#to_s" do
28
+
29
+ subject do
30
+ described_class.new( v1_accepts ).to_s
31
+ end
32
+
33
+ it { should == '1' }
34
+
35
+ end
36
+
37
+ describe "#to_i" do
38
+
39
+ subject do
40
+ described_class.new( v2_accepts ).to_i
41
+ end
42
+
43
+ it { should == 2 }
44
+
45
+ end
46
+
47
+ describe "#exists?" do
48
+
49
+ context 'when the verison is 1' do
50
+
51
+ subject do
52
+ described_class.new( v1_accepts ).exists?
53
+ end
54
+
55
+ it { should be_true }
56
+
57
+ end
58
+
59
+ context 'when the verison is 2' do
60
+
61
+ subject do
62
+ described_class.new( v2_accepts ).exists?
63
+ end
64
+
65
+ it { should be_false }
66
+
67
+ end
68
+
69
+ end
70
+
71
+ describe "#most_recent?" do
72
+
73
+ before :each do
74
+ version.stub!( :max ).and_return 2
75
+ end
76
+
77
+ subject do
78
+ version.most_recent?
79
+ end
80
+
81
+ context 'when the verison is 1' do
82
+
83
+ let :version do
84
+ described_class.new v1_accepts
85
+ end
86
+
87
+ it { should be_false }
88
+
89
+ end
90
+
91
+ context 'when the verison is 2' do
92
+
93
+ let :version do
94
+ described_class.new v2_accepts
95
+ end
96
+
97
+ it { should be_true }
98
+
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apish::FormatResolver do
4
+
5
+ before :each do
6
+ configure_apish
7
+ end
8
+
9
+ let :no_format_accept do
10
+ nil
11
+ end
12
+
13
+ let :json_format_accept do
14
+ 'application/vnd.some-app-v1+json'
15
+ end
16
+
17
+ describe "#format" do
18
+
19
+ context 'when a format is provided but no current format is provided' do
20
+
21
+ subject do
22
+ described_class.new( json_format_accept ).format
23
+ end
24
+
25
+ it { should be_a( String ) }
26
+
27
+ it { should == Mime::JSON.symbol.to_s }
28
+
29
+ end
30
+
31
+ context 'when the format is not set from the URL extension' do
32
+
33
+ context 'and the format is provided in the accept header' do
34
+
35
+ subject do
36
+ described_class.new( json_format_accept,
37
+ Mime::ALL ).format
38
+ end
39
+
40
+ it { should be_a( String ) }
41
+
42
+ it { should == Mime::JSON.symbol.to_s }
43
+
44
+ end
45
+
46
+ context 'and the format is not provided in the accept header' do
47
+
48
+ subject do
49
+ described_class.new( no_format_accept,
50
+ Mime::ALL ).format
51
+ end
52
+
53
+ it { should be_a( String ) }
54
+
55
+ it { should == Mime::ALL.symbol.to_s }
56
+
57
+ end
58
+
59
+ end
60
+
61
+ context 'when the format is set from the URL extension' do
62
+
63
+ context 'and the format is provided in the accept header' do
64
+
65
+ subject do
66
+ described_class.new( json_format_accept,
67
+ Mime::XML ).format
68
+ end
69
+
70
+ it { should be_a( String ) }
71
+
72
+ it { should == Mime::XML.symbol.to_s }
73
+
74
+ end
75
+
76
+ context 'and the format is not provided in the accept header' do
77
+
78
+ subject do
79
+ described_class.new( no_format_accept,
80
+ Mime::XML ).format
81
+ end
82
+
83
+ it { should be_a( String ) }
84
+
85
+ it { should == Mime::XML.symbol.to_s }
86
+
87
+ end
88
+
89
+ end
90
+
91
+ context 'when the format is incorrectly set from the vnd.* header' do
92
+
93
+ subject do
94
+ described_class.new( json_format_accept,
95
+ Mime::Type.new( 'application/vnd.some-app-v1+json' ) ).format
96
+ end
97
+
98
+ it { should be_a( String ) }
99
+
100
+ it { should == Mime::JSON.symbol.to_s }
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'apish'
4
+
5
+ RSpec.configure do |config|
6
+
7
+ end
8
+
9
+ def mime_types_base
10
+ 'some-app'
11
+ end
12
+
13
+ def configure_apish
14
+ Apish.configure do |config|
15
+ config.mime_types_base = mime_types_base
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apish
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - C. Jason Harrelson (midas)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '2'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: actionpack
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: mime-types
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '1'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '1'
78
+ description: Apish provides a set of tools to aid in API creation and management.
79
+ email:
80
+ - jason@lookforwardenterprises.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .ruby-gemset
87
+ - .ruby-version
88
+ - Gemfile
89
+ - LICENSE
90
+ - README.md
91
+ - Rakefile
92
+ - apish.gemspec
93
+ - lib/apish.rb
94
+ - lib/apish/api_version.rb
95
+ - lib/apish/configuration.rb
96
+ - lib/apish/controller.rb
97
+ - lib/apish/controller/check_format.rb
98
+ - lib/apish/controller/rescue.rb
99
+ - lib/apish/error.rb
100
+ - lib/apish/format_resolver.rb
101
+ - lib/apish/responder.rb
102
+ - lib/apish/responder/pagination.rb
103
+ - lib/apish/responder/version.rb
104
+ - lib/apish/version.rb
105
+ - lib/generators/apish/endpoint/endpoint_generator.rb
106
+ - lib/generators/apish/endpoint/templates/controller.erb
107
+ - lib/generators/apish/endpoint/templates/controller_spec.erb
108
+ - lib/generators/apish/endpoint/templates/request_spec.erb
109
+ - lib/generators/apish/endpoint/templates/routing_spec.erb
110
+ - lib/generators/apish/endpoint/templates/view_spec.erb
111
+ - spec/lib/apish/api_version_spec.rb
112
+ - spec/lib/apish/format_resolver_spec.rb
113
+ - spec/spec_helper.rb
114
+ homepage: ''
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 1.8.25
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Apish provides a set of tools to aid in API creation and management. These
138
+ tools include but are not limited to version maangement and responders.
139
+ test_files:
140
+ - spec/lib/apish/api_version_spec.rb
141
+ - spec/lib/apish/format_resolver_spec.rb
142
+ - spec/spec_helper.rb