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.
@@ -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,12 @@
1
+ module Infuser
2
+ class Company < Infuser::Models::Base
3
+
4
+ define_schema :company, :website, :date_created, :last_updated
5
+
6
+ has_collection :emails
7
+ has_collection :phones
8
+ has_collection :faxes
9
+ has_collection :addresses
10
+
11
+ end
12
+ 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,11 @@
1
+ module Infuser
2
+ class Email < Infuser::Collections::Base
3
+
4
+ define_mappings({
5
+ 1 => { :email => :email },
6
+ 2 => { :email_address2 => :email },
7
+ 3 => { :email_address3 => :email }
8
+ })
9
+
10
+ end
11
+ 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
@@ -0,0 +1,12 @@
1
+ module Infuser
2
+ class Fax < Infuser::Collections::Base
3
+
4
+ define_mappings( (1..2).each_with_object({}) do |i, hash|
5
+ hash[i] = {
6
+ "fax#{i}".to_sym => :number,
7
+ "fax#{i}_type".to_sym => :type,
8
+ }
9
+ end )
10
+
11
+ end
12
+ end
@@ -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,11 @@
1
+ module Infuser
2
+ class InvoiceItem < Infuser::Models::Base
3
+
4
+ define_schema :commission_status, :date_created, :description, :discount, :invoice_amt,
5
+ :invoice_id, :order_item_id
6
+
7
+ belongs_to :invoice
8
+ belongs_to :order_item
9
+
10
+ end
11
+ 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