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,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