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.
- data/Gemfile +4 -0
- data/Gemfile.lock +57 -0
- data/README.md +155 -0
- data/Rakefile +6 -22
- data/TODO.md +2 -0
- data/active_zuora.gemspec +25 -68
- data/lib/active_zuora.rb +37 -6
- data/lib/active_zuora/amend.rb +43 -0
- data/lib/active_zuora/base.rb +73 -0
- data/lib/active_zuora/belongs_to_associations.rb +56 -0
- data/lib/active_zuora/connection.rb +41 -0
- data/lib/active_zuora/fields.rb +122 -0
- data/lib/active_zuora/fields/array_field_decorator.rb +28 -0
- data/lib/active_zuora/fields/boolean_field.rb +12 -0
- data/lib/active_zuora/fields/date_time_field.rb +19 -0
- data/lib/active_zuora/fields/decimal_field.rb +12 -0
- data/lib/active_zuora/fields/field.rb +76 -0
- data/lib/active_zuora/fields/integer_field.rb +11 -0
- data/lib/active_zuora/fields/object_field.rb +31 -0
- data/lib/active_zuora/fields/string_field.rb +11 -0
- data/lib/active_zuora/generate.rb +43 -0
- data/lib/active_zuora/generator.rb +220 -0
- data/lib/active_zuora/has_many_associations.rb +37 -0
- data/lib/active_zuora/has_many_proxy.rb +50 -0
- data/lib/active_zuora/persistence.rb +176 -0
- data/lib/active_zuora/relation.rb +253 -0
- data/lib/active_zuora/scoping.rb +50 -0
- data/lib/active_zuora/subscribe.rb +42 -0
- data/lib/active_zuora/version.rb +3 -0
- data/lib/active_zuora/z_object.rb +21 -0
- data/spec/belongs_to_associations_spec.rb +35 -0
- data/spec/has_many_integration_spec.rb +53 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/subscribe_integration_spec.rb +218 -0
- data/spec/zobject_integration_spec.rb +104 -0
- data/wsdl/zuora.wsdl +1548 -0
- metadata +128 -72
- checksums.yaml +0 -7
- data/LICENSE +0 -202
- data/README.rdoc +0 -36
- data/VERSION +0 -1
- data/custom_fields.yml +0 -17
- data/lib/active_zuora/account.rb +0 -31
- data/lib/active_zuora/amendment.rb +0 -7
- data/lib/active_zuora/bill_run.rb +0 -4
- data/lib/active_zuora/contact.rb +0 -7
- data/lib/active_zuora/invoice.rb +0 -46
- data/lib/active_zuora/invoice_item.rb +0 -26
- data/lib/active_zuora/invoice_item_adjustment.rb +0 -4
- data/lib/active_zuora/invoice_payment.rb +0 -11
- data/lib/active_zuora/payment.rb +0 -18
- data/lib/active_zuora/payment_method.rb +0 -10
- data/lib/active_zuora/product.rb +0 -4
- data/lib/active_zuora/product_rate_plan.rb +0 -9
- data/lib/active_zuora/product_rate_plan_charge.rb +0 -11
- data/lib/active_zuora/product_rate_plan_charge_tier.rb +0 -7
- data/lib/active_zuora/product_rate_plan_charge_tier_data.rb +0 -4
- data/lib/active_zuora/rate_plan.rb +0 -16
- data/lib/active_zuora/rate_plan_charge.rb +0 -71
- data/lib/active_zuora/rate_plan_charge_data.rb +0 -4
- data/lib/active_zuora/rate_plan_charge_tier.rb +0 -4
- data/lib/active_zuora/rate_plan_data.rb +0 -4
- data/lib/active_zuora/refund.rb +0 -4
- data/lib/active_zuora/subscribe_options.rb +0 -4
- data/lib/active_zuora/subscribe_request.rb +0 -13
- data/lib/active_zuora/subscribe_with_existing_account_request.rb +0 -4
- data/lib/active_zuora/subscription.rb +0 -17
- data/lib/active_zuora/subscription_data.rb +0 -4
- data/lib/active_zuora/usage.rb +0 -4
- data/lib/active_zuora/zobject.rb +0 -154
- data/lib/soap/property +0 -1
- data/lib/zuora/ZUORA.rb +0 -1560
- data/lib/zuora/ZUORADriver.rb +0 -145
- data/lib/zuora/ZUORAMappingRegistry.rb +0 -1709
- data/lib/zuora/ZuoraServiceClient.rb +0 -124
- data/lib/zuora/api.rb +0 -18
- data/lib/zuora_client.rb +0 -191
- 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,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,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,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
|