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