recurly 2.20.3 → 3.0.0.beta.1

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