discourse_zendesk_api 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +176 -0
- data/lib/zendesk_api/actions.rb +334 -0
- data/lib/zendesk_api/association.rb +195 -0
- data/lib/zendesk_api/associations.rb +212 -0
- data/lib/zendesk_api/client.rb +243 -0
- data/lib/zendesk_api/collection.rb +474 -0
- data/lib/zendesk_api/configuration.rb +79 -0
- data/lib/zendesk_api/core_ext/inflection.rb +3 -0
- data/lib/zendesk_api/delegator.rb +5 -0
- data/lib/zendesk_api/error.rb +49 -0
- data/lib/zendesk_api/helpers.rb +24 -0
- data/lib/zendesk_api/lru_cache.rb +39 -0
- data/lib/zendesk_api/middleware/request/encode_json.rb +26 -0
- data/lib/zendesk_api/middleware/request/etag_cache.rb +52 -0
- data/lib/zendesk_api/middleware/request/raise_rate_limited.rb +31 -0
- data/lib/zendesk_api/middleware/request/retry.rb +59 -0
- data/lib/zendesk_api/middleware/request/upload.rb +86 -0
- data/lib/zendesk_api/middleware/request/url_based_access_token.rb +26 -0
- data/lib/zendesk_api/middleware/response/callback.rb +21 -0
- data/lib/zendesk_api/middleware/response/deflate.rb +17 -0
- data/lib/zendesk_api/middleware/response/gzip.rb +19 -0
- data/lib/zendesk_api/middleware/response/logger.rb +44 -0
- data/lib/zendesk_api/middleware/response/parse_iso_dates.rb +30 -0
- data/lib/zendesk_api/middleware/response/parse_json.rb +23 -0
- data/lib/zendesk_api/middleware/response/raise_error.rb +26 -0
- data/lib/zendesk_api/middleware/response/sanitize_response.rb +11 -0
- data/lib/zendesk_api/resource.rb +208 -0
- data/lib/zendesk_api/resources.rb +971 -0
- data/lib/zendesk_api/sideloading.rb +58 -0
- data/lib/zendesk_api/silent_mash.rb +8 -0
- data/lib/zendesk_api/track_changes.rb +85 -0
- data/lib/zendesk_api/trackie.rb +13 -0
- data/lib/zendesk_api/verbs.rb +65 -0
- data/lib/zendesk_api/version.rb +3 -0
- data/lib/zendesk_api.rb +4 -0
- data/util/resource_handler.rb +74 -0
- data/util/verb_handler.rb +16 -0
- metadata +166 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require "faraday/middleware"
|
2
|
+
|
3
|
+
module ZendeskAPI
|
4
|
+
module Middleware
|
5
|
+
module Request
|
6
|
+
# Request middleware that caches responses based on etags
|
7
|
+
# can be removed once this is merged: https://github.com/pengwynn/faraday_middleware/pull/42
|
8
|
+
# @private
|
9
|
+
class EtagCache < Faraday::Middleware
|
10
|
+
def initialize(app, options = {})
|
11
|
+
@app = app
|
12
|
+
@cache = options[:cache] ||
|
13
|
+
raise("need :cache option e.g. ActiveSupport::Cache::MemoryStore.new")
|
14
|
+
@cache_key_prefix = options.fetch(:cache_key_prefix, :faraday_etags)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cache_key(env)
|
18
|
+
[@cache_key_prefix, env[:url].to_s]
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(environment)
|
22
|
+
return @app.call(environment) unless [:get, :head].include?(environment[:method])
|
23
|
+
|
24
|
+
# send known etag
|
25
|
+
cached = @cache.read(cache_key(environment))
|
26
|
+
|
27
|
+
if cached
|
28
|
+
environment[:request_headers]["If-None-Match"] ||= cached[:response_headers]["Etag"]
|
29
|
+
end
|
30
|
+
|
31
|
+
@app.call(environment).on_complete do |env|
|
32
|
+
if cached && env[:status] == 304 # not modified
|
33
|
+
# Handle differences in serialized env keys in Faraday < 1.0 and 1.0
|
34
|
+
# See https://github.com/lostisland/faraday/pull/847
|
35
|
+
env[:body] = cached[:body]
|
36
|
+
env[:response_body] = cached[:response_body]
|
37
|
+
|
38
|
+
env[:response_headers].merge!(
|
39
|
+
:etag => cached[:response_headers][:etag],
|
40
|
+
:content_type => cached[:response_headers][:content_type],
|
41
|
+
:content_length => cached[:response_headers][:content_length],
|
42
|
+
:content_encoding => cached[:response_headers][:content_encoding]
|
43
|
+
)
|
44
|
+
elsif env[:status] == 200 && env[:response_headers]["Etag"] # modified and cacheable
|
45
|
+
@cache.write(cache_key(env), env.to_hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faraday/middleware'
|
2
|
+
require 'zendesk_api/error'
|
3
|
+
|
4
|
+
module ZendeskAPI
|
5
|
+
module Middleware
|
6
|
+
# @private
|
7
|
+
module Request
|
8
|
+
# Faraday middleware to handle HTTP Status 429 (rate limiting) / 503 (maintenance)
|
9
|
+
# @private
|
10
|
+
class RaiseRateLimited < Faraday::Middleware
|
11
|
+
ERROR_CODES = [429, 503].freeze
|
12
|
+
|
13
|
+
def initialize(app, options = {})
|
14
|
+
super(app)
|
15
|
+
@logger = options[:logger]
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
response = @app.call(env)
|
20
|
+
|
21
|
+
if ERROR_CODES.include?(response.env[:status])
|
22
|
+
@logger&.warn 'You have been rate limited. Raising error...'
|
23
|
+
raise Error::RateLimited, env
|
24
|
+
else
|
25
|
+
response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "faraday/middleware"
|
2
|
+
module ZendeskAPI
|
3
|
+
module Middleware
|
4
|
+
# @private
|
5
|
+
module Request
|
6
|
+
# Faraday middleware to handle HTTP Status 429 (rate limiting) / 503 (maintenance)
|
7
|
+
# @private
|
8
|
+
class Retry < Faraday::Middleware
|
9
|
+
DEFAULT_RETRY_AFTER = 10
|
10
|
+
DEFAULT_ERROR_CODES = [429, 503]
|
11
|
+
|
12
|
+
def initialize(app, options = {})
|
13
|
+
super(app)
|
14
|
+
@logger = options[:logger]
|
15
|
+
@error_codes = options.key?(:retry_codes) && options[:retry_codes] ? options[:retry_codes] : DEFAULT_ERROR_CODES
|
16
|
+
@retry_on_exception = options.key?(:retry_on_exception) && options[:retry_on_exception] ? options[:retry_on_exception] : false
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
original_env = env.dup
|
21
|
+
exception_happened = false
|
22
|
+
if @retry_on_exception
|
23
|
+
begin
|
24
|
+
response = @app.call(env)
|
25
|
+
rescue StandardError => e
|
26
|
+
exception_happened = true
|
27
|
+
end
|
28
|
+
else
|
29
|
+
response = @app.call(env)
|
30
|
+
end
|
31
|
+
|
32
|
+
if exception_happened || @error_codes.include?(response.env[:status])
|
33
|
+
|
34
|
+
if exception_happened
|
35
|
+
seconds_left = DEFAULT_RETRY_AFTER.to_i
|
36
|
+
@logger.warn "An exception happened, waiting #{seconds_left} seconds... #{e}" if @logger
|
37
|
+
else
|
38
|
+
seconds_left = (response.env[:response_headers][:retry_after] || DEFAULT_RETRY_AFTER).to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
@logger.warn "You have been rate limited. Retrying in #{seconds_left} seconds..." if @logger
|
42
|
+
|
43
|
+
seconds_left.times do |i|
|
44
|
+
sleep 1
|
45
|
+
time_left = seconds_left - i
|
46
|
+
@logger.warn "#{time_left}..." if time_left > 0 && time_left % 5 == 0 && @logger
|
47
|
+
end
|
48
|
+
|
49
|
+
@logger.warn "" if @logger
|
50
|
+
|
51
|
+
@app.call(original_env)
|
52
|
+
else
|
53
|
+
response
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "faraday/middleware"
|
2
|
+
require "mini_mime"
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module ZendeskAPI
|
6
|
+
module Middleware
|
7
|
+
module Request
|
8
|
+
# @private
|
9
|
+
class Upload < Faraday::Middleware
|
10
|
+
def call(env)
|
11
|
+
if env[:body]
|
12
|
+
set_file(env[:body], :file, true)
|
13
|
+
traverse_hash(env[:body])
|
14
|
+
end
|
15
|
+
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Sets the proper file parameters :uploaded_data and :filename
|
22
|
+
# If top_level, then it removes key and and sets the parameters directly on hash,
|
23
|
+
# otherwise it adds the parameters to hash[key]
|
24
|
+
def set_file(hash, key, top_level)
|
25
|
+
return unless hash.key?(key)
|
26
|
+
|
27
|
+
file = if hash[key].is_a?(Hash) && hash[key].key?(:file)
|
28
|
+
hash[key].delete(:file)
|
29
|
+
else
|
30
|
+
hash.delete(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
case file
|
34
|
+
when File, Tempfile
|
35
|
+
path = file.path
|
36
|
+
when String
|
37
|
+
path = file
|
38
|
+
else
|
39
|
+
if defined?(ActionDispatch) && file.is_a?(ActionDispatch::Http::UploadedFile)
|
40
|
+
path = file.tempfile.path
|
41
|
+
mime_type = file.content_type
|
42
|
+
else
|
43
|
+
warn "WARNING: Passed invalid filename #{file} of type #{file.class} to upload"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if path
|
48
|
+
if !top_level
|
49
|
+
hash[key] ||= {}
|
50
|
+
hash = hash[key]
|
51
|
+
end
|
52
|
+
|
53
|
+
unless defined?(mime_type) && !mime_type.nil?
|
54
|
+
mime_type = MiniMime.lookup_by_filename(path)
|
55
|
+
mime_type = mime_type ? mime_type.content_type : "application/octet-stream"
|
56
|
+
end
|
57
|
+
|
58
|
+
hash[:filename] ||= if file.respond_to?(:original_filename)
|
59
|
+
file.original_filename
|
60
|
+
else
|
61
|
+
File.basename(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
hash[:uploaded_data] = Faraday::UploadIO.new(path, mime_type, hash[:filename])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Calls #set_file on File instances or Hashes
|
69
|
+
# of the format { :file => File (, :filename => ...) }
|
70
|
+
def traverse_hash(hash)
|
71
|
+
hash.keys.each do |key|
|
72
|
+
if hash[key].is_a?(File)
|
73
|
+
set_file(hash, key, false)
|
74
|
+
elsif hash[key].is_a?(Hash)
|
75
|
+
if hash[key].key?(:file)
|
76
|
+
set_file(hash, key, false)
|
77
|
+
else
|
78
|
+
traverse_hash(hash[key])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# @private
|
3
|
+
module Middleware
|
4
|
+
# @private
|
5
|
+
module Request
|
6
|
+
class UrlBasedAccessToken < Faraday::Middleware
|
7
|
+
def initialize(app, token)
|
8
|
+
super(app)
|
9
|
+
@token = token
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
if env[:url].query
|
14
|
+
env[:url].query += '&'
|
15
|
+
else
|
16
|
+
env[:url].query = ''
|
17
|
+
end
|
18
|
+
|
19
|
+
env[:url].query += "access_token=#{@token}"
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "faraday/response"
|
2
|
+
|
3
|
+
module ZendeskAPI
|
4
|
+
module Middleware
|
5
|
+
module Response
|
6
|
+
# @private
|
7
|
+
class Callback < Faraday::Response::Middleware
|
8
|
+
def initialize(app, client)
|
9
|
+
super(app)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
@app.call(env).on_complete do |env|
|
15
|
+
@client.callbacks.each { |c| c.call(env) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# @private
|
3
|
+
module Middleware
|
4
|
+
# @private
|
5
|
+
module Response
|
6
|
+
# Faraday middleware to handle content-encoding = inflate
|
7
|
+
# @private
|
8
|
+
class Deflate < Faraday::Response::Middleware
|
9
|
+
def on_complete(env)
|
10
|
+
if !env.body.strip.empty? && env[:response_headers]['content-encoding'] == "deflate"
|
11
|
+
env.body = Zlib::Inflate.inflate(env.body)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module ZendeskAPI
|
5
|
+
# @private
|
6
|
+
module Middleware
|
7
|
+
# @private
|
8
|
+
module Response
|
9
|
+
# Faraday middleware to handle content-encoding = gzip
|
10
|
+
class Gzip < Faraday::Response::Middleware
|
11
|
+
def on_complete(env)
|
12
|
+
if !env[:body].strip.empty? && env[:response_headers]['content-encoding'] == "gzip"
|
13
|
+
env[:body] = Zlib::GzipReader.new(StringIO.new(env[:body])).read
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
module Middleware
|
3
|
+
module Response
|
4
|
+
# Faraday middleware to handle logging
|
5
|
+
# @private
|
6
|
+
class Logger < Faraday::Middleware
|
7
|
+
LOG_LENGTH = 1000
|
8
|
+
|
9
|
+
def initialize(app, logger = nil)
|
10
|
+
super(app)
|
11
|
+
|
12
|
+
@logger = logger || begin
|
13
|
+
require 'logger'
|
14
|
+
::Logger.new(STDOUT)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
@logger.info "#{env[:method]} #{env[:url].to_s}"
|
20
|
+
@logger.debug dump_debug(env, :request_headers)
|
21
|
+
|
22
|
+
@app.call(env).on_complete do |env|
|
23
|
+
info = "Status #{env[:status]}"
|
24
|
+
info.concat(" #{env[:body].to_s[0, LOG_LENGTH]}") if (400..499).cover?(env[:status].to_i)
|
25
|
+
|
26
|
+
@logger.info info
|
27
|
+
@logger.debug dump_debug(env, :response_headers)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def dump_debug(env, headers_key)
|
34
|
+
info = env[headers_key].map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
|
35
|
+
unless env[:body].nil?
|
36
|
+
info.concat("\n")
|
37
|
+
info.concat(env[:body].inspect)
|
38
|
+
end
|
39
|
+
info
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'time'
|
2
|
+
require "faraday/response"
|
3
|
+
|
4
|
+
module ZendeskAPI
|
5
|
+
module Middleware
|
6
|
+
module Response
|
7
|
+
# Parse ISO dates from response body
|
8
|
+
# @private
|
9
|
+
class ParseIsoDates < Faraday::Response::Middleware
|
10
|
+
def call(env)
|
11
|
+
@app.call(env).on_complete do |env|
|
12
|
+
parse_dates!(env[:body])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse_dates!(value)
|
19
|
+
case value
|
20
|
+
when Hash then value.each { |key, element| value[key] = parse_dates!(element) }
|
21
|
+
when Array then value.each_with_index { |element, index| value[index] = parse_dates!(element) }
|
22
|
+
when /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\Z/m then Time.parse(value)
|
23
|
+
else
|
24
|
+
value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ZendeskAPI
|
2
|
+
# @private
|
3
|
+
module Middleware
|
4
|
+
# @private
|
5
|
+
module Response
|
6
|
+
class ParseJson < Faraday::Response::Middleware
|
7
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
8
|
+
dependency 'json'
|
9
|
+
|
10
|
+
def on_complete(env)
|
11
|
+
type = env[:response_headers][CONTENT_TYPE].to_s
|
12
|
+
type = type.split(';', 2).first if type.index(';')
|
13
|
+
|
14
|
+
return unless type == 'application/json'
|
15
|
+
|
16
|
+
unless env[:body].strip.empty?
|
17
|
+
env[:body] = JSON.parse(env[:body])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'zendesk_api/error'
|
2
|
+
|
3
|
+
module ZendeskAPI
|
4
|
+
module Middleware
|
5
|
+
module Response
|
6
|
+
class RaiseError < Faraday::Response::RaiseError
|
7
|
+
def call(env)
|
8
|
+
super
|
9
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed => e
|
10
|
+
raise Error::NetworkError.new(e, env)
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_complete(env)
|
14
|
+
case env[:status]
|
15
|
+
when 404
|
16
|
+
raise Error::RecordNotFound.new(env)
|
17
|
+
when 422, 413
|
18
|
+
raise Error::RecordInvalid.new(env)
|
19
|
+
when 100..199, 400..599, 300..303, 305..399
|
20
|
+
raise Error::NetworkError.new(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'zendesk_api/helpers'
|
2
|
+
require 'zendesk_api/trackie'
|
3
|
+
require 'zendesk_api/actions'
|
4
|
+
require 'zendesk_api/association'
|
5
|
+
require 'zendesk_api/associations'
|
6
|
+
require 'zendesk_api/verbs'
|
7
|
+
|
8
|
+
module ZendeskAPI
|
9
|
+
# Represents a resource that only holds data.
|
10
|
+
class Data
|
11
|
+
include Associations
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def inherited(klass)
|
15
|
+
subclasses.push(klass)
|
16
|
+
end
|
17
|
+
|
18
|
+
def subclasses
|
19
|
+
@subclasses ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
# The singular resource name taken from the class name (e.g. ZendeskAPI::Ticket -> ticket)
|
23
|
+
def singular_resource_name
|
24
|
+
@singular_resource_name ||= ZendeskAPI::Helpers.snakecase_string(to_s.split("::").last)
|
25
|
+
end
|
26
|
+
|
27
|
+
# The resource name taken from the class name (e.g. ZendeskAPI::Ticket -> tickets)
|
28
|
+
def resource_name
|
29
|
+
@resource_name ||= Inflection.plural(singular_resource_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def resource_path
|
33
|
+
[@namespace, resource_name].compact.join("/")
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :model_key :resource_name
|
37
|
+
|
38
|
+
def namespace(namespace)
|
39
|
+
@namespace = namespace
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Hash] The resource's attributes
|
44
|
+
attr_reader :attributes
|
45
|
+
# @return [ZendeskAPI::Association] The association
|
46
|
+
attr_accessor :association
|
47
|
+
# @return [Array] The last received errors
|
48
|
+
attr_accessor :errors
|
49
|
+
# Place to dump the last response
|
50
|
+
attr_accessor :response
|
51
|
+
|
52
|
+
# Create a new resource instance.
|
53
|
+
# @param [Client] client The client to use
|
54
|
+
# @param [Hash] attributes The optional attributes that describe the resource
|
55
|
+
def initialize(client, attributes = {})
|
56
|
+
raise "Expected a Hash for attributes, got #{attributes.inspect}" unless attributes.is_a?(Hash)
|
57
|
+
@association = attributes.delete(:association) || Association.new(:class => self.class)
|
58
|
+
@global_params = attributes.delete(:global) || {}
|
59
|
+
@client = client
|
60
|
+
@attributes = ZendeskAPI::Trackie.new(attributes)
|
61
|
+
|
62
|
+
if self.class.associations.none? { |a| a[:name] == self.class.singular_resource_name }
|
63
|
+
ZendeskAPI::Client.check_deprecated_namespace_usage @attributes, self.class.singular_resource_name
|
64
|
+
end
|
65
|
+
|
66
|
+
@attributes.clear_changes unless new_record?
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.new_from_response(client, response, includes = nil)
|
70
|
+
new(client).tap do |resource|
|
71
|
+
resource.handle_response(response)
|
72
|
+
resource.set_includes(resource, includes, response.body) if includes
|
73
|
+
resource.attributes.clear_changes
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Passes the method onto the attributes hash.
|
78
|
+
# If the attributes are nested (e.g. { :tickets => { :id => 1 } }), passes the method onto the nested hash.
|
79
|
+
def method_missing(*args, &block)
|
80
|
+
raise NoMethodError, ":save is not defined" if args.first.to_sym == :save
|
81
|
+
@attributes.send(*args, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
def respond_to_missing?(method, include_private = false)
|
85
|
+
@attributes.respond_to?(method) || super
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the resource id of the object or nil
|
89
|
+
def id
|
90
|
+
key?(:id) ? method_missing(:id) : nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Has this been object been created server-side? Does this by checking for an id.
|
94
|
+
def new_record?
|
95
|
+
id.nil?
|
96
|
+
end
|
97
|
+
|
98
|
+
# @private
|
99
|
+
def loaded_associations
|
100
|
+
self.class.associations.select do |association|
|
101
|
+
loaded = @attributes.method_missing(association[:name])
|
102
|
+
loaded && !(loaded.respond_to?(:empty?) && loaded.empty?)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the path to the resource
|
107
|
+
def path(options = {})
|
108
|
+
@association.generate_path(self, options)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Passes #to_json to the underlying attributes hash
|
112
|
+
def to_json(*args)
|
113
|
+
method_missing(:to_json, *args)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @private
|
117
|
+
def to_s
|
118
|
+
"#{self.class.singular_resource_name}: #{attributes.inspect}"
|
119
|
+
end
|
120
|
+
alias :inspect :to_s
|
121
|
+
|
122
|
+
# Compares resources by class and id. If id is nil, then by object_id
|
123
|
+
def ==(other)
|
124
|
+
return true if other.object_id == object_id
|
125
|
+
|
126
|
+
if other && !(other.is_a?(Data) || other.is_a?(Integer))
|
127
|
+
warn "Trying to compare #{other.class} to a Resource from #{caller.first}"
|
128
|
+
end
|
129
|
+
|
130
|
+
if other.is_a?(Data)
|
131
|
+
other.id && other.id == id
|
132
|
+
elsif other.is_a?(Integer)
|
133
|
+
id == other
|
134
|
+
else
|
135
|
+
false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
alias :eql :==
|
139
|
+
|
140
|
+
# @private
|
141
|
+
def inspect
|
142
|
+
"#<#{self.class.name} #{@attributes.to_hash.inspect}>"
|
143
|
+
end
|
144
|
+
|
145
|
+
alias :to_param :attributes
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def attributes_for_save
|
150
|
+
{ self.class.singular_resource_name.to_sym => attributes.changes }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Indexable resource
|
155
|
+
class DataResource < Data
|
156
|
+
attr_accessor :error, :error_message
|
157
|
+
extend Verbs
|
158
|
+
end
|
159
|
+
|
160
|
+
# Represents a resource that can only GET
|
161
|
+
class ReadResource < DataResource
|
162
|
+
include Read
|
163
|
+
end
|
164
|
+
|
165
|
+
# Represents a resource that can only POST
|
166
|
+
class CreateResource < DataResource
|
167
|
+
include Create
|
168
|
+
end
|
169
|
+
|
170
|
+
# Represents a resource that can only PUT
|
171
|
+
class UpdateResource < DataResource
|
172
|
+
include Update
|
173
|
+
end
|
174
|
+
|
175
|
+
# Represents a resource that can only DELETE
|
176
|
+
class DeleteResource < DataResource
|
177
|
+
include Destroy
|
178
|
+
end
|
179
|
+
|
180
|
+
# Represents a resource that can CRUD (create, read, update, delete).
|
181
|
+
class Resource < DataResource
|
182
|
+
include Read
|
183
|
+
include Create
|
184
|
+
|
185
|
+
include Update
|
186
|
+
include Destroy
|
187
|
+
end
|
188
|
+
|
189
|
+
class SingularResource < Resource
|
190
|
+
def attributes_for_save
|
191
|
+
{ self.class.resource_name.to_sym => attributes.changes }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Namespace parent class for Data/Resource classes
|
196
|
+
module DataNamespace
|
197
|
+
class << self
|
198
|
+
def included(base)
|
199
|
+
@descendants ||= []
|
200
|
+
@descendants << base
|
201
|
+
end
|
202
|
+
|
203
|
+
def descendants
|
204
|
+
@descendants || []
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|