discourse_zendesk_api 1.0.0
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 +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
|