http_status_exceptions 0.1.9
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 +6 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +68 -0
- data/Rakefile +5 -0
- data/http_status_exceptions.gemspec +23 -0
- data/init.rb +1 -0
- data/lib/http_status_exceptions.rb +146 -0
- data/spec/http_status_exception_spec.rb +97 -0
- data/spec/spec_helper.rb +17 -0
- data/tasks/github-gem.rake +323 -0
- metadata +84 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 20082-2009 Willem van Bergen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
<b>WARNING:</b> the gem version that Github currently serves is faulty. The issue
|
2
|
+
is already fixes in the repository, but Github is not yet building new gem
|
3
|
+
versions. As soon as this is fixed, I will release a new version that resolves
|
4
|
+
the issue. For now, install version 0.1.5 which is unaffected:
|
5
|
+
|
6
|
+
sudo gem install wvanbergen-http_status_exceptions \
|
7
|
+
--source http://gems.github.com --version "= 0.1.5"
|
8
|
+
|
9
|
+
= HTTP status exception
|
10
|
+
|
11
|
+
This simple plugin will register exception classes for all HTTP status. These exceptions can then be raised from your controllers, after
|
12
|
+
which a response will be send back to the client with the desired HTTP status, possible with some other content.
|
13
|
+
|
14
|
+
You can use this plugin to access control mechanisms. You can simply raise a HTTPStatus::Forbidden if a user is not allowed to
|
15
|
+
perform a certain action. A nice looking error page will be the result. See the example below:
|
16
|
+
|
17
|
+
See the project wiki (http://github.com/wvanbergen/http_status_exceptions/wikis) for additional documentation.
|
18
|
+
|
19
|
+
== Installation
|
20
|
+
|
21
|
+
Installation is simple. Simply add the gem in your <tt>environment.rb</tt>:
|
22
|
+
|
23
|
+
Rails::Initializer.run do |config|
|
24
|
+
...
|
25
|
+
config.gem 'wvanbergen-http_status_exceptions', :lib => 'http_status_exceptions', :source => 'http://gems.github.com'
|
26
|
+
end
|
27
|
+
|
28
|
+
Run <tt>rake gems:install</tt> to install the gem if needed.
|
29
|
+
|
30
|
+
== Configuration
|
31
|
+
|
32
|
+
You can modify where HTTP status exception looks for its template files like so:
|
33
|
+
|
34
|
+
class ApplicationController < ActionController::Base
|
35
|
+
...
|
36
|
+
HTTPStatus::Base.template_path = 'path_to/http_status_templates'
|
37
|
+
end
|
38
|
+
|
39
|
+
You can also modify which layout is used when rendering a template by setting the <tt>template_layout</tt>:
|
40
|
+
|
41
|
+
class ApplicationController < ActionController::Base
|
42
|
+
...
|
43
|
+
HTTPStatus::Base.template_layout = 'exception'
|
44
|
+
end
|
45
|
+
|
46
|
+
If you don't set a template_layout the current layout for the requested action will be used.
|
47
|
+
|
48
|
+
== Usage
|
49
|
+
|
50
|
+
class BlogController < ApplicationController
|
51
|
+
|
52
|
+
def destroy
|
53
|
+
raise HTTPStatus::Forbidden, 'You cannot delete blogs!' unless current_user.can_delete_blogs?
|
54
|
+
@blog.destroy
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
By default, this will return an empty response with the "forbidden" status code (403). If you want to add content
|
59
|
+
to the response as well, create the following view: <tt>shared/http_status/forbidden.html.erb</tt>. You can use the
|
60
|
+
<tt>@exception</tt>-object in your view:
|
61
|
+
|
62
|
+
<h1>Forbidden</h1>
|
63
|
+
<p> <%= h(@exception.message) %> </p>
|
64
|
+
<hr />
|
65
|
+
<p>HTTP status code <small> <%= @exception.status_code %>: <%= @exception.status.to_s.humanize %></small></p>
|
66
|
+
|
67
|
+
The response will only be sent if the request format is HTML because of the name of the view file. In theory you
|
68
|
+
could make a response for XML requests as well by using <tt>shared/http_status/forbidden.xml.builder</tt> as filename
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'http_status_exceptions'
|
3
|
+
|
4
|
+
# Do not update the version and date values by hand.
|
5
|
+
# This will be done automatically by the gem release script.
|
6
|
+
s.version = "0.1.9"
|
7
|
+
s.date = "2009-10-01"
|
8
|
+
|
9
|
+
s.summary = "A Rails plugin to use exceptions for generating HTTP status responses"
|
10
|
+
s.description = "Clean up your controller code by raising exceptions that generate responses with different HTTP status codes."
|
11
|
+
|
12
|
+
s.add_runtime_dependency('actionpack', '>= 2.1.0')
|
13
|
+
s.add_development_dependency('rspec')
|
14
|
+
|
15
|
+
s.authors = ['Willem van Bergen']
|
16
|
+
s.email = ['willem@vanbergen.org']
|
17
|
+
s.homepage = 'http://github.com/wvanbergen/http_status_exceptions/wikis'
|
18
|
+
|
19
|
+
# Do not update the files and test_files values by hand.
|
20
|
+
# This will be done automatically by the gem release script.
|
21
|
+
s.files = %w(spec/spec_helper.rb http_status_exceptions.gemspec .gitignore init.rb lib/http_status_exceptions.rb Rakefile MIT-LICENSE tasks/github-gem.rake README.rdoc spec/http_status_exception_spec.rb)
|
22
|
+
s.test_files = %w(spec/http_status_exception_spec.rb)
|
23
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'http_status_exceptions.rb'
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# The HTTPStatus module is the core of the http_status_exceptions gem and
|
2
|
+
# contains all functionality.
|
3
|
+
#
|
4
|
+
# The module contains <tt>HTTPStatus::Base</tt> class, which is used as a
|
5
|
+
# superclass for every HTTPStatus exception. Subclasses, like
|
6
|
+
# <tt>HTTPStatus::Forbidden</tt> or <tt>HTTPStatus::NotFound</tt> will be
|
7
|
+
# generated on demand by the <tt>HTTPStatus.const_missing</tt> method.
|
8
|
+
#
|
9
|
+
# Moreover, it contains methods to handle these exceptions and integrate this
|
10
|
+
# functionality into <tt>ActionController::Base</tt>. When this module is in
|
11
|
+
# included in the <tt>ActionController::Base</tt> class, it will call
|
12
|
+
# <tt>rescue_from</tt> on it to handle all <tt>HTTPStatus::Base</tt>
|
13
|
+
# exceptions with the <tt>HTTPStatus#http_status_exceptions</tt> method.
|
14
|
+
#
|
15
|
+
# The exception handler will try to render a response with the correct
|
16
|
+
# HTTPStatus. When no suitable template is found to render the exception with,
|
17
|
+
# it will simply respond with an empty HTTP status code.
|
18
|
+
module HTTPStatus
|
19
|
+
|
20
|
+
# The current gem release version. Do not set this value by hand, it will
|
21
|
+
# be done automatically by them gem release script.
|
22
|
+
VERSION = "0.1.9"
|
23
|
+
|
24
|
+
# The Base HTTP status exception class is used as superclass for every
|
25
|
+
# exception class that is constructed. It implements some shared
|
26
|
+
# functionality for finding the status code and determining the template
|
27
|
+
# path to render.
|
28
|
+
#
|
29
|
+
# Subclasses of this class will be generated on demand when a non-exisiting
|
30
|
+
# constant of the <tt>HTTPStatus</tt> module is requested. This is
|
31
|
+
# implemented in the <tt>HTTPStatus.const_missing</tt> method.
|
32
|
+
class Base < StandardError
|
33
|
+
|
34
|
+
# The path from which the error documents are loaded.
|
35
|
+
cattr_accessor :template_path
|
36
|
+
@@template_path = 'shared/http_status'
|
37
|
+
|
38
|
+
# The layout in which the error documents are rendered
|
39
|
+
cattr_accessor :template_layout
|
40
|
+
@@template_layout = nil # Use the standard layout template setting by default.
|
41
|
+
|
42
|
+
attr_reader :details
|
43
|
+
|
44
|
+
# Initializes the exception instance.
|
45
|
+
# <tt>message</tt>:: The exception message.
|
46
|
+
# <tt>details</tt>:: An object with details about the exception.
|
47
|
+
def initialize(message = nil, details = nil)
|
48
|
+
@details = details
|
49
|
+
super(message)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the HTTP status symbol corresponding to this class. This is one
|
53
|
+
# of the symbols that can be found in the map that can be found in
|
54
|
+
# <tt>ActionController::StatusCodes</tt>.
|
55
|
+
#
|
56
|
+
# This method should be overridden by subclasses, as it returns
|
57
|
+
# <tt>:internal_server_error</tt> by default. This is done automatically
|
58
|
+
# when a new exception class is being generated by
|
59
|
+
# <tt>HTTPStatus.const_missing</tt>.
|
60
|
+
def self.status
|
61
|
+
:internal_server_error
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the HTTP status symbol (as defined by Rails) corresponding to
|
65
|
+
# this instance. By default, it calls the class method of the same name.
|
66
|
+
def status
|
67
|
+
self.class.status
|
68
|
+
end
|
69
|
+
|
70
|
+
# The numeric status code corresponding to this exception class. Uses the
|
71
|
+
# status symbol to code map in <tt>ActionController::StatusCodes</tt>.
|
72
|
+
def self.status_code
|
73
|
+
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[self.status]
|
74
|
+
end
|
75
|
+
|
76
|
+
# The numeric status code corresponding to this exception. By default, it
|
77
|
+
# calls the class method of the same name.
|
78
|
+
def status_code
|
79
|
+
self.class.status_code
|
80
|
+
end
|
81
|
+
|
82
|
+
# The name of the template that should be used as error page for this
|
83
|
+
# exception class.
|
84
|
+
def self.template
|
85
|
+
"#{template_path}/#{status}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# The name of the template that should be used as error page for this
|
89
|
+
# exception. By default, it calls the class method of the same name.
|
90
|
+
def template
|
91
|
+
self.class.template
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# This function will install a rescue_from handler for HTTPStatus::Base
|
96
|
+
# exceptions in the class in which this module is included.
|
97
|
+
#
|
98
|
+
# <tt>base</tt>:: The class in which the module is included. Should be
|
99
|
+
# <tt>ActionController::Base</tt> during the initialization of the gem.
|
100
|
+
def self.included(base)
|
101
|
+
base.send(:rescue_from, HTTPStatus::Base, :with => :http_status_exception)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Generates a <tt>HTTPStatus::Base</tt> subclass on demand based on the
|
105
|
+
# constant name. The constant name should correspond to one of the status
|
106
|
+
# symbols defined in <tt>ActionController::StatusCodes</tt>. The function
|
107
|
+
# will raise an exception if the constant name cannot be mapped onto one of
|
108
|
+
# the status symbols.
|
109
|
+
#
|
110
|
+
# This method will create a new subclass of <tt>HTTPStatus::Base</tt> and
|
111
|
+
# overrides the status class method of the class to return the correct
|
112
|
+
# status symbol.
|
113
|
+
#
|
114
|
+
# <tt>const</tt>:: The name of the missing constant, for which an exception
|
115
|
+
# class should be generated.
|
116
|
+
def self.const_missing(const)
|
117
|
+
status_symbol = const.to_s.underscore.to_sym
|
118
|
+
raise "Unrecognized HTTP Status name!" unless ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.has_key?(status_symbol)
|
119
|
+
klass = Class.new(HTTPStatus::Base)
|
120
|
+
klass.cattr_accessor(:status)
|
121
|
+
klass.status = status_symbol
|
122
|
+
const_set(const, klass)
|
123
|
+
return const_get(const)
|
124
|
+
end
|
125
|
+
|
126
|
+
# The default handler for raised HTTP status exceptions. It will render a
|
127
|
+
# template if available, or respond with an empty response with the HTTP
|
128
|
+
# status corresponding to the exception.
|
129
|
+
#
|
130
|
+
# You can override this method in your <tt>ApplicationController</tt> to
|
131
|
+
# handle the exceptions yourself.
|
132
|
+
#
|
133
|
+
# <tt>exception</tt>:: The HTTP status exception to handle.
|
134
|
+
def http_status_exception(exception)
|
135
|
+
@exception = exception
|
136
|
+
render_options = {:template => exception.template, :status => exception.status}
|
137
|
+
render_options[:layout] = exception.template_layout if exception.template_layout
|
138
|
+
render(render_options)
|
139
|
+
rescue ActionView::MissingTemplate
|
140
|
+
head(exception.status)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Include the HTTPStatus module into <tt>ActionController::Base</tt> to enable
|
145
|
+
# the <tt>http_status_exception</tt> exception handler.
|
146
|
+
ActionController::Base.send(:include, HTTPStatus)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe HTTPStatus::Base, 'class inheritance' do
|
4
|
+
before(:all) do
|
5
|
+
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.stub!(:has_key?).with(:testing_status).and_return(true)
|
6
|
+
@status_exception_class = HTTPStatus::TestingStatus
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
HTTPStatus::Base.template_path = 'shared/http_status'
|
11
|
+
HTTPStatus.send :remove_const, 'TestingStatus'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should set the status symbol based on the class name" do
|
15
|
+
@status_exception_class.status.should == :testing_status
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should use 'shared/http_status' as default view path" do
|
19
|
+
@status_exception_class.template.should == 'shared/http_status/testing_status'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should check ActionController's status code list for the status code based on the class name" do
|
23
|
+
ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE.should_receive(:[]).with(:testing_status)
|
24
|
+
@status_exception_class.status_code
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should use the HTTPStatus::Base.template_path setting to determine the error template" do
|
28
|
+
HTTPStatus::Base.template_path = 'testing'
|
29
|
+
@status_exception_class.template.should == 'testing/testing_status'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise an exception when the class name does not correspond to a HTTP status code" do
|
33
|
+
lambda { HTTPStatus::Nonsense }.should raise_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Run some tests for different valid subclasses.
|
38
|
+
{ 'NotFound' => 404, 'Forbidden' => 403, 'PaymentRequired' => 402, 'InternalServerError' => 500}.each do |status_class, status_code|
|
39
|
+
|
40
|
+
describe "HTTPStatus::#{status_class}" do
|
41
|
+
it "should generate the HTTPStatus::#{status_class} class successfully" do
|
42
|
+
lambda { HTTPStatus.const_get(status_class) }.should_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should create a subclass of HTTPStatus::Base for the #{status_class.underscore.humanize.downcase} status" do
|
46
|
+
HTTPStatus.const_get(status_class).ancestors.should include(HTTPStatus::Base)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return the correct status code (#{status_code}) when using the class" do
|
50
|
+
HTTPStatus.const_get(status_class).status_code.should == status_code
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return the correct status code (#{status_code}) when using the instance" do
|
54
|
+
HTTPStatus.const_get(status_class).new.status_code.should == status_code
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'HTTPStatus#http_status_exception' do
|
60
|
+
before(:each) { @controller = Class.new(ActionController::Base).new }
|
61
|
+
after(:each) { HTTPStatus::Base.template_layout = nil}
|
62
|
+
|
63
|
+
it "should create the :http_status_exception method in ActionController" do
|
64
|
+
@controller.should respond_to(:http_status_exception)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should call :http_status_exception when an exception is raised when handling the action" do
|
68
|
+
exception = HTTPStatus::Base.new('test')
|
69
|
+
@controller.stub!(:perform_action_without_rescue).and_raise(exception)
|
70
|
+
@controller.should_receive(:http_status_exception).with(exception)
|
71
|
+
@controller.send(:perform_action)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should call render with the correct view and correct HTTP status" do
|
75
|
+
@controller.should_receive(:render).with(hash_including(
|
76
|
+
:status => :internal_server_error, :template => "shared/http_status/internal_server_error"))
|
77
|
+
|
78
|
+
@controller.http_status_exception(HTTPStatus::Base.new('test'))
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not call render with a layout by default" do
|
82
|
+
@controller.should_not_receive(:render).with(hash_including(:layout => 'testing'))
|
83
|
+
@controller.http_status_exception(HTTPStatus::Base.new('test'))
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should call render with a layout set when this property is set on the exception class" do
|
87
|
+
@controller.should_receive(:render).with(hash_including(:layout => 'testing'))
|
88
|
+
HTTPStatus::Base.template_layout = 'testing'
|
89
|
+
@controller.http_status_exception(HTTPStatus::Base.new('test'))
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should call head with the correct status code if render cannot found a template" do
|
93
|
+
@controller.stub!(:render).and_raise(ActionView::MissingTemplate.new([], 'template.html.erb'))
|
94
|
+
@controller.should_receive(:head).with(:internal_server_error)
|
95
|
+
@controller.http_status_exception(HTTPStatus::Base.new('test'))
|
96
|
+
end
|
97
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec/autorun'
|
5
|
+
|
6
|
+
require 'action_controller'
|
7
|
+
|
8
|
+
require 'http_status_exceptions'
|
9
|
+
|
10
|
+
# Include all files in the spec_helper directory
|
11
|
+
Dir[File.dirname(__FILE__) + "/lib/**/*.rb"].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
14
|
+
|
15
|
+
Spec::Runner.configure do |config|
|
16
|
+
# nothing special
|
17
|
+
end
|
@@ -0,0 +1,323 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/tasklib'
|
4
|
+
require 'date'
|
5
|
+
require 'git'
|
6
|
+
|
7
|
+
module GithubGem
|
8
|
+
|
9
|
+
# Detects the gemspc file of this project using heuristics.
|
10
|
+
def self.detect_gemspec_file
|
11
|
+
FileList['*.gemspec'].first
|
12
|
+
end
|
13
|
+
|
14
|
+
# Detects the main include file of this project using heuristics
|
15
|
+
def self.detect_main_include
|
16
|
+
if detect_gemspec_file =~ /^(\.*)\.gemspec$/ && File.exist?("lib/#{$1}.rb")
|
17
|
+
"lib/#{$1}.rb"
|
18
|
+
elsif FileList['lib/*.rb'].length == 1
|
19
|
+
FileList['lib/*.rb'].first
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class RakeTasks
|
26
|
+
|
27
|
+
attr_reader :gemspec, :modified_files, :git
|
28
|
+
attr_accessor :gemspec_file, :task_namespace, :main_include, :root_dir, :spec_pattern, :test_pattern, :remote, :remote_branch, :local_branch
|
29
|
+
|
30
|
+
# Initializes the settings, yields itself for configuration
|
31
|
+
# and defines the rake tasks based on the gemspec file.
|
32
|
+
def initialize(task_namespace = :gem)
|
33
|
+
@gemspec_file = GithubGem.detect_gemspec_file
|
34
|
+
@task_namespace = task_namespace
|
35
|
+
@main_include = GithubGem.detect_main_include
|
36
|
+
@modified_files = []
|
37
|
+
@root_dir = Dir.pwd
|
38
|
+
@test_pattern = 'test/**/*_test.rb'
|
39
|
+
@spec_pattern = 'spec/**/*_spec.rb'
|
40
|
+
@local_branch = 'master'
|
41
|
+
@remote = 'origin'
|
42
|
+
@remote_branch = 'master'
|
43
|
+
|
44
|
+
yield(self) if block_given?
|
45
|
+
|
46
|
+
@git = Git.open(@root_dir)
|
47
|
+
load_gemspec!
|
48
|
+
define_tasks!
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
# Define Unit test tasks
|
54
|
+
def define_test_tasks!
|
55
|
+
require 'rake/testtask'
|
56
|
+
|
57
|
+
namespace(:test) do
|
58
|
+
Rake::TestTask.new(:basic) do |t|
|
59
|
+
t.pattern = test_pattern
|
60
|
+
t.verbose = true
|
61
|
+
t.libs << 'test'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "Run all unit tests for #{gemspec.name}"
|
66
|
+
task(:test => ['test:basic'])
|
67
|
+
end
|
68
|
+
|
69
|
+
# Defines RSpec tasks
|
70
|
+
def define_rspec_tasks!
|
71
|
+
require 'spec/rake/spectask'
|
72
|
+
|
73
|
+
namespace(:spec) do
|
74
|
+
desc "Verify all RSpec examples for #{gemspec.name}"
|
75
|
+
Spec::Rake::SpecTask.new(:basic) do |t|
|
76
|
+
t.spec_files = FileList[spec_pattern]
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
|
80
|
+
Spec::Rake::SpecTask.new(:specdoc) do |t|
|
81
|
+
t.spec_files = FileList[spec_pattern]
|
82
|
+
t.spec_opts << '--format' << 'specdoc' << '--color'
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "Run RCov on specs for #{gemspec.name}"
|
86
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
87
|
+
t.spec_files = FileList[spec_pattern]
|
88
|
+
t.rcov = true
|
89
|
+
t.rcov_opts = ['--exclude', '"spec/*,gems/*"', '--rails']
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
|
94
|
+
task(:spec => ['spec:specdoc'])
|
95
|
+
end
|
96
|
+
|
97
|
+
# Defines the rake tasks
|
98
|
+
def define_tasks!
|
99
|
+
|
100
|
+
define_test_tasks! if has_tests?
|
101
|
+
define_rspec_tasks! if has_specs?
|
102
|
+
|
103
|
+
namespace(@task_namespace) do
|
104
|
+
desc "Updates the filelist in the gemspec file"
|
105
|
+
task(:manifest) { manifest_task }
|
106
|
+
|
107
|
+
desc "Builds the .gem package"
|
108
|
+
task(:build => :manifest) { build_task }
|
109
|
+
|
110
|
+
desc "Sets the version of the gem in the gemspec"
|
111
|
+
task(:set_version => [:check_version, :check_current_branch]) { version_task }
|
112
|
+
task(:check_version => :fetch_origin) { check_version_task }
|
113
|
+
|
114
|
+
task(:fetch_origin) { fetch_origin_task }
|
115
|
+
task(:check_current_branch) { check_current_branch_task }
|
116
|
+
task(:check_clean_status) { check_clean_status_task }
|
117
|
+
task(:check_not_diverged => :fetch_origin) { check_not_diverged_task }
|
118
|
+
|
119
|
+
checks = [:check_current_branch, :check_clean_status, :check_not_diverged, :check_version]
|
120
|
+
checks.unshift('spec:basic') if has_specs?
|
121
|
+
checks.unshift('test:basic') if has_tests?
|
122
|
+
checks.push << [:check_rubyforge] if gemspec.rubyforge_project
|
123
|
+
|
124
|
+
desc "Perform all checks that would occur before a release"
|
125
|
+
task(:release_checks => checks)
|
126
|
+
|
127
|
+
release_tasks = [:release_checks, :set_version, :build, :github_release]
|
128
|
+
release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
|
129
|
+
|
130
|
+
desc "Release a new verison of the gem"
|
131
|
+
task(:release => release_tasks) { release_task }
|
132
|
+
|
133
|
+
task(:check_rubyforge) { check_rubyforge_task }
|
134
|
+
task(:rubyforge_release) { rubyforge_release_task }
|
135
|
+
task(:github_release => [:commit_modified_files, :tag_version]) { github_release_task }
|
136
|
+
task(:tag_version) { tag_version_task }
|
137
|
+
task(:commit_modified_files) { commit_modified_files_task }
|
138
|
+
|
139
|
+
desc "Updates the gem release tasks with the latest version on Github"
|
140
|
+
task(:update_tasks) { update_tasks_task }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Updates the files list and test_files list in the gemspec file using the list of files
|
145
|
+
# in the repository and the spec/test file pattern.
|
146
|
+
def manifest_task
|
147
|
+
# Load all the gem's files using "git ls-files"
|
148
|
+
repository_files = git.ls_files.keys
|
149
|
+
test_files = Dir[test_pattern] + Dir[spec_pattern]
|
150
|
+
|
151
|
+
update_gemspec(:files, repository_files)
|
152
|
+
update_gemspec(:test_files, repository_files & test_files)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Builds the gem
|
156
|
+
def build_task
|
157
|
+
sh "gem build -q #{gemspec_file}"
|
158
|
+
Dir.mkdir('pkg') unless File.exist?('pkg')
|
159
|
+
sh "mv #{gemspec.name}-#{gemspec.version}.gem pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Updates the version number in the gemspec file, the VERSION constant in the main
|
163
|
+
# include file and the contents of the VERSION file.
|
164
|
+
def version_task
|
165
|
+
update_gemspec(:version, ENV['VERSION']) if ENV['VERSION']
|
166
|
+
update_gemspec(:date, Date.today)
|
167
|
+
|
168
|
+
update_version_file(gemspec.version)
|
169
|
+
update_version_constant(gemspec.version)
|
170
|
+
end
|
171
|
+
|
172
|
+
def check_version_task
|
173
|
+
raise "#{ENV['VERSION']} is not a valid version number!" if ENV['VERSION'] && !Gem::Version.correct?(ENV['VERSION'])
|
174
|
+
proposed_version = Gem::Version.new(ENV['VERSION'] || gemspec.version)
|
175
|
+
# Loads the latest version number using the created tags
|
176
|
+
newest_version = git.tags.map { |tag| tag.name.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max
|
177
|
+
raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version && newest_version >= proposed_version
|
178
|
+
end
|
179
|
+
|
180
|
+
# Checks whether the current branch is not diverged from the remote branch
|
181
|
+
def check_not_diverged_task
|
182
|
+
raise "The current branch is diverged from the remote branch!" if git.log.between('HEAD', git.remote(remote).branch(remote_branch).gcommit).any?
|
183
|
+
end
|
184
|
+
|
185
|
+
# Checks whether the repository status ic clean
|
186
|
+
def check_clean_status_task
|
187
|
+
raise "The current working copy contains modifications" if git.status.changed.any?
|
188
|
+
end
|
189
|
+
|
190
|
+
# Checks whether the current branch is correct
|
191
|
+
def check_current_branch_task
|
192
|
+
raise "Currently not on #{local_branch} branch!" unless git.branch.name == local_branch.to_s
|
193
|
+
end
|
194
|
+
|
195
|
+
# Fetches the latest updates from Github
|
196
|
+
def fetch_origin_task
|
197
|
+
git.fetch('origin')
|
198
|
+
end
|
199
|
+
|
200
|
+
# Commits every file that has been changed by the release task.
|
201
|
+
def commit_modified_files_task
|
202
|
+
if modified_files.any?
|
203
|
+
modified_files.each { |file| git.add(file) }
|
204
|
+
git.commit("Released #{gemspec.name} gem version #{gemspec.version}")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Adds a tag for the released version
|
209
|
+
def tag_version_task
|
210
|
+
git.add_tag("#{gemspec.name}-#{gemspec.version}")
|
211
|
+
end
|
212
|
+
|
213
|
+
# Pushes the changes and tag to github
|
214
|
+
def github_release_task
|
215
|
+
git.push(remote, remote_branch, true)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Checks whether Rubyforge is configured properly
|
219
|
+
def check_rubyforge_task
|
220
|
+
# Login no longer necessary when using rubyforge 2.0.0 gem
|
221
|
+
# raise "Could not login on rubyforge!" unless `rubyforge login 2>&1`.strip.empty?
|
222
|
+
output = `rubyforge names`.split("\n")
|
223
|
+
raise "Rubyforge group not found!" unless output.any? { |line| %r[^groups\s*\:.*\b#{Regexp.quote(gemspec.rubyforge_project)}\b.*] =~ line }
|
224
|
+
raise "Rubyforge package not found!" unless output.any? { |line| %r[^packages\s*\:.*\b#{Regexp.quote(gemspec.name)}\b.*] =~ line }
|
225
|
+
end
|
226
|
+
|
227
|
+
# Task to release the .gem file toRubyforge.
|
228
|
+
def rubyforge_release_task
|
229
|
+
sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
230
|
+
end
|
231
|
+
|
232
|
+
# Gem release task.
|
233
|
+
# All work is done by the task's dependencies, so just display a release completed message.
|
234
|
+
def release_task
|
235
|
+
puts
|
236
|
+
puts '------------------------------------------------------------'
|
237
|
+
puts "Released #{gemspec.name} version #{gemspec.version}"
|
238
|
+
end
|
239
|
+
|
240
|
+
private
|
241
|
+
|
242
|
+
# Checks whether this project has any RSpec files
|
243
|
+
def has_specs?
|
244
|
+
FileList[spec_pattern].any?
|
245
|
+
end
|
246
|
+
|
247
|
+
# Checks whether this project has any unit test files
|
248
|
+
def has_tests?
|
249
|
+
FileList[test_pattern].any?
|
250
|
+
end
|
251
|
+
|
252
|
+
# Loads the gemspec file
|
253
|
+
def load_gemspec!
|
254
|
+
@gemspec = eval(File.read(@gemspec_file))
|
255
|
+
end
|
256
|
+
|
257
|
+
# Updates the VERSION file with the new version
|
258
|
+
def update_version_file(version)
|
259
|
+
if File.exists?('VERSION')
|
260
|
+
File.open('VERSION', 'w') { |f| f << version.to_s }
|
261
|
+
modified_files << 'VERSION'
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Updates the VERSION constant in the main include file if it exists
|
266
|
+
def update_version_constant(version)
|
267
|
+
if main_include && File.exist?(main_include)
|
268
|
+
file_contents = File.read(main_include)
|
269
|
+
if file_contents.sub!(/^(\s+VERSION\s*=\s*)[^\s].*$/) { $1 + version.to_s.inspect }
|
270
|
+
File.open(main_include, 'w') { |f| f << file_contents }
|
271
|
+
modified_files << main_include
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Updates an attribute of the gemspec file.
|
277
|
+
# This function will open the file, and search/replace the attribute using a regular expression.
|
278
|
+
def update_gemspec(attribute, new_value, literal = false)
|
279
|
+
|
280
|
+
unless literal
|
281
|
+
new_value = case new_value
|
282
|
+
when Array then "%w(#{new_value.join(' ')})"
|
283
|
+
when Hash, String then new_value.inspect
|
284
|
+
when Date then new_value.strftime('%Y-%m-%d').inspect
|
285
|
+
else raise "Cannot write value #{new_value.inspect} to gemspec file!"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
spec = File.read(gemspec_file)
|
290
|
+
regexp = Regexp.new('^(\s+\w+\.' + Regexp.quote(attribute.to_s) + '\s*=\s*)[^\s].*$')
|
291
|
+
if spec.sub!(regexp) { $1 + new_value }
|
292
|
+
File.open(gemspec_file, 'w') { |f| f << spec }
|
293
|
+
modified_files << gemspec_file
|
294
|
+
|
295
|
+
# Reload the gemspec so the changes are incorporated
|
296
|
+
load_gemspec!
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Updates the tasks file using the latest file found on Github
|
301
|
+
def update_tasks_task
|
302
|
+
require 'net/http'
|
303
|
+
|
304
|
+
server = 'github.com'
|
305
|
+
path = '/wvanbergen/github-gem/raw/master/tasks/github-gem.rake'
|
306
|
+
|
307
|
+
Net::HTTP.start(server) do |http|
|
308
|
+
response = http.get(path)
|
309
|
+
open(__FILE__, "w") { |file| file.write(response.body) }
|
310
|
+
end
|
311
|
+
|
312
|
+
relative_file = File.expand_path(__FILE__).sub(%r[^#{git.dir.path}/], '')
|
313
|
+
if git.status[relative_file] && git.status[relative_file].type == 'M'
|
314
|
+
git.add(relative_file)
|
315
|
+
git.commit("Updated to latest gem release management tasks.")
|
316
|
+
puts "Updated to latest version of gem release management tasks."
|
317
|
+
else
|
318
|
+
puts "Release managament tasks already are at the latest version."
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: http_status_exceptions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Willem van Bergen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-01 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: actionpack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.1.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: Clean up your controller code by raising exceptions that generate responses with different HTTP status codes.
|
36
|
+
email:
|
37
|
+
- willem@vanbergen.org
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
- http_status_exceptions.gemspec
|
47
|
+
- .gitignore
|
48
|
+
- init.rb
|
49
|
+
- lib/http_status_exceptions.rb
|
50
|
+
- Rakefile
|
51
|
+
- MIT-LICENSE
|
52
|
+
- tasks/github-gem.rake
|
53
|
+
- README.rdoc
|
54
|
+
- spec/http_status_exception_spec.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/wvanbergen/http_status_exceptions/wikis
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.5
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: A Rails plugin to use exceptions for generating HTTP status responses
|
83
|
+
test_files:
|
84
|
+
- spec/http_status_exception_spec.rb
|