toast 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controller/toast_controller.rb +4 -4
- data/config/routes.rb +1 -0
- data/lib/toast/active_record_extensions.rb +8 -14
- data/lib/toast/association.rb +10 -43
- data/lib/toast/config_dsl.rb +24 -37
- data/lib/toast/engine.rb +17 -0
- data/lib/toast/record.rb +11 -8
- data/lib/toast/resource.rb +25 -15
- data/lib/toast/root_collection.rb +9 -11
- data/lib/toast/single.rb +16 -19
- metadata +4 -4
@@ -4,15 +4,15 @@ class ToastController < ApplicationController
|
|
4
4
|
|
5
5
|
begin
|
6
6
|
|
7
|
-
@resource = Toast::Resource.build( params, request )
|
7
|
+
@resource = Toast::Resource.build( params, request )
|
8
|
+
|
9
|
+
render @resource.apply(request.method, request.body.read)
|
8
10
|
|
9
|
-
render @resource.apply(request.method, request.body.read)
|
10
|
-
|
11
11
|
rescue Toast::ResourceNotFound => e
|
12
12
|
return head(:not_found)
|
13
13
|
|
14
14
|
rescue Toast::PayloadInvalid => e
|
15
|
-
return render :text => e.message, :status => :forbidden
|
15
|
+
return render :text => e.message, :status => :forbidden
|
16
16
|
|
17
17
|
rescue Toast::PayloadFormatError => e
|
18
18
|
return head(:bad_request)
|
data/config/routes.rb
CHANGED
@@ -8,6 +8,7 @@ Rails.application.routes.draw do
|
|
8
8
|
match("#{model.toast_config.namespace}/#{resource_name}(/:id(/:subresource))" => 'toast#catch_all',
|
9
9
|
:constraints => { :id => /\d+/ },
|
10
10
|
:resource => resource_name,
|
11
|
+
:as => resource_name,
|
11
12
|
:defaults => { :format => 'json' })
|
12
13
|
|
13
14
|
match("#{model.toast_config.namespace}/#{resource_name}/:subresource" => 'toast#catch_all',
|
@@ -25,9 +25,12 @@ module Toast
|
|
25
25
|
|
26
26
|
# add instance methods
|
27
27
|
self.class_eval do
|
28
|
-
# Return
|
29
|
-
def
|
30
|
-
"
|
28
|
+
# Return the path segment of the URI of this record
|
29
|
+
def uri_fullpath
|
30
|
+
"/" +
|
31
|
+
(self.class.toast_config.namespace ? self.class.toast_config.namespace+"/" : "") +
|
32
|
+
self.class.to_s.pluralize.underscore + "/" +
|
33
|
+
self.id.to_s
|
31
34
|
end
|
32
35
|
|
33
36
|
# Returns a Hash with all exposed attributes
|
@@ -45,22 +48,13 @@ module Toast
|
|
45
48
|
acc
|
46
49
|
end
|
47
50
|
|
48
|
-
# association URIs
|
49
|
-
exposed_assoc =
|
50
|
-
options[:in_collection] ? self.class.toast_config.in_collection.exposed_associations :
|
51
|
-
self.class.toast_config.exposed_associations
|
52
|
-
|
53
|
-
exposed_assoc.each do |assoc|
|
54
|
-
out[assoc] = self.uri + "/" + assoc
|
55
|
-
end
|
56
|
-
|
57
|
-
out["uri"] = self.uri if options[:with_uri]
|
58
|
-
|
59
51
|
out
|
60
52
|
end
|
61
53
|
end
|
62
54
|
end
|
63
55
|
|
56
|
+
alias acts_as_resource resourceful_model
|
57
|
+
|
64
58
|
# defaults for non resourceful-models
|
65
59
|
def is_resourceful_model?
|
66
60
|
false
|
data/lib/toast/association.rb
CHANGED
@@ -24,12 +24,16 @@ module Toast
|
|
24
24
|
|
25
25
|
if result.is_a? Array
|
26
26
|
{
|
27
|
-
:json => result.map{|r|
|
27
|
+
:json => result.map{|r|
|
28
|
+
r.exposed_attributes(:in_collection => true).
|
29
|
+
merge( uri_fields(r, true) )
|
30
|
+
},
|
28
31
|
:status => :ok
|
29
32
|
}
|
30
33
|
else
|
31
34
|
{
|
32
|
-
:json => result.exposed_attributes(:in_collection => true)
|
35
|
+
:json => result.exposed_attributes(:in_collection => true).
|
36
|
+
merge( uri_fields(result) ),
|
33
37
|
:status => :ok
|
34
38
|
}
|
35
39
|
end
|
@@ -37,48 +41,11 @@ module Toast
|
|
37
41
|
end
|
38
42
|
|
39
43
|
def put payload
|
40
|
-
|
41
|
-
raise MethodNotAllowed if @is_collection
|
42
|
-
|
43
|
-
|
44
|
-
begin
|
45
|
-
payload = ActiveSupport::JSON.decode(payload)
|
46
|
-
rescue
|
47
|
-
raise PayloadFormatError
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
unless payload.is_a? Hash
|
52
|
-
raise PayloadFormatError
|
53
|
-
end
|
54
|
-
|
55
|
-
# update see record
|
56
|
-
if self.media_type != @associate_model.toast_config.media_type
|
57
|
-
raise UnsupportedMediaType
|
58
|
-
end
|
59
|
-
|
60
|
-
# silently ignore all exposed readable, but not writable fields
|
61
|
-
(@associate_model.toast_config.readables - @associate_model.toast_config.writables).each do |rof|
|
62
|
-
payload.delete(rof)
|
63
|
-
end
|
64
|
-
|
65
|
-
record = @record.send(@assoc)
|
66
|
-
|
67
|
-
# set the virtual attributes
|
68
|
-
(payload.keys.to_set - record.attribute_names.to_set).each do |vattr|
|
69
|
-
record.send("#{vattr}=", payload.delete(vattr))
|
70
|
-
end
|
71
|
-
|
72
|
-
# mass-update for the rest
|
73
|
-
record.update_attributes payload
|
74
|
-
{
|
75
|
-
:json => record.exposed_attributes,
|
76
|
-
:status => :ok,
|
77
|
-
:location => record.uri
|
78
|
-
}
|
44
|
+
raise MethodNotAllowed
|
79
45
|
end
|
80
46
|
|
81
47
|
def post payload
|
48
|
+
raise MethodNotAllowed unless @model.toast_config.writables.include? @assoc
|
82
49
|
|
83
50
|
if self.media_type != @associate_model.toast_config.media_type
|
84
51
|
raise UnsupportedMediaType
|
@@ -105,8 +72,8 @@ module Toast
|
|
105
72
|
record = @record.send(@assoc).create! payload
|
106
73
|
|
107
74
|
{
|
108
|
-
:json => record.exposed_attributes,
|
109
|
-
:location => record.
|
75
|
+
:json => record.exposed_attributes.merge( uri_fields(record) ),
|
76
|
+
:location => self.base_uri + record.uri_fullpath,
|
110
77
|
:status => :created
|
111
78
|
}
|
112
79
|
|
data/lib/toast/config_dsl.rb
CHANGED
@@ -7,12 +7,13 @@ module Toast
|
|
7
7
|
|
8
8
|
def initialize model
|
9
9
|
@model = model
|
10
|
-
@readables = [
|
10
|
+
@readables = []
|
11
11
|
@writables = []
|
12
12
|
@collections = []
|
13
13
|
@singles = []
|
14
14
|
@media_type = "application/json"
|
15
|
-
@
|
15
|
+
@deletable = false
|
16
|
+
@postable = false
|
16
17
|
@pass_params_to = []
|
17
18
|
@in_collection = ConfigDSL::InCollection.new model, self
|
18
19
|
end
|
@@ -28,43 +29,40 @@ module Toast
|
|
28
29
|
end
|
29
30
|
|
30
31
|
def readables= arg
|
31
|
-
|
32
|
-
# @readables.push @model.attribute_names - ConfigDSL.sanitize(args.last[:except], "readables")
|
33
|
-
#else
|
34
|
-
@readables.push *ConfigDSL.sanitize(arg,"readables")
|
35
|
-
#end
|
32
|
+
@readables.push *ConfigDSL.sanitize(arg,"readables")
|
36
33
|
end
|
37
34
|
|
38
35
|
# args: Array or :all, :except => Array
|
39
36
|
def readables *arg
|
40
37
|
return(@readables) if arg.empty?
|
41
|
-
self.readables = arg
|
38
|
+
self.readables = arg
|
42
39
|
end
|
43
40
|
|
44
41
|
def writables= arg
|
45
|
-
#if arg.first == :all
|
46
|
-
# @writables.push @model.attribute_names - ConfigDSL.sanitize(args.last[:except], "writables")
|
47
|
-
#else
|
48
|
-
# white list writables (protect the rest from mass-assignment)
|
49
42
|
@model.attr_accessible *arg
|
50
|
-
@writables.push *ConfigDSL.sanitize(arg,"writables")
|
51
|
-
#end
|
43
|
+
@writables.push *ConfigDSL.sanitize(arg,"writables")
|
52
44
|
end
|
53
45
|
|
54
46
|
# args: Array or :all, :except => Array
|
55
47
|
def writables *arg
|
56
48
|
return(@writables) if arg.empty?
|
57
|
-
self.writables = arg
|
49
|
+
self.writables = arg
|
50
|
+
end
|
51
|
+
|
52
|
+
def deletable
|
53
|
+
@deletable = true
|
58
54
|
end
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
@disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
|
56
|
+
def deletable?
|
57
|
+
@deletable
|
63
58
|
end
|
64
59
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
60
|
+
def postable
|
61
|
+
@postable = true
|
62
|
+
end
|
63
|
+
|
64
|
+
def postable?
|
65
|
+
@postable
|
68
66
|
end
|
69
67
|
|
70
68
|
def pass_params_to= arg
|
@@ -73,9 +71,9 @@ module Toast
|
|
73
71
|
|
74
72
|
def pass_params_to *arg
|
75
73
|
return(@pass_params_to) if arg.empty?
|
76
|
-
self.pass_params_to = arg
|
74
|
+
self.pass_params_to = arg
|
77
75
|
end
|
78
|
-
|
76
|
+
|
79
77
|
def collections= collections=[]
|
80
78
|
@collections = ConfigDSL.sanitize(collections, "collections")
|
81
79
|
end
|
@@ -111,15 +109,14 @@ module Toast
|
|
111
109
|
|
112
110
|
def initialize model, base_config
|
113
111
|
@model = model
|
114
|
-
@readables = base_config.readables # must assign a reference
|
115
|
-
@writables = base_config.writables # must assign a reference
|
116
|
-
@disallow_methods = []
|
112
|
+
@readables = base_config.readables # must assign a reference
|
113
|
+
@writables = base_config.writables # must assign a reference
|
117
114
|
@media_type = "application/json"
|
118
115
|
end
|
119
116
|
|
120
117
|
def readables= readables
|
121
118
|
@writables = [] # forget inherited writables
|
122
|
-
@readables = ConfigDSL.sanitize(readables,"readables")
|
119
|
+
@readables = ConfigDSL.sanitize(readables,"readables")
|
123
120
|
end
|
124
121
|
|
125
122
|
def readables *arg
|
@@ -146,16 +143,6 @@ module Toast
|
|
146
143
|
assocs = @model.reflect_on_all_associations.map{|a| a.name.to_s}
|
147
144
|
(@readables + @writables).uniq.select{|f| assocs.include?(f)}
|
148
145
|
end
|
149
|
-
|
150
|
-
def disallow_methods= arg
|
151
|
-
@disallow_methods.push *ConfigDSL.sanitize(arg,"disallow methods")
|
152
|
-
end
|
153
|
-
|
154
|
-
def disallow_methods *arg
|
155
|
-
return(@disallow_methods) if arg.empty?
|
156
|
-
self.disallow_methods = arg
|
157
|
-
end
|
158
|
-
|
159
146
|
end
|
160
147
|
|
161
148
|
|
data/lib/toast/engine.rb
CHANGED
@@ -24,6 +24,23 @@ module Toast
|
|
24
24
|
# raised when DB is not setup yet. (rake db:schema:load)
|
25
25
|
end
|
26
26
|
|
27
|
+
# Monkey patch the request class for Rails 3.0, Rack 1.2
|
28
|
+
# Backport from Rack 1.3
|
29
|
+
if Rack.release == "1.2"
|
30
|
+
class Rack::Request
|
31
|
+
def base_url
|
32
|
+
url = scheme + "://"
|
33
|
+
url << host
|
34
|
+
|
35
|
+
if scheme == "https" && port != 443 ||
|
36
|
+
scheme == "http" && port != 80
|
37
|
+
url << ":#{port}"
|
38
|
+
end
|
39
|
+
|
40
|
+
url
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
27
44
|
end
|
28
45
|
end
|
29
46
|
end
|
data/lib/toast/record.rb
CHANGED
@@ -31,12 +31,12 @@ module Toast
|
|
31
31
|
unless payload.is_a? Hash
|
32
32
|
raise PayloadFormatError
|
33
33
|
end
|
34
|
-
|
35
|
-
#
|
36
|
-
(@model.toast_config.readables - @model.toast_config.writables).each do |rof|
|
34
|
+
|
35
|
+
# ignore all exposed readable, but not writable fields
|
36
|
+
(@model.toast_config.readables - @model.toast_config.writables + ["uri"]).each do |rof|
|
37
37
|
payload.delete(rof)
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
# set the virtual attributes
|
41
41
|
(payload.keys.to_set - @record.attribute_names.to_set).each do |vattr|
|
42
42
|
@record.send("#{vattr}=", payload.delete(vattr))
|
@@ -45,9 +45,9 @@ module Toast
|
|
45
45
|
# mass-update for the rest
|
46
46
|
@record.update_attributes payload
|
47
47
|
{
|
48
|
-
:json => @record.exposed_attributes,
|
48
|
+
:json => @record.exposed_attributes.merge( uri_fields(@record) ),
|
49
49
|
:status => :ok,
|
50
|
-
:location => @record.
|
50
|
+
:location => self.base_uri + @record.uri_fullpath
|
51
51
|
}
|
52
52
|
end
|
53
53
|
|
@@ -58,9 +58,9 @@ module Toast
|
|
58
58
|
:template => "resources/#{model.to_s.underscore}",
|
59
59
|
:locals => { model.to_s.underscore.to_sym => @record } # full record, view should filter
|
60
60
|
}
|
61
|
-
when "json"
|
61
|
+
when "json"
|
62
62
|
{
|
63
|
-
:json => @record.exposed_attributes,
|
63
|
+
:json => @record.exposed_attributes.merge( uri_fields(@record) ),
|
64
64
|
:status => :ok
|
65
65
|
}
|
66
66
|
else
|
@@ -69,11 +69,14 @@ module Toast
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def delete
|
72
|
+
raise MethodNotAllowed unless @model.toast_config.deletable?
|
73
|
+
|
72
74
|
@record.destroy
|
73
75
|
{
|
74
76
|
:nothing => true,
|
75
77
|
:status => :ok
|
76
78
|
}
|
77
79
|
end
|
80
|
+
|
78
81
|
end
|
79
82
|
end
|
data/lib/toast/resource.rb
CHANGED
@@ -10,40 +10,37 @@ module Toast
|
|
10
10
|
# Record, RootCollection, Association, Single
|
11
11
|
class Resource
|
12
12
|
|
13
|
-
attr_accessor :media_type
|
13
|
+
attr_accessor :media_type, :base_uri
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
raise 'ToastResource#new: use #build to create an instance'
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.build params, request
|
20
|
-
|
21
20
|
resource_name = params[:resource]
|
22
21
|
id = params[:id]
|
23
22
|
subresource_name = params[:subresource]
|
24
23
|
format = params[:format]
|
25
24
|
|
26
25
|
begin
|
27
|
-
|
26
|
+
|
28
27
|
model = get_class_by_resource_name resource_name
|
29
|
-
|
30
|
-
# base is complete URL until the resource name
|
31
|
-
model.uri_base = request.url.match(/(.*)\/#{resource_name}(?:\..+|\/|\z)/)[1]
|
32
28
|
|
33
29
|
# decide which sub type
|
34
30
|
rsc = if id.nil? and model.toast_config.singles.include?(subresource_name)
|
35
|
-
Toast::Single.new(model, subresource_name, params.clone)
|
31
|
+
Toast::Single.new(model, subresource_name, params.clone)
|
36
32
|
elsif id.nil?
|
37
33
|
Toast::RootCollection.new(model, subresource_name, params.clone)
|
38
34
|
elsif subresource_name.nil?
|
39
35
|
Toast::Record.new(model, id, format)
|
40
36
|
elsif model.toast_config.exposed_associations.include? subresource_name
|
41
|
-
Toast::Association.new(model, id, subresource_name, format)
|
37
|
+
Toast::Association.new(model, id, subresource_name, format)
|
42
38
|
else
|
43
39
|
raise ResourceNotFound
|
44
40
|
end
|
45
|
-
|
41
|
+
|
46
42
|
rsc.media_type = request.media_type
|
43
|
+
rsc.base_uri = request.base_url
|
47
44
|
|
48
45
|
rsc
|
49
46
|
rescue NameError
|
@@ -67,18 +64,31 @@ module Toast
|
|
67
64
|
end
|
68
65
|
|
69
66
|
def apply method, payload
|
70
|
-
|
71
|
-
raise MethodNotAllowed if self.model.toast_config.disallow_methods.include?(method.downcase)
|
72
|
-
|
73
67
|
case method
|
74
68
|
when "PUT","POST"
|
75
69
|
self.send(method.downcase, payload)
|
76
70
|
when "DELETE","GET"
|
77
|
-
|
71
|
+
self.send(method.downcase)
|
78
72
|
else
|
79
73
|
raise MethodNotAllowed
|
80
|
-
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def uri_fields record, in_collection=false
|
79
|
+
out = {}
|
80
|
+
|
81
|
+
exposed_assoc =
|
82
|
+
in_collection ? record.class.toast_config.in_collection.exposed_associations :
|
83
|
+
record.class.toast_config.exposed_associations
|
84
|
+
|
85
|
+
exposed_assoc.each do |assoc|
|
86
|
+
out[assoc] = "#{self.base_uri}#{record.uri_fullpath}/#{assoc}"
|
87
|
+
end
|
88
|
+
|
89
|
+
out["uri"] = self.base_uri + record.uri_fullpath
|
90
|
+
|
91
|
+
out
|
81
92
|
end
|
82
93
|
end
|
83
94
|
end
|
84
|
-
|
@@ -18,9 +18,6 @@ module Toast
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def get
|
21
|
-
if @model.toast_config.in_collection.disallow_methods.include? "get"
|
22
|
-
raise MethodNotAllowed
|
23
|
-
end
|
24
21
|
|
25
22
|
records = if @model.toast_config.pass_params_to.include?(@collection)
|
26
23
|
@model.send(@collection, @params)
|
@@ -36,7 +33,10 @@ module Toast
|
|
36
33
|
}
|
37
34
|
when "json"
|
38
35
|
{
|
39
|
-
:json => records.map{|r|
|
36
|
+
:json => records.map{|r|
|
37
|
+
r.exposed_attributes(:in_collection => true).
|
38
|
+
merge( uri_fields(r, true) )
|
39
|
+
},
|
40
40
|
:status => :ok
|
41
41
|
}
|
42
42
|
else
|
@@ -49,10 +49,8 @@ module Toast
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def post payload
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
52
|
+
raise MethodNotAllowed unless @model.toast_config.postable?
|
53
|
+
|
56
54
|
if @collection != "all"
|
57
55
|
raise MethodNotAllowed
|
58
56
|
end
|
@@ -71,7 +69,7 @@ module Toast
|
|
71
69
|
end
|
72
70
|
|
73
71
|
# silently ignore all exposed readable, but not writable fields
|
74
|
-
(@model.toast_config.readables - @model.toast_config.writables).each do |rof|
|
72
|
+
(@model.toast_config.readables - @model.toast_config.writables + ["uri"]).each do |rof|
|
75
73
|
payload.delete(rof)
|
76
74
|
end
|
77
75
|
|
@@ -79,8 +77,8 @@ module Toast
|
|
79
77
|
record = @model.create! payload
|
80
78
|
|
81
79
|
{
|
82
|
-
:json => record.exposed_attributes,
|
83
|
-
:location => record.
|
80
|
+
:json => record.exposed_attributes.merge( uri_fields(record) ),
|
81
|
+
:location => self.base_uri + record.uri_fullpath,
|
84
82
|
:status => :created
|
85
83
|
}
|
86
84
|
|
data/lib/toast/single.rb
CHANGED
@@ -6,47 +6,43 @@ module Toast
|
|
6
6
|
|
7
7
|
# The single resource name must be a class method of the model and
|
8
8
|
# must return nil or an instance.
|
9
|
-
|
9
|
+
|
10
10
|
# GET is the only allowed verb. To make changes the URI with ID has
|
11
11
|
# to be used.
|
12
12
|
class Single < Resource
|
13
|
-
|
13
|
+
|
14
14
|
attr_reader :model
|
15
|
-
|
15
|
+
|
16
16
|
def initialize model, subresource_name, params
|
17
|
-
|
17
|
+
|
18
18
|
unless model.toast_config.singles.include? subresource_name
|
19
19
|
raise ResourceNotFound
|
20
20
|
end
|
21
21
|
|
22
22
|
@model = model
|
23
|
-
@single_finder = subresource_name
|
24
23
|
@params = params
|
25
24
|
@format = params[:format]
|
26
|
-
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
raise MethodNotAllowed
|
31
|
-
end
|
32
|
-
|
33
|
-
record = if @model.toast_config.pass_params_to.include?(@single_finder)
|
34
|
-
@model.send(@single_finder, @params)
|
26
|
+
@record = if @model.toast_config.pass_params_to.include?(subresource_name)
|
27
|
+
@model.send(subresource_name, @params)
|
35
28
|
else
|
36
|
-
@model.send(
|
29
|
+
@model.send(subresource_name)
|
37
30
|
end
|
38
31
|
|
39
|
-
raise ResourceNotFound if record.nil?
|
40
|
-
|
32
|
+
raise ResourceNotFound if @record.nil?
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def get
|
41
37
|
case @format
|
42
38
|
when "html"
|
43
39
|
{
|
44
40
|
:template => "resources/#{model.to_s.underscore}",
|
45
|
-
:locals => { model.to_s.pluralize.underscore.to_sym => record }
|
41
|
+
:locals => { model.to_s.pluralize.underscore.to_sym => @record }
|
46
42
|
}
|
47
|
-
when "json"
|
43
|
+
when "json"
|
48
44
|
{
|
49
|
-
:json => record.exposed_attributes,
|
45
|
+
:json => @record.exposed_attributes.merge( uri_fields(@record) ),
|
50
46
|
:status => :ok
|
51
47
|
}
|
52
48
|
else
|
@@ -54,6 +50,7 @@ module Toast
|
|
54
50
|
end
|
55
51
|
end
|
56
52
|
|
53
|
+
|
57
54
|
def put
|
58
55
|
raise MethodNotAllowed
|
59
56
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
version: 0.5.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Robert Annies
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-03-
|
17
|
+
date: 2012-03-20 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|