restful_spec 1.0.0

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,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in restful_spec.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jason Waldrip
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,58 @@
1
+ # RestfulSpec
2
+
3
+ Restful spec is an easy way to add a machine readable, comprehensive specification to your apis, without having to write a lot of documentation. The gem automatically discovers routes and builds out resources accordingly. With the option to customize a bit with a handy DSL.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'restful_spec'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install restful_spec
18
+
19
+ ## Usage
20
+
21
+ ### Include the Module
22
+ ```
23
+ class MyController < ApplicationController
24
+ include RestfulSpec
25
+
26
+ # ...
27
+ end
28
+
29
+ ```
30
+
31
+ ### Add a bit of customization. Only if you want!
32
+ ```
33
+ class MyController < ApplicationController
34
+ include RestfulSpec
35
+
36
+ specification do
37
+ description "An API for MyController"
38
+ attributes except: [:id, :created_at]
39
+ accepts_formats :json, :xml
40
+ accepts_parameters attributes: :all, for: 'POST /my'
41
+ responds_with :collection, for: 'GET /my'
42
+ responds_with :member, for: 'GET /my/:id'
43
+ end
44
+
45
+ #...
46
+
47
+ end
48
+
49
+ ```
50
+
51
+
52
+ ## Contributing
53
+
54
+ 1. Fork it
55
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
56
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
57
+ 4. Push to the branch (`git push origin my-new-feature`)
58
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,42 @@
1
+ require "restful_spec/version"
2
+
3
+ module RestfulSpec
4
+ extend ActiveSupport::Concern
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Specification
8
+ autoload :Error
9
+
10
+ included do
11
+
12
+ class_attribute :current_specification
13
+ self.current_specification = Specification.new self
14
+
15
+ private :current_specification
16
+
17
+ specification do
18
+ accepts_parameters for: %w(PUT POST)
19
+ accepts_formats :json, :xml
20
+ responds_with :member, for: %w(GET POST)
21
+ responds_with :specification, for: 'OPTIONS'
22
+ responds_with :collection, for: controller.controller_name
23
+ responds_with :success, for: %w(PUT DELETE)
24
+ end
25
+ end
26
+
27
+ def spec
28
+ render json: current_specification
29
+ end
30
+
31
+ module ClassMethods
32
+
33
+ def specification(resource = nil, &block)
34
+ current_specification.resource = resource if resource
35
+ current_specification.parse(&block) if block_given?
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ require 'restful_spec/railtie' if defined?(Rails) && Rails.version >= '3.2'
@@ -0,0 +1,12 @@
1
+ module RestfulSpec::Error
2
+
3
+ class InvalidAction < StandardError
4
+
5
+ def initialize(message, actions)
6
+ message += "\n\nValid actions are: \n\n#{actions.join("\n")}\n\n"
7
+ super(message)
8
+ end
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,33 @@
1
+ class RestfulSpec::Railtie < Rails::Railtie
2
+
3
+ initializer "Add spec/options route to routing" do
4
+ ActiveSupport.on_load :action_controller do
5
+ ActionDispatch::Routing::Mapper.send :include, SpecRoutes
6
+ end
7
+ end
8
+
9
+ module SpecRoutes
10
+
11
+ def resources(*resources, &block)
12
+ super
13
+ options = resources.extract_options!
14
+ resource_scope(:resources, ActionDispatch::Routing::Mapper::Resources::Resource.new(resources.pop, options)) do
15
+ collection do
16
+ match '/' => "#{parent_resource.controller}#spec", via: :options, as: :specification
17
+ end
18
+ end
19
+ end
20
+
21
+ def resource(*resources, &block)
22
+ super
23
+ options = resources.extract_options!
24
+ resource_scope(:resource, ActionDispatch::Routing::Mapper::Resources::SingletonResource.new(resources.pop, options)) do
25
+ collection do
26
+ match '/' => "#{parent_resource.controller}#spec", via: :options, as: :specification
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,153 @@
1
+ class RestfulSpec::Specification
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :Action
5
+
6
+ attr_reader :controller, :resource
7
+ delegate :as_json, to: :spec_hash
8
+
9
+ def resource=(val)
10
+ @resource = val.classify.constantize
11
+ end
12
+
13
+ def parse(&block)
14
+ instance_eval(&block) if block_given?
15
+ end
16
+
17
+ private
18
+
19
+ def initialize(controller)
20
+ @controller = controller
21
+ self.resource = controller.controller_name
22
+ @attributes = @resource.column_names.map(&:to_sym) if active_record?
23
+ @attributes ||= []
24
+ @description = "Api for #{resource_name}"
25
+ @actions = nil
26
+ @formats = []
27
+ end
28
+
29
+ def resource_name
30
+ resource.name.titleize
31
+ end
32
+
33
+ def accepts_formats(*args)
34
+ @formats = args.map{ |i| i.is_a?(Set) ? i.to_a : i }.flatten
35
+ end
36
+
37
+ alias :accepts_format :accepts_formats
38
+
39
+ def accessible_attributes
40
+ resource.respond_to?(:accessible_attributes) ? resource.accessible_attributes : Set.new
41
+ end
42
+
43
+ def accepts_parameters(options={})
44
+
45
+ raise RestfulSpec::Error::InvalidAction,
46
+ "accepts_parameter requires a valid action!",
47
+ actions unless (actions = Array.wrap(options.delete :for)).present?
48
+
49
+ actions = actions.map { |a| get_action(a) }.flatten
50
+
51
+ params = SmartSelect.new(accessible_attributes, options[:attributes]) do
52
+ select :only
53
+ reject :except
54
+ end
55
+
56
+ actions.reduce([]) do |action_map, action|
57
+ raise RestfulSpec::Error::InvalidAction,
58
+ "#{action_string} is not a valid action for #{@controller.class.name}!", self.actions unless action.present?
59
+
60
+ action.parameters = params.to_a.select(&:present?)
61
+ action_map << action
62
+ end
63
+
64
+ end
65
+
66
+ alias :accepts_parameter :accepts_parameters
67
+
68
+ def responds_with(responder, options)
69
+ raise RestfulSpec::Error::InvalidAction,
70
+ "responds_with requires a valid action!",
71
+ actions unless options[:for].present?
72
+ actions = [options[:for]].flatten.map { |a| get_action(a) }.flatten
73
+ actions.each do |action|
74
+ raise RestfulSpec::Error::InvalidAction,
75
+ "#{action_string} is not a valid action for #{@controller.class.name}!",
76
+ actions unless action.present?
77
+ action.responds_with = responder
78
+ end
79
+ end
80
+
81
+ def get_action(string)
82
+ Array.wrap(actions).select do |action|
83
+ action.match?(string)
84
+ end
85
+ end
86
+
87
+ def actions
88
+ @actions ||= Action.from_routes(controller)
89
+ end
90
+
91
+ def description(string)
92
+ @description = string
93
+ end
94
+
95
+ def attributes(*args)
96
+
97
+ options = args.extract_options!
98
+ args = @attributes unless args.present?
99
+ @attributes = SmartSelect.new(args, options) do
100
+ select :only
101
+ reject :except
102
+ add :methods
103
+ end
104
+
105
+ end
106
+
107
+ def spec_hash
108
+ {
109
+ resource: resource.to_s.titleize,
110
+ description: @description,
111
+ formats: @formats,
112
+ attributes: @attributes,
113
+ actions: actions
114
+ }
115
+ end
116
+
117
+ def active_record?
118
+ resource.ancestors.include?(ActiveRecord::Base)
119
+ end
120
+
121
+ class SmartSelect < Array
122
+
123
+ attr_reader :options
124
+
125
+ def _included_(matcher)
126
+ ->(item) { Array.wrap(options[matcher]).include?(item) }
127
+ end
128
+
129
+ def initialize(*args, &block)
130
+ @options = args.extract_options!
131
+ super(args.first.to_a)
132
+ instance_eval(&block) if block_given?
133
+ end
134
+
135
+ def add(matcher)
136
+ self.replace self + Array.wrap(options[matcher])
137
+ end
138
+
139
+ def select(matcher)
140
+ self.replace( super &_included_(matcher) ) if options[matcher].present?
141
+ end
142
+
143
+ alias :select! :select
144
+
145
+ def reject(matcher)
146
+ self.replace( super &_included_(matcher) ) if options[matcher].present?
147
+ end
148
+
149
+ alias :reject! :reject
150
+
151
+ end
152
+
153
+ end
@@ -0,0 +1,61 @@
1
+ class RestfulSpec::Specification::Action
2
+
3
+ attr_accessor :parameters, :responds_with
4
+ attr_reader :route
5
+
6
+ def self.from_routes(controller)
7
+ Rails.application.routes.routes.reduce([]) do |actions, route|
8
+ verbs = if route.verb.is_a?(Regexp)
9
+ array = route.verb.source.split('|')
10
+ array.map { |verb| verb.gsub(/[^a-z]/i, '') }
11
+ elsif route.verb.present?
12
+ Array.wrap(route.verb)
13
+ else
14
+ %w(GET POST PUT DELETE)
15
+ end
16
+
17
+ verbs.each do |verb|
18
+ action = new(verb, route, controller)
19
+ actions << action if action.exists?
20
+ end
21
+
22
+ actions
23
+ end
24
+ end
25
+
26
+ def initialize(verb, route, controller)
27
+ @route = route
28
+ @controller = controller
29
+
30
+ @name = route.name
31
+ @path = route.path.respond_to?(:spec) ? route.path.spec.to_s : route.path
32
+ @path.gsub!(/\(\.:format\)$/,'')
33
+ @method = verb
34
+
35
+ self.parameters = Set.new
36
+ self.responds_with = nil
37
+ end
38
+
39
+ def exists?
40
+ route.defaults[:controller] == @controller.controller_path && @controller.method_defined?(route.defaults[:action])
41
+ end
42
+
43
+ def as_json(*args)
44
+ hash = {}
45
+ hash[:method] = @method
46
+ hash[:path] = @path
47
+ hash[:name] = @name
48
+ hash[:parameters] = parameters
49
+ hash[:responds_with] = responds_with
50
+ hash.as_json(*args)
51
+ end
52
+
53
+ def match?(string)
54
+ to_s == string || @method == string || @name == string || @path == string
55
+ end
56
+
57
+ def to_s
58
+ [@method, @path].join(' ')
59
+ end
60
+
61
+ end
@@ -0,0 +1,3 @@
1
+ module RestfulSpec
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'restful_spec/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+
8
+ gem.name = "restful_spec"
9
+ gem.version = RestfulSpec::VERSION
10
+ gem.authors = ["Jason Waldrip"]
11
+ gem.email = ["jason@waldrip.net"]
12
+ gem.description = %q{Restful spec is an easy way to add a machine readable, comprehensive specification to your apis, without having to write a lot of documentation. The gem automatically discovers routes and builds out resources accordingly. With the option to customize a bit with a handy DSL.}
13
+ gem.summary = %q{Specifications your your rest API's}
14
+ gem.homepage = ""
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency "activesupport"
22
+ #gem.add_dependency "rails", "> 3.2"
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: restful_spec
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Waldrip
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
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
+ description: Restful spec is an easy way to add a machine readable, comprehensive
31
+ specification to your apis, without having to write a lot of documentation. The
32
+ gem automatically discovers routes and builds out resources accordingly. With the
33
+ option to customize a bit with a handy DSL.
34
+ email:
35
+ - jason@waldrip.net
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - .gitignore
41
+ - Gemfile
42
+ - LICENSE.txt
43
+ - README.md
44
+ - Rakefile
45
+ - lib/restful_spec.rb
46
+ - lib/restful_spec/error.rb
47
+ - lib/restful_spec/railtie.rb
48
+ - lib/restful_spec/specification.rb
49
+ - lib/restful_spec/specification/action.rb
50
+ - lib/restful_spec/version.rb
51
+ - restful_spec.gemspec
52
+ homepage: ''
53
+ licenses: []
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.23
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Specifications your your rest API's
76
+ test_files: []
77
+ has_rdoc: