grape_sinatra_helpers 0.0.1
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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +43 -0
- data/Rakefile +2 -0
- data/grape_sinatra_helpers.gemspec +17 -0
- data/lib/grape_sinatra_helpers/version.rb +3 -0
- data/lib/grape_sinatra_helpers.rb +158 -0
- metadata +54 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Derek Lindahl
|
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,43 @@
|
|
1
|
+
# Grape Sinatra Helpers
|
2
|
+
|
3
|
+
This is a small subset of helper methods available in `Sinatra::Base` that I
|
4
|
+
have ported over to [Grape]
|
5
|
+
|
6
|
+
* `cache_control`
|
7
|
+
* `expires`
|
8
|
+
* `last_modified`
|
9
|
+
* `etag`
|
10
|
+
|
11
|
+
And a few others that the above directly depend on.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'grape_sinatra_helpers'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install grape_sinatra_helpers
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Use these methods as you normally would with the Sinatra equivalent.
|
30
|
+
|
31
|
+
## TODO
|
32
|
+
|
33
|
+
* Port over Sinatra test cases
|
34
|
+
|
35
|
+
## Contributing
|
36
|
+
|
37
|
+
1. Fork it
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
39
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
41
|
+
5. Create new Pull Request
|
42
|
+
|
43
|
+
[grape]: https://github.com/intridea/grape
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/grape_sinatra_helpers/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Derek Lindahl"]
|
6
|
+
gem.email = ["dlindahl@customink.com"]
|
7
|
+
gem.description = %q{Small subset of Sinatra helper methods ported to Grape}
|
8
|
+
gem.summary = %q{A small subset of Sinatra::Base helper methods that have been ported over to Grape}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "grape_sinatra_helpers"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = GrapeSinatraHelpers::VERSION
|
17
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require "grape_sinatra_helpers/version"
|
2
|
+
|
3
|
+
module GrapeSinatraHelpers
|
4
|
+
|
5
|
+
def self.included( klass )
|
6
|
+
klass.send(:helpers) do
|
7
|
+
|
8
|
+
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
9
|
+
# Any number of non-value directives (:public, :private, :no_cache,
|
10
|
+
# :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
|
11
|
+
# a Hash of value directives (:max_age, :min_stale, :s_max_age).
|
12
|
+
#
|
13
|
+
# cache_control :public, :must_revalidate, :max_age => 60
|
14
|
+
# => Cache-Control: public, must-revalidate, max-age=60
|
15
|
+
#
|
16
|
+
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
17
|
+
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
18
|
+
def cache_control(*values)
|
19
|
+
if values.last.kind_of?(Hash)
|
20
|
+
hash = values.pop
|
21
|
+
hash.reject! { |k,v| v == false }
|
22
|
+
hash.reject! { |k,v| values << k if v == true }
|
23
|
+
else
|
24
|
+
hash = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
values.map! { |value| value.to_s.tr('_','-') }
|
28
|
+
hash.each do |key, value|
|
29
|
+
key = key.to_s.tr('_', '-')
|
30
|
+
value = value.to_i if key == "max-age"
|
31
|
+
values << [key, value].join('=')
|
32
|
+
end
|
33
|
+
|
34
|
+
header 'Cache-Control', values.join(', ') if values.any?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Set the Expires header and Cache-Control/max-age directive. Amount
|
38
|
+
# can be an integer number of seconds in the future or a Time object
|
39
|
+
# indicating when the response should be considered "stale". The remaining
|
40
|
+
# "values" arguments are passed to the #cache_control helper:
|
41
|
+
#
|
42
|
+
# expires 500, :public, :must_revalidate
|
43
|
+
# => Cache-Control: public, must-revalidate, max-age=60
|
44
|
+
# => Expires: Mon, 08 Jun 2009 08:50:17 GMT
|
45
|
+
#
|
46
|
+
def expires(amount, *values)
|
47
|
+
values << {} unless values.last.kind_of?(Hash)
|
48
|
+
|
49
|
+
if amount.is_a? Integer
|
50
|
+
time = Time.now + amount.to_i
|
51
|
+
max_age = amount
|
52
|
+
else
|
53
|
+
time = time_for amount
|
54
|
+
max_age = time - Time.now
|
55
|
+
end
|
56
|
+
|
57
|
+
values.last.merge!(:max_age => max_age)
|
58
|
+
cache_control(*values)
|
59
|
+
|
60
|
+
header 'Expires', time.httpdate
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
|
64
|
+
# and halt if conditional GET matches. The +time+ argument is a Time,
|
65
|
+
# DateTime, or other object that responds to +to_time+.
|
66
|
+
#
|
67
|
+
# When the current request includes an 'If-Modified-Since' header that is
|
68
|
+
# equal or later than the time specified, execution is immediately halted
|
69
|
+
# with a '304 Not Modified' response.
|
70
|
+
def last_modified(time)
|
71
|
+
return unless time
|
72
|
+
time = time_for time
|
73
|
+
header 'Last-Modified', time.httpdate
|
74
|
+
return if request.env['HTTP_IF_NONE_MATCH']
|
75
|
+
|
76
|
+
if request.env['HTTP_IF_MODIFIED_SINCE']
|
77
|
+
# compare based on seconds since epoch
|
78
|
+
since = Time.httpdate(request.env['HTTP_IF_MODIFIED_SINCE']).to_i
|
79
|
+
error!('304 Not Modified', 304) if since >= time.to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
if request.env['HTTP_IF_UNMODIFIED_SINCE']
|
83
|
+
# compare based on seconds since epoch
|
84
|
+
since = Time.httpdate(request.env['HTTP_IF_UNMODIFIED_SINCE']).to_i
|
85
|
+
error!('412 Precondition Failed', 412) if since < time.to_i
|
86
|
+
end
|
87
|
+
rescue ArgumentError
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
91
|
+
# GET matches. The +value+ argument is an identifier that uniquely
|
92
|
+
# identifies the current version of the resource. The +kind+ argument
|
93
|
+
# indicates whether the etag should be used as a :strong (default) or :weak
|
94
|
+
# cache validator.
|
95
|
+
#
|
96
|
+
# When the current request includes an 'If-None-Match' header with a
|
97
|
+
# matching etag, execution is immediately halted. If the request method is
|
98
|
+
# GET or HEAD, a '304 Not Modified' response is sent.
|
99
|
+
def etag(value, options = {})
|
100
|
+
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
101
|
+
options = {:kind => options} unless Hash === options
|
102
|
+
kind = options[:kind] || :strong
|
103
|
+
new_resource = options.fetch(:new_resource) { request.post? }
|
104
|
+
|
105
|
+
unless [:strong, :weak].include?(kind)
|
106
|
+
raise ArgumentError, ":strong or :weak expected"
|
107
|
+
end
|
108
|
+
|
109
|
+
# value = '"%s"' % value
|
110
|
+
value = "%s" % value
|
111
|
+
value = 'W/' + value if kind == :weak
|
112
|
+
header 'ETag', value
|
113
|
+
|
114
|
+
if etag_matches? request.env['HTTP_IF_NONE_MATCH'], new_resource
|
115
|
+
request.safe? ? error!('304 Not Modified', 304) : error!('412 Precondition Failed', 412)
|
116
|
+
end
|
117
|
+
|
118
|
+
if request.env['HTTP_IF_MATCH']
|
119
|
+
error!('412 Precondition Failed', 412) unless etag_matches? request.env['HTTP_IF_MATCH'], new_resource
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Helper method checking if a ETag value list includes the current ETag.
|
124
|
+
def etag_matches?(list, new_resource = request.post?)
|
125
|
+
return !new_resource if list == '*'
|
126
|
+
list.to_s.split(/\s*,\s*/).include? header['ETag']
|
127
|
+
end
|
128
|
+
|
129
|
+
# Generates a Time object from the given value.
|
130
|
+
# Used by #expires and #last_modified.
|
131
|
+
def time_for(value)
|
132
|
+
if value.respond_to? :to_time
|
133
|
+
value.to_time
|
134
|
+
elsif value.is_a? Time
|
135
|
+
value
|
136
|
+
elsif value.respond_to? :new_offset
|
137
|
+
# DateTime#to_time does the same on 1.9
|
138
|
+
d = value.new_offset 0
|
139
|
+
t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction
|
140
|
+
t.getlocal
|
141
|
+
elsif value.respond_to? :mday
|
142
|
+
# Date#to_time does the same on 1.9
|
143
|
+
Time.local(value.year, value.mon, value.mday)
|
144
|
+
elsif value.is_a? Numeric
|
145
|
+
Time.at value
|
146
|
+
else
|
147
|
+
Time.parse value.to_s
|
148
|
+
end
|
149
|
+
rescue ArgumentError => boom
|
150
|
+
raise boom
|
151
|
+
rescue Exception
|
152
|
+
raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grape_sinatra_helpers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Derek Lindahl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-17 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Small subset of Sinatra helper methods ported to Grape
|
15
|
+
email:
|
16
|
+
- dlindahl@customink.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- grape_sinatra_helpers.gemspec
|
27
|
+
- lib/grape_sinatra_helpers.rb
|
28
|
+
- lib/grape_sinatra_helpers/version.rb
|
29
|
+
homepage: ''
|
30
|
+
licenses: []
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.10
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: A small subset of Sinatra::Base helper methods that have been ported over
|
53
|
+
to Grape
|
54
|
+
test_files: []
|