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,253 @@
1
+ module ActiveZuora
2
+ class Relation
3
+
4
+ attr_accessor :selected_field_names, :filters, :order_attribute, :order_direction
5
+
6
+ attr_reader :zobject_class
7
+
8
+ def initialize(zobject_class, selected_field_names=[:id])
9
+ @zobject_class, @selected_field_names, @filters = zobject_class, selected_field_names, []
10
+ @order_attribute, @order_direction = :created_date, :asc
11
+ end
12
+
13
+ def dup
14
+ dup = super
15
+ dup.selected_field_names = dup.selected_field_names.dup
16
+ dup.filters = dup.filters.dup
17
+ dup.unload
18
+ dup
19
+ end
20
+
21
+ #
22
+ # Conditions / Selecting
23
+ #
24
+
25
+ def select(*field_names)
26
+ dup.tap { |dup| dup.selected_field_names = field_names.flatten }
27
+ end
28
+
29
+ def where(conditions)
30
+ dup.tap { |dup| dup.filters << ['and', conditions] }
31
+ end
32
+
33
+ alias :and :where
34
+
35
+ def or(conditions)
36
+ dup.tap { |dup| dup.filters << ['or', conditions] }\
37
+ end
38
+
39
+ def order(attribute, direction = :asc)
40
+ dup.tap do |dup|
41
+ dup.order_attribute = attribute
42
+ dup.order_direction = direction
43
+ end
44
+ end
45
+
46
+ def scoped
47
+ # Account.select(:id).where(:status => "Draft") do
48
+ # Account.all # => select id from Account where status = "Draft"
49
+ # end
50
+ previous_scope, zobject_class.current_scope = zobject_class.current_scope, self
51
+ yield
52
+ ensure
53
+ zobject_class.current_scope = previous_scope
54
+ end
55
+
56
+ def merge(relation)
57
+ if relation.is_a?(Hash)
58
+ where(relation)
59
+ else
60
+ dup.tap do |dup|
61
+ dup.filters.concat relation.filters
62
+ dup.filters.uniq!
63
+ dup.order_attribute = relation.order_attribute
64
+ dup.order_direction = relation.order_direction
65
+ end
66
+ end
67
+ end
68
+
69
+ #
70
+ # Finding / Loading
71
+ #
72
+
73
+ def to_zql
74
+ select_statement + " from " + zobject_class.zuora_object_name + " " + where_statement
75
+ end
76
+
77
+ def find(id)
78
+ return nil if id.blank?
79
+ where(:id => id).first
80
+ end
81
+
82
+ def find_each(&block)
83
+ # Iterate through each item, but don't save the results in memory.
84
+ if loaded?
85
+ # If we're already loaded, iterate through the cached records.
86
+ to_a.each(&block)
87
+ else
88
+ query.each(&block)
89
+ end
90
+ end
91
+
92
+ def to_a
93
+ @records ||= query
94
+ end
95
+
96
+ alias :all :to_a
97
+
98
+ def loaded?
99
+ !@records.nil?
100
+ end
101
+
102
+ def unload
103
+ @records = nil
104
+ self
105
+ end
106
+
107
+ def reload
108
+ unload.to_a
109
+ self
110
+ end
111
+
112
+ def query(&block)
113
+ # Keep querying until all pages are retrieved.
114
+ # Throws an exception for an invalid query.
115
+ response = zobject_class.connection.request(:query){ |soap| soap.body = { :query_string => to_zql } }
116
+ query_response = response[:query_response]
117
+ records = objectify_query_results(query_response[:result][:records])
118
+ records.each(&:block) if block_given?
119
+ # If there are more pages of records, keep fetching
120
+ # them until done.
121
+ until query_response[:result][:done]
122
+ query_response = zobject_class.connection.request(:query_more) do |soap|
123
+ soap.body = { :query_locator => response[:query_response][:result][:query_locator] }
124
+ end[:query_more_response]
125
+ more_records = objectify_query_results(query_response[:result][:records])
126
+ more_records.each(&:block) if block_given?
127
+ records.concat more_records
128
+ end
129
+ sort_records!(records)
130
+ rescue Savon::SOAP::Fault => exception
131
+ # Add the zql to the exception message and re-raise.
132
+ exception.message << ": #{to_zql}"
133
+ raise
134
+ end
135
+
136
+ #
137
+ # Updating / Deleting
138
+ #
139
+
140
+ def update_all(attributes={})
141
+ # Update using an attribute hash, or you can pass a block
142
+ # and update the attributes directly on the objects.
143
+ if block_given?
144
+ to_a.each { |record| yield record }
145
+ else
146
+ to_a.each { |record| record.attributes = attributes }
147
+ end
148
+ zobject_class.update(to_a)
149
+ end
150
+
151
+ def delete_all
152
+ zobject_class.delete(to_a.map(&:id))
153
+ end
154
+
155
+ protected
156
+
157
+ def method_missing(method, *args, &block)
158
+ # This is how the chaing can happen on class methods or named scopes on the
159
+ # ZObject class.
160
+ if Array.method_defined?(method)
161
+ to_a.send(method, *args, &block)
162
+ elsif zobject_class.respond_to?(method)
163
+ scoped { zobject_class.send(method, *args, &block) }
164
+ else
165
+ super
166
+ end
167
+ end
168
+
169
+ #
170
+ # Helper methods to build the ZQL.
171
+ #
172
+
173
+ def select_statement
174
+ "select " + selected_field_names.map { |field_name| zuora_field_name(field_name) }.join(', ')
175
+ end
176
+
177
+ def where_statement
178
+ return '' if @filters.empty?
179
+ tokens = []
180
+ @filters.each do |logical_operator, conditions|
181
+ if conditions.is_a?(Hash)
182
+ conditions.each do |field_name, comparisons|
183
+ zuora_field_name = zuora_field_name(field_name)
184
+ comparisons = { '=' => comparisons } unless comparisons.is_a?(Hash)
185
+ comparisons.each do |operator, value|
186
+ tokens.concat [logical_operator, zuora_field_name, operator, escape_filter_value(value)]
187
+ end
188
+ end
189
+ else
190
+ tokens.concat [logical_operator, conditions.to_s]
191
+ end
192
+ end
193
+ tokens[0] = "where"
194
+ tokens.join ' '
195
+ end
196
+
197
+ def zuora_field_name(name)
198
+ zobject_class.get_field!(name).zuora_name
199
+ end
200
+
201
+ def escape_filter_value(value)
202
+ if value.nil?
203
+ "null"
204
+ elsif value.is_a?(String)
205
+ "'#{value.gsub("'","\\\\'")}'"
206
+ elsif value.is_a?(DateTime) || value.is_a?(Time)
207
+ # If we already have a DateTime or Time, use the zone it already has.
208
+ escape_filter_value(value.strftime("%FT%T%:z")) # 2007-11-19T08:37:48-06:00
209
+ elsif value.is_a?(Date)
210
+ # Create a DateTime from the date using Zuora's timezone.
211
+ escape_filter_value(value.to_datetime.change(:offset => "+0800"))
212
+ else
213
+ value
214
+ end
215
+ end
216
+
217
+ def objectify_query_results(results)
218
+ return [] if results.blank?
219
+ # Sometimes Zuora will return only a single record, not in an array.
220
+ results = [results] unless results.is_a?(Array)
221
+ results.map do |attributes|
222
+ # Strip any noisy attributes from the results that have to do with
223
+ # SOAP namespaces.
224
+ attributes.delete_if { |key, value| key.to_s.start_with? "@" }
225
+ # Instantiate the zobject class, but don't track the changes.
226
+ zobject_class.new(attributes).tap { |record| record.clear_changed_attributes }
227
+ end
228
+ end
229
+
230
+ def sort_records!(records)
231
+ return records unless order_attribute.present?
232
+ records.sort! do |a, b|
233
+ if a.nil?
234
+ -1
235
+ elsif b.nil?
236
+ 1
237
+ else
238
+ a.send(order_attribute) <=> b.send(order_attribute)
239
+ end
240
+ end
241
+ records.reverse! if order_direction == :desc
242
+ records
243
+ end
244
+
245
+ end
246
+ end
247
+
248
+
249
+
250
+
251
+
252
+
253
+
@@ -0,0 +1,50 @@
1
+ module ActiveZuora
2
+ module Scoping
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class << self
8
+
9
+ # Delegate to :scoped
10
+ delegate :find, :all, :find_each, :to => :scoped
11
+ delegate :select, :where, :and, :or, :order, :to => :scoped
12
+ delegate :first, :last, :each, :map, :any?, :empty?, :blank?, :present?, :size, :count, :to => :scoped
13
+
14
+ # Keep track of a current scope.
15
+ attr_accessor :current_scope
16
+
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ def scoped
23
+ current_scope || relation
24
+ end
25
+
26
+ def unscoped
27
+ block_given? ? relation.scoped { yield } : relation
28
+ end
29
+
30
+ def exclude_from_queries(*field_names)
31
+ (@excluded_from_queries ||= []).concat field_names.map(&:to_sym)
32
+ end
33
+
34
+ def relation
35
+ query_field_names = field_names - (@excluded_from_queries ||= [])
36
+ Relation.new(self, query_field_names)
37
+ end
38
+
39
+ def scope(name, body)
40
+ # Body can be a Relation or a lambda that returns a relation.
41
+ define_singleton_method(name) do |*args|
42
+ body.respond_to?(:call) ? body.call(*args) : scoped.merge(body)
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+ end
50
+
@@ -0,0 +1,42 @@
1
+ module ActiveZuora
2
+ module Subscribe
3
+
4
+ # This is meant to be included onto a SubscribeRequest class.
5
+ # Returns true/false on success.
6
+ # Result hash is stored in #result.
7
+ # If success, the subscription id and account id will be set in those objects.
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 subscribe
18
+ self.result = self.class.connection.request(:subscribe) do |soap|
19
+ soap.body do |xml|
20
+ build_xml(xml, soap,
21
+ :namespace => soap.namespace,
22
+ :element_name => :subscribes,
23
+ :force_type => true)
24
+ end
25
+ end[:subscribe_response][:result]
26
+ if result[:success]
27
+ account.id = result[:account_id]
28
+ subscription_data.subscription.id = result[:subscription_id]
29
+ clear_changed_attributes
30
+ true
31
+ else
32
+ add_zuora_errors(result[:errors])
33
+ false
34
+ end
35
+ end
36
+
37
+ def subscribe!
38
+ raise "Could not subscribe: #{errors.full_messages.join ', '}" unless subscribe
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveZuora
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveZuora
2
+ module ZObject
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Base
8
+ include Scoping
9
+ include HasManyAssociations
10
+ include Persistence
11
+ field :id, :string
12
+ end
13
+
14
+ def ==(another_zobject)
15
+ another_zobject.is_a?(ZObject) &&
16
+ zuora_object_name == another_zobject.zuora_object_name &&
17
+ id == another_zobject.id
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe "BelongsToAssociations" do
4
+
5
+ class Blog
6
+ include ActiveZuora::ZObject
7
+ end
8
+
9
+ class Comment
10
+ include ActiveZuora::Base
11
+ belongs_to :blog
12
+ end
13
+
14
+ it "should define a attribute assignment method method for the object" do
15
+ blog = Blog.new :id => "blog1"
16
+ comment = Comment.new :blog => blog
17
+ comment.blog_loaded?.should be_true
18
+ comment.blog.should == blog
19
+ comment.blog_id.should == blog.id
20
+ comment.blog = nil
21
+ comment.blog_loaded?.should be_true
22
+ comment.blog.should be_nil
23
+ comment.blog_id.should be_nil
24
+ end
25
+
26
+ it "should define a attribute assignment method for the object id" do
27
+ blog = Blog.new :id => "blog1"
28
+ comment = Comment.new :blog => blog
29
+ comment.blog_loaded?.should be_true
30
+ comment.blog_id = "blog2"
31
+ comment.blog_loaded?.should be_false
32
+ end
33
+
34
+ end
35
+
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe "HasManyRelations" do
4
+
5
+ integration_test do
6
+
7
+ before :all do
8
+ @account = Z::Account.create!(
9
+ :name => "ZObject Integration Test Account",
10
+ :status => "Draft",
11
+ :currency => "USD",
12
+ :bill_cycle_day => 1)
13
+ @billy = Z::Contact.create!(
14
+ :account => @account,
15
+ :first_name => "Billy",
16
+ :last_name => "Blanks")
17
+ @franky = Z::Contact.create!(
18
+ :account => @account,
19
+ :first_name => "Franky",
20
+ :last_name => "Funhouse")
21
+ end
22
+
23
+ after :all do
24
+ # Delete the account to cleanup in case a test failed.
25
+ @account.delete if @account
26
+ end
27
+
28
+ it "can specify conditions and order" do
29
+ Z::Account.instance_eval do
30
+ has_many :billies, :conditions => { :first_name => "Billy" }, :order => [:first_name, :desc], :class_name => 'Z::Contact'
31
+ end
32
+ @account.billies.to_a == [@billy]
33
+ @account.billies.scope.order_attribute.should == :first_name
34
+ @account.billies.scope.order_direction.should == :desc
35
+ end
36
+
37
+ it "can behave like an array" do
38
+ @account.contacts.size.should == 2
39
+ @account.contacts.map(&:first_name).should =~ %w{Billy Franky}
40
+ end
41
+
42
+ it "can respond to functions on the Relation" do
43
+ @account.contacts.unload
44
+ @account.contacts.loaded?.should be_false
45
+ @account.contacts.reload
46
+ @account.contacts.loaded?.should be_true
47
+ @account.contacts.where(:last_name => "Funhouse").to_a.should == [@franky]
48
+ @account.contacts.loaded?.should be_true
49
+ @account.contacts.to_a.should =~ [@billy, @franky]
50
+ end
51
+
52
+ end
53
+ end