hovercraft 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 vanstee
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,30 @@
1
+ # Hovercraft
2
+
3
+ Generate a RESTful API from a directory of ActiveRecord models.
4
+
5
+ ## Get Up and Running
6
+
7
+ 1. Throw this in your Gemfile:
8
+
9
+ `gem 'hovercraft'`
10
+
11
+ 2. Put your ActiveRecord models in `models/` (make sure the file names
12
+ are the same as the class names).
13
+
14
+ 3. Create a rackup file that generates the application:
15
+
16
+ ```ruby
17
+ run Hovercraft::Server.new
18
+ ```
19
+
20
+ 4. Run the application like normal:
21
+
22
+ `bundle exec rackup`
23
+
24
+ ## Give Back
25
+
26
+ 1. Fork it
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task default: :spec
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hovercraft/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['vanstee']
6
+ gem.email = ['vanstee@highgroove.com']
7
+ gem.description = %q{Generate a RESTful API from a directory of ActiveRecord models}
8
+ gem.summary = %q{There's a lot of boiler plate code that goes into
9
+ creating an API so why not just generate all that code
10
+ from the models. Just throw your models in a models
11
+ directory and call the server from the rackup file and
12
+ you have yourself a perfect API.}
13
+ gem.homepage = ''
14
+
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ gem.test_files = `git ls-files -- spec/*`.split("\n")
18
+ gem.name = 'hovercraft'
19
+ gem.require_paths = ['lib']
20
+ gem.version = Hovercraft::VERSION
21
+
22
+ gem.add_dependency 'sinatra-activerecord'
23
+ gem.add_dependency 'rack-contrib'
24
+
25
+ gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'pry'
27
+ gem.add_development_dependency 'rake'
28
+ gem.add_development_dependency 'rack-test'
29
+ end
@@ -0,0 +1,47 @@
1
+ require 'sinatra/base'
2
+
3
+ module Hovercraft
4
+ module Actions
5
+ def generate_index(model_class, model_name, plural_model_name)
6
+ get("/#{plural_model_name}.:format") do
7
+ model_instances = model_class.all
8
+ status 200
9
+ model_instances.to_json
10
+ end
11
+ end
12
+
13
+ def generate_create(model_class, model_name, plural_model_name)
14
+ post("/#{plural_model_name}.:format") do
15
+ model_instance = model_class.create(params[model_name.to_sym])
16
+ status model_instance.valid? ? 201 : 400
17
+ model_instance.to_json
18
+ end
19
+ end
20
+
21
+ def generate_show(model_class, model_name, plural_model_name)
22
+ get("/#{plural_model_name}/:id.:format") do
23
+ model_instance = model_class.find(params[:id])
24
+ status 200
25
+ model_instance.to_json
26
+ end
27
+ end
28
+
29
+ def generate_update(model_class, model_name, plural_model_name)
30
+ put("/#{plural_model_name}/:id.:format") do
31
+ model_instance = model_class.find(params[:id])
32
+ model_instance.update_attributes(params[model_name.to_sym])
33
+ status model_instance.valid? ? 204 : 400
34
+ model_instance.to_json
35
+ end
36
+ end
37
+
38
+ def generate_destroy(model_class, model_name, plural_model_name)
39
+ delete("/#{plural_model_name}/:id.:format") do
40
+ model_instance = model_class.find(params[:id])
41
+ model_instance.destroy
42
+ status 204
43
+ model_instance.to_json
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ require 'hovercraft/loader'
2
+ require 'hovercraft/actions'
3
+ require 'sinatra/base'
4
+ require 'rack/contrib'
5
+ require 'forwardable'
6
+
7
+ module Hovercraft
8
+ class Builder
9
+ extend Forwardable
10
+
11
+ def_delegator :@loader, :with_each_model
12
+
13
+ def initialize
14
+ @loader = Loader.new
15
+ end
16
+
17
+ def application
18
+ application = Sinatra.new
19
+ application = configure(application)
20
+ application = generate_routes(application)
21
+ application
22
+ end
23
+
24
+ def configure(application)
25
+ application.register(Hovercraft::Actions)
26
+ application.use(Rack::PostBodyContentTypeParser)
27
+ application
28
+ end
29
+
30
+ def generate_routes(application)
31
+ with_each_model do |model_class, model_name, plural_model_name|
32
+ application.methods.grep(/generate/).each do |action|
33
+ application.send(action, model_class, model_name, plural_model_name)
34
+ end
35
+ end
36
+ application
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ module Hovercraft
2
+ class Caller
3
+ CALLERS_TO_IGNORE = [
4
+ /\/hovercraft(\/(caller|loader|builder|server))?\.rb$/, # hovercraft libary
5
+ /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # sinatra library
6
+ /^\(.*\)$/, # generated code
7
+ /lib\/ruby/, # ruby core libraries
8
+ /rubygems\/custom_require\.rb$/, # rubygems require hacks
9
+ /active_support/, # active_support require hack
10
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
11
+ /<internal:/, # internal in ruby >= 1.9.2
12
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
13
+ ]
14
+
15
+ def directory
16
+ File.dirname(caller_file)
17
+ end
18
+
19
+ def caller_file
20
+ cleaned_caller_files.first || $PROGRAM_NAME
21
+ end
22
+
23
+ def cleaned_caller_files
24
+ caller(1).map { |line| line.split(/:(?=\d|in )/, 3)[0, 1] }.
25
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }.
26
+ flatten
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ require 'hovercraft/caller'
2
+ require 'active_support/inflector'
3
+ require 'pry'
4
+
5
+ module Hovercraft
6
+ class Loader
7
+ def initialize
8
+ @caller = Caller.new
9
+ end
10
+
11
+ def with_each_model
12
+ models.each do |model_class|
13
+ model_name = model_class.name.underscore
14
+ plural_model_name = model_name.pluralize
15
+ yield(model_class, model_name, plural_model_name)
16
+ end
17
+ end
18
+
19
+ def models
20
+ @models ||= require_models
21
+ end
22
+
23
+ private
24
+
25
+ def require_models
26
+ Dir.glob(File.join(models_directory, '**/*.rb')).map do |file|
27
+ require file
28
+ class_from(file)
29
+ end
30
+ end
31
+
32
+ def models_directory
33
+ File.join(@caller.directory, 'models')
34
+ end
35
+
36
+ def class_from(file)
37
+ file.gsub!(/#{models_directory}|.rb/, '')
38
+ file.classify.safe_constantize
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ require 'hovercraft/builder'
2
+
3
+ module Hovercraft
4
+ class Server
5
+ def initialize
6
+ builder = Builder.new
7
+ @application = builder.application
8
+ end
9
+
10
+ def call(env)
11
+ @application.call(env)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Hovercraft
2
+ VERSION = '0.0.1'
3
+ end
data/lib/hovercraft.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'hovercraft/server'
2
+ require 'hovercraft/version'
@@ -0,0 +1,158 @@
1
+ require 'hovercraft/actions'
2
+ require 'rack/test'
3
+
4
+ describe Hovercraft::Actions do
5
+ include Rack::Test::Methods
6
+
7
+ let(:application) { Sinatra.new }
8
+ let(:model_name) { 'employee' }
9
+ let(:model_name_pluralize) { 'employees' }
10
+ let(:model_class) { Class.new }
11
+
12
+ let(:model) do
13
+ stub(
14
+ to_json:
15
+ '
16
+ {
17
+ "employee": {
18
+ "name": "Philip J. Fry",
19
+ "career": "Intergalactic Delivery Boy"
20
+ }
21
+ }
22
+ ',
23
+ valid?: true,
24
+ update_attributes: self,
25
+ destroy: self
26
+ )
27
+ end
28
+
29
+ let(:models) do
30
+ stub(
31
+ to_json:
32
+ '
33
+ [
34
+ {
35
+ "employee": {
36
+ "name": "Philip J. Fry",
37
+ "career": "Intergalactic Delivery Boy"
38
+ }
39
+ },
40
+ {
41
+ "employee": {
42
+ "name": "John A. Zoidberg",
43
+ "career": "Physician"
44
+ }
45
+ ]
46
+ '
47
+ )
48
+ end
49
+
50
+ alias :app :application
51
+
52
+ before do
53
+ application.register(Hovercraft::Actions)
54
+
55
+ model_class.stub(all: models, create: model, find: model)
56
+ end
57
+
58
+ describe '#generate_index' do
59
+ before do
60
+ application.generate_index(model_class, model_name, model_name_pluralize)
61
+ end
62
+
63
+ it 'generates a GET collection route' do
64
+ application.routes['GET'][0][0].should == %r{^/employees(?:\.|%2E)([^/?#]+)$}
65
+ application.routes['GET'][0][1].should == ['format']
66
+ end
67
+
68
+ it 'returns a list of models' do
69
+ get '/employees.json'
70
+
71
+ last_response.status.should == 200
72
+ end
73
+ end
74
+
75
+ describe '#generate_create' do
76
+ before do
77
+ application.generate_create(model_class, model_name, model_name_pluralize)
78
+ end
79
+
80
+ it 'generates a POST collection route' do
81
+ application.routes['POST'][0][0].should == %r{^/employees(?:\.|%2E)([^/?#]+)$}
82
+ application.routes['POST'][0][1].should == ['format']
83
+ end
84
+
85
+ it 'creates and returns a model' do
86
+ post '/employees.json', zoidberg: { occipation: 'doctor' }
87
+
88
+ last_response.status.should == 201
89
+ end
90
+
91
+ it 'fails and returns an error' do
92
+ model.stub(valid?: false)
93
+
94
+ post '/employees.json', employee: { name: 'Bender Bending Rodriguez', career: 'Girder-bender' }
95
+
96
+ last_response.status.should == 400
97
+ end
98
+ end
99
+
100
+ describe '#generate_show' do
101
+ before do
102
+ application.generate_show(model_class, model_name, model_name_pluralize)
103
+ end
104
+
105
+ it 'generates a GET member route' do
106
+ application.routes['GET'][0][0].should == %r{^/employees/([^/?#]+)(?:\.|%2E)([^/?#]+)$}
107
+ application.routes['GET'][0][1].should == ['id', 'format']
108
+ end
109
+
110
+ it 'returns a model' do
111
+ get '/employees/1.json'
112
+
113
+ last_response.status.should == 200
114
+ end
115
+ end
116
+
117
+ describe '#generate_update' do
118
+ before do
119
+ application.generate_update(model_class, model_name, model_name_pluralize)
120
+ end
121
+
122
+ it 'generates a PUT member route' do
123
+ application.routes['PUT'][0][0].should == %r{^/employees/([^/?#]+)(?:\.|%2E)([^/?#]+)$}
124
+ application.routes['PUT'][0][1].should == ['id', 'format']
125
+ end
126
+
127
+ it 'updates and returns a model' do
128
+ put '/employees/1.json'
129
+
130
+ last_response.status.should == 204
131
+ end
132
+
133
+ it 'fails and returns an error' do
134
+ model.stub(valid?: false)
135
+
136
+ put '/employees/1.json', employee: { career: 'Unemployed' }
137
+
138
+ last_response.status.should == 400
139
+ end
140
+ end
141
+
142
+ describe '#generate_destroy' do
143
+ before do
144
+ application.generate_destroy(model_class, model_name, model_name_pluralize)
145
+ end
146
+
147
+ it 'generates a DELETE member route' do
148
+ application.routes['DELETE'][0][0].should == %r{^/employees/([^/?#]+)(?:\.|%2E)([^/?#]+)$}
149
+ application.routes['DELETE'][0][1].should == ['id', 'format']
150
+ end
151
+
152
+ it 'destroys and returns a model' do
153
+ delete '/employees/1.json'
154
+
155
+ last_response.status.should == 204
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,60 @@
1
+ require 'hovercraft/builder'
2
+
3
+ describe Hovercraft::Builder do
4
+ let(:model) { stub(name: 'Robot') }
5
+ let(:models) { Array.new(3) { model } }
6
+ let(:params) { models.map { |m| [m, 'robot', 'robots'] } }
7
+
8
+ before do
9
+ subject.stub(:with_each_model).
10
+ and_yield(*params[0]).
11
+ and_yield(*params[1]).
12
+ and_yield(*params[2])
13
+ end
14
+
15
+ describe '#application' do
16
+ it 'creates a sinatra application' do
17
+ subject.application.ancestors.should include(Sinatra::Base)
18
+ end
19
+
20
+ it 'configures the application' do
21
+ subject.should_receive(:configure)
22
+
23
+ subject.application
24
+ end
25
+
26
+ it 'generates routes for the application' do
27
+ subject.should_receive(:generate_routes)
28
+
29
+ subject.application
30
+ end
31
+ end
32
+
33
+ describe '#configure' do
34
+ let(:application) { stub(register: nil, use: nil) }
35
+
36
+ it 'registers the methods to generate actions' do
37
+ application.should_receive(:register).with(Hovercraft::Actions)
38
+
39
+ subject.configure(application)
40
+ end
41
+
42
+ it 'uses a post body parsing middleware' do
43
+ application.should_receive(:use).with(Rack::PostBodyContentTypeParser)
44
+
45
+ subject.configure(application)
46
+ end
47
+ end
48
+
49
+ describe '#generate_routes' do
50
+ let(:application) { stub }
51
+
52
+ it 'calls all generate methods for each model' do
53
+ [:index, :create, :show, :update, :destroy].each do |action|
54
+ application.should_receive("generate_#{action}").exactly(3).times
55
+ end
56
+
57
+ subject.generate_routes(application)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,28 @@
1
+ require 'hovercraft/caller'
2
+
3
+ describe Hovercraft::Caller do
4
+ describe '#directory' do
5
+ before { subject.stub(caller_file: '/gems/futurama/parallel_universe_box.rb:10 in `universe`') }
6
+
7
+ it 'returns the directory of the file at the top of the execution stack' do
8
+ subject.directory.should == '/gems/futurama'
9
+ end
10
+ end
11
+
12
+ describe '#cleaned_caller_files' do
13
+ let(:files) do
14
+ [
15
+ '/gems/futurama/parallel_universe_box.rb:10 in `universe`',
16
+ '/gems/futurama/parallel_universe_box.rb:3 in `initialize`',
17
+ '<internal: (irb)'
18
+ ]
19
+ end
20
+
21
+ before { subject.stub(caller: files) }
22
+
23
+ it 'returns a cleaned list of files on the execution stack' do
24
+ subject.cleaned_caller_files.should_not include('<internal: (irb)')
25
+ subject.cleaned_caller_files.should have(2).files
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ require 'hovercraft/loader'
2
+
3
+ describe Hovercraft::Loader do
4
+ let(:model) { stub(name: 'Robot') }
5
+ let(:models) { Array.new(3) { model } }
6
+ let(:params) { models.map { |m| [m, 'robot', 'robots'] } }
7
+
8
+ describe '#with_each_model' do
9
+ before { subject.stub(models: models) }
10
+
11
+ it 'yields the class, name, and plural name of each model' do
12
+ expect { |b| subject.with_each_model(&b) }.to yield_successive_args(*params)
13
+ end
14
+ end
15
+
16
+ describe '#models' do
17
+ it 'is memoized' do
18
+ subject.should_receive(:require_models).once.and_return([])
19
+
20
+ 2.times { subject.models }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ require 'hovercraft/server'
2
+
3
+ describe Hovercraft::Server do
4
+ it 'is a rack application' do
5
+ subject.respond_to?(:call).should be_true
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hovercraft
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - vanstee
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra-activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rack-contrib
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pry
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rack-test
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Generate a RESTful API from a directory of ActiveRecord models
111
+ email:
112
+ - vanstee@highgroove.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - Gemfile
119
+ - LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - hovercraft.gemspec
123
+ - lib/hovercraft.rb
124
+ - lib/hovercraft/actions.rb
125
+ - lib/hovercraft/builder.rb
126
+ - lib/hovercraft/caller.rb
127
+ - lib/hovercraft/loader.rb
128
+ - lib/hovercraft/server.rb
129
+ - lib/hovercraft/version.rb
130
+ - spec/hovercraft/actions_spec.rb
131
+ - spec/hovercraft/builder_spec.rb
132
+ - spec/hovercraft/caller_spec.rb
133
+ - spec/hovercraft/loader_spec.rb
134
+ - spec/hovercraft/server_spec.rb
135
+ homepage: ''
136
+ licenses: []
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ segments:
148
+ - 0
149
+ hash: 2214615904925685358
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ segments:
157
+ - 0
158
+ hash: 2214615904925685358
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 1.8.23
162
+ signing_key:
163
+ specification_version: 3
164
+ summary: There's a lot of boiler plate code that goes into creating an API so why
165
+ not just generate all that code from the models. Just throw your models in a models
166
+ directory and call the server from the rackup file and you have yourself a perfect
167
+ API.
168
+ test_files:
169
+ - spec/hovercraft/actions_spec.rb
170
+ - spec/hovercraft/builder_spec.rb
171
+ - spec/hovercraft/caller_spec.rb
172
+ - spec/hovercraft/loader_spec.rb
173
+ - spec/hovercraft/server_spec.rb