active_zuora 1.5.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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