active_zuora 1.5.2 → 2.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.
Files changed (78) hide show
  1. data/Gemfile +4 -0
  2. data/Gemfile.lock +57 -0
  3. data/README.md +155 -0
  4. data/Rakefile +6 -22
  5. data/TODO.md +2 -0
  6. data/active_zuora.gemspec +25 -68
  7. data/lib/active_zuora.rb +37 -6
  8. data/lib/active_zuora/amend.rb +43 -0
  9. data/lib/active_zuora/base.rb +73 -0
  10. data/lib/active_zuora/belongs_to_associations.rb +56 -0
  11. data/lib/active_zuora/connection.rb +41 -0
  12. data/lib/active_zuora/fields.rb +122 -0
  13. data/lib/active_zuora/fields/array_field_decorator.rb +28 -0
  14. data/lib/active_zuora/fields/boolean_field.rb +12 -0
  15. data/lib/active_zuora/fields/date_time_field.rb +19 -0
  16. data/lib/active_zuora/fields/decimal_field.rb +12 -0
  17. data/lib/active_zuora/fields/field.rb +76 -0
  18. data/lib/active_zuora/fields/integer_field.rb +11 -0
  19. data/lib/active_zuora/fields/object_field.rb +31 -0
  20. data/lib/active_zuora/fields/string_field.rb +11 -0
  21. data/lib/active_zuora/generate.rb +43 -0
  22. data/lib/active_zuora/generator.rb +220 -0
  23. data/lib/active_zuora/has_many_associations.rb +37 -0
  24. data/lib/active_zuora/has_many_proxy.rb +50 -0
  25. data/lib/active_zuora/persistence.rb +176 -0
  26. data/lib/active_zuora/relation.rb +253 -0
  27. data/lib/active_zuora/scoping.rb +50 -0
  28. data/lib/active_zuora/subscribe.rb +42 -0
  29. data/lib/active_zuora/version.rb +3 -0
  30. data/lib/active_zuora/z_object.rb +21 -0
  31. data/spec/belongs_to_associations_spec.rb +35 -0
  32. data/spec/has_many_integration_spec.rb +53 -0
  33. data/spec/spec_helper.rb +21 -0
  34. data/spec/subscribe_integration_spec.rb +218 -0
  35. data/spec/zobject_integration_spec.rb +104 -0
  36. data/wsdl/zuora.wsdl +1548 -0
  37. metadata +128 -72
  38. checksums.yaml +0 -7
  39. data/LICENSE +0 -202
  40. data/README.rdoc +0 -36
  41. data/VERSION +0 -1
  42. data/custom_fields.yml +0 -17
  43. data/lib/active_zuora/account.rb +0 -31
  44. data/lib/active_zuora/amendment.rb +0 -7
  45. data/lib/active_zuora/bill_run.rb +0 -4
  46. data/lib/active_zuora/contact.rb +0 -7
  47. data/lib/active_zuora/invoice.rb +0 -46
  48. data/lib/active_zuora/invoice_item.rb +0 -26
  49. data/lib/active_zuora/invoice_item_adjustment.rb +0 -4
  50. data/lib/active_zuora/invoice_payment.rb +0 -11
  51. data/lib/active_zuora/payment.rb +0 -18
  52. data/lib/active_zuora/payment_method.rb +0 -10
  53. data/lib/active_zuora/product.rb +0 -4
  54. data/lib/active_zuora/product_rate_plan.rb +0 -9
  55. data/lib/active_zuora/product_rate_plan_charge.rb +0 -11
  56. data/lib/active_zuora/product_rate_plan_charge_tier.rb +0 -7
  57. data/lib/active_zuora/product_rate_plan_charge_tier_data.rb +0 -4
  58. data/lib/active_zuora/rate_plan.rb +0 -16
  59. data/lib/active_zuora/rate_plan_charge.rb +0 -71
  60. data/lib/active_zuora/rate_plan_charge_data.rb +0 -4
  61. data/lib/active_zuora/rate_plan_charge_tier.rb +0 -4
  62. data/lib/active_zuora/rate_plan_data.rb +0 -4
  63. data/lib/active_zuora/refund.rb +0 -4
  64. data/lib/active_zuora/subscribe_options.rb +0 -4
  65. data/lib/active_zuora/subscribe_request.rb +0 -13
  66. data/lib/active_zuora/subscribe_with_existing_account_request.rb +0 -4
  67. data/lib/active_zuora/subscription.rb +0 -17
  68. data/lib/active_zuora/subscription_data.rb +0 -4
  69. data/lib/active_zuora/usage.rb +0 -4
  70. data/lib/active_zuora/zobject.rb +0 -154
  71. data/lib/soap/property +0 -1
  72. data/lib/zuora/ZUORA.rb +0 -1560
  73. data/lib/zuora/ZUORADriver.rb +0 -145
  74. data/lib/zuora/ZUORAMappingRegistry.rb +0 -1709
  75. data/lib/zuora/ZuoraServiceClient.rb +0 -124
  76. data/lib/zuora/api.rb +0 -18
  77. data/lib/zuora_client.rb +0 -191
  78. data/lib/zuora_interface.rb +0 -215
@@ -0,0 +1,56 @@
1
+ module ActiveZuora
2
+ module BelongsToAssociations
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ def belongs_to(item, options={})
9
+ class_name = options[:class_name] || nested_class_name(item.to_s.camelize)
10
+ foreign_key = options[:foreign_key] || :"#{item}_id"
11
+ # Add the field if it doesn't already exist.
12
+ field foreign_key, :string unless field? foreign_key
13
+ ivar = "@#{item}"
14
+ loaded_ivar = "@#{item}_loaded"
15
+ # Define the methods on an included module, so we can override
16
+ # them using super.
17
+ generated_attribute_methods.module_eval do
18
+ define_method(item) do
19
+ # Return the object if it was already loaded.
20
+ if instance_variable_get(loaded_ivar)
21
+ return instance_variable_get(ivar)
22
+ else
23
+ # Otherwise find it.
24
+ record = class_name.constantize.find self.send(foreign_key)
25
+ send("#{item}=", record)
26
+ record
27
+ end
28
+ end
29
+ define_method("#{item}=") do |record|
30
+ instance_variable_set(loaded_ivar, true)
31
+ instance_variable_set(ivar, record)
32
+ # Set the foreign key id attribute as well.
33
+ write_attribute(foreign_key, record.try(:id))
34
+ record
35
+ end
36
+ redefine_method("#{foreign_key}=") do |item_id|
37
+ item_id = write_attribute(foreign_key, item_id)
38
+ # Unload the object if the id is different.
39
+ if send("#{item}_loaded?") && send(item).id != item_id
40
+ instance_variable_set(loaded_ivar, false)
41
+ end
42
+ item_id
43
+ end
44
+ define_method("#{item}_loaded?") do
45
+ instance_variable_get(loaded_ivar) || false
46
+ end
47
+ define_method("reload_#{item}") do
48
+ instance_variable_set(loaded_ivar, false)
49
+ send(item)
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveZuora
2
+ class Connection
3
+
4
+ attr_reader :soap_client
5
+
6
+ WSDL = File.expand_path('../../../wsdl/zuora.wsdl', __FILE__)
7
+
8
+ def initialize(configuration={})
9
+ # Store login credentials and create SOAP client.
10
+ @username = configuration[:username]
11
+ @password = configuration[:password]
12
+ @session_timeout = configuration[:session_timeout] || 15.minutes
13
+ @soap_client = Savon::Client.new do
14
+ wsdl.document = configuration[:wsdl] || WSDL
15
+ http.proxy = configuration[:http_proxy] if configuration[:http_proxy]
16
+ end
17
+ end
18
+
19
+ def login
20
+ # Returns a session_id upon success, raises an exception on failure.
21
+ # Instance variables aren't available within the soap request block.
22
+ body = { :username => @username, :password => @password }
23
+ @soap_client.request(:login){ soap.body = body }[:login_response][:result][:session]
24
+ end
25
+
26
+ def request(*args, &block)
27
+ # instance variables aren't available within the soap request block for some reason.
28
+ header = { 'SessionHeader' => { 'session' => @session_id } }
29
+ @soap_client.request(*args) do
30
+ soap.header = header
31
+ yield(soap)
32
+ end
33
+ rescue Savon::SOAP::Fault => exception
34
+ # Catch invalid sessions, and re-issue the request.
35
+ raise unless exception.message =~ /INVALID_SESSION/
36
+ @session_id = login
37
+ request(*args, &block)
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,122 @@
1
+ require 'active_zuora/fields/field'
2
+ require 'active_zuora/fields/boolean_field'
3
+ require 'active_zuora/fields/date_time_field'
4
+ require 'active_zuora/fields/decimal_field'
5
+ require 'active_zuora/fields/integer_field'
6
+ require 'active_zuora/fields/object_field'
7
+ require 'active_zuora/fields/string_field'
8
+ require 'active_zuora/fields/array_field_decorator'
9
+
10
+ module ActiveZuora
11
+ module Fields
12
+
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ include ActiveModel::Dirty
17
+ delegate :fields, :field_names, :field?, :get_field, :get_field!,
18
+ :default_attributes, :to => 'self.class'
19
+ end
20
+
21
+ def initialize(attributes={})
22
+ # Start with defaults, and override those with the given attributes.
23
+ self.attributes = default_attributes.merge(attributes)
24
+ end
25
+
26
+ def attributes
27
+ # A requirement of ActiveModel::Attributes.
28
+ # Hash must use string keys.
29
+ attributes = {}
30
+ fields.each { |field| attributes[field.name] = send(field.name) }
31
+ attributes
32
+ end
33
+
34
+ def attributes=(attributes)
35
+ attributes.each { |key, value| send("#{key}=", value) }
36
+ end
37
+
38
+ def untracked_attributes=(attributes)
39
+ # Loads attributes without tracking dirt.
40
+ self.attributes = attributes
41
+ clear_changed_attributes
42
+ attributes
43
+ end
44
+
45
+ def write_attribute(name, value)
46
+ field = get_field!(name)
47
+ value = field.type_cast(value)
48
+ attribute_will_change!(name) if value != send(name)
49
+ instance_variable_set("@#{name}", value)
50
+ value
51
+ end
52
+
53
+ def clear_changed_attributes
54
+ changed_attributes.clear
55
+ # If we have any fields that are also Base objects,
56
+ # clear their attributes as well.
57
+ fields.each { |field| field.clear_changed_attributes(send(field.name)) }
58
+ end
59
+
60
+ module ClassMethods
61
+
62
+ def fields_by_name
63
+ # { :field_name_symbol => field_object }
64
+ @fields ||= {}
65
+ end
66
+
67
+ def fields
68
+ fields_by_name.values
69
+ end
70
+
71
+ def field_names
72
+ fields_by_name.keys
73
+ end
74
+
75
+ def field?(name)
76
+ fields_by_name.key?(name.to_sym)
77
+ end
78
+
79
+ def add_field(name, field)
80
+ fields_by_name[name.to_sym] = field
81
+ # Define the setters, getters, and changed helpers.
82
+ field.define_instance_methods(self)
83
+ end
84
+
85
+ def get_field(name)
86
+ fields_by_name[name.to_sym]
87
+ end
88
+
89
+ def get_field!(name)
90
+ get_field(name) || raise(ArgumentError.new("No field in #{self} named #{name}"))
91
+ end
92
+
93
+ def field(name, type, options={})
94
+ # Check if this field should be an array, don't pass
95
+ # this option down to the field.
96
+ field_is_array = options.delete(:array) || false
97
+ # Create and register the field.
98
+ field = case type
99
+ when :string then StringField.new(name, namespace, options)
100
+ when :boolean then BooleanField.new(name, namespace, options)
101
+ when :integer then IntegerField.new(name, namespace, options)
102
+ when :decimal then DecimalField.new(name, namespace, options)
103
+ when :datetime then DateTimeField.new(name, namespace, options)
104
+ when :object
105
+ class_name = options[:class_name] || nested_class_name(name.to_s.camelize)
106
+ ObjectField.new(name, namespace, class_name, options)
107
+ else
108
+ ArgumentError.new "Unknown field type: #{type}"
109
+ end
110
+ field = ArrayFieldDecorator.new(field) if field_is_array
111
+ add_field(name, field)
112
+ end
113
+
114
+ def default_attributes
115
+ default_attributes = {}
116
+ fields.each { |field| default_attributes[field.name] = field.default }
117
+ default_attributes
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveZuora
2
+ class ArrayFieldDecorator < SimpleDelegator
3
+
4
+ # Wraps a Field object and typecasts/builds
5
+ # item as an array of the given field.
6
+
7
+ def type_cast(values)
8
+ # Force into an array and run type_cast on each element.
9
+ [values].flatten.compact.map { |value| __getobj__.type_cast(value) }
10
+ end
11
+
12
+ def build_xml(xml, soap, values, options={})
13
+ # It may be wierd that we're mapping and taking the last value,
14
+ # But there's an issue with xml builder where if a block
15
+ # returns an array, it will output the array in the XML.
16
+ # So instead, we'll have our block return the value
17
+ # of the last build_xml call.
18
+ [values].flatten.compact.map do |value|
19
+ __getobj__.build_xml(xml, soap, value, options)
20
+ end.last
21
+ end
22
+
23
+ def clear_changed_attributes(values)
24
+ [values].flatten.compact.map { |value| __getobj__.clear_changed_attributes(value) }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveZuora
2
+ class BooleanField < Field
3
+
4
+ def type_cast(value)
5
+ return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
6
+ return true if value.to_s.downcase == 'true'
7
+ return false if value.to_s.downcase == 'false'
8
+ default
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveZuora
2
+ class DateTimeField < Field
3
+
4
+ def type_cast(value)
5
+ return value if value.nil? || value.is_a?(Date)
6
+ return value.to_datetime if value.is_a?(Time)
7
+ value.to_datetime rescue default
8
+ end
9
+
10
+ def build_xml(xml, soap, value, options={})
11
+ # All dates need to be in PST time. Since all user-set attributes
12
+ # in Zuora are really only dates, we'll chop off the time.
13
+ # 2012-05-22T00:00:00-08:00
14
+ value = value ? value.strftime("%Y-%m-%dT00:00:00-08:00") : ''
15
+ super(xml, soap, value, options)
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveZuora
2
+ class DecimalField < Field
3
+
4
+ def type_cast(value)
5
+ return value if value.nil? || value.is_a?(BigDecimal)
6
+ return default if value.blank?
7
+ return value.to_d if value.respond_to?(:to_d)
8
+ default
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,76 @@
1
+ module ActiveZuora
2
+ class Field
3
+
4
+ attr_accessor :name, :zuora_name, :default, :namespace
5
+
6
+ def initialize(name, namespace, options={})
7
+ @name = name.to_s
8
+ @namespace = namespace
9
+ @zuora_name = options[:zuora_name] || @name.camelize
10
+ @default = options[:default]
11
+ end
12
+
13
+ def type_cast(value)
14
+ value
15
+ end
16
+
17
+ def define_instance_methods(zuora_class)
18
+ # We store this into a variable so we can use it in our
19
+ # class eval below.
20
+ field_name = name
21
+ # Add dirty helpers
22
+ # The value will still be stored in an instance
23
+ # variable with the name of the field.
24
+ # But we'll define extra writer methods so that we
25
+ # can handle any input from savon.
26
+ # Savon just calls underscore on the element names,
27
+ # but our fields allow for any combination
28
+ # of field name and Zuora name.
29
+ # This is especially useful for custom fields which
30
+ # are named like CustomField__c. You might choose
31
+ # to make this field :custom_field, and therefore
32
+ # we'll need a writer for :custom_field, :custom_field_c,
33
+ # and I threw in a :CustomField__c just for good measure.
34
+ writers = [field_name, zuora_name, zuora_name.underscore].uniq
35
+ zuora_class.class_eval do
36
+ # Define the methods on an included module, so we can override
37
+ # them using super.
38
+ generated_attribute_methods.module_eval do
39
+ # Getter
40
+ attr_reader field_name
41
+ # Boolean check.
42
+ define_method "#{field_name}?" do
43
+ !!send(field_name)
44
+ end
45
+ # Writers
46
+ writers.each do |writer_name|
47
+ define_method "#{writer_name}=" do |value|
48
+ write_attribute(field_name, value)
49
+ end
50
+ end
51
+ end
52
+ # Dirty attribute helpers.
53
+ define_attribute_methods [field_name]
54
+ end
55
+ end
56
+
57
+ def build_xml(xml, soap, value, options={})
58
+ qualifier = soap.namespace_by_uri(namespace)
59
+ nil_strategy = options[:nil_strategy] || :omit
60
+ # The extra qualifiers attribute needs to be passed in
61
+ # in case the field is another complexType that needs
62
+ # to be namespaced.
63
+ if !value.nil? || nil_strategy == :whitespace
64
+ xml.tag!(qualifier, zuora_name.to_sym, value.to_s)
65
+ elsif nil_strategy == :fields_to_null
66
+ xml.tag!(qualifier, :fieldsToNull, zuora_name.to_sym)
67
+ end
68
+ end
69
+
70
+ def clear_changed_attributes(value)
71
+ # If the value of this field has attribute changes to clear,
72
+ # override this function.
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveZuora
2
+ class IntegerField < Field
3
+
4
+ def type_cast(value)
5
+ return value if value.nil? || value.is_a?(Integer)
6
+ return value.to_i if value.respond_to?(:to_i)
7
+ default
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveZuora
2
+ class ObjectField < Field
3
+
4
+ # A field that is itself another Zuora complex type.
5
+ # Hashes will automatically be converted to an instance of the given class.
6
+
7
+ attr_accessor :class_name
8
+
9
+ def initialize(name, namespace, class_name, options={})
10
+ @class_name = class_name
11
+ super(name, namespace, options)
12
+ end
13
+
14
+ def type_cast(value)
15
+ if value.is_a?(Hash)
16
+ value = class_name.constantize.new(value)
17
+ end
18
+ value
19
+ end
20
+
21
+ def build_xml(xml, soap, value, options={})
22
+ # For complex types, simply omit it if it's nil.
23
+ value.build_xml(xml, soap, :namespace => namespace, :element_name => zuora_name) if value
24
+ end
25
+
26
+ def clear_changed_attributes(value)
27
+ value.clear_changed_attributes if value
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveZuora
2
+ class StringField < Field
3
+
4
+ def type_cast(value)
5
+ return value if value.nil? || value.is_a?(String)
6
+ return value.to_s if value.respond_to?(:to_s)
7
+ default
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveZuora
2
+ module Generate
3
+
4
+ # This is meant to be included onto an Invoice class.
5
+ # Returns true/false on success.
6
+ # Result hash is stored in #result.
7
+ # If success, the id will be set in the object.
8
+ # If failure, errors will be present on object.
9
+
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include Base
14
+ attr_accessor :result
15
+ end
16
+
17
+ def generate
18
+ self.result = self.class.connection.request(:generate) do |soap|
19
+ soap.body do |xml|
20
+ build_xml(xml, soap,
21
+ :namespace => soap.namespace,
22
+ :element_name => :zObjects,
23
+ :force_type => true)
24
+ end
25
+ end[:generate_response][:result]
26
+
27
+ if result[:success]
28
+ self.id = result[:id]
29
+ self.status = 'Draft'
30
+ clear_changed_attributes
31
+ true
32
+ else
33
+ add_zuora_errors(result[:errors])
34
+ false
35
+ end
36
+ end
37
+
38
+ def generate!
39
+ raise "Could not generate: #{errors.full_messages.join ', '}" unless generate
40
+ end
41
+
42
+ end
43
+ end