merchant_sidekick 0.4.2
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/.gitignore +12 -0
- data/Changelog.md +38 -0
- data/Gemfile +2 -0
- data/MIT-LICENSE +19 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/lib/merchant_sidekick.rb +45 -0
- data/lib/merchant_sidekick/active_merchant/credit_card_payment.rb +117 -0
- data/lib/merchant_sidekick/active_merchant/gateways/authorize_net_gateway.rb +26 -0
- data/lib/merchant_sidekick/active_merchant/gateways/base.rb +29 -0
- data/lib/merchant_sidekick/active_merchant/gateways/bogus_gateway.rb +19 -0
- data/lib/merchant_sidekick/active_merchant/gateways/paypal_gateway.rb +43 -0
- data/lib/merchant_sidekick/addressable/address.rb +400 -0
- data/lib/merchant_sidekick/addressable/addressable.rb +353 -0
- data/lib/merchant_sidekick/buyer.rb +99 -0
- data/lib/merchant_sidekick/gateway.rb +81 -0
- data/lib/merchant_sidekick/install.rb +19 -0
- data/lib/merchant_sidekick/invoice.rb +179 -0
- data/lib/merchant_sidekick/line_item.rb +128 -0
- data/lib/merchant_sidekick/migrations/addressable.rb +47 -0
- data/lib/merchant_sidekick/migrations/billing.rb +100 -0
- data/lib/merchant_sidekick/migrations/shopping_cart.rb +28 -0
- data/lib/merchant_sidekick/money.rb +38 -0
- data/lib/merchant_sidekick/order.rb +244 -0
- data/lib/merchant_sidekick/payment.rb +59 -0
- data/lib/merchant_sidekick/purchase_invoice.rb +180 -0
- data/lib/merchant_sidekick/purchase_order.rb +350 -0
- data/lib/merchant_sidekick/railtie.rb +7 -0
- data/lib/merchant_sidekick/sales_invoice.rb +56 -0
- data/lib/merchant_sidekick/sales_order.rb +122 -0
- data/lib/merchant_sidekick/sellable.rb +88 -0
- data/lib/merchant_sidekick/seller.rb +93 -0
- data/lib/merchant_sidekick/shopping_cart/cart.rb +225 -0
- data/lib/merchant_sidekick/shopping_cart/line_item.rb +152 -0
- data/lib/merchant_sidekick/version.rb +3 -0
- data/merchant_sidekick.gemspec +37 -0
- data/spec/address_spec.rb +153 -0
- data/spec/addressable_spec.rb +250 -0
- data/spec/buyer_spec.rb +203 -0
- data/spec/cart_line_item_spec.rb +58 -0
- data/spec/cart_spec.rb +213 -0
- data/spec/config/merchant_sidekick.yml +10 -0
- data/spec/credit_card_payment_spec.rb +175 -0
- data/spec/fixtures/addresses.yml +97 -0
- data/spec/fixtures/line_items.yml +18 -0
- data/spec/fixtures/orders.yml +24 -0
- data/spec/fixtures/payments.yml +17 -0
- data/spec/fixtures/products.yml +12 -0
- data/spec/fixtures/users.yml +11 -0
- data/spec/gateway_spec.rb +136 -0
- data/spec/invoice_spec.rb +79 -0
- data/spec/line_item_spec.rb +65 -0
- data/spec/order_spec.rb +85 -0
- data/spec/payment_spec.rb +14 -0
- data/spec/purchase_invoice_spec.rb +70 -0
- data/spec/purchase_order_spec.rb +191 -0
- data/spec/sales_invoice_spec.rb +58 -0
- data/spec/sales_order_spec.rb +107 -0
- data/spec/schema.rb +28 -0
- data/spec/sellable_spec.rb +34 -0
- data/spec/seller_spec.rb +201 -0
- data/spec/spec_helper.rb +255 -0
- metadata +201 -0
@@ -0,0 +1,400 @@
|
|
1
|
+
module MerchantSidekick
|
2
|
+
module Addressable
|
3
|
+
# Super class of all types of addresses
|
4
|
+
class Address < ActiveRecord::Base
|
5
|
+
self.table_name = "addresses"
|
6
|
+
|
7
|
+
#--- column mapping
|
8
|
+
cattr_accessor :street_address_column
|
9
|
+
@@street_address_column = :street
|
10
|
+
|
11
|
+
cattr_accessor :city_column
|
12
|
+
@@city_column = :city
|
13
|
+
|
14
|
+
cattr_accessor :postal_code_column
|
15
|
+
@@postal_code_column = :postal_code
|
16
|
+
|
17
|
+
cattr_accessor :province_column
|
18
|
+
@@province_column = :province
|
19
|
+
|
20
|
+
cattr_accessor :province_code_column
|
21
|
+
@@province_code_column = :province_code
|
22
|
+
|
23
|
+
cattr_accessor :country_column
|
24
|
+
@@country_column = :country
|
25
|
+
|
26
|
+
cattr_accessor :country_code_column
|
27
|
+
@@country_code_column = :country_code
|
28
|
+
|
29
|
+
cattr_accessor :gender_column
|
30
|
+
@@gender_column = :gender
|
31
|
+
|
32
|
+
cattr_accessor :first_name_column
|
33
|
+
@@first_name_column = :first_name
|
34
|
+
|
35
|
+
cattr_accessor :middle_name_column
|
36
|
+
@@middle_name_column = false
|
37
|
+
|
38
|
+
cattr_accessor :last_name_column
|
39
|
+
@@last_name_column = :last_name
|
40
|
+
|
41
|
+
#--- associations
|
42
|
+
belongs_to :addressable, :polymorphic => true
|
43
|
+
|
44
|
+
#--- validations
|
45
|
+
# extend your Address class with validation as you please
|
46
|
+
|
47
|
+
#--- callbacks
|
48
|
+
|
49
|
+
# This particular call back could be used to save
|
50
|
+
# geocordinates if addressable defines :before_save_address method
|
51
|
+
def before_save
|
52
|
+
# trigger before_save_address
|
53
|
+
self.addressable.send(:before_save_address, self) if addressable && addressable.respond_to?(:before_save_address)
|
54
|
+
end
|
55
|
+
|
56
|
+
#--- class methods
|
57
|
+
|
58
|
+
class << self
|
59
|
+
|
60
|
+
def kind
|
61
|
+
name.underscore
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the binding to be used in sub classes of Address
|
65
|
+
def get_binding
|
66
|
+
binding
|
67
|
+
end
|
68
|
+
|
69
|
+
# Helper class method to look up all addresss for
|
70
|
+
# addressable class name and addressable id.
|
71
|
+
def find_address_for_addressable(addressable_str, addressable_id)
|
72
|
+
find(:all,
|
73
|
+
:conditions => ["addressable_type = ? AND addressable_id = ?", addressable_str, addressable_id],
|
74
|
+
:order => "created_at DESC"
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Helper class method to look up a addressable object
|
79
|
+
# given the addressable class name and id
|
80
|
+
def find_addressable(addressable_str, addressable_id)
|
81
|
+
addressable_str.constantize.find(addressable_id)
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO not used
|
85
|
+
def translate_column_key(in_column)
|
86
|
+
out_column = class_variable_get("@@#{in_column}")
|
87
|
+
case out_column.class.name
|
88
|
+
when /NilClass/ then in_column
|
89
|
+
when /FalseClass/ then nil
|
90
|
+
else out_column
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def content_column_names
|
95
|
+
content_columns.map(&:name) - %w(kind addressable_type addressable_id updated_at created_at)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
#--- instance methods
|
101
|
+
|
102
|
+
# geokit getter
|
103
|
+
# returns a hash of geokit compatible GeoKit::Location attributes
|
104
|
+
def geokit_attributes
|
105
|
+
{
|
106
|
+
:zip => self.postal_code,
|
107
|
+
:city => self.city,
|
108
|
+
:street_address => self.street_address,
|
109
|
+
:state => self.province_code || self.province,
|
110
|
+
:country_code => self.country_code
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
# geokit setter
|
115
|
+
def geokit_attributes=(geo_attr)
|
116
|
+
self.attributes = {
|
117
|
+
:postal_code => geo_attr[:zip],
|
118
|
+
:city => geo_attr[:city],
|
119
|
+
:street_address => geo_attr[:street_address],
|
120
|
+
:province_code => geo_attr[:state],
|
121
|
+
:country_code => geo_attr[:country_code]
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
# attributes for active merchant address
|
126
|
+
def merchant_attributes(options={})
|
127
|
+
{
|
128
|
+
:name => self.name,
|
129
|
+
:address1 => self.address_line_1,
|
130
|
+
:address2 => self.address_line_2,
|
131
|
+
:city => self.city,
|
132
|
+
:state => (self.province_code || self.province),
|
133
|
+
:country => (self.country_code || self.country),
|
134
|
+
:zip => self.postal_code,
|
135
|
+
:phone => self.phone
|
136
|
+
}.merge(options)
|
137
|
+
end
|
138
|
+
alias_method :to_merchant_attributes, :merchant_attributes
|
139
|
+
|
140
|
+
# getter
|
141
|
+
def street
|
142
|
+
self[street_address_column]
|
143
|
+
end
|
144
|
+
alias_method :street_address, :street
|
145
|
+
|
146
|
+
# setter
|
147
|
+
def street=(a_street)
|
148
|
+
self[street_address_column] = a_street
|
149
|
+
end
|
150
|
+
alias_method :street_address=, :street=
|
151
|
+
|
152
|
+
# postal_code reader
|
153
|
+
def postal_code
|
154
|
+
self[postal_code_column]
|
155
|
+
end
|
156
|
+
alias_method :zip, :postal_code
|
157
|
+
|
158
|
+
# postal_code instead of ZIP
|
159
|
+
def postal_code=(a_zip)
|
160
|
+
self[postal_code_column] = a_zip
|
161
|
+
end
|
162
|
+
alias_method :zip=, :postal_code=
|
163
|
+
|
164
|
+
# address_line_1 getter, first line of street_address
|
165
|
+
# address1 alias for active merchant
|
166
|
+
def address_line_1
|
167
|
+
(self.street.gsub(/\r/, '').split(/\n/)[0] || self.street).strip if self.street
|
168
|
+
end
|
169
|
+
alias_method :address1, :address_line_1
|
170
|
+
|
171
|
+
# setter
|
172
|
+
def address_line_1=(addr1)
|
173
|
+
self.street = "#{addr1}\n#{address_line_2}"
|
174
|
+
end
|
175
|
+
alias_method :address1=, :address_line_1=
|
176
|
+
|
177
|
+
# address_line_2 getter, second line and following of street
|
178
|
+
# address2 alias for active merchant
|
179
|
+
def address_line_2
|
180
|
+
self.street.gsub(/\r/, '').split(/\n/)[1] if self.street
|
181
|
+
end
|
182
|
+
alias_method :address2, :address_line_2
|
183
|
+
|
184
|
+
# setter
|
185
|
+
def address_line_2=(addr2)
|
186
|
+
self.street = "#{address_line_1}\n#{addr2}"
|
187
|
+
end
|
188
|
+
alias_method :address2=, :address_line_2=
|
189
|
+
|
190
|
+
# province getter
|
191
|
+
def province
|
192
|
+
self[province_column]
|
193
|
+
end
|
194
|
+
alias_method :state, :province
|
195
|
+
|
196
|
+
# province setter
|
197
|
+
def province=(a_province)
|
198
|
+
self[province_column] = a_province
|
199
|
+
end
|
200
|
+
alias_method :state=, :province=
|
201
|
+
|
202
|
+
# province code getter
|
203
|
+
def province_code
|
204
|
+
self[province_code_column] if province_code?
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.province_code?
|
208
|
+
return true if province_code_column
|
209
|
+
false
|
210
|
+
end
|
211
|
+
|
212
|
+
def province_code?
|
213
|
+
return true if province_code_column
|
214
|
+
false
|
215
|
+
end
|
216
|
+
|
217
|
+
# province code setter
|
218
|
+
def province_code=(a_province_code)
|
219
|
+
self[province_code_column] = a_province_code if province_code?
|
220
|
+
end
|
221
|
+
|
222
|
+
# country getter
|
223
|
+
def country
|
224
|
+
self[country_column]
|
225
|
+
end
|
226
|
+
|
227
|
+
# country setter
|
228
|
+
def country=(a_country)
|
229
|
+
self[country_column] = a_country
|
230
|
+
end
|
231
|
+
|
232
|
+
# country code getter
|
233
|
+
def country_code
|
234
|
+
self[country_code_column] if country_code?
|
235
|
+
end
|
236
|
+
|
237
|
+
def country_code?
|
238
|
+
return true if country_code_column
|
239
|
+
false
|
240
|
+
end
|
241
|
+
|
242
|
+
# country code setter
|
243
|
+
def country_code=(a_country_code)
|
244
|
+
self[country_code_column] = a_country_code if country_code?
|
245
|
+
end
|
246
|
+
|
247
|
+
# getter
|
248
|
+
def first_name
|
249
|
+
self[first_name_column] if first_name?
|
250
|
+
end
|
251
|
+
alias_method :firstname, :first_name
|
252
|
+
|
253
|
+
def first_name?
|
254
|
+
return true if first_name_column
|
255
|
+
false
|
256
|
+
end
|
257
|
+
|
258
|
+
# setter
|
259
|
+
def first_name=(a_first_name)
|
260
|
+
self[first_name_column] = a_first_name if first_name?
|
261
|
+
end
|
262
|
+
alias_method :firstname=, :first_name=
|
263
|
+
|
264
|
+
# getter
|
265
|
+
def last_name
|
266
|
+
self[last_name_column] if last_name?
|
267
|
+
end
|
268
|
+
alias_method :lastname, :last_name
|
269
|
+
|
270
|
+
def last_name?
|
271
|
+
return true if last_name_column
|
272
|
+
false
|
273
|
+
end
|
274
|
+
|
275
|
+
# setter
|
276
|
+
def last_name=(a_last_name)
|
277
|
+
self[last_name_column] = a_last_name if last_name?
|
278
|
+
end
|
279
|
+
alias_method :lastname=, :last_name=
|
280
|
+
|
281
|
+
# getter
|
282
|
+
def middle_name
|
283
|
+
self[middle_name_column] if middle_name?
|
284
|
+
end
|
285
|
+
alias_method :middlename, :middle_name
|
286
|
+
|
287
|
+
def self.middle_name?
|
288
|
+
return true if middle_name_column
|
289
|
+
false
|
290
|
+
end
|
291
|
+
|
292
|
+
def middle_name?
|
293
|
+
return true if middle_name_column
|
294
|
+
false
|
295
|
+
end
|
296
|
+
|
297
|
+
# setter
|
298
|
+
def middle_name=(a_middle_name)
|
299
|
+
self[middle_name_column] = a_middle_name if middle_name?
|
300
|
+
end
|
301
|
+
|
302
|
+
# setter
|
303
|
+
def gender=(a_gender)
|
304
|
+
if gender?
|
305
|
+
if a_gender.is_a? Symbol
|
306
|
+
self[gender_column] = case a_gender
|
307
|
+
when :male then 'm'
|
308
|
+
when :female then 'f'
|
309
|
+
else ''
|
310
|
+
end
|
311
|
+
elsif a_gender.is_a? String
|
312
|
+
self[gender_column] = case a_gender
|
313
|
+
when 'm' then 'm'
|
314
|
+
when 'f' then 'f'
|
315
|
+
else ''
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# gender getter
|
322
|
+
def gender
|
323
|
+
self[gender_column] if gender?
|
324
|
+
end
|
325
|
+
|
326
|
+
def gender?
|
327
|
+
return true if gender_column
|
328
|
+
false
|
329
|
+
end
|
330
|
+
|
331
|
+
def is_gender_male?
|
332
|
+
self.gender == 'm'
|
333
|
+
end
|
334
|
+
|
335
|
+
def is_gender_female?
|
336
|
+
self.gender == 'f'
|
337
|
+
end
|
338
|
+
|
339
|
+
# Concatenates First-, Middle-, last_name to one string
|
340
|
+
def name
|
341
|
+
result = []
|
342
|
+
result << first_name
|
343
|
+
result << middle_name
|
344
|
+
result << last_name
|
345
|
+
result = result.compact.map {|m| m.to_s.strip }.reject {|i| i.empty? }
|
346
|
+
return result.join(' ') unless result.empty?
|
347
|
+
end
|
348
|
+
|
349
|
+
# Similar as in Person, only displays like "Mr" or "Prof. Dr."
|
350
|
+
def salutation(options={})
|
351
|
+
(self.is_gender_male? ? (return "Mr") : (return "Ms")) if self.gender
|
352
|
+
''
|
353
|
+
end
|
354
|
+
alias_method :salutation_display, :salutation
|
355
|
+
|
356
|
+
# Returns the salutation and name, like "Prof. Dr. Thomas Mann" or "Mr Adam Smith"
|
357
|
+
def salutation_and_name
|
358
|
+
"#{salutation} #{name}".strip
|
359
|
+
end
|
360
|
+
alias_method :salutation_and_name_display, :salutation_and_name
|
361
|
+
|
362
|
+
# returns the province (as full text) or the province_code (e.g. CA)
|
363
|
+
def province_or_province_code
|
364
|
+
self.province.to_s.empty? ? self.province_code : self.province
|
365
|
+
end
|
366
|
+
|
367
|
+
# returns either the full country name or the country code (e.g. DE)
|
368
|
+
def country_or_country_code
|
369
|
+
self.country.to_s.empty? ? self.country_code : self.country
|
370
|
+
end
|
371
|
+
|
372
|
+
# Writes the address as comma delimited string
|
373
|
+
def to_s
|
374
|
+
result = []
|
375
|
+
result << self.address_line_1
|
376
|
+
result << self.address_line_2
|
377
|
+
result << self.city
|
378
|
+
result << self.province_or_province_code
|
379
|
+
result << self.postal_code
|
380
|
+
result << self.country_or_country_code
|
381
|
+
result.compact.map {|m| m.to_s.strip }.reject {|i| i.empty? }.join(", ")
|
382
|
+
end
|
383
|
+
|
384
|
+
# return only attributes with relevant content
|
385
|
+
def content_attributes
|
386
|
+
self.attributes.reject {|k,v| !self.content_column_names.include?(k.to_s)}.symbolize_keys
|
387
|
+
end
|
388
|
+
|
389
|
+
# returns content column name strings
|
390
|
+
def content_column_names
|
391
|
+
self.class.content_column_names
|
392
|
+
end
|
393
|
+
|
394
|
+
# E.g. :billing_addres, :shipping_address
|
395
|
+
def kind
|
396
|
+
self.class.kind
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
@@ -0,0 +1,353 @@
|
|
1
|
+
module MerchantSidekick #:nodoc:
|
2
|
+
module Addressable #:nodoc:
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# Addressable adds address associations in the following way:
|
9
|
+
#
|
10
|
+
# * supports multiple types of addresses, e.g. BusinessAddress
|
11
|
+
# * associations for each type
|
12
|
+
# * adds scopes, finders and other helpers
|
13
|
+
#
|
14
|
+
# E.g.
|
15
|
+
#
|
16
|
+
# class User < ActiveRecord::base
|
17
|
+
# has_addresses :personal, :business, :billing
|
18
|
+
# ...
|
19
|
+
# # => @user.personal_addresses
|
20
|
+
# # => @user.find_personal_address
|
21
|
+
# # => @user.find_or_build_personal_address
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# or
|
25
|
+
#
|
26
|
+
# class User < ActiveRecord::base
|
27
|
+
# has_address
|
28
|
+
# ...
|
29
|
+
# # => @user.address
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
#
|
33
|
+
# class User < ActiveRecord::base
|
34
|
+
# has_address :mailing
|
35
|
+
# ...
|
36
|
+
# # => @user.mailing_address
|
37
|
+
# # => @user.find_mailing_address
|
38
|
+
# # => @user.find_or_build_mailing_address
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
module ClassMethods
|
42
|
+
|
43
|
+
# Defines a single address or a single address per address type
|
44
|
+
def has_address(*arguments)
|
45
|
+
attributes, options = [], {:has_one => true, :has_many => false}
|
46
|
+
arguments.each do |argument|
|
47
|
+
case argument.class.name
|
48
|
+
when 'Hash'
|
49
|
+
options = options.merge(argument)
|
50
|
+
else
|
51
|
+
attributes << argument
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if attributes.empty?
|
56
|
+
has_one :address, :as => :addressable, :dependent => :destroy,
|
57
|
+
:class_name => "MerchantSidekick::Addressable::Address"
|
58
|
+
|
59
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
60
|
+
def build_address_with_addressable(attributes={}, options={})
|
61
|
+
build_address_without_addressable(attributes.merge(:addressable => self), options)
|
62
|
+
end
|
63
|
+
alias_method_chain :build_address, :addressable
|
64
|
+
|
65
|
+
def address_attributes=(attributes)
|
66
|
+
self.address ? self.address.attributes = attributes : self.build_address(attributes)
|
67
|
+
end
|
68
|
+
END
|
69
|
+
else
|
70
|
+
attributes.each do |attribute|
|
71
|
+
# Address decendent
|
72
|
+
# Note: the <attribute>.pluralize.classify makes sure that classify works
|
73
|
+
# for singular attribute, e.g. :business -> BusinessAddress, otherwise,
|
74
|
+
# Rails default would inflect :business -> BusinesAddress
|
75
|
+
address_class = <<-ADDRESS
|
76
|
+
class #{attribute.to_s.pluralize.classify}Address < MerchantSidekick::Addressable::Address
|
77
|
+
def self.kind
|
78
|
+
'#{attribute}'.to_sym
|
79
|
+
end
|
80
|
+
|
81
|
+
#{ attributes.collect {|a| "def self.#{a}?; #{a == attribute ? 'true' : 'false'}; end" }.join("\n") }
|
82
|
+
|
83
|
+
def kind
|
84
|
+
'#{attribute}'.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
#{ attributes.collect {|a| "def #{a}?; #{a == attribute ? 'true' : 'false'}; end" }.join("\n") }
|
88
|
+
end
|
89
|
+
ADDRESS
|
90
|
+
eval address_class, TOPLEVEL_BINDING
|
91
|
+
|
92
|
+
has_one "#{attribute}_address".to_sym,
|
93
|
+
:class_name => "#{attribute.to_s.pluralize.classify}Address",
|
94
|
+
:as => :addressable,
|
95
|
+
:dependent => :destroy
|
96
|
+
|
97
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
98
|
+
def build_#{attribute}_address_with_addressable(attributes={}, options={})
|
99
|
+
build_#{attribute}_address_without_addressable(attributes.merge(:addressable => self), options)
|
100
|
+
end
|
101
|
+
alias_method_chain :build_#{attribute}_address, :addressable
|
102
|
+
|
103
|
+
def find_#{attribute}_address(options={})
|
104
|
+
find_address(:#{attribute}, options)
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_default_#{attribute}_address
|
108
|
+
find_default_address(:#{attribute})
|
109
|
+
end
|
110
|
+
alias_method :default_#{attribute}_address, :find_default_#{attribute}_address
|
111
|
+
|
112
|
+
def find_or_build_#{attribute}_address(options={})
|
113
|
+
find_or_build_address(:#{attribute}, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_#{attribute}_address_or_clone_from(from_address, options={})
|
117
|
+
find_or_clone_address(:#{attribute}, from_address, options)
|
118
|
+
end
|
119
|
+
|
120
|
+
def #{attribute}_address_attributes=(attributes)
|
121
|
+
self.#{attribute}_address ? self.#{attribute}_address.attributes = attributes : self.build_#{attribute}_address(attributes)
|
122
|
+
end
|
123
|
+
END
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class_attribute :acts_as_addressable_options, :instance_writer => false
|
128
|
+
self.acts_as_addressable_options = {
|
129
|
+
:attributes => attributes,
|
130
|
+
:association_type => options[:has_one] ? :has_one : :has_many
|
131
|
+
}
|
132
|
+
|
133
|
+
include MerchantSidekick::Addressable::InstanceMethods
|
134
|
+
extend MerchantSidekick::Addressable::SingletonMethods
|
135
|
+
end
|
136
|
+
|
137
|
+
# Defines a single address or a single address per address type
|
138
|
+
def has_addresses(*arguments)
|
139
|
+
attributes, options = [], {:has_one => false, :has_many => true}
|
140
|
+
arguments.each do |argument|
|
141
|
+
case argument.class.name
|
142
|
+
when 'Hash'
|
143
|
+
options = defaults.merge(argument)
|
144
|
+
else
|
145
|
+
attributes << argument
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
has_many :addresses, :as => :addressable, :dependent => :destroy,
|
150
|
+
:class_name => "MerchantSidekick::Addressable::Address"
|
151
|
+
|
152
|
+
attributes.each do |attribute|
|
153
|
+
address_class = <<-ADDRESS
|
154
|
+
class #{attribute.to_s.pluralize.classify}Address < MerchantSidekick::Addressable::Address
|
155
|
+
def self.kind
|
156
|
+
'#{attribute}'.to_sym
|
157
|
+
end
|
158
|
+
|
159
|
+
#{ attributes.collect {|a| "def self.#{a}?; #{a == attribute ? 'true' : 'false'}; end" }.join("\n") }
|
160
|
+
|
161
|
+
def kind
|
162
|
+
'#{attribute}'.to_sym
|
163
|
+
end
|
164
|
+
|
165
|
+
#{ attributes.collect {|a| "def #{a}?; #{a == attribute ? 'true' : 'false'}; end" }.join("\n") }
|
166
|
+
end
|
167
|
+
ADDRESS
|
168
|
+
eval address_class, TOPLEVEL_BINDING
|
169
|
+
|
170
|
+
has_many "#{attribute.to_s.classify}Address".pluralize.underscore.to_sym,
|
171
|
+
:class_name => "#{attribute.to_s.pluralize.classify}Address",
|
172
|
+
:as => :addressable,
|
173
|
+
:dependent => :destroy
|
174
|
+
|
175
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
176
|
+
def find_#{attribute}_addresses(options={})
|
177
|
+
find_addresses(:all, :#{attribute}, options)
|
178
|
+
end
|
179
|
+
|
180
|
+
def find_default_#{attribute}_address
|
181
|
+
find_default_address(:#{attribute})
|
182
|
+
end
|
183
|
+
alias_method :default_#{attribute}_address, :find_default_#{attribute}_address
|
184
|
+
|
185
|
+
def find_or_build_#{attribute}_address(options={})
|
186
|
+
find_or_build_address(:#{attribute}, options)
|
187
|
+
end
|
188
|
+
|
189
|
+
def find_#{attribute}_address_or_clone_from(from_address, options={})
|
190
|
+
find_or_clone_address(:#{attribute}, from_address, options)
|
191
|
+
end
|
192
|
+
END
|
193
|
+
end
|
194
|
+
|
195
|
+
class_attribute :acts_as_addressable_options, :instance_writer => false
|
196
|
+
self.acts_as_addressable_options = {
|
197
|
+
:attributes => attributes,
|
198
|
+
:association_type => options[:has_one] ? :has_one : :has_many
|
199
|
+
}
|
200
|
+
|
201
|
+
include MerchantSidekick::Addressable::InstanceMethods
|
202
|
+
extend MerchantSidekick::Addressable::SingletonMethods
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
# This module contains class methods
|
208
|
+
module SingletonMethods
|
209
|
+
|
210
|
+
# Helper method to lookup for addresses for a given object.
|
211
|
+
# Example:
|
212
|
+
# Address.find_address_for a_customer_instance
|
213
|
+
def find_all_addresses_for(obj)
|
214
|
+
addressable = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
|
215
|
+
Address.find(
|
216
|
+
:all,
|
217
|
+
:conditions => ["addressable_id = ? AND addressable_type = ?", obj.id, addressable]
|
218
|
+
)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
# This module contains instance methods
|
224
|
+
module InstanceMethods
|
225
|
+
|
226
|
+
# addressable.find_addresses(:first, :billing, conditions)
|
227
|
+
def find_addresses(selector, kind, options = {})
|
228
|
+
defaults = {:order => "created_at DESC"}
|
229
|
+
options = defaults.merge(options).symbolize_keys
|
230
|
+
|
231
|
+
if :has_one == acts_as_addressable_options[:association_type]
|
232
|
+
conditions = options[:conditions] || ''
|
233
|
+
scoped = Address.scoped
|
234
|
+
scoped = scoped.where("addressable_id = ? AND addressable_type = ? AND type LIKE ?",
|
235
|
+
self.id, self.class.base_class.name, "#{kind.to_s.pluralize.classify}Address")
|
236
|
+
scoped = scoped.where(conditions) unless conditions.blank?
|
237
|
+
options.merge!(:conditions => conditions)
|
238
|
+
scoped.send(selector)
|
239
|
+
elsif :has_many == acts_as_addressable_options[:association_type]
|
240
|
+
self.send("#{kind}_addresses").find(selector, options)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def find_address(kind, options={})
|
245
|
+
find_addresses(:first, kind, options)
|
246
|
+
end
|
247
|
+
|
248
|
+
# returns the default address for either :has_one or :has_many
|
249
|
+
# address definitions
|
250
|
+
# Usage:
|
251
|
+
# find_default_address :billing
|
252
|
+
#
|
253
|
+
def find_default_address(kind=nil)
|
254
|
+
kind ||= auto_kind
|
255
|
+
kind = kind.to_sym
|
256
|
+
if :has_one == acts_as_addressable_options[:association_type]
|
257
|
+
if acts_as_addressable_options[:attributes].empty?
|
258
|
+
self.address
|
259
|
+
else
|
260
|
+
self.send("#{kind}_address")
|
261
|
+
end
|
262
|
+
elsif :has_many == acts_as_addressable_options[:association_type]
|
263
|
+
if acts_as_addressable_options[:attributes].empty?
|
264
|
+
self.addresses.find(:first, :order => "udpated_at DESC")
|
265
|
+
else
|
266
|
+
self.send("#{kind}_addresses").find(:first, :order => "udpated_at DESC")
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Find address of kind 'type' or instantiate a new address to relationship.
|
272
|
+
# Optionally, submit attributes to instantiate the address with.
|
273
|
+
# Examples:
|
274
|
+
# find_or_build_address :business
|
275
|
+
# find_or_build_address :business, :street => "100 Infinity Loop"
|
276
|
+
# find_or_build_address :street => "100 Infinity Loop"
|
277
|
+
def find_or_build_address(*args)
|
278
|
+
options = {}
|
279
|
+
attributes = []
|
280
|
+
# if first argument, it determines the type => :kind
|
281
|
+
args.each do |argument|
|
282
|
+
case argument.class.name
|
283
|
+
when /Symbol/, /String/
|
284
|
+
attributes << argument
|
285
|
+
when /Hash/
|
286
|
+
options.merge!( argument )
|
287
|
+
end
|
288
|
+
end
|
289
|
+
kind = attributes.first
|
290
|
+
unless address = find_address(kind, :conditions => options)
|
291
|
+
if :has_one == acts_as_addressable_options[:association_type]
|
292
|
+
if acts_as_addressable_options[:attributes].empty?
|
293
|
+
address = self.build_address(options)
|
294
|
+
else
|
295
|
+
address = self.send("build_#{kind}_address", options)
|
296
|
+
end
|
297
|
+
else
|
298
|
+
if acts_as_addressable_options[:attributes].empty?
|
299
|
+
address = self.addresses.build(options)
|
300
|
+
else
|
301
|
+
address = self.send("#{kind}_addresses").build(options)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
address
|
306
|
+
end
|
307
|
+
|
308
|
+
# Used for finding the billing address if none is present
|
309
|
+
# the billing address is cloned from business address and
|
310
|
+
# if that is not found then a new billing address is created
|
311
|
+
# Usage:
|
312
|
+
# find_or_clone_address :billing, an_address, { :company_name => "Bla Inc." }
|
313
|
+
# or
|
314
|
+
# find_or_clone_address :billing, :shipping # finds billing or copies from shipping address
|
315
|
+
#
|
316
|
+
def find_or_clone_address(to_type, from_address=nil, options={})
|
317
|
+
unless to_address = find_default_address(to_type)
|
318
|
+
if from_address.nil?
|
319
|
+
from_address = "#{to_type}_address".camelize.constantize.new(options)
|
320
|
+
elsif from_address.is_a? Symbol
|
321
|
+
from_address = find_default_address(from_address)
|
322
|
+
elsif from_address.is_a? Hash
|
323
|
+
from_address = "#{to_type}_address".camelize.constantize.new(from_address)
|
324
|
+
end
|
325
|
+
if from_address
|
326
|
+
if :has_one == acts_as_addressable_options[:association_type]
|
327
|
+
to_address = self.send("build_#{to_type}_address", from_address.content_attributes)
|
328
|
+
elsif :has_many == acts_as_addressable_options[:association_type]
|
329
|
+
to_address = self.send("#{to_type}_addresses").build from_address.content_attributes
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
to_address
|
334
|
+
end
|
335
|
+
|
336
|
+
private
|
337
|
+
|
338
|
+
def auto_kind
|
339
|
+
if :has_one==acts_as_addressable_options[:association_type]
|
340
|
+
if acts_as_addressable_options[:attributes].empty?
|
341
|
+
:address
|
342
|
+
else
|
343
|
+
acts_as_addressable_options[:attributes].first
|
344
|
+
end
|
345
|
+
else
|
346
|
+
acts_as_addressable_options[:attributes].first
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
end
|