infuser 1.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/README.md +242 -0
- data/Rakefile +8 -0
- data/infuser.gemspec +22 -0
- data/lib/infuser.rb +49 -0
- data/lib/infuser/address.rb +38 -0
- data/lib/infuser/client.rb +32 -0
- data/lib/infuser/collections/base.rb +70 -0
- data/lib/infuser/collections/child_proxy.rb +57 -0
- data/lib/infuser/collections/proxy.rb +76 -0
- data/lib/infuser/company.rb +12 -0
- data/lib/infuser/configuration.rb +35 -0
- data/lib/infuser/contact.rb +37 -0
- data/lib/infuser/email.rb +11 -0
- data/lib/infuser/exceptions.rb +57 -0
- data/lib/infuser/fax.rb +12 -0
- data/lib/infuser/helpers/hashie.rb +17 -0
- data/lib/infuser/invoice.rb +21 -0
- data/lib/infuser/invoice_item.rb +11 -0
- data/lib/infuser/logger.rb +32 -0
- data/lib/infuser/models/base.rb +205 -0
- data/lib/infuser/order_item.rb +10 -0
- data/lib/infuser/phone.rb +13 -0
- data/lib/infuser/requester.rb +80 -0
- data/lib/infuser/tables/base.rb +77 -0
- data/lib/infuser/tables/company.rb +6 -0
- data/lib/infuser/tables/contact.rb +6 -0
- data/lib/infuser/tables/invoice.rb +20 -0
- data/lib/infuser/tables/invoice_item.rb +6 -0
- data/lib/infuser/tables/order_item.rb +6 -0
- data/lib/infuser/token_refresher.rb +35 -0
- data/lib/infuser/version.rb +5 -0
- metadata +117 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module Infuser
|
2
|
+
module Collections
|
3
|
+
class ChildProxy
|
4
|
+
|
5
|
+
# For has_many, eg contact has_many invoices
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :parent, :child_klass
|
10
|
+
|
11
|
+
def initialize parent, child_klass
|
12
|
+
@parent = parent
|
13
|
+
@child_klass = child_klass
|
14
|
+
end
|
15
|
+
|
16
|
+
def set
|
17
|
+
@set ||= table.find_by(parent_klass_field => parent.id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove item
|
21
|
+
item.destroy
|
22
|
+
set.delete item
|
23
|
+
end
|
24
|
+
|
25
|
+
def << item
|
26
|
+
item.save if item.new_record?
|
27
|
+
item.send(:update, { parent_klass_field => parent.id })
|
28
|
+
set << item
|
29
|
+
end
|
30
|
+
|
31
|
+
def each &block
|
32
|
+
set.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def find id
|
36
|
+
set.find { |item| item.id == id }
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def parent_klass_field
|
43
|
+
"#{parent.klass_name.classify}ID"
|
44
|
+
end
|
45
|
+
|
46
|
+
def table
|
47
|
+
Infuser::Tables.const_get(child_klass.to_s.classify).new(client)
|
48
|
+
end
|
49
|
+
|
50
|
+
def client
|
51
|
+
# an unhappy hack
|
52
|
+
parent.send(:client)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Infuser
|
2
|
+
module Collections
|
3
|
+
class Proxy
|
4
|
+
|
5
|
+
# For items with subitems that all live within the same table, eg a contact and it's 3 email addresses
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
attr_reader :klass
|
10
|
+
|
11
|
+
def initialize klass
|
12
|
+
@klass = Infuser.const_get(klass.to_s.classify)
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse dump
|
16
|
+
dump.each do |key, value|
|
17
|
+
if id = klass.inverted_mappings[key.underscore.to_sym]
|
18
|
+
find_or_create(id).send("#{key.underscore}=", value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def schema
|
24
|
+
klass.schema
|
25
|
+
end
|
26
|
+
|
27
|
+
def set
|
28
|
+
@set ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove item
|
32
|
+
set.delete item
|
33
|
+
end
|
34
|
+
|
35
|
+
def can_add_more?
|
36
|
+
set.size < klass.mappings.keys.max
|
37
|
+
end
|
38
|
+
|
39
|
+
def << item
|
40
|
+
if set.size == klass.mappings.keys.max
|
41
|
+
raise(Infuser::ArgumentError, "The collection is full, you can not add another item.")
|
42
|
+
elsif item.class.name != klass.name
|
43
|
+
raise(Infuser::ArgumentError, "The item you are adding does not belong in this collection.")
|
44
|
+
else
|
45
|
+
item.id = ( (1..klass.mappings.keys.max).to_a - set.map(&:id) ).sort.first
|
46
|
+
set << item
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def each &block
|
51
|
+
set.each(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def find id
|
55
|
+
set.find { |item| item.id == id }
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_or_create id
|
59
|
+
find(id) || begin
|
60
|
+
item = klass.new(id: id)
|
61
|
+
set << item
|
62
|
+
item
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def data
|
67
|
+
set.each_with_object({}) do |item, hash|
|
68
|
+
item.data.each do |key, value|
|
69
|
+
hash[key] = value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Infuser
|
2
|
+
module Configuration
|
3
|
+
|
4
|
+
OPTION_KEYS = [
|
5
|
+
:api_key,
|
6
|
+
:api_secret,
|
7
|
+
:user_agent,
|
8
|
+
:logger,
|
9
|
+
:retry_count,
|
10
|
+
:duplication_check
|
11
|
+
]
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
mattr_accessor *OPTION_KEYS
|
16
|
+
|
17
|
+
self.user_agent = "Infuser-#{VERSION} (RubyGem)"
|
18
|
+
self.logger = Infuser::Logger.new
|
19
|
+
self.retry_count = 5
|
20
|
+
self.duplication_check = :email_and_name
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield self
|
24
|
+
end
|
25
|
+
|
26
|
+
def attributes
|
27
|
+
OPTION_KEYS.each_with_object({}) { |key, hash| hash[key] = send(key) }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Infuser
|
2
|
+
class Contact < Infuser::Models::Base
|
3
|
+
|
4
|
+
define_schema :first_name, :middle_name, :nickname, :last_name, :suffix, :title,
|
5
|
+
:company_id, :job_title, :assistant_name, :assistant_phone,
|
6
|
+
:contact_notes, :contact_type,
|
7
|
+
:referral_code, :spouse_name, :username, :website,
|
8
|
+
:date_created, :last_updated
|
9
|
+
|
10
|
+
belongs_to :company
|
11
|
+
|
12
|
+
has_collection :emails
|
13
|
+
has_collection :phones
|
14
|
+
has_collection :faxes
|
15
|
+
has_collection :addresses
|
16
|
+
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def add
|
21
|
+
dedup_type = Infuser::Configuration.duplication_check
|
22
|
+
if dedup_type
|
23
|
+
dedup_type = dedup_type.to_s.split('_').map(&:titlecase).join
|
24
|
+
unless ['Email', 'EmailAndName', 'EmailAndNameAndCompany'].include?(dedup_type)
|
25
|
+
raise(Infuser::ArgumentError, 'Invalid duplication_check specified in Configuration.')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
@id = if dedup_type
|
30
|
+
client.get('ContactService.addWithDupCheck', data, dedup_type)
|
31
|
+
else
|
32
|
+
client.get('ContactService.add', data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Infuser
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
def initialize msg
|
6
|
+
Infuser.logger.error msg
|
7
|
+
super msg
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
class ArgumentError < Error; end
|
13
|
+
class InvalidConfig < Error; end
|
14
|
+
class InvalidKey < Error; end
|
15
|
+
class UnexpectedError < Error; end
|
16
|
+
class DatabaseError < Error; end
|
17
|
+
class RecordNotFound < Error; end
|
18
|
+
class LoadingError < Error; end
|
19
|
+
class NoTableAccess < Error; end
|
20
|
+
class NoFieldAccess < Error; end
|
21
|
+
class NoTableFound < Error; end
|
22
|
+
class NoFieldFound < Error; end
|
23
|
+
class NoFieldsError < Error; end
|
24
|
+
class InvalidParameter < Error; end
|
25
|
+
class FailedLoginAttempt < Error; end
|
26
|
+
class NoAccess < Error; end
|
27
|
+
class FailedLoginAttemptPasswordExpired < Error; end
|
28
|
+
class ExpiredToken < Error; end
|
29
|
+
|
30
|
+
class ExceptionHandler
|
31
|
+
|
32
|
+
ERRORS = {
|
33
|
+
1 => Infuser::InvalidConfig,
|
34
|
+
2 => Infuser::InvalidKey,
|
35
|
+
3 => Infuser::UnexpectedError,
|
36
|
+
4 => Infuser::DatabaseError,
|
37
|
+
5 => Infuser::RecordNotFound,
|
38
|
+
6 => Infuser::LoadingError,
|
39
|
+
7 => Infuser::NoTableAccess,
|
40
|
+
8 => Infuser::NoFieldAccess,
|
41
|
+
9 => Infuser::NoTableFound,
|
42
|
+
10 => Infuser::NoFieldFound,
|
43
|
+
11 => Infuser::NoFieldsError,
|
44
|
+
12 => Infuser::InvalidParameter,
|
45
|
+
13 => Infuser::FailedLoginAttempt,
|
46
|
+
14 => Infuser::NoAccess,
|
47
|
+
15 => Infuser::FailedLoginAttemptPasswordExpired
|
48
|
+
}
|
49
|
+
|
50
|
+
def initialize xmlrpc_exception
|
51
|
+
error_class = ERRORS[xmlrpc_exception.faultCode] || Infuser::Error
|
52
|
+
raise error_class, xmlrpc_exception.faultString
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/lib/infuser/fax.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Infuser
|
2
|
+
module Helpers
|
3
|
+
module Hashie
|
4
|
+
|
5
|
+
def camelize_hash hash
|
6
|
+
hash.each_with_object({}) do |(key, value), h|
|
7
|
+
h[key.to_s.split('_').map { |w| safe_classify(w) }.join] = value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def safe_classify w
|
12
|
+
w[0] = w[0].upcase; w
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Infuser
|
2
|
+
class Invoice < Infuser::Models::Base
|
3
|
+
|
4
|
+
define_schema :affiliate_id, :contact_id, :credit_status, :date_created, :description, :invoice_total,
|
5
|
+
:invoice_type, :job_id, :lead_affiliate_id, :pay_plan_status, :pay_status, :product_sold,
|
6
|
+
:promo_code, :refund_status, :synced, :total_due, :total_paid
|
7
|
+
|
8
|
+
belongs_to :contact
|
9
|
+
|
10
|
+
has_many :invoice_items
|
11
|
+
|
12
|
+
def add_item *data
|
13
|
+
# Add in specific order:
|
14
|
+
# product_id
|
15
|
+
# type (UNKNOWN = 0, SHIPPING = 1, TAX = 2, SERVICE = 3, PRODUCT = 4, UPSELL = 5, FINANCECHARGE = 6, SPECIAL = 7)
|
16
|
+
# price, quantity, description, notes
|
17
|
+
client.get("InvoiceService.addOrderItem", id, *data)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Infuser
|
2
|
+
class Logger
|
3
|
+
|
4
|
+
def info msg
|
5
|
+
output msg
|
6
|
+
end
|
7
|
+
|
8
|
+
def warn msg
|
9
|
+
output "WARN: #{msg}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def error msg
|
13
|
+
output "ERROR: #{msg}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def debug msg
|
17
|
+
output msg
|
18
|
+
end
|
19
|
+
|
20
|
+
def fatal
|
21
|
+
output "FATAL: #{msg}"
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def output data
|
28
|
+
$stdout.puts data
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module Infuser
|
2
|
+
module Models
|
3
|
+
class Base
|
4
|
+
include Infuser::Helpers::Hashie
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def schema
|
9
|
+
@schema || []
|
10
|
+
end
|
11
|
+
|
12
|
+
def collection_names
|
13
|
+
@collection_names || []
|
14
|
+
end
|
15
|
+
|
16
|
+
def children_names
|
17
|
+
@children_names || []
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_schema *cols
|
21
|
+
@schema = *cols
|
22
|
+
attr_accessor *cols
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_collection type
|
26
|
+
type = type.to_s.underscore
|
27
|
+
@collection_names ||= []
|
28
|
+
@collection_names << type
|
29
|
+
|
30
|
+
define_method type.to_s.underscore do
|
31
|
+
collections[type]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_many type
|
36
|
+
type = type.to_s.underscore
|
37
|
+
@children_names ||= []
|
38
|
+
@children_names << type
|
39
|
+
|
40
|
+
define_method type.to_s.underscore do
|
41
|
+
children[type]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def belongs_to type
|
46
|
+
type = type.to_s.underscore
|
47
|
+
|
48
|
+
define_method "#{type}=" do |arg|
|
49
|
+
update_association(type, arg)
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method "clear_#{type}" do
|
53
|
+
clear_association(type)
|
54
|
+
end
|
55
|
+
|
56
|
+
define_method "#{type}" do
|
57
|
+
fetch_association(type)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def fieldset
|
62
|
+
core = schema.dup
|
63
|
+
coll = collection_names.map { |name| Infuser.const_get(name.classify).schema.dup }
|
64
|
+
(core + coll).flatten.compact.map do |f|
|
65
|
+
# don't use titlecase or upcase or fields like CompanyID turn into company
|
66
|
+
f.to_s.split('_').map { |w| w[0] = w[0].upcase; w }.join
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def klass_name
|
71
|
+
name.split('::').last
|
72
|
+
end
|
73
|
+
|
74
|
+
def service_name
|
75
|
+
"#{klass_name.underscore}_service".classify
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :id
|
81
|
+
|
82
|
+
def initialize data = {}
|
83
|
+
data.each do |key, value|
|
84
|
+
send("#{key}=", value)
|
85
|
+
end
|
86
|
+
|
87
|
+
@collections = self.class.collection_names.each_with_object({}) do |name, hash|
|
88
|
+
hash[name] = Infuser::Collections::Proxy.new(name)
|
89
|
+
end
|
90
|
+
|
91
|
+
@children = self.class.children_names.each_with_object({}) do |name, hash|
|
92
|
+
hash[name] = Infuser::Collections::ChildProxy.new(self, name)
|
93
|
+
end
|
94
|
+
|
95
|
+
return self
|
96
|
+
end
|
97
|
+
|
98
|
+
def inspect
|
99
|
+
"#<#{self.class.name} #{attributes.map { |k, v| "#{k}: #{v || 'nil'}" }.join(', ')}>"
|
100
|
+
end
|
101
|
+
|
102
|
+
def attributes
|
103
|
+
data = self.class.schema.dup.each_with_object({}) { |key, hash| hash[key] = send(key) }
|
104
|
+
collections.each do |name, proxy|
|
105
|
+
data[name] = proxy.map(&:attributes)
|
106
|
+
end
|
107
|
+
data
|
108
|
+
end
|
109
|
+
|
110
|
+
def data
|
111
|
+
data = self.class.schema.dup.each_with_object({}) { |key, hash| hash[key] = send(key) }
|
112
|
+
collections.each do |name, proxy|
|
113
|
+
data.merge!(proxy.data)
|
114
|
+
end
|
115
|
+
camelize_hash(data).select { |k, v| !v.nil? }
|
116
|
+
end
|
117
|
+
|
118
|
+
def new_record?
|
119
|
+
id.nil?
|
120
|
+
end
|
121
|
+
|
122
|
+
def persisted?
|
123
|
+
!new_record?
|
124
|
+
end
|
125
|
+
|
126
|
+
def save
|
127
|
+
new_record? ? add : update
|
128
|
+
return self
|
129
|
+
end
|
130
|
+
|
131
|
+
def destroy
|
132
|
+
client.get("DataService.delete", klass_name, id)
|
133
|
+
end
|
134
|
+
|
135
|
+
def load
|
136
|
+
populate client.get("DataService.load", klass_name, id, fieldset)
|
137
|
+
end
|
138
|
+
|
139
|
+
def populate hash
|
140
|
+
hash.each do |key, value|
|
141
|
+
send("#{key.underscore}=", value) if respond_to?(key.underscore.to_sym)
|
142
|
+
end
|
143
|
+
|
144
|
+
collections.each do |name, proxy|
|
145
|
+
proxy.parse(hash)
|
146
|
+
end
|
147
|
+
|
148
|
+
return self
|
149
|
+
end
|
150
|
+
|
151
|
+
def fieldset
|
152
|
+
self.class.fieldset
|
153
|
+
end
|
154
|
+
|
155
|
+
def klass_name
|
156
|
+
self.class.klass_name
|
157
|
+
end
|
158
|
+
|
159
|
+
def service_name
|
160
|
+
self.class.service_name
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def add
|
167
|
+
@id = client.get("DataService.add", klass_name, data)
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
def update fields = data
|
172
|
+
client.get("DataService.update", klass_name, id, fields)
|
173
|
+
end
|
174
|
+
|
175
|
+
def client
|
176
|
+
@client || raise(Infuser::Error, "No client found. Do not use the #{klass_name} class directly. Use the Infuser::Client instead.")
|
177
|
+
end
|
178
|
+
|
179
|
+
def collections
|
180
|
+
@collections
|
181
|
+
end
|
182
|
+
|
183
|
+
def children
|
184
|
+
@children
|
185
|
+
end
|
186
|
+
|
187
|
+
def update_association type, item
|
188
|
+
raise(Infuser::ArgumentError, "Item has not been saved yet and cannot be used as an association") if !item.id
|
189
|
+
update({ "#{type.classify}ID" => item.id })
|
190
|
+
true
|
191
|
+
end
|
192
|
+
|
193
|
+
def clear_association type
|
194
|
+
raise NotImplementedError
|
195
|
+
end
|
196
|
+
|
197
|
+
def fetch_association type
|
198
|
+
id_field = "#{type}_id"
|
199
|
+
raise(Infuser::RecordNotFound) if send(id_field).nil?
|
200
|
+
Infuser::Tables.const_get(type.classify).new(client).find send(id_field)
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|