recurly 2.18.24 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (217) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +158 -110
  8. data/Rakefile +6 -0
  9. data/bin/bundle +105 -0
  10. data/bin/coderay +29 -0
  11. data/bin/console +14 -0
  12. data/bin/htmldiff +29 -0
  13. data/bin/ldiff +29 -0
  14. data/bin/pry +29 -0
  15. data/bin/rake +29 -0
  16. data/bin/rspec +29 -0
  17. data/bin/setup +8 -0
  18. data/bin/yard +29 -0
  19. data/bin/yardoc +29 -0
  20. data/bin/yri +29 -0
  21. data/lib/data/ca-certificates.crt +31 -0
  22. data/lib/recurly/client/operations.rb +935 -0
  23. data/lib/recurly/client.rb +198 -0
  24. data/lib/recurly/errors/api_errors.rb +35 -0
  25. data/lib/recurly/errors/network_errors.rb +8 -0
  26. data/lib/recurly/errors.rb +34 -0
  27. data/lib/recurly/pager.rb +119 -0
  28. data/lib/recurly/request.rb +30 -0
  29. data/lib/recurly/requests/account_acquisition_updatable.rb +22 -0
  30. data/lib/recurly/requests/account_create_only.rb +18 -0
  31. data/lib/recurly/requests/account_updatable.rb +50 -0
  32. data/lib/recurly/requests/add_on_create.rb +38 -0
  33. data/lib/recurly/requests/add_on_update.rb +38 -0
  34. data/lib/recurly/requests/address.rb +42 -0
  35. data/lib/recurly/requests/billing_info_create.rb +58 -0
  36. data/lib/recurly/requests/coupon_create_only.rb +66 -0
  37. data/lib/recurly/requests/coupon_updatable.rb +30 -0
  38. data/lib/recurly/requests/create_account.rb +62 -0
  39. data/lib/recurly/requests/create_coupon.rb +90 -0
  40. data/lib/recurly/requests/invoice_create.rb +42 -0
  41. data/lib/recurly/requests/invoice_refund.rb +30 -0
  42. data/lib/recurly/requests/line_item_create.rb +46 -0
  43. data/lib/recurly/requests/plan_create.rb +66 -0
  44. data/lib/recurly/requests/plan_update.rb +70 -0
  45. data/lib/recurly/requests/shipping_address_create.rb +58 -0
  46. data/lib/recurly/requests/shipping_address_update.rb +62 -0
  47. data/lib/recurly/requests/subscription_add_on_create.rb +22 -0
  48. data/lib/recurly/requests/subscription_change_create.rb +42 -0
  49. data/lib/recurly/requests/subscription_create.rb +86 -0
  50. data/lib/recurly/requests/subscription_update.rb +42 -0
  51. data/lib/recurly/requests/update_coupon.rb +30 -0
  52. data/lib/recurly/resource.rb +16 -1112
  53. data/lib/recurly/resources/account.rb +86 -0
  54. data/lib/recurly/resources/account_acquisition.rb +42 -0
  55. data/lib/recurly/resources/account_balance.rb +22 -0
  56. data/lib/recurly/resources/account_note.rb +30 -0
  57. data/lib/recurly/resources/add_on.rb +62 -0
  58. data/lib/recurly/resources/address.rb +42 -0
  59. data/lib/recurly/resources/billing_info.rb +62 -0
  60. data/lib/recurly/resources/coupon.rb +110 -0
  61. data/lib/recurly/resources/coupon_discount.rb +22 -0
  62. data/lib/recurly/resources/coupon_redemption.rb +46 -0
  63. data/lib/recurly/resources/credit_payment.rb +62 -0
  64. data/lib/recurly/resources/error.rb +18 -0
  65. data/lib/recurly/resources/error_may_have_transaction.rb +22 -0
  66. data/lib/recurly/resources/invoice.rb +138 -0
  67. data/lib/recurly/resources/invoice_collection.rb +18 -0
  68. data/lib/recurly/resources/line_item.rb +166 -0
  69. data/lib/recurly/resources/plan.rb +86 -0
  70. data/lib/recurly/resources/settings.rb +18 -0
  71. data/lib/recurly/resources/shipping_address.rb +74 -0
  72. data/lib/recurly/resources/site.rb +46 -0
  73. data/lib/recurly/resources/subscription.rb +134 -0
  74. data/lib/recurly/resources/subscription_add_on.rb +42 -0
  75. data/lib/recurly/resources/subscription_change.rb +54 -0
  76. data/lib/recurly/resources/tax_info.rb +18 -0
  77. data/lib/recurly/resources/transaction.rb +146 -0
  78. data/lib/recurly/resources/unique_coupon_code.rb +38 -0
  79. data/lib/recurly/resources/user.rb +38 -0
  80. data/lib/recurly/schema/json_deserializer.rb +53 -0
  81. data/lib/recurly/schema/json_parser.rb +71 -0
  82. data/lib/recurly/schema/request_caster.rb +66 -0
  83. data/lib/recurly/schema/schema_factory.rb +50 -0
  84. data/lib/recurly/schema/schema_validator.rb +125 -0
  85. data/lib/recurly/schema.rb +114 -0
  86. data/lib/recurly/version.rb +1 -10
  87. data/lib/recurly.rb +14 -151
  88. data/recurly.gemspec +32 -0
  89. data/scripts/build +4 -0
  90. data/scripts/clean +6 -0
  91. data/scripts/test +3 -0
  92. metadata +129 -179
  93. data/lib/recurly/account.rb +0 -214
  94. data/lib/recurly/account_acquisition.rb +0 -27
  95. data/lib/recurly/account_balance.rb +0 -22
  96. data/lib/recurly/add_on.rb +0 -51
  97. data/lib/recurly/address.rb +0 -25
  98. data/lib/recurly/adjustment.rb +0 -84
  99. data/lib/recurly/api/errors.rb +0 -208
  100. data/lib/recurly/api/net_http_adapter.rb +0 -111
  101. data/lib/recurly/api.rb +0 -110
  102. data/lib/recurly/billing_info.rb +0 -114
  103. data/lib/recurly/coupon.rb +0 -136
  104. data/lib/recurly/credit_payment.rb +0 -32
  105. data/lib/recurly/currency_percentage_tier.rb +0 -17
  106. data/lib/recurly/custom_field.rb +0 -15
  107. data/lib/recurly/delivery.rb +0 -19
  108. data/lib/recurly/dunning_campaign.rb +0 -30
  109. data/lib/recurly/dunning_cycle.rb +0 -18
  110. data/lib/recurly/error.rb +0 -13
  111. data/lib/recurly/gift_card.rb +0 -85
  112. data/lib/recurly/helper.rb +0 -51
  113. data/lib/recurly/invoice.rb +0 -305
  114. data/lib/recurly/invoice_collection.rb +0 -14
  115. data/lib/recurly/invoice_template.rb +0 -14
  116. data/lib/recurly/item.rb +0 -36
  117. data/lib/recurly/js.rb +0 -14
  118. data/lib/recurly/juris_detail.rb +0 -15
  119. data/lib/recurly/measured_unit.rb +0 -16
  120. data/lib/recurly/money.rb +0 -120
  121. data/lib/recurly/note.rb +0 -14
  122. data/lib/recurly/percentage_tier.rb +0 -17
  123. data/lib/recurly/plan.rb +0 -44
  124. data/lib/recurly/purchase.rb +0 -238
  125. data/lib/recurly/redemption.rb +0 -46
  126. data/lib/recurly/resource/association.rb +0 -16
  127. data/lib/recurly/resource/errors.rb +0 -20
  128. data/lib/recurly/resource/pager.rb +0 -313
  129. data/lib/recurly/shipping_address.rb +0 -26
  130. data/lib/recurly/shipping_fee.rb +0 -17
  131. data/lib/recurly/shipping_method.rb +0 -13
  132. data/lib/recurly/sub_add_on_percentage_tier.rb +0 -17
  133. data/lib/recurly/subscription/add_ons.rb +0 -82
  134. data/lib/recurly/subscription.rb +0 -367
  135. data/lib/recurly/subscription_add_on.rb +0 -63
  136. data/lib/recurly/tax_detail.rb +0 -18
  137. data/lib/recurly/tax_type.rb +0 -13
  138. data/lib/recurly/tier.rb +0 -18
  139. data/lib/recurly/transaction/errors.rb +0 -115
  140. data/lib/recurly/transaction.rb +0 -131
  141. data/lib/recurly/usage.rb +0 -28
  142. data/lib/recurly/verify.rb +0 -12
  143. data/lib/recurly/webhook/account_notification.rb +0 -13
  144. data/lib/recurly/webhook/billing_info_update_failed_notification.rb +0 -6
  145. data/lib/recurly/webhook/billing_info_updated_notification.rb +0 -6
  146. data/lib/recurly/webhook/canceled_account_notification.rb +0 -6
  147. data/lib/recurly/webhook/canceled_gift_card_notification.rb +0 -6
  148. data/lib/recurly/webhook/canceled_subscription_notification.rb +0 -6
  149. data/lib/recurly/webhook/closed_credit_invoice_notification.rb +0 -6
  150. data/lib/recurly/webhook/closed_invoice_notification.rb +0 -6
  151. data/lib/recurly/webhook/credit_payment_notification.rb +0 -12
  152. data/lib/recurly/webhook/deactivated_item_notification.rb +0 -6
  153. data/lib/recurly/webhook/deleted_shipping_address_notification.rb +0 -6
  154. data/lib/recurly/webhook/dunning_notification.rb +0 -14
  155. data/lib/recurly/webhook/expired_subscription_notification.rb +0 -6
  156. data/lib/recurly/webhook/failed_charge_invoice_notification.rb +0 -6
  157. data/lib/recurly/webhook/failed_payment_notification.rb +0 -6
  158. data/lib/recurly/webhook/fraud_info_updated_notification.rb +0 -6
  159. data/lib/recurly/webhook/gift_card_notification.rb +0 -8
  160. data/lib/recurly/webhook/invoice_notification.rb +0 -12
  161. data/lib/recurly/webhook/item_notification.rb +0 -7
  162. data/lib/recurly/webhook/low_balance_gift_card_notification.rb +0 -6
  163. data/lib/recurly/webhook/new_account_notification.rb +0 -6
  164. data/lib/recurly/webhook/new_charge_invoice_notification.rb +0 -6
  165. data/lib/recurly/webhook/new_credit_invoice_notification.rb +0 -6
  166. data/lib/recurly/webhook/new_credit_payment_notification.rb +0 -6
  167. data/lib/recurly/webhook/new_dunning_event_notification.rb +0 -6
  168. data/lib/recurly/webhook/new_invoice_notification.rb +0 -6
  169. data/lib/recurly/webhook/new_item_notification.rb +0 -6
  170. data/lib/recurly/webhook/new_shipping_address_notification.rb +0 -6
  171. data/lib/recurly/webhook/new_subscription_notification.rb +0 -6
  172. data/lib/recurly/webhook/new_usage_notification.rb +0 -8
  173. data/lib/recurly/webhook/notification.rb +0 -18
  174. data/lib/recurly/webhook/paid_charge_invoice_notification.rb +0 -6
  175. data/lib/recurly/webhook/past_due_charge_invoice_notification.rb +0 -6
  176. data/lib/recurly/webhook/past_due_invoice_notification.rb +0 -6
  177. data/lib/recurly/webhook/paused_subscription_renewal_notification.rb +0 -6
  178. data/lib/recurly/webhook/prerenewal_notification.rb +0 -6
  179. data/lib/recurly/webhook/processing_charge_invoice_notification.rb +0 -6
  180. data/lib/recurly/webhook/processing_credit_invoice_notification.rb +0 -6
  181. data/lib/recurly/webhook/processing_invoice_notification.rb +0 -6
  182. data/lib/recurly/webhook/processing_payment_notification.rb +0 -6
  183. data/lib/recurly/webhook/purchased_gift_card_notification.rb +0 -7
  184. data/lib/recurly/webhook/reactivated_account_notification.rb +0 -6
  185. data/lib/recurly/webhook/reactivated_item_notification.rb +0 -6
  186. data/lib/recurly/webhook/redeemed_gift_card_notification.rb +0 -7
  187. data/lib/recurly/webhook/regenerated_gift_card_notification.rb +0 -6
  188. data/lib/recurly/webhook/renewed_subscription_notification.rb +0 -6
  189. data/lib/recurly/webhook/reopened_charge_invoice_notification.rb +0 -6
  190. data/lib/recurly/webhook/reopened_credit_invoice_notification.rb +0 -6
  191. data/lib/recurly/webhook/scheduled_payment_notification.rb +0 -6
  192. data/lib/recurly/webhook/scheduled_subscription_pause_notification.rb +0 -6
  193. data/lib/recurly/webhook/scheduled_subscription_update_notification.rb +0 -6
  194. data/lib/recurly/webhook/subscription_notification.rb +0 -12
  195. data/lib/recurly/webhook/subscription_pause_canceled_notification.rb +0 -6
  196. data/lib/recurly/webhook/subscription_pause_modified_notification.rb +0 -6
  197. data/lib/recurly/webhook/subscription_paused_notification.rb +0 -6
  198. data/lib/recurly/webhook/subscription_resumed_notification.rb +0 -6
  199. data/lib/recurly/webhook/successful_payment_notification.rb +0 -6
  200. data/lib/recurly/webhook/successful_refund_notification.rb +0 -6
  201. data/lib/recurly/webhook/transaction_authorized_notification.rb +0 -6
  202. data/lib/recurly/webhook/transaction_notification.rb +0 -12
  203. data/lib/recurly/webhook/transaction_status_updated_notification.rb +0 -6
  204. data/lib/recurly/webhook/updated_account_notification.rb +0 -6
  205. data/lib/recurly/webhook/updated_balance_gift_card_notification.rb +0 -7
  206. data/lib/recurly/webhook/updated_gift_card_notification.rb +0 -6
  207. data/lib/recurly/webhook/updated_invoice_notification.rb +0 -6
  208. data/lib/recurly/webhook/updated_item_notification.rb +0 -6
  209. data/lib/recurly/webhook/updated_shipping_address_notification.rb +0 -6
  210. data/lib/recurly/webhook/updated_subscription_notification.rb +0 -6
  211. data/lib/recurly/webhook/void_payment_notification.rb +0 -6
  212. data/lib/recurly/webhook/voided_credit_invoice_notification.rb +0 -6
  213. data/lib/recurly/webhook/voided_credit_payment_notification.rb +0 -6
  214. data/lib/recurly/webhook.rb +0 -113
  215. data/lib/recurly/xml/nokogiri.rb +0 -60
  216. data/lib/recurly/xml/rexml.rb +0 -52
  217. data/lib/recurly/xml.rb +0 -122
@@ -1,1131 +1,35 @@
1
- require 'date'
2
- require 'erb'
3
-
4
1
  module Recurly
5
- # The base class for all Recurly resources (e.g. {Account}, {Subscription},
6
- # {Transaction}).
7
- #
8
- # Resources behave much like
9
- # {ActiveModel}[http://rubydoc.info/gems/activemodel] classes, especially
10
- # like {ActiveRecord}[http://rubydoc.info/gems/activerecord].
11
- #
12
- # == Life Cycle
13
- #
14
- # To take you through the typical life cycle of a resource, we'll use
15
- # {Recurly::Account} as an example.
16
- #
17
- # === Creating a Record
18
- #
19
- # You can instantiate a record before attempting to save it.
20
- #
21
- # account = Recurly::Account.new :first_name => 'Walter'
22
- #
23
- # Once instantiated, you can assign and reassign any attribute.
24
- #
25
- # account.first_name = 'Walt'
26
- # account.last_name = 'White'
27
- #
28
- # When you're ready to save, do so.
29
- #
30
- # account.save # => false
31
- #
32
- # If save returns +false+, validation likely failed. You can check the record
33
- # for errors.
34
- #
35
- # account.errors # => {"account_code"=>["can't be blank"]}
36
- #
37
- # Once the errors are fixed, you can try again.
38
- #
39
- # account.account_code = 'heisenberg'
40
- # account.save # => true
41
- #
42
- # The object will be updated with any information provided by the server
43
- # (including any UUIDs set).
44
- #
45
- # account.created_at # => 2011-04-30 07:13:35 -0700
46
- #
47
- # You can also create accounts in one fell swoop.
48
- #
49
- # Recurly::Account.create(
50
- # :first_name => 'Jesse'
51
- # :last_name => 'Pinkman'
52
- # :account_code => 'capn_cook'
53
- # )
54
- # # => #<Recurly::Account account_code: "capn_cook" ...>
55
- #
56
- # You can use alternative "bang" methods for exception control. If the record
57
- # fails to save, a Recurly::Resource::Invalid exception will be raised.
58
- #
59
- # begin
60
- # account = Recurly::Account.new :first_name => 'Junior'
61
- # account.save!
62
- # rescue Recurly::Resource::Invalid
63
- # p account.errors
64
- # end
65
- #
66
- # You can access the invalid record from the exception itself (if, for
67
- # example, you use the <tt>create!</tt> method).
68
- #
69
- # begin
70
- # Recurly::Account.create! :first_name => 'Skylar', :last_name => 'White'
71
- # rescue Recurly::Resource::Invalid => e
72
- # p e.record.errors
73
- # end
74
- #
75
- # === Fetching a Record
76
- #
77
- # Records are fetched by their unique identifiers.
78
- #
79
- # account = Recurly::Account.find 'better_call_saul'
80
- # # => #<Recurly::Account account_code: "better_call_saul" ...>
81
- #
82
- # If the record doesn't exist, a Recurly::Resource::NotFound exception will
83
- # be raised.
84
- #
85
- # === Updating a Record
86
- #
87
- # Once fetched, a record can be updated with a hash of attributes.
88
- #
89
- # account.update_attributes :first_name => 'Saul', :last_name => 'Goodman'
90
- # # => true
91
- #
92
- # (A bang method, update_attributes!, will raise Recurly::Resource::Invalid.)
93
- #
94
- # You can also update a record by setting attributes and calling save.
95
- #
96
- # account.last_name = 'McGill'
97
- # account.save # Alternatively, call save!
98
- #
99
- # === Deleting a Record
100
- #
101
- # To delete (deactivate, close, etc.) a fetched record, merely call destroy
102
- # on it.
103
- #
104
- # account.destroy # => true
105
- #
106
- # === Fetching a List of Records
107
- #
108
- # If you want to iterate over a list of accounts, you can use a Pager.
109
- #
110
- # pager = Account.paginate :per_page => 50
111
- #
112
- # If you want to iterate over _every_ record, a convenience method will
113
- # automatically paginate:
114
- #
115
- # Account.find_each { |account| p account }
2
+ # This class represents an object as it exists on the
3
+ # Recurly servers. It is generated from a response. If you wish to
4
+ # update or change a resource, you need to send a request to the server
5
+ # and get a new Resource.
116
6
  class Resource
117
- require 'recurly/resource/errors'
118
- require 'recurly/resource/pager'
119
- require 'recurly/resource/association'
120
-
121
- # Raised when a record cannot be found.
122
- #
123
- # @example
124
- # begin
125
- # Recurly::Account.find 'tortuga'
126
- # rescue Recurly::Resource::NotFound => e
127
- # e.message # => "Can't find Account with account_code = tortuga"
128
- # end
129
- class NotFound < API::NotFound
130
- def initialize(message)
131
- set_message message
132
- end
133
- end
134
-
135
- # Raised when a record is invalid.
136
- #
137
- # @example
138
- # begin
139
- # Recurly::Account.create! :first_name => "Flynn"
140
- # rescue Recurly::Resource::Invalid => e
141
- # e.record.errors # => errors: {"account_code"=>["can't be blank"]}>
142
- # end
143
- class Invalid < Error
144
- # @return [Resource, nil] The invalid record.
145
- attr_reader :record
146
-
147
- def initialize(message)
148
- if message.is_a? Resource
149
- @record = message
150
- set_message(record_to_message)
151
- else
152
- set_message(message)
153
- end
154
- end
155
-
156
- private
157
-
158
- def record_to_message
159
- @record.errors.map do |k, v|
160
- message = v.join(', ')
161
- k == 'base' ? message : "#{k} #{message}"
162
- end.join('; ')
163
- end
164
- end
165
-
166
- class << self
167
- # @return [String] The demodulized name of the resource class.
168
- # @example
169
- # Recurly::Account.name # => "Account"
170
- def resource_name
171
- Helper.demodulize name
172
- end
173
-
174
- # @return [String] The underscored, pluralized name of the resource
175
- # class.
176
- # @example
177
- # Recurly::Account.collection_name # => "accounts"
178
- def collection_name
179
- Helper.pluralize Helper.underscore(resource_name)
180
- end
181
- alias collection_path collection_name
182
-
183
- # @return [String] The underscored name of the resource class.
184
- # @example
185
- # Recurly::Account.member_name # => "account"
186
- def member_name
187
- Helper.underscore resource_name
188
- end
189
-
190
- # @return [String] The relative path to a resource's identifier from the
191
- # API's base URI.
192
- # @param uuid [String, nil]
193
- # @example
194
- # Recurly::Account.member_path "code" # => "accounts/code"
195
- # Recurly::Account.member_path nil # => "accounts"
196
- def member_path(uuid)
197
- uuid = ERB::Util.url_encode(uuid) if uuid
198
- [collection_path, uuid].compact.join '/'
199
- end
200
-
201
- # @return [String] The root key for this resource's xml document
202
- def xml_root_key
203
- self.member_name
204
- end
205
-
206
- # @return [Array] Per attribute, defines readers, writers, boolean and
207
- # change-tracking methods.
208
- # @param attribute_names [Array] An array of attribute names.
209
- # @example
210
- # class Account < Resource
211
- # define_attribute_methods [:name]
212
- # end
213
- #
214
- # a = Account.new
215
- # a.name? # => false
216
- # a.name # => nil
217
- # a.name = "Stephen"
218
- # a.name? # => true
219
- # a.name # => "Stephen"
220
- # a.name_changed? # => true
221
- # a.name_was # => nil
222
- # a.name_change # => [nil, "Stephen"]
223
- def define_attribute_methods(attribute_names)
224
- @attribute_names = attribute_names.map! { |m| m.to_s }.sort!.freeze
225
- remove_const :AttributeMethods if constants.include? :AttributeMethods
226
- include const_set :AttributeMethods, Module.new {
227
- attribute_names.each do |name|
228
- define_method(name) { self[name] } # Get.
229
- define_method("#{name}=") { |value| self[name] = value } # Set.
230
- define_method("#{name}?") { !!self[name] } # Present.
231
- define_method("#{name}_change") { changes[name] } # Dirt...
232
- define_method("#{name}_changed?") { changed_attributes.key? name }
233
- define_method("#{name}_was") { changed_attributes[name] }
234
- define_method("#{name}_previously_changed?") {
235
- previous_changes.key? name
236
- }
237
- define_method("#{name}_previously_was") {
238
- previous_changes[name].first if previous_changes.key? name
239
- }
240
- end
241
- }
242
- end
243
-
244
- # @return [Array, nil] The list of attribute names defined for the
245
- # resource class.
246
- attr_reader :attribute_names
247
-
248
- # @return [Pager] A pager with an iterable collection of records
249
- # @param options [Hash] A hash of pagination options
250
- # @option options [Integer] :per_page The number of records returned per
251
- # page
252
- # @option options [DateTime, Time, Integer] :cursor A timestamp that the
253
- # pager will skim back to and return records created before it
254
- # @option options [String] :etag When set, will raise
255
- # {Recurly::API::NotModified} if the pager's loaded page content has
256
- # not changed
257
- # @example Fetch 50 records and iterate over them
258
- # Recurly::Account.paginate(:per_page => 50).each { |a| p a }
259
- # @example Fetch records before January 1, 2011
260
- # Recurly::Account.paginate(:cursor => Time.new(2011, 1, 1))
261
- def paginate(options = {})
262
- Pager.new self, options
263
- end
264
- alias scoped paginate
265
- alias where paginate
266
-
267
- def all(options = {})
268
- paginate(options).to_a
269
- end
270
-
271
- # @return [Hash] Defined scopes per resource.
272
- def scopes
273
- @scopes ||= Recurly::Helper.hash_with_indifferent_read_access
274
- end
275
-
276
- # @return [Module] Module of scopes methods.
277
- def scopes_helper
278
- @scopes_helper ||= Module.new.tap { |helper| extend helper }
279
- end
280
-
281
- # Defines a new resource scope.
282
- #
283
- # @return [Proc]
284
- # @param [Symbol] name the scope name
285
- # @param [Hash] params the scope params
286
- def scope(name, params = {})
287
- scopes[name = name.to_s] = params
288
- scopes_helper.send(:define_method, name) { paginate scopes[name] }
289
- end
290
-
291
- # Iterates through every record by automatically paging.
292
- #
293
- # @option options [Hash] Optional hash to pass to Pager#paginate
294
- #
295
- # @return [nil]
296
- # @param [Integer] per_page The number of records returned per request.
297
- # @yield [record]
298
- # @see Pager#paginate
299
- # @example
300
- # Recurly::Account.find_each { |a| p a }
301
- # @example With sorting and filter
302
- # opts = {
303
- # begin_time: DateTime.new(2016,1,1),
304
- # sort: :updated_at
305
- # }
306
- # Recurly::Account.find_each(opts) do |a|
307
- # puts a.inspect
308
- # end
309
- def find_each(options = {}, &block)
310
- paginate(options).find_each(&block)
311
- end
312
-
313
- # @return [Integer] The total record count of the resource in question.
314
- # @see Pager#count
315
- # @example
316
- # Recurly::Account.count # => 42
317
- def count
318
- paginate.count
319
- end
320
-
321
- # @api internal
322
- # @return [Resource, nil]
323
- def first
324
- paginate(:per_page => 1).first
325
- end
326
-
327
- # @return [Resource] A record matching the designated unique identifier.
328
- # @param [String] uuid The unique identifier of the resource to be
329
- # retrieved.
330
- # @param [Hash] options A hash of options.
331
- # @option options [String] :etag When set, will raise {API::NotModified}
332
- # if the record content has not changed.
333
- # @raise [Error] If the resource has no identifier (and thus cannot be
334
- # retrieved).
335
- # @raise [NotFound] If no resource can be found for the supplied
336
- # identifier (or the supplied identifier is +nil+).
337
- # @raise [API::NotModified] If the <tt>:etag</tt> option is set and
338
- # matches the server's.
339
- # @example
340
- # Recurly::Account.find "heisenberg"
341
- # # => #<Recurly::Account account_code: "heisenberg", ...>
342
- # Use the following identifiers for these types of objects:
343
- # for accounts use account_code
344
- # for plans use plan_code
345
- # for invoices use invoice_number
346
- # for subscriptions use uuid
347
- # for transactions use uuid
348
- def find(uuid, options = {})
349
- if uuid.nil? || uuid.to_s.empty?
350
- raise NotFound, "can't find a record with nil identifier"
351
- end
352
-
353
- begin
354
- from_response API.get(member_path(uuid), {}, options)
355
- rescue API::NotFound => e
356
- raise NotFound, e.description
357
- end
358
- end
359
-
360
- # Instantiates and attempts to save a record.
361
- #
362
- # @return [Resource] The record.
363
- # @raise [Transaction::Error] A monetary transaction failed.
364
- # @see create!
365
- def create(attributes = {})
366
- new(attributes) { |record| record.save }
367
- end
368
-
369
- # Instantiates and attempts to save a record.
370
- #
371
- # @return [Resource] The saved record.
372
- # @raise [Invalid] The record is invalid.
373
- # @raise [Transaction::Error] A monetary transaction failed.
374
- # @see create
375
- def create!(attributes = {})
376
- new(attributes) { |record| record.save! }
377
- end
378
-
379
- # Instantiates a record from an HTTP response, setting the record's
380
- # response attribute in the process.
381
- #
382
- # @return [Resource]
383
- # @param response [Net::HTTPResponse]
384
- def from_response(response)
385
- content_type = response['Content-Type']
386
-
387
- case content_type
388
- when %r{application/pdf}
389
- response.body
390
- when %r{application/xml}
391
- record = from_xml response.body
392
- record.instance_eval { @etag, @response = response['ETag'], response }
393
- record
394
- else
395
- raise Recurly::Error, "Content-Type \"#{content_type}\" is not accepted"
396
- end
397
- end
398
-
399
- # Instantiates a record from an XML blob: either a String or XML element.
400
- #
401
- # Assuming the record is from an API response, the record is flagged as
402
- # persisted.
403
- #
404
- # @return [Resource]
405
- # @param xml [String, REXML::Element, Nokogiri::XML::Node]
406
- # @see from_response
407
- def from_xml(xml)
408
- xml = XML.new xml
409
- record = new
410
-
411
- xml.root.attributes.each do |name, value|
412
- record.instance_variable_set "@#{name}", value.to_s
413
- end
414
-
415
- xml.each_element do |el|
416
- # skip this element if it's an xml comment
417
- next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.is_a?(Nokogiri::XML::Comment)
418
-
419
- if el.name == 'a'
420
- record.links[el.attribute('name').value] = {
421
- :method => el.attribute('method').to_s,
422
- :href => el.attribute('href').value
423
- }
424
- next
425
- end
7
+ extend Schema::SchemaFactory
8
+ extend Schema::JsonDeserializer
9
+ include Schema::SchemaValidator
426
10
 
427
- # Nokogiri on Jruby-1.7.19 likes to throw NullPointer exceptions
428
- # if you try to run certian operations like el.attribute(''). Since
429
- # we dont care about text nodes, let's just skip them
430
- next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.node_type == Nokogiri::XML::Node::TEXT_NODE
431
-
432
- if association = find_association(el.name)
433
- class_name = association_class_name(association, el.name)
434
- resource_class = Recurly.const_get(class_name)
435
- is_many = association.relation == :has_many
436
-
437
- # Is this a link, or is it embedded data?
438
- if el.children.empty? && href = el.attribute('href')
439
- if is_many
440
- record[el.name] = Pager.new(
441
- resource_class, :uri => href.value, :parent => record
442
- )
443
- else
444
- record.links[el.name] = {
445
- :resource_class => resource_class,
446
- :method => :get,
447
- :href => href.value
448
- }
449
- end
450
- else
451
- if is_many
452
- resources = el.elements.map { |e| resource_class.from_xml(e) }
453
- record[el.name] = resources
454
- else
455
- record[el.name] = resource_class.from_xml(el)
456
- end
457
- end
458
- else
459
- # TODO name tax_type conflicts with the TaxType
460
- # class so if we get to this point was can assume
461
- # it's the string. Will need to refactor this
462
- if el.name == 'tax_type'
463
- record[el.name] = el.text
464
- else
465
- val = XML.cast(el)
466
-
467
- # TODO we have to clear changed attributes after
468
- # parsing here or else it always serializes. Need
469
- # a better way of handling changed attributes
470
- if el.name == 'address' && val.kind_of?(Hash)
471
- address = Address.new(val)
472
- address.instance_variable_set(:@changed_attributes, {})
473
- record[el.name] = address
474
- else
475
- record[el.name] = val
476
- end
477
- end
478
- end
479
- end
480
-
481
- record.persist! if record.respond_to? :persist!
482
- record
483
- end
484
-
485
- # @return [Array] A list of associations for the current class.
486
- def associations
487
- @associations ||= []
488
- end
489
-
490
- # @return [Array] A list of associated resource classes with
491
- # the relation [:has_many, :has_one, :belongs_to] for the current class.
492
- def associations_for_relation(relation)
493
- associations.select{ |a| a.relation == relation }.map(&:resource_class)
494
- end
495
-
496
- def association_class_name(association, el_name)
497
- return association.class_name if association.class_name
498
- Helper.classify(el_name)
499
- end
500
-
501
- # @return [Association, nil] Find association for the current class
502
- # with resource class name.
503
- def find_association(resource_class)
504
- associations.find{ |a| a.resource_class.to_s == resource_class.to_s }
505
- end
506
-
507
- def associations_helper
508
- @associations_helper ||= Module.new.tap { |helper| include helper }
509
- end
510
-
511
- # Establishes a has_many association.
512
- #
513
- # @return [Proc, nil]
514
- # @param collection_name [Symbol] Association name.
515
- # @param options [Hash] A hash of association options.
516
- # @option options [true, false] :readonly Define a setter when false, defaults to true
517
- # [String] :class_name Actual associated resource class name
518
- # if not same as collection_name.
519
- def has_many(collection_name, options = {})
520
- associations << Association.new(:has_many, collection_name.to_s, options)
521
- associations_helper.module_eval do
522
- define_method collection_name do
523
- if self[collection_name]
524
- self[collection_name]
525
- else
526
- attributes[collection_name.to_s] = []
527
- end
528
- end
529
- if options.key?(:readonly) && options[:readonly] == false
530
- define_method "#{collection_name}=" do |collection|
531
- self[collection_name] = collection
532
- end
533
- end
534
- end
535
- end
536
-
537
- # Establishes a has_one association.
538
- #
539
- # @return [Proc, nil]
540
- # @param member_name [Symbol] Association name.
541
- # @param options [Hash] A hash of association options.
542
- # @option options [true, false] :readonly Don't define a setter.
543
- # [String] :class_name Actual associated resource class name
544
- # if not same as member_name.
545
- def has_one(member_name, options = {})
546
- associations << Association.new(:has_one, member_name.to_s, options)
547
- associations_helper.module_eval do
548
- define_method(member_name) { self[member_name] }
549
- if options.key?(:readonly) && options[:readonly] == false
550
- associated = Recurly.const_get Helper.classify(member_name), false
551
- define_method "#{member_name}=" do |member|
552
- associated_uri = "#{path}/#{member_name}"
553
- self[member_name] = case member
554
- when Hash
555
- associated.send :new, member.merge(:uri => associated_uri)
556
- when associated
557
- member.uri = associated_uri and member
558
- else
559
- raise ArgumentError, "expected #{associated}"
560
- end
561
- end
562
- define_method "build_#{member_name}" do |*args|
563
- attributes = args.shift || {}
564
- self[member_name] = associated.send(
565
- :new, attributes.merge(:uri => "#{path}/#{associated.member_name}")
566
- ).tap { |child| child.attributes[self.class.member_name] = self }
567
- end
568
- define_method "create_#{member_name}" do |*args|
569
- send("build_#{member_name}", *args).tap { |child| child.save }
570
- end
571
- end
572
- end
573
- end
574
-
575
- # Establishes a belongs_to association.
576
- #
577
- # @return [Proc]
578
- # @param parent_name [Symbol] Association name.
579
- # @param options [Hash] A hash of association options.
580
- # @option options [true, false] :readonly Don't define a setter.
581
- # [String] :class_name Actual associated resource class name
582
- # if not same as parent_name.
583
- def belongs_to(parent_name, options = {})
584
- associations << Association.new(:belongs_to, parent_name.to_s, options)
585
- associations_helper.module_eval do
586
- define_method(parent_name) { self[parent_name] }
587
- if options.key?(:readonly) && options[:readonly] == false
588
- define_method "#{parent_name}=" do |parent|
589
- self[parent_name] = parent
590
- end
591
- end
592
- end
593
- end
594
-
595
- # @return [:has_many, :has_one, :belongs_to, nil] An association type.
596
- def reflect_on_association(name)
597
- a = find_association(name)
598
- a.relation if a
599
- end
600
-
601
- def embedded!(root_index = false)
602
- protected :initialize
603
- private_class_method(*%w(create create!))
604
- unless root_index
605
- private_class_method(*%w(all find_each first paginate scoped where))
606
- end
607
- end
608
-
609
- def find_resource_class(name)
610
- resource_name = Helper.classify(name)
611
- if Recurly.const_defined?(resource_name, false) && Recurly.const_get(resource_name, false).instance_of?(Class)
612
- Recurly.const_get(resource_name, false)
613
- end
614
- end
615
- end
616
-
617
- # @return [Hash] The raw hash of record attributes.
618
11
  attr_reader :attributes
619
12
 
620
- # @return [Net::HTTPResponse, nil] The most recent response object for the
621
- # record (updated during {#save} and {#destroy}).
622
- attr_reader :response
623
-
624
- # @return [String, nil] An ETag for the current record.
625
- attr_reader :etag
626
-
627
- # @return [String, nil] A writer to override the URI the record saves to.
628
- attr_writer :uri
629
-
630
- # @return [Resource] A new resource instance.
631
- # @param attributes [Hash] A hash of attributes.
632
- def initialize(attributes = {})
633
- if instance_of? Resource
634
- raise Error,
635
- "#{self.class} is an abstract class and cannot be instantiated"
636
- end
637
-
638
- @attributes, @new_record, @destroyed, @uri, @href = {}, true, false
639
- self.attributes = attributes
640
- yield self if block_given?
641
- end
642
-
643
- # @return [self] Reloads the record from the server.
644
- def reload(response = nil)
645
- if response
646
- return if response.body.to_s.length.zero?
647
- fresh = self.class.from_response response
648
- else
649
- options = {:etag => (etag unless changed?)}
650
- fresh = if @href
651
- self.class.from_response API.get(@href, {}, options)
652
- else
653
- self.class.find(to_param, options)
654
- end
655
- end
656
- fresh and copy_from fresh
657
- persist! true
658
- self
659
- rescue API::NotModified
660
- self
661
- end
662
-
663
- # @return [Hash] Hash of changed attributes.
664
- # @see #changes
665
- def changed_attributes
666
- @changed_attributes ||= {}
667
- end
668
-
669
- # @return [Array] A list of changed attribute keys.
670
- def changed
671
- changed_attributes.keys
672
- end
673
-
674
- # Do any attributes have unsaved changes?
675
- # @return [true, false]
676
- def changed?
677
- !changed_attributes.empty?
678
- end
679
-
680
- # @return [Hash] Map of changed attributes to original value and new value.
681
- def changes
682
- changed_attributes.inject({}) { |changes, (key, original_value)|
683
- changes[key] = [original_value, self[key]] and changes
684
- }
685
- end
686
-
687
- # @return [Hash] Previously-changed attributes.
688
- # @see #changes
689
- def previous_changes
690
- @previous_changes ||= {}
691
- end
692
-
693
- # Is the record new (i.e., not saved on Recurly's servers)?
694
- #
695
- # @return [true, false]
696
- # @see #persisted?
697
- # @see #destroyed?
698
- def new_record?
699
- @new_record
700
- end
701
-
702
- # Has the record been destroyed? (Set +true+ after a successful destroy.)
703
- # @return [true, false]
704
- # @see #new_record?
705
- # @see #persisted?
706
- def destroyed?
707
- @destroyed
708
- end
709
-
710
- # Has the record persisted (i.e., saved on Recurly's servers)?
711
- #
712
- # @return [true, false]
713
- # @see #new_record?
714
- # @see #destroyed?
715
- def persisted?
716
- !(new_record? || destroyed?)
717
- end
718
-
719
- # The value of a specified attribute, lazily fetching any defined
720
- # association.
721
- #
722
- # @param key [Symbol, String] The name of the attribute to be fetched.
723
- # @example
724
- # account.read_attribute :first_name # => "Ted"
725
- # account[:last_name] # => "Beneke"
726
- # @see #write_attribute
727
- def read_attribute(key)
728
- key = key.to_s
729
- if attributes.key? key
730
- value = attributes[key]
731
- elsif links.key?(key) && self.class.reflect_on_association(key)
732
- value = attributes[key] = follow_link key
733
- end
734
- value
735
- end
736
- alias [] read_attribute
737
-
738
- # Sets the value of a specified attribute.
739
- #
740
- # @param key [Symbol, String] The name of the attribute to be set.
741
- # @param value [Object] The value the attribute will be set to.
742
- # @example
743
- # account.write_attribute :first_name, 'Gus'
744
- # account[:company_name] = 'Los Pollos Hermanos'
745
- # @see #read_attribute
746
- def write_attribute(key, value)
747
- if changed_attributes.key?(key = key.to_s)
748
- changed_attributes.delete key if changed_attributes[key] == value
749
- elsif self[key] != value
750
- changed_attributes[key] = self[key]
751
- end
752
-
753
- association = self.class.find_association(key)
754
- if association
755
- value = fetch_associated(key, value)
756
- # FIXME: More explicit; less magic.
757
- elsif add_money_tag?(key, value)
758
- value = Money.new(value, self, key)
759
- end
760
-
761
- attributes[key] = value
762
- end
763
- alias []= write_attribute
764
-
765
- # Apply a given hash of attributes to a record.
766
- #
767
- # @return [Hash]
768
- # @param attributes [Hash] A hash of attributes.
769
- def attributes=(attributes = {})
770
- attributes.each_pair { |k, v|
771
- respond_to?(name = "#{k}=") and send(name, v) or self[k] = v
772
- }
773
- end
774
-
775
- def as_json(options = nil)
776
- attributes.reject { |k, v| v.is_a?(Recurly::Resource::Pager) }
777
- end
778
-
779
- # @return [Hash] The raw hash of record href links.
780
- def links
781
- @links ||= {}
782
- end
783
-
784
- # Whether a record has a link with the given name.
785
- #
786
- # @param key [Symbol, String] The name of the link to check for.
787
- # @example
788
- # account.link? :billing_info # => true
789
- def link?(key)
790
- links.key?(key.to_s)
791
- end
792
-
793
- # Fetch the value of a link by following the associated href.
794
- #
795
- # @param key [Symbol, String] The name of the link to be followed.
796
- # @param options [Hash] A hash of API options.
797
- # @example
798
- # account.read_link :billing_info # => <Recurly::BillingInfo>
799
- def follow_link(key, options = {})
800
- if link = links[key = key.to_s]
801
- response = API.send link[:method], link[:href], options[:body], options
802
- if resource_class = link[:resource_class]
803
- response = resource_class.from_response response
804
- response.attributes[self.class.member_name] = self
805
- end
806
- response
807
- end
808
- rescue Recurly::API::NotFound
809
- raise unless resource_class
810
- end
811
-
812
- # Serializes the record to XML.
813
- #
814
- # @return [String] An XML string.
815
- # @param options [Hash] A hash of XML options.
816
- # @example
817
- # Recurly::Account.new(:account_code => 'code').to_xml
818
- # # => "<account><account_code>code</account_code></account>"
819
- def to_xml(options = {})
820
- builder = options[:builder] || XML.new("<#{self.class.xml_root_key}/>")
821
- xml_keys.each { |key|
822
- value = respond_to?(key) ? send(key) : self[key]
823
- node = builder.add_element key
824
-
825
- # Duck-typing here is problematic because of ActiveSupport's #to_xml.
826
- case value
827
- when Resource
828
- value.to_xml options.merge(:builder => node)
829
- when Array, Subscription::AddOns
830
- value.each do |e|
831
- if e.is_a? Recurly::Resource
832
- # create a node to hold this resource
833
- e_node = node.add_element Helper.singularize(key)
834
- # serialize the resource into this node
835
- e.to_xml(options.merge(builder: e_node))
836
- else
837
- # it's just a primitive value
838
- node.add_element(Helper.singularize(key), e)
839
- end
840
- end
841
- when Hash, Recurly::Money
842
- value.each_pair { |k, v| node.add_element k.to_s, v }
843
- else
844
- node.text = value
845
- end
846
- }
847
- builder.to_s
848
- end
849
-
850
- # Attempts to save the record, returning the success of the request.
851
- #
852
- # @return [true, false]
853
- # @raise [Transaction::Error] A monetary transaction failed.
854
- # @example
855
- # account = Recurly::Account.new
856
- # account.save # => false
857
- # account.account_code = 'account_code'
858
- # account.save # => true
859
- # @see #save!
860
- def save
861
- if new_record? || changed?
862
- clear_errors
863
- @response = API.send(
864
- persisted? ? :put : :post, path, to_xml
865
- )
866
- reload response
867
- persist! true
868
- end
869
- true
870
- rescue API::UnprocessableEntity => e
871
- apply_errors e
872
- Transaction::Error.validate! e, (self if is_a?(Transaction))
13
+ def requires_client?
873
14
  false
874
15
  end
875
16
 
876
- # Attempts to save the record, returning +true+ if the record was saved and
877
- # raising {Invalid} otherwise.
878
- #
879
- # @return [true]
880
- # @raise [Invalid] The record was invalid.
881
- # @raise [Transaction::Error] A monetary transaction failed.
882
- # @example
883
- # account = Recurly::Account.new
884
- # account.save! # raises Recurly::Resource::Invalid
885
- # account.account_code = 'account_code'
886
- # account.save! # => true
887
- # @see #save
888
- def save!
889
- save || raise(Invalid.new(self))
890
- end
891
-
892
- # @return [true, false, nil] The validity of the record: +true+ if the
893
- # record was successfully saved (or persisted and unchanged), +false+ if
894
- # the record was not successfully saved, or +nil+ for a record with an
895
- # unknown state (i.e. (i.e. new records that haven't been saved and
896
- # persisted records with changed attributes).
897
- # @example
898
- # account = Recurly::Account.new
899
- # account.valid? # => nil
900
- # account.save # => false
901
- # account.valid? # => false
902
- # account.account_code = 'account_code'
903
- # account.save # => true
904
- # account.valid? # => true
905
- def valid?
906
- return true if persisted? && !changed?
907
- errors_empty = errors.values.flatten.empty?
908
- return if errors_empty && changed?
909
- errors_empty
910
- end
911
-
912
- # Update a record with a given hash of attributes.
913
- #
914
- # @return [true, false] The success of the update.
915
- # @param attributes [Hash] A hash of attributes.
916
- # @raise [Transaction::Error] A monetary transaction failed.
917
- # @example
918
- # account = Account.find 'junior'
919
- # account.update_attributes :account_code => 'flynn' # => true
920
- # @see #update_attributes!
921
- def update_attributes(attributes = {})
922
- self.attributes = attributes and save
923
- end
924
-
925
- # Update a record with a given hash of attributes.
926
- #
927
- # @return [true] The update was successful.
928
- # @param attributes [Hash] A hash of attributes.
929
- # @raise [Invalid] The record was invalid.
930
- # @raise [Transaction::Error] A monetary transaction failed.
931
- # @example
932
- # account = Account.find 'gale_boetticher'
933
- # account.update_attributes! :account_code => nil # Raises an exception.
934
- # @see #update_attributes
935
- def update_attributes!(attributes = {})
936
- self.attributes = attributes and save!
937
- end
938
-
939
- # @return [Hash] A hash with indifferent read access containing any
940
- # validation errors where the key is the attribute name and the value is
941
- # an array of error messages.
942
- # @example
943
- # account.errors # => {"account_code"=>["can't be blank"]}
944
- # account.errors[:account_code] # => ["can't be blank"]
945
- def errors
946
- @errors ||= Errors.new { |h, k| h[k] = [] }
947
- end
948
-
949
- # Marks a record as persisted, i.e. not a new or deleted record, resetting
950
- # any tracked attribute changes in the process. (This is an internal method
951
- # and should probably not be called unless you know what you're doing.)
952
- #
953
- # @api internal
954
- # @return [true]
955
- def persist!(saved = false)
956
- @new_record, @uri = false
957
- if changed?
958
- @previous_changes = changes if saved
959
- changed_attributes.clear
960
- end
961
- true
962
- end
963
-
964
- # @return [String, nil] The unique resource identifier (URI) of the record
965
- # (if persisted).
966
- # @example
967
- # Recurly::Account.new(:account_code => "account_code").uri # => nil
968
- # Recurly::Account.find("account_code").uri
969
- # # => "https://api.recurly.com/v2/accounts/account_code"
970
- def uri
971
- @href ||= ((API.base_uri + path).to_s if persisted?)
972
- end
973
-
974
- # Attempts to destroy the record.
975
- #
976
- # @return [true, false] +true+ if successful, +false+ if unable to destroy
977
- # (if the record does not persist on Recurly).
978
- # @raise [NotFound] The record cannot be found.
979
- # @example
980
- # account = Recurly::Account.find account_code
981
- # race_condition = Recurly::Account.find account_code
982
- # account.destroy # => true
983
- # account.destroy # => false (already destroyed)
984
- # race_condition.destroy # raises Recurly::Resource::NotFound
985
- def destroy
986
- return false unless persisted?
987
- @response = API.delete uri
988
- @destroyed = true
989
- rescue API::NotFound => e
990
- raise NotFound, e.description
991
- end
992
-
993
- def signable_attributes
994
- Hash[xml_keys.map { |key| [key, self[key]] }]
995
- end
996
-
997
- def ==(other)
998
- other.is_a?(self.class) && other.to_s == to_s
999
- end
1000
-
1001
- def marshal_dump
1002
- [
1003
- @attributes.reject { |k, v| v.is_a?(Proc) },
1004
- @new_record,
1005
- @destroyed,
1006
- @uri,
1007
- @href,
1008
- changed_attributes,
1009
- previous_changes,
1010
- response,
1011
- etag,
1012
- links,
1013
- @type
1014
- ]
1015
- end
1016
-
1017
- def marshal_load(serialization)
1018
- @attributes,
1019
- @new_record,
1020
- @destroyed,
1021
- @uri,
1022
- @href,
1023
- @changed_attributes,
1024
- @previous_changes,
1025
- @response,
1026
- @etag,
1027
- @links,
1028
- @type = serialization
1029
- end
1030
-
1031
- # @return [String]
1032
- def inspect(attributes = self.class.attribute_names.to_a)
1033
- string = "#<#{self.class}"
1034
- string << "##@type" if respond_to?(:type)
1035
- attributes += %w(errors) if errors.any?
1036
- string << " %s" % attributes.map { |k|
1037
- "#{k}: #{self.send(k).inspect}"
1038
- }.join(', ')
1039
- string << '>'
1040
- end
1041
- alias to_s inspect
1042
-
1043
- def apply_errors(exception)
1044
- @response = exception.response
1045
- document = XML.new exception.response.body
1046
-
1047
- if document.root.name == 'error'
1048
- # Single error is returned from the API
1049
- attribute_path = document['symbol'].text.split '.'
1050
- invalid! [attribute_path[1]], document['description'].text
1051
- else
1052
- # Array of errors was returned by the API
1053
- document.each_element 'error' do |el|
1054
- attribute_path = el.attribute('field').value.split '.'
1055
- invalid! attribute_path[1, attribute_path.length], el.text
1056
- end
1057
- end
17
+ def ==(other_resource)
18
+ self.attributes == other_resource.attributes
1058
19
  end
1059
20
 
1060
21
  protected
1061
22
 
1062
- def path
1063
- @href or @uri or if persisted?
1064
- self.class.member_path to_param
1065
- else
1066
- self.class.collection_path
1067
- end
1068
- end
1069
-
1070
- def invalid!(attribute_path, error)
1071
- if attribute_path.length == 1
1072
- errors[attribute_path[0]] << error
1073
- else
1074
- child, k, v = attribute_path.shift.scan(/[^\[\]=]+/)
1075
- if c = k ? self[child].find { |d| d[k] == v } : self[child]
1076
- c.invalid! attribute_path, error if c.methods.include? :invalid!
1077
- e = errors[child] << 'is invalid' and e.uniq!
1078
- end
1079
- end
1080
- end
1081
-
1082
- def clear_errors
1083
- errors.clear
1084
- self.class.associations do |association|
1085
- next unless respond_to? "#{association}=" # Clear writable only.
1086
- [*self[association]].each do |associated|
1087
- associated.clear_errors if associated.respond_to? :clear_errors
1088
- end
1089
- end
1090
- end
1091
-
1092
- def copy_from(other)
1093
- other.instance_variables.each do |ivar|
1094
- instance_variable_set ivar, other.instance_variable_get(ivar)
1095
- end
1096
- end
1097
-
1098
- private
1099
-
1100
- def fetch_associated(name, value, options = {})
1101
- case value
1102
- when Array
1103
- value.map do |v|
1104
- fetch_associated(Helper.singularize(name), v, association_name: name)
1105
- end
1106
- when Hash
1107
- association_name = options[:association_name] || name
1108
- associated_class_name = self.class.find_association(association_name).class_name
1109
- associated_class_name ||= Helper.classify(name)
1110
- Recurly.const_get(associated_class_name, false).send(:new, value)
1111
- when Proc, Resource, Resource::Pager, nil
1112
- value
1113
- else
1114
- raise "unexpected association #{name.inspect}=#{value.inspect}"
1115
- end
23
+ def initialize(attributes = {})
24
+ @attributes = attributes.clone
1116
25
  end
1117
26
 
1118
- def xml_keys
1119
- changed_attributes.keys.sort
27
+ def to_s
28
+ self.inspect
1120
29
  end
1121
30
 
1122
- def add_money_tag?(key, value)
1123
- value &&
1124
- key.end_with?('_in_cents') &&
1125
- !respond_to?(:currency) &&
1126
- !value.is_a?(Money) &&
1127
- !is_a?(PercentageTier)&&
1128
- !is_a?(SubAddOnPercentageTier)
31
+ def schema
32
+ self.class.schema
1129
33
  end
1130
34
  end
1131
35
  end