Commit 3d0157a52ebfc566a9df373c28c81f72dbf1a754

Authored by Isaac Lewis
1 parent 3c0896d2

add logoff

lib/syspro.rb
... ... @@ -5,6 +5,9 @@ require "logger"
5 5 require "openssl"
6 6  
7 7 require "syspro/api_resource"
  8 +require "syspro/get_version"
  9 +require "syspro/logoff"
  10 +require "syspro/logon"
8 11 require "syspro/syspro_client"
9 12 require "syspro/singleton_api_resource"
10 13 require "syspro/syspro_object"
... ... @@ -12,10 +15,7 @@ require "syspro/syspro_response"
12 15 require "syspro/util"
13 16 require "syspro/version"
14 17  
15   -require "syspro/api_operations/get_version"
16 18 require "syspro/api_operations/request"
17   -require "syspro/api_operations/logon"
18   -
19 19  
20 20 module Syspro
21 21 @api_base = "http://syspro.wildlandlabs.com:90/SYSPROWCFService/Rest"
... ...
lib/syspro/api_operations/request.rb
1 1 module Syspro
2 2 module ApiOperations
3 3 module Request
4   - def request(method, url, params = {}, opts = {})
5   - client = SysproClient.active_client
  4 + module ClassMethods
  5 + def request(method, url, params = {}, opts = {})
  6 + warn_on_opts_in_params(params)
  7 +
  8 + opts = Util.normalize_opts(opts)
  9 + opts[:client] ||= SysproClient.active_client
6 10  
7 11 headers = opts.clone
  12 + user_id = headers.delete(:user_id)
  13 + client = headers.delete(:client)
  14 + # Assume all remaining opts must be headers
8 15  
9 16 resp = client.execute_request(
10 17 method, url,
11 18 headers: headers,
  19 + user_id: user_id,
12 20 params: params
13 21 )
14 22  
15 23 resp
16   - end
  24 + end
17 25  
18   - def warn_on_opts_in_params(params)
  26 + private
  27 +
  28 + def warn_on_opts_in_params(params)
19 29 Util::OPTS_USER_SPECIFIED.each do |opt|
20 30 if params.key?(opt)
21 31 $stderr.puts("WARNING: #{opt} should be in opts instead of params.")
22 32 end
23 33 end
  34 + end
  35 + end # ClassMethods
  36 +
  37 + def self.included(base)
  38 + base.extend(ClassMethods)
  39 + end
  40 +
  41 + protected
  42 +
  43 + def request(method, url, params = {}, opts = {})
  44 + opts = @opts.merge(Util.normalize_opts(opts))
  45 + self.class.request(method, url, params, opts)
24 46 end
25 47 end
26 48 end
... ...
lib/syspro/api_resource.rb
1   -require_relative "syspro_client"
2   -require_relative "api_operations/request"
  1 +require "syspro/syspro_object"
  2 +require "syspro/api_operations/request"
3 3  
4 4 module Syspro
5   - class ApiResource < Syspro::SysproClient
  5 + class ApiResource < SysproObject
  6 + include Syspro::ApiOperations::Request
6 7  
7 8 def self.class_name
8 9 name.split("::")[-1]
9 10 end
10 11  
11 12 def self.resource_url
12   - if self == APIResource
  13 + if self == ApiResource
13 14 raise NotImplementedError, "APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)"
14 15 end
15   - "/v1/#{CGI.escape(class_name.downcase)}s"
  16 + "/#{CGI.escape(class_name.downcase)}"
16 17 end
17 18  
18 19 def resource_url
19   - unless (id = self["id"])
20   - raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", "id")
21   - end
22   - "#{self.class.resource_url}/#{CGI.escape(id)}"
  20 + #unless (id = self["id"])
  21 + #raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", "id")
  22 + #end
  23 + #"#{self.class.resource_url}/#{CGI.escape(id)}"
23 24 end
24 25  
25 26 def refresh
... ...
lib/syspro/api_operations/get_version.rb renamed to lib/syspro/get_version.rb
1   -require_relative "request"
2   -
3 1 module Syspro
4   - module ApiOperations
5   - class GetVersion
6   - include ApiOperations::Request
7   -
8   - def get_version
9   - resp = self.request(:get, resource_url)
10   - version = VersionObject.new(resp[0].http_body)
11   - end
12   -
13   - def resource_url
14   - "/GetVersion"
15   - end
  2 + class GetVersion < ApiResource
  3 + def self.get_version
  4 + resp = self.request(:get, resource_url)
  5 + version = VersionObject.new(resp[0].http_body)
  6 + end
16 7  
17   - VersionObject = Struct.new(:version)
  8 + def resource_url
  9 + "/GetVersion"
18 10 end
  11 +
  12 + VersionObject = Struct.new(:version)
19 13 end
20 14 end
21 15  
... ...
lib/syspro/logoff.rb 0 โ†’ 100644
  1 +module Syspro
  2 + class Logoff < ApiResource
  3 + def self.logoff(user_id)
  4 + params = { "UserId" => user_id }
  5 + resp = self.request(:get, resource_url, params)
  6 +
  7 + if resp[0].http_body == "0"
  8 + true
  9 + else
  10 + resp[0].http_body
  11 + end
  12 + end
  13 +
  14 + def resource_url
  15 + "/Logoff"
  16 + end
  17 + end
  18 +end
... ...
lib/syspro/api_operations/logon.rb renamed to lib/syspro/logon.rb
1   -require_relative "request"
2   -
3 1 module Syspro
4   - module ApiOperations
5   - class Logon
6   - include ApiOperations::Request
7   -
8   - def logon(username, password, company_id, company_password = nil)
9   - params = {
10   - "Operator" => username,
11   - "OperatorPassword" => password,
12   - "CompanyId" => company_id,
13   - "CompanyPassword" => company_password
14   - }
15   - resp = self.request(:get, resource_url, params)
16   - user_id = UserIdObject.new(resp[0].http_body)
17   - end
18   -
19   - def resource_url
20   - "/Logon"
21   - end
  2 + class Logon < ApiResource
  3 + def self.logon(username, password, company_id, company_password = nil)
  4 + params = {
  5 + "Operator" => username,
  6 + "OperatorPassword" => password,
  7 + "CompanyId" => company_id,
  8 + "CompanyPassword" => company_password
  9 + }
  10 + resp = self.request(:get, resource_url, params)
  11 + user_id = UserIdObject.new(resp[0].http_body)
  12 + end
22 13  
23   - UserIdObject = Struct.new(:guid)
  14 + def resource_url
  15 + "/Logon"
24 16 end
  17 +
  18 + UserIdObject = Struct.new(:guid)
25 19 end
26 20 end
27 21  
... ...
lib/syspro/singleton_api_resource.rb
... ... @@ -6,7 +6,7 @@ module Syspro
6 6 if self == SingletonAPIResource
7 7 raise NotImplementedError, "SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Customer, etc.)"
8 8 end
9   - "/v1/#{CGI.escape(class_name.downcase)}"
  9 + "/#{CGI.escape(class_name.downcase)}"
10 10 end
11 11  
12 12 def resource_url
... ...
lib/syspro/syspro_client.rb
... ... @@ -58,8 +58,27 @@ module Syspro
58 58 end
59 59 end
60 60  
61   - def execute_request(method, path, api_base: nil, headers: {}, params: {})
  61 + # Executes the API call within the given block. Usage looks like:
  62 + #
  63 + # client = StripeClient.new
  64 + # charge, resp = client.request { Charge.create }
  65 + #
  66 + def request
  67 + @last_response = nil
  68 + old_stripe_client = Thread.current[:stripe_client]
  69 + Thread.current[:stripe_client] = self
  70 +
  71 + begin
  72 + res = yield
  73 + [res, @last_response]
  74 + ensure
  75 + Thread.current[:stripe_client] = old_stripe_client
  76 + end
  77 + end
  78 +
  79 + def execute_request(method, path, user_id: nil, api_base: nil, headers: {}, params: {})
62 80 api_base ||= Syspro.api_base
  81 + user_id ||= ""
63 82  
64 83 params = Util.objects_to_ids(params)
65 84 url = api_url(path, api_base)
... ... @@ -87,6 +106,7 @@ module Syspro
87 106 context.body = body
88 107 context.method = method
89 108 context.path = path
  109 + context.user_id = user_id
90 110 context.query_params = query_params ? Util.encode_parameters(query_params) : nil
91 111  
92 112 http_resp = execute_request_with_rescues(api_base, context) do
... ... @@ -243,6 +263,7 @@ module Syspro
243 263 attr_accessor :path
244 264 attr_accessor :query_params
245 265 attr_accessor :request_id
  266 + attr_accessor :user_id
246 267  
247 268 # The idea with this method is that we might want to update some of
248 269 # context information because a response that we've received from the API
... ...
lib/syspro/syspro_object.rb
1 1 module Syspro
2 2 class SysproObject
  3 + include Enumerable
3 4  
4   - # Re-initializes the object based on a hash of values (usually one that's
5   - # come back from an API call). Adds or removes value accessors as necessary
6   - # and updates the state of internal data.
7   - #
8   - # Protected on purpose! Please do not expose.
9   - #
10   - # ==== Options
11   - #
12   - # * +:values:+ Hash used to update accessors and values.
13   - # * +:opts:+ Options for SysproObject like an API key.
14   - # * +:partial:+ Indicates that the re-initialization should not attempt to
15   - # remove accessors.
16   - def initialize_from(values, opts, partial = false)
  5 + def initialize(id = nil, opts = {})
17 6 @opts = Util.normalize_opts(opts)
  7 + end
18 8  
19   - # the `#send` is here so that we can keep this method private
20   - @original_values = self.class.send(:deep_copy, values)
21   -
22   - removed = partial ? Set.new : Set.new(@values.keys - values.keys)
23   - added = Set.new(values.keys - @values.keys)
24   -
25   - # Wipe old state before setting new. This is useful for e.g. updating a
26   - # customer, where there is no persistent card parameter. Mark those values
27   - # which don't persist as transient
28   -
29   - remove_accessors(removed)
30   - add_accessors(added, values)
  9 + # Determines the equality of two Syspro objects. Syspro objects are
  10 + # considered to be equal if they have the same set of values and each one
  11 + # of those values is the same.
  12 + def ==(other)
  13 + other.is_a?(SysproObject) && @values == other.instance_variable_get(:@values)
  14 + end
31 15  
32   - removed.each do |k|
33   - @values.delete(k)
34   - @transient_values.add(k)
35   - @unsaved_values.delete(k)
36   - end
  16 + def to_s(*_args)
  17 + JSON.pretty_generate(to_hash)
  18 + end
37 19  
38   - update_attributes(values, opts, dirty: false)
39   - values.each_key do |k|
40   - @transient_values.delete(k)
41   - @unsaved_values.delete(k)
42   - end
  20 + def inspect
  21 + id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ""
  22 + "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(@values)
  23 + end
43 24  
44   - self
  25 + def keys
  26 + @values.keys
45 27 end
46 28  
47   - def remove_accessors(keys)
48   - # not available in the #instance_eval below
49   - protected_fields = self.class.protected_fields
  29 + def values
  30 + @values.values
  31 + end
50 32  
51   - metaclass.instance_eval do
52   - keys.each do |k|
53   - next if protected_fields.include?(k)
54   - next if @@permanent_attributes.include?(k)
  33 + def to_hash
  34 + maybe_to_hash = lambda do |value|
  35 + value && value.respond_to?(:to_hash) ? value.to_hash : value
  36 + end
55 37  
56   - # Remove methods for the accessor's reader and writer.
57   - [k, :"#{k}=", :"#{k}?"].each do |method_name|
58   - remove_method(method_name) if method_defined?(method_name)
59   - end
60   - end
  38 + @values.each_with_object({}) do |(key, value), acc|
  39 + acc[key] = case value
  40 + when Array
  41 + value.map(&maybe_to_hash)
  42 + else
  43 + maybe_to_hash.call(value)
  44 + end
61 45 end
62 46 end
63 47  
64   - def add_accessors(keys, values)
65   - # not available in the #instance_eval below
66   - protected_fields = self.class.protected_fields
67   -
68   - metaclass.instance_eval do
69   - keys.each do |k|
70   - next if protected_fields.include?(k)
71   - next if @@permanent_attributes.include?(k)
72   -
73   - if k == :method
74   - # Object#method is a built-in Ruby method that accepts a symbol
75   - # and returns the corresponding Method object. Because the API may
76   - # also use `method` as a field name, we check the arity of *args
77   - # to decide whether to act as a getter or call the parent method.
78   - define_method(k) { |*args| args.empty? ? @values[k] : super(*args) }
79   - else
80   - define_method(k) { @values[k] }
81   - end
82   -
83   - define_method(:"#{k}=") do |v|
84   - if v == ""
85   - raise ArgumentError, "You cannot set #{k} to an empty string. " \
86   - "We interpret empty strings as nil in requests. " \
87   - "You may set (object).#{k} = nil to delete the property."
88   - end
89   - @values[k] = Util.convert_to_stripe_object(v, @opts)
90   - dirty_value!(@values[k])
91   - @unsaved_values.add(k)
92   - end
  48 + def each(&blk)
  49 + @values.each(&blk)
  50 + end
93 51  
94   - if [FalseClass, TrueClass].include?(values[k].class)
95   - define_method(:"#{k}?") { @values[k] }
96   - end
  52 + private
  53 +
  54 + # Produces a deep copy of the given object including support for arrays,
  55 + # hashes, and SysproObject.
  56 + def self.deep_copy(obj)
  57 + case obj
  58 + when Array
  59 + obj.map { |e| deep_copy(e) }
  60 + when Hash
  61 + obj.each_with_object({}) do |(k, v), copy|
  62 + copy[k] = deep_copy(v)
  63 + copy
97 64 end
  65 + when SysproObject
  66 + obj.class.construct_from(
  67 + deep_copy(obj.instance_variable_get(:@values)),
  68 + obj.instance_variable_get(:@opts).select do |k, _v|
  69 + Util::OPTS_COPYABLE.include?(k)
  70 + end
  71 + )
  72 + else
  73 + obj
98 74 end
99 75 end
  76 + private_class_method :deep_copy
  77 +
100 78 end
101 79 end
... ...