ticketevolution-ruby 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. data/.gitignore +8 -0
  2. data/.rdebugrc +1 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE +23 -0
  7. data/README.markdown +237 -0
  8. data/Rakefile +124 -0
  9. data/examples/events.rb +64 -0
  10. data/lib/docs/endpoints.markdown +47 -0
  11. data/lib/docs/installation.markdown +5 -0
  12. data/lib/docs/introduction.markdown +16 -0
  13. data/lib/docs/objects.markdown +40 -0
  14. data/lib/ticket_evolution/account.rb +4 -0
  15. data/lib/ticket_evolution/accounts.rb +6 -0
  16. data/lib/ticket_evolution/address.rb +4 -0
  17. data/lib/ticket_evolution/brokerage.rb +4 -0
  18. data/lib/ticket_evolution/brokerages.rb +7 -0
  19. data/lib/ticket_evolution/categories.rb +7 -0
  20. data/lib/ticket_evolution/category.rb +4 -0
  21. data/lib/ticket_evolution/client.rb +4 -0
  22. data/lib/ticket_evolution/clients/addresses.rb +10 -0
  23. data/lib/ticket_evolution/clients/credit_cards.rb +8 -0
  24. data/lib/ticket_evolution/clients/email_addresses.rb +10 -0
  25. data/lib/ticket_evolution/clients/phone_numbers.rb +10 -0
  26. data/lib/ticket_evolution/clients.rb +8 -0
  27. data/lib/ticket_evolution/configuration.rb +4 -0
  28. data/lib/ticket_evolution/configurations.rb +6 -0
  29. data/lib/ticket_evolution/core/api_error.rb +11 -0
  30. data/lib/ticket_evolution/core/base.rb +12 -0
  31. data/lib/ticket_evolution/core/builder.rb +74 -0
  32. data/lib/ticket_evolution/core/collection.rb +32 -0
  33. data/lib/ticket_evolution/core/connection.rb +99 -0
  34. data/lib/ticket_evolution/core/datum.rb +7 -0
  35. data/lib/ticket_evolution/core/endpoint/request_handler.rb +46 -0
  36. data/lib/ticket_evolution/core/endpoint.rb +51 -0
  37. data/lib/ticket_evolution/core/model.rb +64 -0
  38. data/lib/ticket_evolution/core/models/samples.rb +8 -0
  39. data/lib/ticket_evolution/core/samples.rb +6 -0
  40. data/lib/ticket_evolution/core/singular_class.rb +7 -0
  41. data/lib/ticket_evolution/core/time.rb +19 -0
  42. data/lib/ticket_evolution/credit_card.rb +4 -0
  43. data/lib/ticket_evolution/email_address.rb +4 -0
  44. data/lib/ticket_evolution/errors/connection_not_found.rb +4 -0
  45. data/lib/ticket_evolution/errors/endpoint_configuration_error.rb +5 -0
  46. data/lib/ticket_evolution/errors/invalid_configuration.rb +4 -0
  47. data/lib/ticket_evolution/errors/method_unavailable_error.rb +4 -0
  48. data/lib/ticket_evolution/event.rb +4 -0
  49. data/lib/ticket_evolution/events.rb +7 -0
  50. data/lib/ticket_evolution/modules/create.rb +18 -0
  51. data/lib/ticket_evolution/modules/deleted.rb +13 -0
  52. data/lib/ticket_evolution/modules/list.rb +15 -0
  53. data/lib/ticket_evolution/modules/search.rb +13 -0
  54. data/lib/ticket_evolution/modules/show.rb +20 -0
  55. data/lib/ticket_evolution/modules/update.rb +24 -0
  56. data/lib/ticket_evolution/office.rb +4 -0
  57. data/lib/ticket_evolution/offices.rb +7 -0
  58. data/lib/ticket_evolution/order.rb +15 -0
  59. data/lib/ticket_evolution/orders.rb +30 -0
  60. data/lib/ticket_evolution/performer.rb +4 -0
  61. data/lib/ticket_evolution/performers.rb +8 -0
  62. data/lib/ticket_evolution/phone_number.rb +4 -0
  63. data/lib/ticket_evolution/quote.rb +4 -0
  64. data/lib/ticket_evolution/quotes.rb +7 -0
  65. data/lib/ticket_evolution/search.rb +22 -0
  66. data/lib/ticket_evolution/shipment.rb +4 -0
  67. data/lib/ticket_evolution/shipments.rb +8 -0
  68. data/lib/ticket_evolution/ticket_group.rb +4 -0
  69. data/lib/ticket_evolution/ticket_groups.rb +6 -0
  70. data/lib/ticket_evolution/user.rb +4 -0
  71. data/lib/ticket_evolution/users.rb +7 -0
  72. data/lib/ticket_evolution/venue.rb +4 -0
  73. data/lib/ticket_evolution/venues.rb +8 -0
  74. data/lib/ticket_evolution/version.rb +3 -0
  75. data/lib/ticket_evolution.rb +108 -0
  76. data/spec/fixtures/fake.rb +97 -0
  77. data/spec/fixtures/net/endpoints/accounts.yml +175 -0
  78. data/spec/fixtures/net/endpoints/brokerages.yml +257 -0
  79. data/spec/fixtures/net/endpoints/categories.yml +85 -0
  80. data/spec/fixtures/net/endpoints/clients.yml +243 -0
  81. data/spec/fixtures/net/endpoints/search.yml +93 -0
  82. data/spec/lib/ticket_evolution/account_spec.rb +7 -0
  83. data/spec/lib/ticket_evolution/accounts_spec.rb +45 -0
  84. data/spec/lib/ticket_evolution/address_spec.rb +7 -0
  85. data/spec/lib/ticket_evolution/brokerage_spec.rb +7 -0
  86. data/spec/lib/ticket_evolution/brokerages_spec.rb +50 -0
  87. data/spec/lib/ticket_evolution/categories_spec.rb +27 -0
  88. data/spec/lib/ticket_evolution/category_spec.rb +7 -0
  89. data/spec/lib/ticket_evolution/client_spec.rb +7 -0
  90. data/spec/lib/ticket_evolution/clients/addresses_spec.rb +13 -0
  91. data/spec/lib/ticket_evolution/clients/credit_cards_spec.rb +11 -0
  92. data/spec/lib/ticket_evolution/clients/email_addresses_spec.rb +13 -0
  93. data/spec/lib/ticket_evolution/clients/phone_numbers_spec.rb +13 -0
  94. data/spec/lib/ticket_evolution/clients_spec.rb +37 -0
  95. data/spec/lib/ticket_evolution/configuration_spec.rb +7 -0
  96. data/spec/lib/ticket_evolution/configurations_spec.rb +10 -0
  97. data/spec/lib/ticket_evolution/core/api_error_spec.rb +13 -0
  98. data/spec/lib/ticket_evolution/core/base_spec.rb +29 -0
  99. data/spec/lib/ticket_evolution/core/builder_spec.rb +95 -0
  100. data/spec/lib/ticket_evolution/core/collection_spec.rb +23 -0
  101. data/spec/lib/ticket_evolution/core/connection_spec.rb +220 -0
  102. data/spec/lib/ticket_evolution/core/datum_spec.rb +15 -0
  103. data/spec/lib/ticket_evolution/core/endpoint_spec.rb +12 -0
  104. data/spec/lib/ticket_evolution/core/model_spec.rb +167 -0
  105. data/spec/lib/ticket_evolution/core/time_spec.rb +28 -0
  106. data/spec/lib/ticket_evolution/credit_card_spec.rb +7 -0
  107. data/spec/lib/ticket_evolution/email_address_spec.rb +7 -0
  108. data/spec/lib/ticket_evolution/errors/connection_not_found_spec.rb +7 -0
  109. data/spec/lib/ticket_evolution/errors/endpoint_configuration_error_spec.rb +8 -0
  110. data/spec/lib/ticket_evolution/errors/invalid_configuration_spec.rb +8 -0
  111. data/spec/lib/ticket_evolution/errors/method_unavailable_error_spec.rb +7 -0
  112. data/spec/lib/ticket_evolution/event_spec.rb +7 -0
  113. data/spec/lib/ticket_evolution/events_spec.rb +11 -0
  114. data/spec/lib/ticket_evolution/office_spec.rb +7 -0
  115. data/spec/lib/ticket_evolution/offices_spec.rb +11 -0
  116. data/spec/lib/ticket_evolution/order_spec.rb +44 -0
  117. data/spec/lib/ticket_evolution/orders_spec.rb +123 -0
  118. data/spec/lib/ticket_evolution/performer_spec.rb +7 -0
  119. data/spec/lib/ticket_evolution/performers_spec.rb +12 -0
  120. data/spec/lib/ticket_evolution/phone_number_spec.rb +7 -0
  121. data/spec/lib/ticket_evolution/quote_spec.rb +7 -0
  122. data/spec/lib/ticket_evolution/quotes_spec.rb +11 -0
  123. data/spec/lib/ticket_evolution/search_spec.rb +31 -0
  124. data/spec/lib/ticket_evolution/shipment_spec.rb +7 -0
  125. data/spec/lib/ticket_evolution/shipments_spec.rb +13 -0
  126. data/spec/lib/ticket_evolution/ticket_group_spec.rb +7 -0
  127. data/spec/lib/ticket_evolution/ticket_groups_spec.rb +14 -0
  128. data/spec/lib/ticket_evolution/user_spec.rb +7 -0
  129. data/spec/lib/ticket_evolution/users_spec.rb +15 -0
  130. data/spec/lib/ticket_evolution/venue_spec.rb +7 -0
  131. data/spec/lib/ticket_evolution/venues_spec.rb +12 -0
  132. data/spec/lib/ticket_evolution_spec.rb +20 -0
  133. data/spec/shared_examples/endpoints/class.rb +241 -0
  134. data/spec/shared_examples/endpoints/create.rb +41 -0
  135. data/spec/shared_examples/endpoints/deleted.rb +41 -0
  136. data/spec/shared_examples/endpoints/list.rb +41 -0
  137. data/spec/shared_examples/endpoints/search.rb +42 -0
  138. data/spec/shared_examples/endpoints/show.rb +38 -0
  139. data/spec/shared_examples/endpoints/update.rb +59 -0
  140. data/spec/shared_examples/errors.rb +5 -0
  141. data/spec/shared_examples/models.rb +29 -0
  142. data/spec/spec_helper.rb +21 -0
  143. data/spec/support/connection.rb +10 -0
  144. data/spec/support/vcr.rb +12 -0
  145. data/ticketevolution-ruby.gemspec +33 -0
  146. metadata +384 -0
@@ -0,0 +1,8 @@
1
+ module TicketEvolution
2
+ class Clients
3
+ class CreditCards < TicketEvolution::Endpoint
4
+ include TicketEvolution::Modules::Create
5
+ include TicketEvolution::Modules::List
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module TicketEvolution
2
+ class Clients
3
+ class EmailAddresses < TicketEvolution::Endpoint
4
+ include TicketEvolution::Modules::Create
5
+ include TicketEvolution::Modules::List
6
+ include TicketEvolution::Modules::Show
7
+ include TicketEvolution::Modules::Update
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module TicketEvolution
2
+ class Clients
3
+ class PhoneNumbers < TicketEvolution::Endpoint
4
+ include TicketEvolution::Modules::Create
5
+ include TicketEvolution::Modules::List
6
+ include TicketEvolution::Modules::Show
7
+ include TicketEvolution::Modules::Update
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module TicketEvolution
2
+ class Clients < Endpoint
3
+ include TicketEvolution::Modules::Create
4
+ include TicketEvolution::Modules::List
5
+ include TicketEvolution::Modules::Show
6
+ include TicketEvolution::Modules::Update
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class Configuration < Model
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module TicketEvolution
2
+ class Configurations < Endpoint
3
+ include TicketEvolution::Modules::List
4
+ include TicketEvolution::Modules::Show
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module TicketEvolution
2
+ class ApiError
3
+ attr_reader :error, :message, :code
4
+
5
+ def initialize(response)
6
+ @code = response.response_code
7
+ @message = response.server_message
8
+ @error = response.body['error']
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module TicketEvolution
2
+ class Base
3
+ def method_missing(method, *args)
4
+ seek = method.to_s.camelize.to_sym
5
+ if TicketEvolution.const_defined?(seek)
6
+ "TicketEvolution::#{seek}".constantize.new({:parent => self})
7
+ else
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,74 @@
1
+ module TicketEvolution
2
+ class Builder < OpenStruct
3
+ include SingularClass
4
+
5
+ def initialize(*stuff)
6
+ super
7
+ @table.each do |k, v|
8
+ send("#{k}=", process_datum(v))
9
+ end
10
+ end
11
+
12
+ def to_hash
13
+ hash = {}
14
+ @table.each do |k, v|
15
+ hash[k] = from_ostruct(v)
16
+ end
17
+ hash
18
+ end
19
+
20
+ # Ruby 1.8.7 / REE compatibility
21
+ def id=(id)
22
+ @table[:id] = id
23
+ end
24
+
25
+ def id
26
+ @table[:id]
27
+ end
28
+
29
+ private
30
+
31
+ def process_datum(v)
32
+ case v.class.to_s.to_sym
33
+ when :Hash
34
+ Datum.new(v)
35
+ when :Array
36
+ v.map{|x| process_datum(x)}
37
+ when :String
38
+ Time.parse(v)
39
+ else
40
+ v
41
+ end
42
+ end
43
+
44
+ def from_ostruct(v)
45
+ if v.kind_of? OpenStruct
46
+ v.to_hash
47
+ elsif v.class.to_s == "Array"
48
+ v.map{|x| from_ostruct(v)}
49
+ else
50
+ v
51
+ end
52
+ end
53
+
54
+ def method_missing(meth, *args)
55
+ if args.size == 1
56
+ super(meth, process_datum(args.first))
57
+ elsif args.size == 0
58
+ super(meth)
59
+ else
60
+ super(meth, process_datum(args))
61
+ end
62
+ end
63
+
64
+ def datum_exists?(name)
65
+ defined?(name.constantize) and defined?(singular_class(name.constantize))
66
+ end
67
+
68
+ def class_name_from_url(url)
69
+ url.split('/').reverse.each do |segment|
70
+ return "TicketEvolution::#{segment.capitalize}" if segment.split('')[-1] == 's'
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,32 @@
1
+ module TicketEvolution
2
+ class Collection
3
+ attr_accessor :total_entries, :per_page, :current_page, :entries
4
+
5
+ include Enumerable
6
+
7
+ def initialize(options = {})
8
+ options.each {|k,v| send("#{k}=", v)}
9
+ @entries ||= []
10
+ end
11
+
12
+ def self.build_from_response(response, entries_key, singular_class)
13
+ entries = response.body[entries_key] || []
14
+ new(
15
+ :total_entries => response.body['total_entries'],
16
+ :per_page => response.body['per_page'],
17
+ :current_page => response.body['current_page'],
18
+ :entries => entries.collect do |entry|
19
+ singular_class.new(entry.merge({:connection => response.body[:connection]}))
20
+ end
21
+ )
22
+ end
23
+
24
+ def size
25
+ @entries.size
26
+ end
27
+
28
+ def each(&block)
29
+ @entries.each(&block)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,99 @@
1
+ module TicketEvolution
2
+ class Connection < Base
3
+ cattr_reader :default_options, :expected_options, :oldest_version_in_service
4
+ cattr_accessor :protocol, :url_base
5
+
6
+ @@oldest_version_in_service = 8
7
+
8
+ @@default_options = HashWithIndifferentAccess.new({
9
+ :version => @@oldest_version_in_service,
10
+ :mode => :sandbox,
11
+ :ssl_verify => true
12
+ })
13
+
14
+ @@expected_options = [
15
+ 'version',
16
+ 'mode',
17
+ 'token',
18
+ 'secret',
19
+ 'ssl_verify'
20
+ ]
21
+
22
+ @@url_base = "ticketevolution.com"
23
+ @@protocol = "https"
24
+
25
+ def initialize(opts = {})
26
+ @config = self.class.default_options.merge(opts)
27
+ @config.delete_if{|k, v| ! TicketEvolution::Connection.expected_options.include?(k)}
28
+
29
+ # Error Notification
30
+ if @config.keys.sort_by{|x|x} == TicketEvolution::Connection.expected_options.sort_by{|x|x}
31
+ raise InvalidConfiguration.new("Invalid Token Format") unless @config[:token] =~ /^[a-zA-Z0-9]{32}$/
32
+ raise InvalidConfiguration.new("Invalid Secret Format") unless @config[:secret] =~ /^\S{40}$/
33
+ raise InvalidConfiguration.new("Please Use API Version #{TicketEvolution::Connection.oldest_version_in_service} or Above") unless @config[:version] >= TicketEvolution::Connection.oldest_version_in_service
34
+ else
35
+ raise InvalidConfiguration.new("Missing: #{(self.class.expected_options - @config.keys).join(', ')}")
36
+ end
37
+ end
38
+
39
+ def url
40
+ @url ||= [].tap do |parts|
41
+ parts << TicketEvolution::Connection.protocol
42
+ parts << "://api."
43
+ parts << "#{@config[:mode]}." unless @config[:mode] == :production
44
+ parts << TicketEvolution::Connection.url_base
45
+ end.join
46
+ end
47
+
48
+ def sign(method, path, content = nil)
49
+ Base64.encode64(
50
+ OpenSSL::HMAC.digest(
51
+ OpenSSL::Digest::Digest.new('sha256'),
52
+ @config[:secret],
53
+ "#{method} #{process_params(method, path, content).gsub(TicketEvolution::Connection.protocol+'://', '')}"
54
+ )).chomp
55
+ end
56
+
57
+ def build_request(method, path, params = nil)
58
+ uri = URI.join(self.url, path).to_s
59
+ Curl::Easy.new(generate_url(method, uri, params)) do |request|
60
+ if @config.has_key?(:ssl_verify)
61
+ request.ssl_verify_host = @config[:ssl_verify]
62
+ request.ssl_verify_peer = @config[:ssl_verify]
63
+ end
64
+ request.post_body = post_body(params) unless method == :GET
65
+ request.headers["Accept"] = "application/vnd.ticketevolution.api+json; version=#{@config[:version]}"
66
+ request.headers["X-Signature"] = sign(method, uri, params)
67
+ request.headers["X-Token"] = @config[:token]
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def post_body(params)
74
+ MultiJson.encode(params)
75
+ end
76
+
77
+ def generate_url(method, uri, params)
78
+ case method
79
+ when :GET
80
+ process_params(method, uri, params)
81
+ else
82
+ uri
83
+ end
84
+ end
85
+
86
+ def process_params(method, path, params)
87
+ suffix = if params.present?
88
+ case method
89
+ when :GET
90
+ params.to_query
91
+ else
92
+ post_body(params)
93
+ end
94
+ end
95
+
96
+ "#{URI.join(url, path).to_s}?" + suffix.to_s
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,7 @@
1
+ module TicketEvolution
2
+ class Datum < Builder
3
+ def [](key)
4
+ send(key)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ module TicketEvolution
2
+ class Endpoint < Base
3
+ module RequestHandler
4
+
5
+ # Response Code Mappings From TicketEvolution API
6
+ CODES = {
7
+ 200 => ["OK","Generally returned by successful GET requests. "],
8
+ 201 => ["Created","Generally returned by successful POST requests. "],
9
+ 202 => ["Accepted","Generally returned when a request has succeeded, but has been scheduled processing at a later time. "],
10
+ 301 => ["Moved Permanently","Used when a resource's URL has changed."],
11
+ 302 => ["Found","Returned when there's a redirect that should be followed."],
12
+ 400 => ["Bad Request","Generally returned on POST and PUT requests when validation fails for the given input. "],
13
+ 401 => ["Unauthorized","Returned when the authentication credentials are invalid."],
14
+ 404 => ["Not Found","The requested resource could not be located."],
15
+ 406 => ["Not Acceptable","The requested content type or version is invalid."],
16
+ 500 => ["Internal Server Error","Used a general error response for processing errors or other issues with the web service. "],
17
+ 503 => ["Service Unavailable","Returned when the API service is temporarily unavailable. This could also indicate that the rate limit for the given token has been reached. If this status is received, the request should be retried."]
18
+ }
19
+
20
+ def request(method, path, params = nil, &response_handler)
21
+ response = self.build_request(method, path, params)
22
+ response.http(method)
23
+ response = self.naturalize_response(response)
24
+ if response.response_code >= 400
25
+ TicketEvolution::ApiError.new(response)
26
+ else
27
+ response_handler.call(response)
28
+ end
29
+ end
30
+
31
+ def build_request(method, path, params = nil)
32
+ raise EndpointConfigurationError, "#{self.class.to_s}#request requires it's first parameter to be a valid HTTP method" unless [:GET, :POST, :PUT, :DELETE].include? method.to_sym
33
+ self.connection.build_request(method, "#{self.base_path}#{path}", params)
34
+ end
35
+
36
+ def naturalize_response(response)
37
+ OpenStruct.new.tap do |resp|
38
+ resp.header = response.header_str
39
+ resp.response_code = response.response_code
40
+ resp.body = MultiJson.decode(response.body_str).merge({:connection => self.connection})
41
+ resp.server_message = CODES[response.response_code].last
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ module TicketEvolution
2
+ class Endpoint < Base
3
+ include RequestHandler
4
+ include SingularClass
5
+
6
+ def initialize(options = nil)
7
+ raise EndpointConfigurationError, "#{self.class.to_s} instances require a hash as their first parameter" unless options.is_a? Hash
8
+ raise EndpointConfigurationError, "The options hash must include a parent key / value pair" unless options[:parent].present?
9
+ raise EndpointConfigurationError, "#{self.class.to_s} instances require a parent which inherits from TicketEvolution::Base" unless options[:parent].kind_of? TicketEvolution::Base
10
+ options.each do |k, v|
11
+ self.singleton_class.send(:attr_accessor, k)
12
+ send("#{k}=", v)
13
+ end
14
+ raise EndpointConfigurationError, "The parent passed in the options hash must be a TicketEvolution::Connection object or have one in it's parent chain" unless has_connection?
15
+ end
16
+
17
+ def base_path
18
+ [].tap do |parts|
19
+ parts << parent.base_path if parent.kind_of? TicketEvolution::Endpoint
20
+ parts << "/"+endpoint_name
21
+ parts << "/#{self.id}" if self.respond_to?("id=") and self.id.present?
22
+ end.join
23
+ end
24
+
25
+ def connection
26
+ if self.parent.is_a? TicketEvolution::Connection
27
+ self.parent
28
+ elsif self.parent.respond_to? :parent
29
+ self.parent.connection
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ def has_connection?
36
+ connection.present?
37
+ end
38
+
39
+ def endpoint_name
40
+ self.class.name.demodulize.underscore
41
+ end
42
+
43
+ private
44
+
45
+ def ensure_id
46
+ raise TicketEvolution::MethodUnavailableError.new \
47
+ "#{self.class.to_s}##{caller.first.split('`').last.split("'").first} can only be called if there is an id present on this #{self.class.to_s} instance" \
48
+ unless self.respond_to?("id=") and self.id.present?
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ module TicketEvolution
2
+ class Model < Builder
3
+ def initialize(params = {})
4
+ @connection = params.delete(:connection)
5
+ raise TicketEvolution::ConnectionNotFound.new \
6
+ "#{self.class.name} must receive a TicketEvolution::Connection object on initialize" \
7
+ unless @connection.is_a? TicketEvolution::Connection
8
+ @scope = params['url'].split('/')[0..2].join('/') if params['url'] =~ /^(\/[a-z_]+s\/\d){2}$/
9
+ super(params)
10
+ end
11
+
12
+ def plural_class_name
13
+ parts = ["TicketEvolution", self.class.name.demodulize.pluralize.camelize]
14
+ parts[0] = self.scope[:class] if @scope.present?
15
+ parts.join('::')
16
+ end
17
+
18
+ def plural_class
19
+ self.plural_class_name.constantize
20
+ end
21
+
22
+ def attributes
23
+ HashWithIndifferentAccess.new(to_hash)
24
+ end
25
+
26
+ def attributes=(params)
27
+ params.each do |k, v|
28
+ send("#{k}=", v)
29
+ end
30
+ end
31
+
32
+ def scope
33
+ if @scope.present?
34
+ {}.tap do |scope|
35
+ parts = @scope.split('/')
36
+ scope[:class] = "TicketEvolution::#{parts[1].camelize}"
37
+ scope[:id] = parts[2].to_i
38
+ end
39
+ else
40
+ nil
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def process_datum(v)
47
+ if v.is_a? Hash and v['url'].present?
48
+ name = class_name_from_url(v['url'])
49
+ datum_exists?(name) ? singular_class(class_name_from_url(name)).new(v.merge({:connection => @connection})) : Datum.new(v)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def method_missing(method, *args)
56
+ seek = method.to_s.camelize
57
+ if seek !~ /=/ and plural_class.const_defined?(seek.to_sym)
58
+ "#{plural_class_name}::#{seek}".constantize.new(:parent => plural_class.new(:connection => @connection, :id => self.id))
59
+ else
60
+ super
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,8 @@
1
+ module TicketEvolution
2
+ class Models < TicketEvolution::Endpoint
3
+ class Samples < TicketEvolution::Base
4
+ # This class exists to decouple tests of Model from actual functionality
5
+ # There must be a better way
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module TicketEvolution
2
+ class Samples < Base
3
+ # This class exists to decouple tests of Base from actual functionality
4
+ def initialize(*attrs); end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module TicketEvolution
2
+ module SingularClass
3
+ def singular_class(klass = self.class)
4
+ "TicketEvolution::#{(klass.is_a?(String) ? klass : klass.name).demodulize.singularize.camelize}".constantize
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module TicketEvolution
2
+ class Time < ::Time
3
+ def self.parse(str)
4
+ if str =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/
5
+ parts = str.split(/[-T:Z]/).collect(&:to_i)
6
+ Time.gm(
7
+ parts[0],
8
+ parts[1],
9
+ parts[2],
10
+ parts[3],
11
+ parts[4],
12
+ parts[5]
13
+ )
14
+ else
15
+ str
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class CreditCard < Model
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class EmailAddress < Model
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class ConnectionNotFound < Exception
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module TicketEvolution
2
+ class EndpointConfigurationError < Exception
3
+ end
4
+ end
5
+
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class InvalidConfiguration < Exception
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class MethodUnavailableError < Exception
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module TicketEvolution
2
+ class Event < Model
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module TicketEvolution
2
+ class Events < Endpoint
3
+ include TicketEvolution::Modules::Deleted
4
+ include TicketEvolution::Modules::List
5
+ include TicketEvolution::Modules::Show
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module TicketEvolution
2
+ module Modules
3
+ module Create
4
+ def create(params = nil)
5
+ params = { endpoint_name.to_sym => [params] } if params.present?
6
+ request(:POST, nil, params, &method(:build_for_create))
7
+ end
8
+
9
+ def build_for_create(response)
10
+ singular_class.new(response.body[endpoint_name].first.merge({
11
+ :status_code => response.response_code,
12
+ :server_message => response.server_message,
13
+ :connection => response.body[:connection]
14
+ }))
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module TicketEvolution
2
+ module Modules
3
+ module Deleted
4
+ def deleted(params = nil)
5
+ request(:GET, '/deleted', params, &method(:build_for_deleted))
6
+ end
7
+
8
+ def build_for_deleted(response)
9
+ TicketEvolution::Collection.build_from_response(response, self.class.name.demodulize.underscore, singular_class)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module TicketEvolution
2
+ module Modules
3
+ module List
4
+ def list(params = nil)
5
+ request(:GET, nil, params, &method(:build_for_list))
6
+ end
7
+
8
+ alias :all :list
9
+
10
+ def build_for_list(response)
11
+ TicketEvolution::Collection.build_from_response(response, self.class.name.demodulize.underscore, singular_class)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module TicketEvolution
2
+ module Modules
3
+ module Search
4
+ def search(params = nil)
5
+ request(:GET, '/search', params, &method(:build_for_search))
6
+ end
7
+
8
+ def build_for_search(response)
9
+ TicketEvolution::Collection.build_from_response(response, self.class.name.demodulize.underscore, singular_class)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module TicketEvolution
2
+ module Modules
3
+ module Show
4
+ def show(id)
5
+ request(:GET, "/#{id}", &method(:build_for_show))
6
+ end
7
+
8
+ alias :find :show
9
+
10
+ def build_for_show(response)
11
+ singular_class.new(
12
+ response.body.merge({
13
+ :status_code => response.response_code,
14
+ :server_message => response.server_message
15
+ })
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ module TicketEvolution
2
+ module Modules
3
+ module Update
4
+ def self.included(klass)
5
+ Class.new{extend SingularClass}.singular_class(klass.name).send(:include, Module.new{
6
+ def update_attributes(params)
7
+ params.each{|k, v| send("#{k}=", process_datum(v))}
8
+ plural_class.new({:parent => @connection, :id => params.delete(:id)}).update(params)
9
+ end
10
+
11
+ def save
12
+ atts = self.attributes
13
+ plural_class.new({:parent => @connection, :id => atts.delete(:id)}).update(atts)
14
+ end
15
+ })
16
+ end
17
+
18
+ def update(params = nil)
19
+ ensure_id
20
+ request(:PUT, "/#{self.id}", params)
21
+ end
22
+ end
23
+ end
24
+ end