flapjack-diner 1.4.0 → 2.0.0.a4
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -1
- data/README.md +620 -413
- data/flapjack-diner.gemspec +1 -1
- data/lib/flapjack-diner/argument_validator.rb +77 -7
- data/lib/flapjack-diner/configuration.rb +409 -0
- data/lib/flapjack-diner/index_range.rb +42 -0
- data/lib/flapjack-diner/log_formatter.rb +22 -0
- data/lib/flapjack-diner/query.rb +114 -0
- data/lib/flapjack-diner/relationships.rb +180 -0
- data/lib/flapjack-diner/request.rb +280 -0
- data/lib/flapjack-diner/resources.rb +64 -0
- data/lib/flapjack-diner/response.rb +91 -0
- data/lib/flapjack-diner/tools.rb +47 -251
- data/lib/flapjack-diner/utility.rb +16 -0
- data/lib/flapjack-diner/version.rb +1 -1
- data/lib/flapjack-diner.rb +54 -20
- data/spec/argument_validator_spec.rb +87 -28
- data/spec/flapjack-diner_spec.rb +42 -64
- data/spec/relationships_spec.rb +211 -0
- data/spec/resources/checks_spec.rb +219 -79
- data/spec/resources/contacts_spec.rb +179 -151
- data/spec/resources/events_spec.rb +208 -0
- data/spec/resources/maintenance_periods_spec.rb +177 -565
- data/spec/resources/media_spec.rb +157 -171
- data/spec/resources/metrics_spec.rb +45 -0
- data/spec/resources/rules_spec.rb +278 -0
- data/spec/resources/states_spec.rb +93 -0
- data/spec/resources/statistics_spec.rb +53 -0
- data/spec/resources/tags_spec.rb +243 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/fixture_data.rb +541 -0
- metadata +33 -31
- data/.rubocop.yml +0 -21
- data/.rubocop_todo.yml +0 -135
- data/lib/flapjack-diner/resources/checks.rb +0 -64
- data/lib/flapjack-diner/resources/contacts.rb +0 -70
- data/lib/flapjack-diner/resources/entities.rb +0 -68
- data/lib/flapjack-diner/resources/maintenance_periods.rb +0 -82
- data/lib/flapjack-diner/resources/media.rb +0 -61
- data/lib/flapjack-diner/resources/notification_rules.rb +0 -66
- data/lib/flapjack-diner/resources/notifications.rb +0 -28
- data/lib/flapjack-diner/resources/pagerduty_credentials.rb +0 -59
- data/lib/flapjack-diner/resources/reports.rb +0 -33
- data/spec/pacts/flapjack-diner-flapjack.json +0 -4515
- data/spec/resources/entities_spec.rb +0 -181
- data/spec/resources/notification_rules_spec.rb +0 -341
- data/spec/resources/notifications_spec.rb +0 -208
- data/spec/resources/pagerduty_credentials_spec.rb +0 -237
- data/spec/resources/reports_spec.rb +0 -255
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'flapjack-diner/utility'
|
2
|
+
|
3
|
+
module Flapjack
|
4
|
+
module Diner
|
5
|
+
class Response
|
6
|
+
SUCCESS_STATUS_CODES = [200, 201, 204]
|
7
|
+
|
8
|
+
attr_reader :output, :context, :error
|
9
|
+
|
10
|
+
def initialize(resp, opts = {})
|
11
|
+
@response = resp
|
12
|
+
@output = nil
|
13
|
+
@context = nil
|
14
|
+
@error = nil
|
15
|
+
@return_keys_as_strings = Flapjack::Diner.return_keys_as_strings
|
16
|
+
end
|
17
|
+
|
18
|
+
def process
|
19
|
+
if 204.eql?(@response.code)
|
20
|
+
@output = true
|
21
|
+
@context = nil
|
22
|
+
@error = nil
|
23
|
+
return
|
24
|
+
end
|
25
|
+
if @response.respond_to?(:parsed_response)
|
26
|
+
parsed = @response.parsed_response
|
27
|
+
end
|
28
|
+
strify = @return_keys_as_strings.is_a?(TrueClass)
|
29
|
+
if [200, 201].include?(@response.code)
|
30
|
+
@output = handle_data(parsed, strify)
|
31
|
+
return
|
32
|
+
end
|
33
|
+
@error = handle_errors(parsed, strify)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def flatten_jsonapi_data(data)
|
39
|
+
ret = nil
|
40
|
+
case data
|
41
|
+
when Array
|
42
|
+
ret = data.inject([]) do |memo, d|
|
43
|
+
attrs = d['attributes'] || {}
|
44
|
+
d.each_pair do |k, v|
|
45
|
+
next if 'attributes'.eql?(k)
|
46
|
+
attrs.update(k => v)
|
47
|
+
end
|
48
|
+
memo += [attrs]
|
49
|
+
memo
|
50
|
+
end
|
51
|
+
when Hash
|
52
|
+
ret = data['attributes'] || {}
|
53
|
+
data.each_pair do |k, v|
|
54
|
+
next if 'attributes'.eql?(k)
|
55
|
+
ret.update(k => v)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
ret = data
|
59
|
+
end
|
60
|
+
ret
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_data(parsed, strify)
|
64
|
+
return parsed if parsed.nil? || !parsed.is_a?(Hash) ||
|
65
|
+
!parsed.key?('data')
|
66
|
+
@context = {}
|
67
|
+
c_incl_key = strify ? 'included' : :included
|
68
|
+
if parsed.key?('included')
|
69
|
+
incl = flatten_jsonapi_data(parsed['included'])
|
70
|
+
@context[c_incl_key] = incl.each_with_object({}) do |i, memo|
|
71
|
+
memo[i['type']] ||= {}
|
72
|
+
memo[i['type']][i['id']] = strify ? i : Flapjack::Diner::Utility.symbolize(i)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
(%w(relationships meta) & parsed.keys).each do |k|
|
76
|
+
c = parsed[k]
|
77
|
+
@context[strify ? k : k.to_sym] = (strify ? c : Flapjack::Diner::Utility.symbolize(c))
|
78
|
+
end
|
79
|
+
ret = flatten_jsonapi_data(parsed['data'])
|
80
|
+
strify ? ret : Flapjack::Diner::Utility.symbolize(ret)
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_errors(parsed, strify)
|
84
|
+
return parsed if parsed.nil? || !parsed.is_a?(Hash) ||
|
85
|
+
!parsed.key?('errors')
|
86
|
+
errs = parsed['errors']
|
87
|
+
strify ? errs : Flapjack::Diner::Utility.symbolize(errs)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/flapjack-diner/tools.rb
CHANGED
@@ -1,276 +1,72 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
1
|
module Flapjack
|
4
2
|
module Diner
|
5
3
|
module Tools
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def log_request(method_type, req_uri, data = nil)
|
13
|
-
return if logger.nil? || req_uri.nil?
|
14
|
-
log_msg = "#{method_type} #{req_uri}"
|
15
|
-
unless %w(GET DELETE).include?(method_type) || data.nil?
|
16
|
-
log_msg << "\n Body: #{data.inspect}"
|
4
|
+
module ClassMethods
|
5
|
+
def included_data
|
6
|
+
return if context.nil?
|
7
|
+
context[return_keys_as_strings ? 'included' : :included]
|
17
8
|
end
|
18
|
-
logger.info log_msg
|
19
|
-
end
|
20
9
|
|
21
|
-
|
22
|
-
|
23
|
-
req_uri = build_uri(path, ids, data)
|
24
|
-
log_request('GET', req_uri, data)
|
25
|
-
handled = handle_response(get(req_uri.request_uri))
|
10
|
+
def related(record, rel, incl = included_data)
|
11
|
+
return if incl.nil?
|
26
12
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
return_keys_as_strings.is_a?(TrueClass) ? result : symbolize(result)
|
31
|
-
end
|
32
|
-
|
33
|
-
def perform_post(path, ids = [], data = nil)
|
34
|
-
@last_error = nil
|
35
|
-
req_uri = build_uri(path, ids)
|
36
|
-
log_request('POST', req_uri, data)
|
37
|
-
opts = if data.nil?
|
38
|
-
{}
|
39
|
-
else
|
40
|
-
{:body => prepare_nested_query(data).to_json,
|
41
|
-
:headers => {'Content-Type' => 'application/vnd.api+json'}}
|
42
|
-
end
|
43
|
-
handle_response(post(req_uri.request_uri, opts))
|
44
|
-
end
|
13
|
+
type = record[return_keys_as_strings ? 'type' : :type]
|
14
|
+
return if type.nil?
|
45
15
|
|
46
|
-
|
47
|
-
|
48
|
-
req_uri = build_uri(path, ids)
|
49
|
-
log_request('PATCH', req_uri, data)
|
50
|
-
opts = if data.nil?
|
51
|
-
{}
|
52
|
-
else
|
53
|
-
{:body => prepare_nested_query(data).to_json,
|
54
|
-
:headers => {'Content-Type' => 'application/json-patch+json'}}
|
55
|
-
end
|
56
|
-
handle_response(patch(req_uri.request_uri, opts))
|
57
|
-
end
|
58
|
-
|
59
|
-
def perform_delete(path, ids = [], data = nil)
|
60
|
-
@last_error = nil
|
61
|
-
req_uri = build_uri(path, ids, data)
|
62
|
-
log_request('DELETE', req_uri, data)
|
63
|
-
handle_response(delete(req_uri.request_uri))
|
64
|
-
end
|
65
|
-
|
66
|
-
def log_response(response)
|
67
|
-
return if logger.nil? || !response.respond_to?(:code)
|
68
|
-
response_message = " Response Code: #{response.code}"
|
69
|
-
unless response.message.nil? || (response.message.eql?(''))
|
70
|
-
response_message << " #{response.message}"
|
71
|
-
end
|
72
|
-
logger.info response_message
|
73
|
-
return if response.body.nil?
|
74
|
-
logger.info " Response Body: #{response.body[0..300]}"
|
75
|
-
end
|
76
|
-
|
77
|
-
def handle_response(response)
|
78
|
-
log_response(response)
|
79
|
-
return true if 204.eql?(response.code)
|
80
|
-
parsed = if response.respond_to?(:parsed_response)
|
81
|
-
response.parsed_response
|
82
|
-
else
|
83
|
-
nil
|
84
|
-
end
|
85
|
-
return parsed if [200, 201].include?(response.code)
|
86
|
-
@last_error = handle_error(response.code, parsed)
|
87
|
-
nil
|
88
|
-
end
|
89
|
-
|
90
|
-
def handle_error(code, parsed)
|
91
|
-
case parsed
|
92
|
-
when Hash
|
93
|
-
err = {'status_code' => code}.merge(parsed)
|
94
|
-
return_keys_as_strings.is_a?(TrueClass) ? err : symbolize(err)
|
95
|
-
else
|
96
|
-
parsed
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def validate_params(query = {}, &validation)
|
101
|
-
return unless block_given?
|
102
|
-
case query
|
103
|
-
when Array
|
104
|
-
query.each do |q|
|
105
|
-
ArgumentValidator.new(q).instance_eval(&validation)
|
16
|
+
res = Flapjack::Diner::Configuration::RESOURCES.values.detect do |r|
|
17
|
+
type.eql?(r[:resource])
|
106
18
|
end
|
107
|
-
|
108
|
-
ArgumentValidator.new(query).instance_eval(&validation)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# copied from Rack::Utils -- builds the query string for GETs
|
113
|
-
def build_nested_query(value, prefix = nil)
|
114
|
-
case value
|
115
|
-
when Array
|
116
|
-
build_array_query(value, prefix)
|
117
|
-
when Hash
|
118
|
-
build_hash_query(value, prefix)
|
119
|
-
else
|
120
|
-
build_data_query(value, prefix)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def build_array_query(value, prefix)
|
125
|
-
value.map {|v| build_nested_query(v, "#{prefix}[]") }.join('&')
|
126
|
-
end
|
127
|
-
|
128
|
-
def build_hash_query(value, prefix)
|
129
|
-
value.map do |k, v|
|
130
|
-
data = prefix ? "#{prefix}[#{escape(k)}]" : escape(k)
|
131
|
-
build_nested_query(v, data)
|
132
|
-
end.join('&')
|
133
|
-
end
|
19
|
+
return if res.nil? || res[:relationships].nil?
|
134
20
|
|
135
|
-
|
136
|
-
|
137
|
-
raise(ArgumentError, 'Value must be a Hash') if prefix.nil?
|
138
|
-
"#{prefix}=#{escape(value.iso8601)}"
|
139
|
-
elsif value.is_a?(String) || value.is_a?(Integer)
|
140
|
-
raise(ArgumentError, 'Value must be a Hash') if prefix.nil?
|
141
|
-
"#{prefix}=#{escape(value.to_s)}"
|
142
|
-
else
|
143
|
-
prefix
|
144
|
-
end
|
145
|
-
end
|
21
|
+
rel_cfg = res[:relationships][rel.to_sym]
|
22
|
+
return if rel_cfg.nil?
|
146
23
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
def unwrap_ids(*args)
|
156
|
-
args.select {|a| a.is_a?(String) || a.is_a?(Integer) }
|
157
|
-
end
|
158
|
-
|
159
|
-
def unwrap_params(*args)
|
160
|
-
args.each_with_object({}) do |e, a|
|
161
|
-
a.update(symbolize(e)) if e.is_a?(Hash)
|
24
|
+
rel_type = rel_cfg[:resource]
|
25
|
+
has_data = incl.key?(rel_type)
|
26
|
+
case rel_cfg[:number]
|
27
|
+
when :singular
|
28
|
+
has_data ? singularly_related(record, rel, rel_type, incl) : nil
|
29
|
+
else
|
30
|
+
has_data ? multiply_related(record, rel, rel_type, incl) : []
|
31
|
+
end
|
162
32
|
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def unwrap_data(*args)
|
166
|
-
args.select {|a| a.is_a?(Array) && a.all? {|av| av.is_a?(Hash) } }
|
167
|
-
.reduce([], &:'+')
|
168
|
-
end
|
169
|
-
|
170
|
-
def unwrap_create_data(*args)
|
171
|
-
data_h = args.select {|a| a.is_a?(Hash) }
|
172
|
-
data_a = unwrap_data(*args)
|
173
|
-
|
174
|
-
raise 'Create data may be passed as a Hash or an Array of Hashes, ' \
|
175
|
-
'not both' unless data_h.empty? || data_a.empty?
|
176
|
-
|
177
|
-
data = data_h
|
178
|
-
data = data_a if data.empty?
|
179
|
-
data = nil if data.empty?
|
180
|
-
data
|
181
|
-
end
|
182
|
-
|
183
|
-
def patch_replace(type, k, v)
|
184
|
-
{:op => 'replace',
|
185
|
-
:path => "/#{type}/0/#{k}",
|
186
|
-
:value => v}
|
187
|
-
end
|
188
33
|
|
189
|
-
|
190
|
-
{:op => 'add',
|
191
|
-
:path => "/#{type}/0/links/#{linked}/-",
|
192
|
-
:value => v}
|
193
|
-
end
|
34
|
+
private
|
194
35
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
36
|
+
def singularly_related(record, rel, type, incl)
|
37
|
+
relat, data, id_a, rel = related_accessors(rel)
|
38
|
+
return if record[relat].nil? ||
|
39
|
+
record[relat][rel].nil? ||
|
40
|
+
record[relat][rel][data].nil?
|
199
41
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
when Array
|
204
|
-
prepare_array_query(value)
|
205
|
-
when Hash
|
206
|
-
prepare_hash_query(value)
|
207
|
-
else
|
208
|
-
prepare_data_query(value)
|
42
|
+
id = record[relat][rel][data][id_a]
|
43
|
+
return if id.nil? || !incl.key?(type)
|
44
|
+
incl[type][id]
|
209
45
|
end
|
210
|
-
end
|
211
|
-
|
212
|
-
def prepare_array_query(value)
|
213
|
-
value.map {|v| prepare_nested_query(v) }
|
214
|
-
end
|
215
46
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
47
|
+
def multiply_related(record, rel, type, incl)
|
48
|
+
relat, data, id_a, rel = related_accessors(rel)
|
49
|
+
return [] if record[relat].nil? ||
|
50
|
+
record[relat][rel].nil? ||
|
51
|
+
record[relat][rel][data].nil? ||
|
52
|
+
record[relat][rel][data].empty?
|
221
53
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
else
|
226
|
-
case value
|
227
|
-
when Integer, TrueClass, FalseClass, NilClass
|
228
|
-
value
|
229
|
-
else
|
230
|
-
value.to_s
|
231
|
-
end
|
54
|
+
ids = record[relat][rel][data].map {|m| m[id_a] }
|
55
|
+
return [] if ids.empty? || !incl.key?(type)
|
56
|
+
incl[type].values_at(*ids)
|
232
57
|
end
|
233
|
-
end
|
234
58
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
port_str.to_i
|
59
|
+
def related_accessors(*args)
|
60
|
+
acc = [:relationships, :data, :id]
|
61
|
+
return (acc + args).map(&:to_s) if return_keys_as_strings
|
62
|
+
(acc + args.map(&:to_sym))
|
240
63
|
end
|
241
64
|
end
|
242
65
|
|
243
|
-
def
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
}ix =~ base_uri
|
248
|
-
|
249
|
-
protocol = protocol.nil? ? 'http' : protocol.downcase
|
250
|
-
[protocol, host, normalise_port(port, protocol)]
|
251
|
-
end
|
252
|
-
|
253
|
-
def build_uri(path, ids = [], params = [])
|
254
|
-
pr, ho, po = protocol_host_port
|
255
|
-
path += '/' + escaped_ids(ids) unless ids.nil? || ids.empty?
|
256
|
-
query = if params.nil? || params.empty?
|
257
|
-
nil
|
258
|
-
else
|
259
|
-
build_nested_query(params)
|
260
|
-
end
|
261
|
-
URI::HTTP.build(:protocol => pr, :host => ho, :port => po,
|
262
|
-
:path => path, :query => query)
|
263
|
-
end
|
264
|
-
|
265
|
-
def symbolize(obj)
|
266
|
-
case obj
|
267
|
-
when Hash
|
268
|
-
obj.each_with_object({}) {|(k, v), a| a[k.to_sym] = symbolize(v) }
|
269
|
-
when Array
|
270
|
-
obj.each_with_object([]) {|e, a| a << symbolize(e) }
|
271
|
-
else
|
272
|
-
obj
|
273
|
-
end
|
66
|
+
def self.included(base)
|
67
|
+
base.extend ClassMethods
|
68
|
+
# base.class_eval do
|
69
|
+
# end
|
274
70
|
end
|
275
71
|
end
|
276
72
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Flapjack
|
2
|
+
module Diner
|
3
|
+
module Utility
|
4
|
+
def self.symbolize(obj)
|
5
|
+
case obj
|
6
|
+
when Hash
|
7
|
+
obj.each_with_object({}) {|(k, v), a| a[k.to_sym] = symbolize(v) }
|
8
|
+
when Array
|
9
|
+
obj.each_with_object([]) {|e, a| a << symbolize(e) }
|
10
|
+
else
|
11
|
+
obj
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/flapjack-diner.rb
CHANGED
@@ -1,41 +1,75 @@
|
|
1
1
|
require 'httparty'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
+
require 'flapjack-diner/log_formatter'
|
4
5
|
require 'flapjack-diner/version'
|
5
6
|
require 'flapjack-diner/argument_validator'
|
7
|
+
require 'flapjack-diner/index_range'
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
require 'flapjack-diner/resources'
|
10
|
+
require 'flapjack-diner/relationships'
|
11
|
+
require 'flapjack-diner/tools'
|
9
12
|
|
10
|
-
|
11
|
-
|
13
|
+
# HTTParty master contains a non-hacky way of doing this, but 0.13.5 doesn't
|
14
|
+
module HTTParty
|
15
|
+
module Logger
|
16
|
+
def self.build(logger, level, formatter)
|
17
|
+
level ||= :info
|
18
|
+
formatter ||= :apache
|
12
19
|
|
13
|
-
|
20
|
+
case formatter
|
21
|
+
when :'flapjack-diner'
|
22
|
+
Flapjack::Diner::LogFormatter.new(logger, level)
|
23
|
+
when :curl
|
24
|
+
Logger::CurlLogger.new(logger, level)
|
25
|
+
else
|
26
|
+
Logger::ApacheLogger.new(logger, level)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
14
31
|
|
15
32
|
# NB: clients will need to handle any exceptions caused by,
|
16
33
|
# e.g., network failures or non-parseable JSON data.
|
17
|
-
|
18
34
|
module Flapjack
|
35
|
+
|
19
36
|
# Top level module for Flapjack::Diner API consumer.
|
20
37
|
module Diner
|
21
|
-
|
38
|
+
UUID_RE = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
22
39
|
|
40
|
+
include HTTParty
|
23
41
|
format :json
|
24
42
|
|
25
43
|
class << self
|
26
|
-
attr_accessor :
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
44
|
+
attr_accessor :return_keys_as_strings
|
45
|
+
|
46
|
+
# redefine HTTParty logger methods for getter/setter style
|
47
|
+
alias :original_logger :logger
|
48
|
+
def logger=(lgr)
|
49
|
+
original_logger(lgr, :info, :'flapjack-diner')
|
50
|
+
end
|
51
|
+
def logger
|
52
|
+
default_options[:logger]
|
53
|
+
end
|
54
|
+
|
55
|
+
def output
|
56
|
+
return if !instance_variable_defined?('@response') || @response.nil?
|
57
|
+
@response.output
|
58
|
+
end
|
59
|
+
|
60
|
+
def context
|
61
|
+
return if !instance_variable_defined?('@response') || @response.nil?
|
62
|
+
@response.context
|
63
|
+
end
|
64
|
+
|
65
|
+
def error
|
66
|
+
return if !instance_variable_defined?('@response') || @response.nil?
|
67
|
+
@response.error
|
68
|
+
end
|
39
69
|
end
|
70
|
+
|
71
|
+
include Flapjack::Diner::Resources
|
72
|
+
include Flapjack::Diner::Relationships
|
73
|
+
include Flapjack::Diner::Tools
|
40
74
|
end
|
41
75
|
end
|