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,220 @@
1
+ module ActiveZuora
2
+ class Generator
3
+
4
+ attr_reader :document, :classes
5
+
6
+ def initialize(document, options={})
7
+ # document is a parsed wsdl document.
8
+ @document = document
9
+ @classes = []
10
+ @class_nesting = options[:inside] || ActiveZuora
11
+ end
12
+
13
+ def generate_classes
14
+
15
+ # Defines the classes based on the wsdl document.
16
+ # Assumes the following namespaces in the wsdl.
17
+ # xmlns="http://schemas.xmlsoap.org/wsdl/"
18
+ # xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
19
+ # xmlns:xs="http://www.w3.org/2001/XMLSchema"
20
+ # xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
21
+ # xmlns:zns="http://api.zuora.com/"
22
+ # xmlns:ons="http://object.api.zuora.com/"
23
+ # xmlns:fns="http://fault.api.zuora.com/"
24
+ @document.xpath('.//xs:schema[@targetNamespace]').each do |schema|
25
+ namespace = schema.attribute("targetNamespace").value
26
+
27
+ schema.xpath('.//xs:complexType[@name]').each do |complex_type|
28
+ class_name = complex_type.attribute("name").value
29
+ # Skip the zObject base class, we define our own.
30
+ next if class_name == "zObject"
31
+
32
+ zuora_class = Class.new
33
+ @class_nesting.const_set(class_name, zuora_class)
34
+ @classes << zuora_class
35
+
36
+ # Include the Base module for adding fields.
37
+ zuora_class.send :include, Base
38
+ zuora_class.namespace = namespace
39
+
40
+ # If it's a zObject, include that module as well.
41
+ if complex_type.xpath(".//xs:extension[@base='ons:zObject']").any?
42
+ zuora_class.send :include, ZObject
43
+ end
44
+
45
+ # Define the fields
46
+ complex_type.xpath('.//xs:element[@name][@type]').each do |element|
47
+ # attributes: name, type, nillable, minoccurs, maxoccurs
48
+ zuora_name = element.attribute("name").value
49
+ field_name = zuora_name.underscore
50
+ field_type = element.attribute("type").value
51
+ is_array = element_is_an_array?(element)
52
+
53
+ case field_type
54
+ when "string", "xs:string", "zns:ID", "xs:base64Binary"
55
+ zuora_class.field field_name, :string,
56
+ :zuora_name => zuora_name, :array => is_array
57
+ when "boolean", "xs:boolean"
58
+ zuora_class.field field_name, :boolean,
59
+ :zuora_name => zuora_name, :array => is_array
60
+ when "int", "short", "long", "xs:int"
61
+ zuora_class.field field_name, :integer,
62
+ :zuora_name => zuora_name, :array => is_array
63
+ when "decimal"
64
+ zuora_class.field field_name, :decimal,
65
+ :zuora_name => zuora_name, :array => is_array
66
+ when "dateTime"
67
+ zuora_class.field field_name, :datetime,
68
+ :zuora_name => zuora_name, :array => is_array
69
+ when /\A(zns:|ons:)/
70
+ zuora_class.field field_name, :object,
71
+ :zuora_name => zuora_name, :array => is_array,
72
+ :class_name => zuora_class.nested_class_name(field_type.split(':').last)
73
+ else
74
+ puts "Unkown field type: #{field_type}"
75
+ end
76
+ end # each element
77
+
78
+ end # each complexType
79
+ end # each schema
80
+
81
+ add_obvious_associations
82
+ add_extra_customizations
83
+ end
84
+
85
+ private
86
+
87
+ def add_obvious_associations
88
+ # When a zuora class has a field called InvoiceId, it's a safe
89
+ # assuption that it references the an Invoice.
90
+ # Build those associations automatically.
91
+ @classes.each do |zuora_class|
92
+ zuora_class.fields.each do |field|
93
+ # If it looks like an Id field and the name
94
+ # matches a generated ZObject class
95
+ if match = field.zuora_name.match(/\A(.+?)Id\Z/)
96
+ if zobject_class = zobject_class_with_name(match[1])
97
+ # Add a belongs to relationship.
98
+ zuora_class.belongs_to zobject_class.zuora_object_name.underscore
99
+ # If the current class is also a ZObject, add a has_many
100
+ # to the referenced class.
101
+ if zuora_class < ZObject
102
+ zobject_class.has_many zuora_class.zuora_object_name.underscore.pluralize
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def add_extra_customizations
111
+ # We can't know everything from the wsdl, like which fields are
112
+ # usable in queries. This function does some final customizations
113
+ # based on the existing Zuora documentation.
114
+ # Also, it's possible classes customized here weren't defined
115
+ # in your wsdl, so it will only customize them if they are defined.
116
+
117
+ nesting = @class_nesting
118
+
119
+ customize 'Account' do
120
+ belongs_to :bill_to, :class_name => nested_class_name('Contact') if field? :bill_to
121
+ if field? :parent_id
122
+ belongs_to :parent, :class_name => nested_class_name('Account')
123
+ has_many :children, :class_name => nested_class_name('Account'), :foreign_key => :parent_id, :inverse_of => :parent
124
+ end
125
+ belongs_to :sold_to, :class_name => nested_class_name('Contact') if field? :sold_to
126
+ validates :currency, :presence => true if field? :currency
127
+ validates :name, :presence => true if field? :name
128
+ validates :status, :presence => true if field? :status
129
+ end
130
+
131
+ customize 'Amendment' do
132
+ exclude_from_queries :rate_plan_data,
133
+ :destination_account_id, :destination_invoice_owner_id
134
+ end
135
+
136
+ customize 'AmendRequest' do
137
+ include Amend
138
+ end
139
+
140
+ customize 'Import' do
141
+ exclude_from_queries :file_content
142
+ end
143
+
144
+ customize 'Invoice' do
145
+ include Generate
146
+ exclude_from_queries :regenerate_invoice_pdf
147
+ end
148
+
149
+ customize 'InvoiceItemAdjustment' do
150
+ exclude_from_queries :customer_name, :customer_number
151
+ end
152
+
153
+ customize 'Payment' do
154
+ exclude_from_queries :applied_invoice_amount,
155
+ :gateway_option_data, :invoice_id, :invoice_number
156
+ end
157
+
158
+ customize 'PaymentMethod' do
159
+ exclude_from_queries :ach_account_number, :credit_card_number,
160
+ :credit_card_security_code, :gateway_option_data, :skip_validation
161
+ end
162
+
163
+ customize 'ProductRatePlanCharge' do
164
+ exclude_from_queries :product_rate_plan_charge_tier_data
165
+ end
166
+
167
+ customize 'Usage' do
168
+ exclude_from_queries :ancestor_account_id, :invoice_id, :invoice_number
169
+ end
170
+
171
+ customize 'RatePlanCharge' do
172
+ exclude_from_queries :rollover_balance
173
+ # Can only use overageprice or price or includedunits or
174
+ # discountamount or discountpercentage in one query.
175
+ # We'll pick price.
176
+ exclude_from_queries :overage_price, :included_units,
177
+ :discount_amount, :discount_percentage
178
+ end
179
+
180
+ customize 'Refund' do
181
+ exclude_from_queries :gateway_option_data, :payment_id
182
+ end
183
+
184
+ customize 'Subscription' do
185
+ exclude_from_queries :ancestor_account_id
186
+ belongs_to :creator_account, :class_name => nested_class_name('Account') if field? :creator_account_id
187
+ belongs_to :creator_invoice_owner, :class_name => nested_class_name('Account') if field? :creator_invoice_owner_id
188
+ belongs_to :invoice_owner, :class_name => nested_class_name('Account') if field? :invoice_owner_id
189
+ belongs_to :original, :class_name => name if field? :original_id
190
+ belongs_to :original_subscription, :class_name => name if field? :original_subscription_id
191
+ belongs_to :previous_subscription, :class_name => name if field? :previous_subscription_id
192
+ end
193
+
194
+ customize 'SubscribeRequest' do
195
+ include Subscribe
196
+ end
197
+ end
198
+
199
+ def customize(zuora_class_name, &block)
200
+ if @class_nesting.const_defined?(zuora_class_name)
201
+ @class_nesting.const_get(zuora_class_name).instance_eval(&block)
202
+ end
203
+ end
204
+
205
+
206
+ def element_is_an_array?(element)
207
+ attribute_is_more_than_one?(element.attribute("minOccurs")) ||
208
+ attribute_is_more_than_one?(element.attribute("maxOccurs"))
209
+ end
210
+
211
+ def attribute_is_more_than_one?(attribute)
212
+ attribute && ( attribute.value == "unbounded" || attribute.value.to_i > 1 )
213
+ end
214
+
215
+ def zobject_class_with_name(name)
216
+ @classes.find { |zuora_class| zuora_class.zuora_object_name == name && zuora_class < ZObject }
217
+ end
218
+
219
+ end
220
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveZuora
2
+ module HasManyAssociations
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ def has_many(items, options={})
9
+ class_name = options[:class_name] || nested_class_name(items.to_s.singularize.camelize)
10
+ foreign_key = options[:foreign_key] || :"#{zuora_object_name.underscore}_id"
11
+ conditions = options[:conditions]
12
+ # :order => :name
13
+ # :order => [:name, :desc]
14
+ order_attribute, order_direction = [*options[:order]]
15
+ ivar = "@#{items}"
16
+ # Define the methods on an included module, so we can override
17
+ # them using super.
18
+ generated_attribute_methods.module_eval do
19
+ define_method(items) do
20
+ if instance_variable_get(ivar)
21
+ return instance_variable_get(ivar)
22
+ else
23
+ relation = class_name.constantize.where(foreign_key => self.id)
24
+ relation = relation.merge(conditions) if conditions.present?
25
+ relation.order_attribute = order_attribute if order_attribute.present?
26
+ relation.order_direction = order_direction if order_direction.present?
27
+ proxy = HasManyProxy.new(self, relation, options)
28
+ instance_variable_set(ivar, proxy)
29
+ proxy
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveZuora
2
+ class HasManyProxy
3
+
4
+ # Wraps around the Relation representing a has_many association
5
+ # to add features like inverse_of loading.
6
+
7
+ attr_reader :scope, :owner
8
+
9
+ delegate :"==", :"===", :"=~", :inspect, :to_s, :to => :to_a
10
+
11
+ def initialize(owner, scope, options={})
12
+ @owner, @scope = owner, scope
13
+ # inverse_of by default. You can opt out with :inverse_of => false
14
+ @inverse_of = (options[:inverse_of] || owner.zuora_object_name.underscore) unless options[:inverse_of] == false
15
+ end
16
+
17
+ def to_a
18
+ if @scope.loaded? || !@inverse_of
19
+ @scope.to_a
20
+ else
21
+ @scope.to_a.each { |record| record.send("#{@inverse_of}=", owner) }
22
+ @scope.to_a
23
+ end
24
+ end
25
+
26
+ alias :all :to_a
27
+
28
+ def reload
29
+ # If reload is called directly on the scope, it will reload
30
+ # without our extra functionality, like inverse_of loading.
31
+ @scope.unload
32
+ to_a
33
+ end
34
+
35
+ protected
36
+
37
+ def method_missing(method, *args, &block)
38
+ # If we do anything that needs loading the scope, then we'll load it.
39
+ if Array.method_defined?(method)
40
+ to_a.send(method, *args, &block)
41
+ else
42
+ # Otherwise send all messages to the @scope.
43
+ @scope.send(method, *args, &block)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+
50
+
@@ -0,0 +1,176 @@
1
+ module ActiveZuora
2
+ module Persistence
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ MAX_BATCH_SIZE = 50
7
+
8
+ def new_record?
9
+ id.blank?
10
+ end
11
+
12
+ def save
13
+ new_record? ? create : update
14
+ end
15
+
16
+ def save!
17
+ raise "Could Not Save Zuora Object: #{errors.full_messages.join ', '}" unless save
18
+ end
19
+
20
+ def update_attributes(attributes)
21
+ self.attributes = attributes
22
+ save
23
+ end
24
+
25
+ def update_attributes!(attributes)
26
+ self.attributes = attributes
27
+ save!
28
+ end
29
+
30
+ def delete
31
+ self.class.delete(id) > 0
32
+ end
33
+
34
+ def reload
35
+ raise ArgumentError.new("You can't reload a new record") if new_record?
36
+ self.untracked_attributes = self.class.find(id).attributes
37
+ self
38
+ end
39
+
40
+ def xml_field_names
41
+ # If we're rendering an existing record, always include the id.
42
+ new_record? ? super : ([:id] + super).uniq
43
+ end
44
+
45
+ private
46
+
47
+ def create
48
+ return false unless new_record? && valid?
49
+ result = self.class.connection.request(:create) do |soap|
50
+ soap.body do |xml|
51
+ build_xml(xml, soap,
52
+ :namespace => soap.namespace,
53
+ :element_name => :zObjects,
54
+ :force_type => true)
55
+ end
56
+ end[:create_response][:result]
57
+ if result[:success]
58
+ self.id = result[:id]
59
+ clear_changed_attributes
60
+ true
61
+ else
62
+ errors.add(:base, result[:errors][:message]) if result[:errors]
63
+ false
64
+ end
65
+ end
66
+
67
+ def update
68
+ self.class.update(self)
69
+ self.errors.blank?
70
+ end
71
+
72
+ module ClassMethods
73
+
74
+ def create(attributes={})
75
+ new(attributes).tap(&:save)
76
+ end
77
+
78
+ def create!(attributes={})
79
+ new(attributes).tap(&:save!)
80
+ end
81
+
82
+ # Takes an array of zobjects and batch saves new and updated records separately
83
+ def save(*zobjects)
84
+ new_records = 0
85
+ updated_records = 0
86
+
87
+ # Get all new objects
88
+ new_objects = zobjects.flatten.select do |zobject|
89
+ zobject.new_record? && zobject.changed.present? && zobject.valid?
90
+ end
91
+
92
+ # Get all updated objects
93
+ updated_objects = zobjects.flatten.select do |zobject|
94
+ !zobject.new_record? && zobject.changed.present? && zobject.valid?
95
+ end
96
+
97
+ # Make calls in batches of 50
98
+ new_objects.each_slice(MAX_BATCH_SIZE) do |batch|
99
+ new_records += process_save(batch, :create)
100
+ end
101
+
102
+ updated_objects.each_slice(MAX_BATCH_SIZE) do |batch|
103
+ updated_records += process_save(batch, :update)
104
+ end
105
+
106
+ new_records + updated_records
107
+ end
108
+
109
+ # For backwards compatability
110
+ def update(*zobjects)
111
+ save(zobjects)
112
+ end
113
+
114
+ def process_save(zobjects, action)
115
+ unless [:create, :update].include? action
116
+ raise "Invalid action type for saving. Must be create or update."
117
+ end
118
+
119
+ return 0 if zobjects.empty?
120
+ results = connection.request(action) do |soap|
121
+ soap.body do |xml|
122
+ zobjects.map do |zobject|
123
+ zobject.build_xml(xml, soap,
124
+ :namespace => soap.namespace,
125
+ :element_name => :zObjects,
126
+ :force_type => true,
127
+ :nil_strategy => :fields_to_nil)
128
+ end.last
129
+ end
130
+ end["#{action.to_s}_response".to_sym][:result]
131
+ results = [results] unless results.is_a?(Array)
132
+ zobjects.each_with_index do |zobject, i|
133
+ # If it's an update, grab by id, otherwise by index
134
+ if action == :update
135
+ result = results.find { |r| r[:id] == zobject.id } ||
136
+ { :errors => { :message => "No result returned." } }
137
+ else
138
+ result = results[i] || { :errors => { :message => "No result returned." } }
139
+ end
140
+ if result[:success]
141
+ zobject.clear_changed_attributes
142
+ else
143
+ zobject.add_zuora_errors result[:errors]
144
+ end
145
+ end
146
+ # Return the count of updates that succeeded.
147
+ results.select{ |result| result[:success] }.size
148
+ end
149
+
150
+ def delete(*ids)
151
+ ids.flatten!
152
+ deleted_records = 0
153
+ ids.each_slice(MAX_BATCH_SIZE) do |batch|
154
+ deleted_records += process_delete(batch)
155
+ end
156
+ deleted_records
157
+ end
158
+
159
+ def process_delete(*ids)
160
+ ids.flatten!
161
+ results = connection.request(:delete) do |soap|
162
+ qualifier = soap.namespace_by_uri(soap.namespace)
163
+ soap.body do |xml|
164
+ xml.tag!(qualifier, :type, zuora_object_name)
165
+ ids.map { |id| xml.tag!(qualifier, :ids, id) }.last
166
+ end
167
+ end[:delete_response][:result]
168
+ results = [results] unless results.is_a?(Array)
169
+ # Return the count of deletes that succeeded.
170
+ results.select{ |result| result[:success] }.size
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+ end