recurly 2.19.12 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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 +124 -273
  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 -98
  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 -33
  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 -51
  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 -322
  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 -58
  138. data/lib/recurly/plan_ramp_interval.rb +0 -10
  139. data/lib/recurly/purchase.rb +0 -238
  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 -372
  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