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.
Files changed (63) hide show
  1. data/.gitignore +12 -0
  2. data/Changelog.md +38 -0
  3. data/Gemfile +2 -0
  4. data/MIT-LICENSE +19 -0
  5. data/README.md +88 -0
  6. data/Rakefile +10 -0
  7. data/lib/merchant_sidekick.rb +45 -0
  8. data/lib/merchant_sidekick/active_merchant/credit_card_payment.rb +117 -0
  9. data/lib/merchant_sidekick/active_merchant/gateways/authorize_net_gateway.rb +26 -0
  10. data/lib/merchant_sidekick/active_merchant/gateways/base.rb +29 -0
  11. data/lib/merchant_sidekick/active_merchant/gateways/bogus_gateway.rb +19 -0
  12. data/lib/merchant_sidekick/active_merchant/gateways/paypal_gateway.rb +43 -0
  13. data/lib/merchant_sidekick/addressable/address.rb +400 -0
  14. data/lib/merchant_sidekick/addressable/addressable.rb +353 -0
  15. data/lib/merchant_sidekick/buyer.rb +99 -0
  16. data/lib/merchant_sidekick/gateway.rb +81 -0
  17. data/lib/merchant_sidekick/install.rb +19 -0
  18. data/lib/merchant_sidekick/invoice.rb +179 -0
  19. data/lib/merchant_sidekick/line_item.rb +128 -0
  20. data/lib/merchant_sidekick/migrations/addressable.rb +47 -0
  21. data/lib/merchant_sidekick/migrations/billing.rb +100 -0
  22. data/lib/merchant_sidekick/migrations/shopping_cart.rb +28 -0
  23. data/lib/merchant_sidekick/money.rb +38 -0
  24. data/lib/merchant_sidekick/order.rb +244 -0
  25. data/lib/merchant_sidekick/payment.rb +59 -0
  26. data/lib/merchant_sidekick/purchase_invoice.rb +180 -0
  27. data/lib/merchant_sidekick/purchase_order.rb +350 -0
  28. data/lib/merchant_sidekick/railtie.rb +7 -0
  29. data/lib/merchant_sidekick/sales_invoice.rb +56 -0
  30. data/lib/merchant_sidekick/sales_order.rb +122 -0
  31. data/lib/merchant_sidekick/sellable.rb +88 -0
  32. data/lib/merchant_sidekick/seller.rb +93 -0
  33. data/lib/merchant_sidekick/shopping_cart/cart.rb +225 -0
  34. data/lib/merchant_sidekick/shopping_cart/line_item.rb +152 -0
  35. data/lib/merchant_sidekick/version.rb +3 -0
  36. data/merchant_sidekick.gemspec +37 -0
  37. data/spec/address_spec.rb +153 -0
  38. data/spec/addressable_spec.rb +250 -0
  39. data/spec/buyer_spec.rb +203 -0
  40. data/spec/cart_line_item_spec.rb +58 -0
  41. data/spec/cart_spec.rb +213 -0
  42. data/spec/config/merchant_sidekick.yml +10 -0
  43. data/spec/credit_card_payment_spec.rb +175 -0
  44. data/spec/fixtures/addresses.yml +97 -0
  45. data/spec/fixtures/line_items.yml +18 -0
  46. data/spec/fixtures/orders.yml +24 -0
  47. data/spec/fixtures/payments.yml +17 -0
  48. data/spec/fixtures/products.yml +12 -0
  49. data/spec/fixtures/users.yml +11 -0
  50. data/spec/gateway_spec.rb +136 -0
  51. data/spec/invoice_spec.rb +79 -0
  52. data/spec/line_item_spec.rb +65 -0
  53. data/spec/order_spec.rb +85 -0
  54. data/spec/payment_spec.rb +14 -0
  55. data/spec/purchase_invoice_spec.rb +70 -0
  56. data/spec/purchase_order_spec.rb +191 -0
  57. data/spec/sales_invoice_spec.rb +58 -0
  58. data/spec/sales_order_spec.rb +107 -0
  59. data/spec/schema.rb +28 -0
  60. data/spec/sellable_spec.rb +34 -0
  61. data/spec/seller_spec.rb +201 -0
  62. data/spec/spec_helper.rb +255 -0
  63. 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