recurly 2.18.21 → 3.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +158 -110
- data/Rakefile +6 -0
- data/bin/bundle +105 -0
- data/bin/coderay +29 -0
- data/bin/console +14 -0
- data/bin/htmldiff +29 -0
- data/bin/ldiff +29 -0
- data/bin/pry +29 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/bin/yard +29 -0
- data/bin/yardoc +29 -0
- data/bin/yri +29 -0
- data/lib/data/ca-certificates.crt +31 -0
- data/lib/recurly/client/operations.rb +935 -0
- data/lib/recurly/client.rb +198 -0
- data/lib/recurly/errors/api_errors.rb +35 -0
- data/lib/recurly/errors/network_errors.rb +8 -0
- data/lib/recurly/errors.rb +34 -0
- data/lib/recurly/pager.rb +119 -0
- data/lib/recurly/request.rb +30 -0
- data/lib/recurly/requests/account_acquisition_updatable.rb +22 -0
- data/lib/recurly/requests/account_create_only.rb +18 -0
- data/lib/recurly/requests/account_updatable.rb +50 -0
- data/lib/recurly/requests/add_on_create.rb +38 -0
- data/lib/recurly/requests/add_on_update.rb +38 -0
- data/lib/recurly/requests/address.rb +42 -0
- data/lib/recurly/requests/billing_info_create.rb +58 -0
- data/lib/recurly/requests/coupon_create_only.rb +66 -0
- data/lib/recurly/requests/coupon_updatable.rb +30 -0
- data/lib/recurly/requests/create_account.rb +62 -0
- data/lib/recurly/requests/create_coupon.rb +90 -0
- data/lib/recurly/requests/invoice_create.rb +42 -0
- data/lib/recurly/requests/invoice_refund.rb +30 -0
- data/lib/recurly/requests/line_item_create.rb +46 -0
- data/lib/recurly/requests/plan_create.rb +66 -0
- data/lib/recurly/requests/plan_update.rb +70 -0
- data/lib/recurly/requests/shipping_address_create.rb +58 -0
- data/lib/recurly/requests/shipping_address_update.rb +62 -0
- data/lib/recurly/requests/subscription_add_on_create.rb +22 -0
- data/lib/recurly/requests/subscription_change_create.rb +42 -0
- data/lib/recurly/requests/subscription_create.rb +86 -0
- data/lib/recurly/requests/subscription_update.rb +42 -0
- data/lib/recurly/requests/update_coupon.rb +30 -0
- data/lib/recurly/resource.rb +16 -1103
- data/lib/recurly/resources/account.rb +86 -0
- data/lib/recurly/resources/account_acquisition.rb +42 -0
- data/lib/recurly/resources/account_balance.rb +22 -0
- data/lib/recurly/resources/account_note.rb +30 -0
- data/lib/recurly/resources/add_on.rb +62 -0
- data/lib/recurly/resources/address.rb +42 -0
- data/lib/recurly/resources/billing_info.rb +62 -0
- data/lib/recurly/resources/coupon.rb +110 -0
- data/lib/recurly/resources/coupon_discount.rb +22 -0
- data/lib/recurly/resources/coupon_redemption.rb +46 -0
- data/lib/recurly/resources/credit_payment.rb +62 -0
- data/lib/recurly/resources/error.rb +18 -0
- data/lib/recurly/resources/error_may_have_transaction.rb +22 -0
- data/lib/recurly/resources/invoice.rb +138 -0
- data/lib/recurly/resources/invoice_collection.rb +18 -0
- data/lib/recurly/resources/line_item.rb +166 -0
- data/lib/recurly/resources/plan.rb +86 -0
- data/lib/recurly/resources/settings.rb +18 -0
- data/lib/recurly/resources/shipping_address.rb +74 -0
- data/lib/recurly/resources/site.rb +46 -0
- data/lib/recurly/resources/subscription.rb +134 -0
- data/lib/recurly/resources/subscription_add_on.rb +42 -0
- data/lib/recurly/resources/subscription_change.rb +54 -0
- data/lib/recurly/resources/tax_info.rb +18 -0
- data/lib/recurly/resources/transaction.rb +146 -0
- data/lib/recurly/resources/unique_coupon_code.rb +38 -0
- data/lib/recurly/resources/user.rb +38 -0
- data/lib/recurly/schema/json_deserializer.rb +53 -0
- data/lib/recurly/schema/json_parser.rb +71 -0
- data/lib/recurly/schema/request_caster.rb +66 -0
- data/lib/recurly/schema/schema_factory.rb +50 -0
- data/lib/recurly/schema/schema_validator.rb +125 -0
- data/lib/recurly/schema.rb +114 -0
- data/lib/recurly/version.rb +1 -10
- data/lib/recurly.rb +14 -145
- data/recurly.gemspec +32 -0
- data/scripts/build +4 -0
- data/scripts/clean +6 -0
- data/scripts/test +3 -0
- metadata +129 -173
- data/lib/recurly/account.rb +0 -209
- data/lib/recurly/account_acquisition.rb +0 -27
- data/lib/recurly/account_balance.rb +0 -21
- data/lib/recurly/add_on.rb +0 -46
- data/lib/recurly/address.rb +0 -25
- data/lib/recurly/adjustment.rb +0 -81
- data/lib/recurly/api/errors.rb +0 -208
- data/lib/recurly/api/net_http_adapter.rb +0 -111
- data/lib/recurly/api.rb +0 -110
- data/lib/recurly/billing_info.rb +0 -113
- data/lib/recurly/coupon.rb +0 -136
- 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 -85
- data/lib/recurly/helper.rb +0 -51
- data/lib/recurly/invoice.rb +0 -304
- data/lib/recurly/invoice_collection.rb +0 -14
- data/lib/recurly/item.rb +0 -36
- data/lib/recurly/js.rb +0 -14
- data/lib/recurly/juris_detail.rb +0 -15
- 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 -43
- data/lib/recurly/purchase.rb +0 -238
- 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/shipping_fee.rb +0 -17
- data/lib/recurly/shipping_method.rb +0 -13
- data/lib/recurly/subscription/add_ons.rb +0 -82
- data/lib/recurly/subscription.rb +0 -366
- data/lib/recurly/subscription_add_on.rb +0 -58
- data/lib/recurly/tax_detail.rb +0 -18
- data/lib/recurly/tax_type.rb +0 -13
- data/lib/recurly/tier.rb +0 -18
- data/lib/recurly/transaction/errors.rb +0 -115
- data/lib/recurly/transaction.rb +0 -131
- data/lib/recurly/usage.rb +0 -28
- data/lib/recurly/verify.rb +0 -12
- data/lib/recurly/webhook/account_notification.rb +0 -13
- data/lib/recurly/webhook/billing_info_update_failed_notification.rb +0 -6
- 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_gift_card_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/deactivated_item_notification.rb +0 -6
- data/lib/recurly/webhook/deleted_shipping_address_notification.rb +0 -6
- 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/fraud_info_updated_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/item_notification.rb +0 -7
- 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_item_notification.rb +0 -6
- data/lib/recurly/webhook/new_shipping_address_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/paused_subscription_renewal_notification.rb +0 -6
- data/lib/recurly/webhook/prerenewal_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/reactivated_item_notification.rb +0 -6
- data/lib/recurly/webhook/redeemed_gift_card_notification.rb +0 -7
- data/lib/recurly/webhook/regenerated_gift_card_notification.rb +0 -6
- 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/scheduled_subscription_pause_notification.rb +0 -6
- data/lib/recurly/webhook/scheduled_subscription_update_notification.rb +0 -6
- data/lib/recurly/webhook/subscription_notification.rb +0 -12
- data/lib/recurly/webhook/subscription_pause_canceled_notification.rb +0 -6
- data/lib/recurly/webhook/subscription_pause_modified_notification.rb +0 -6
- data/lib/recurly/webhook/subscription_paused_notification.rb +0 -6
- data/lib/recurly/webhook/subscription_resumed_notification.rb +0 -6
- 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_gift_card_notification.rb +0 -6
- data/lib/recurly/webhook/updated_invoice_notification.rb +0 -6
- data/lib/recurly/webhook/updated_item_notification.rb +0 -6
- data/lib/recurly/webhook/updated_shipping_address_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 -113
- data/lib/recurly/xml/nokogiri.rb +0 -60
- data/lib/recurly/xml/rexml.rb +0 -52
- data/lib/recurly/xml.rb +0 -122
@@ -0,0 +1,86 @@
|
|
1
|
+
module Recurly
|
2
|
+
module Requests
|
3
|
+
class SubscriptionCreate < Request
|
4
|
+
|
5
|
+
# @!attribute account
|
6
|
+
# @return [Hash] Account
|
7
|
+
define_attribute :account, Hash
|
8
|
+
|
9
|
+
# @!attribute add_ons
|
10
|
+
# @return [Array[String]] Add-ons
|
11
|
+
define_attribute :add_ons, Array, {:item_type => String}
|
12
|
+
|
13
|
+
# @!attribute collection_method
|
14
|
+
# @return [String] Collection method
|
15
|
+
define_attribute :collection_method, String, {:enum => ["automatic", "manual"]}
|
16
|
+
|
17
|
+
# @!attribute coupon_code
|
18
|
+
# @return [String] Optional coupon code to redeem on the account and discount the subscription. Please note, the subscription request will fail if the coupon is invalid.
|
19
|
+
define_attribute :coupon_code, String
|
20
|
+
|
21
|
+
# @!attribute credit_customer_notes
|
22
|
+
# @return [String] If there are pending credits on the account that will be invoiced during the subscription creation, these will be used as the Customer Notes on the credit invoice.
|
23
|
+
define_attribute :credit_customer_notes, String
|
24
|
+
|
25
|
+
# @!attribute currency
|
26
|
+
# @return [String] 3-letter ISO 4217 currency code.
|
27
|
+
define_attribute :currency, String
|
28
|
+
|
29
|
+
# @!attribute customer_notes
|
30
|
+
# @return [String] This will default to the Customer Notes text specified on the Invoice Settings. Specify custom notes to add or override Customer Notes. Custom notes will stay with a subscription on all renewals.
|
31
|
+
define_attribute :customer_notes, String
|
32
|
+
|
33
|
+
# @!attribute first_renewal_date
|
34
|
+
# @return [DateTime] If set,indicates when the first renewal should occur. Subsequent renewals will be offset from this date. The first invoice will be prorated appropriately so that the customer only pays for the portion of the first billing period for which the subscription applies. Useful for forcing a subscription to renew on the first of the month.
|
35
|
+
define_attribute :first_renewal_date, DateTime
|
36
|
+
|
37
|
+
# @!attribute net_terms
|
38
|
+
# @return [Integer] Integer representing the number of days after an invoice's creation that the invoice will become past due. If an invoice's net terms are set to '0', it is due 'On Receipt' and will become past due 24 hours after it’s created. If an invoice is due net 30, it will become past due at 31 days exactly.
|
39
|
+
define_attribute :net_terms, Integer
|
40
|
+
|
41
|
+
# @!attribute plan_code
|
42
|
+
# @return [String] Plan code
|
43
|
+
define_attribute :plan_code, String
|
44
|
+
|
45
|
+
# @!attribute plan_id
|
46
|
+
# @return [String] Plan ID
|
47
|
+
define_attribute :plan_id, String
|
48
|
+
|
49
|
+
# @!attribute po_number
|
50
|
+
# @return [String] For manual invoicing, this identifies the PO number associated with the subscription.
|
51
|
+
define_attribute :po_number, String
|
52
|
+
|
53
|
+
# @!attribute quantity
|
54
|
+
# @return [Integer] Optionally override the default quantity of 1.
|
55
|
+
define_attribute :quantity, Integer
|
56
|
+
|
57
|
+
# @!attribute shipping_address
|
58
|
+
# @return [Hash] Create a shipping address on the account and assign it to the subscription.
|
59
|
+
define_attribute :shipping_address, Hash
|
60
|
+
|
61
|
+
# @!attribute shipping_address_id
|
62
|
+
# @return [String] Assign a shipping address from the account's existing shipping addresses. If this and `shipping_address` are both present, `shipping_address` will take precedence.
|
63
|
+
define_attribute :shipping_address_id, String
|
64
|
+
|
65
|
+
# @!attribute starts_at
|
66
|
+
# @return [DateTime] If set, the subscription will begin in the future on this date. The subscription will apply the setup fee and trial period, unless the plan has no trial.
|
67
|
+
define_attribute :starts_at, DateTime
|
68
|
+
|
69
|
+
# @!attribute terms_and_conditions
|
70
|
+
# @return [String] This will default to the Terms and Conditions text specified on the Invoice Settings page in your Recurly admin. Specify custom notes to add or override Terms and Conditions. Custom notes will stay with a subscription on all renewals.
|
71
|
+
define_attribute :terms_and_conditions, String
|
72
|
+
|
73
|
+
# @!attribute total_billing_cycles
|
74
|
+
# @return [Integer] Renews the subscription for a specified number of total cycles, then automatically cancels. Defaults to the subscription renewing indefinitely.
|
75
|
+
define_attribute :total_billing_cycles, Integer
|
76
|
+
|
77
|
+
# @!attribute trial_ends_at
|
78
|
+
# @return [DateTime] If set, overrides the default trial behavior for the subscription. The date must be in the future.
|
79
|
+
define_attribute :trial_ends_at, DateTime
|
80
|
+
|
81
|
+
# @!attribute unit_amount
|
82
|
+
# @return [Float] Override the unit amount of the subscription plan by setting this value in cents. If not provided, the subscription will inherit the price from the subscription plan for the provided currency.
|
83
|
+
define_attribute :unit_amount, Float
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Recurly
|
2
|
+
module Requests
|
3
|
+
class SubscriptionUpdate < Request
|
4
|
+
|
5
|
+
# @!attribute collection_method
|
6
|
+
# @return [String] Change collection method
|
7
|
+
define_attribute :collection_method, String, {:enum => ["automatic", "manual"]}
|
8
|
+
|
9
|
+
# @!attribute customer_notes
|
10
|
+
# @return [String] Specify custom notes to add or override Customer Notes. Custom notes will stay with a subscription on all renewals.
|
11
|
+
define_attribute :customer_notes, String
|
12
|
+
|
13
|
+
# @!attribute net_terms
|
14
|
+
# @return [Integer] Integer representing the number of days after an invoice's creation that the invoice will become past due. If an invoice's net terms are set to '0', it is due 'On Receipt' and will become past due 24 hours after it’s created. If an invoice is due net 30, it will become past due at 31 days exactly.
|
15
|
+
define_attribute :net_terms, Integer
|
16
|
+
|
17
|
+
# @!attribute next_renewal_at
|
18
|
+
# @return [DateTime] For an active subscription, this will change the next renewal date. For a subscription in a trial period, modifying the renewal date will change when the trial expires.
|
19
|
+
define_attribute :next_renewal_at, DateTime
|
20
|
+
|
21
|
+
# @!attribute po_number
|
22
|
+
# @return [String] For manual invoicing, this identifies the PO number associated with the subscription.
|
23
|
+
define_attribute :po_number, String
|
24
|
+
|
25
|
+
# @!attribute remaining_billing_cycles
|
26
|
+
# @return [Integer] Renews the subscription for a specified number of cycles, then automatically cancels.
|
27
|
+
define_attribute :remaining_billing_cycles, Integer
|
28
|
+
|
29
|
+
# @!attribute shipping_address
|
30
|
+
# @return [Hash] Create a shipping address on the account and assign it to the subscription. If this and `shipping_address_id` are both present, `shipping_address_id` will take precedence."
|
31
|
+
define_attribute :shipping_address, Hash
|
32
|
+
|
33
|
+
# @!attribute shipping_address_id
|
34
|
+
# @return [String] Assign a shipping address from the account's existing shipping addresses.
|
35
|
+
define_attribute :shipping_address_id, String
|
36
|
+
|
37
|
+
# @!attribute terms_and_conditions
|
38
|
+
# @return [String] Specify custom notes to add or override Terms and Conditions. Custom notes will stay with a subscription on all renewals.
|
39
|
+
define_attribute :terms_and_conditions, String
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Recurly
|
2
|
+
module Requests
|
3
|
+
class UpdateCoupon < Request
|
4
|
+
|
5
|
+
# @!attribute hosted_description
|
6
|
+
# @return [String] This description will show up when a customer redeems a coupon on your Hosted Payment Pages, or if you choose to show the description on your own checkout page.
|
7
|
+
define_attribute :hosted_description, String
|
8
|
+
|
9
|
+
# @!attribute invoice_description
|
10
|
+
# @return [String] Description of the coupon on the invoice.
|
11
|
+
define_attribute :invoice_description, String
|
12
|
+
|
13
|
+
# @!attribute max_redemptions
|
14
|
+
# @return [Integer] A maximum number of redemptions for the coupon. The coupon will expire when it hits its maximum redemptions.
|
15
|
+
define_attribute :max_redemptions, Integer
|
16
|
+
|
17
|
+
# @!attribute max_redemptions_per_account
|
18
|
+
# @return [Integer] Redemptions per account is the number of times a specific account can redeem the coupon. Set redemptions per account to `1` if you want to keep customers from gaming the system and getting more than one discount from the coupon campaign.
|
19
|
+
define_attribute :max_redemptions_per_account, Integer
|
20
|
+
|
21
|
+
# @!attribute name
|
22
|
+
# @return [String] The internal name for the coupon.
|
23
|
+
define_attribute :name, String
|
24
|
+
|
25
|
+
# @!attribute redeem_by_date
|
26
|
+
# @return [String] The date and time the coupon will expire and can no longer be redeemed. Time is always 11:59:59, the end-of-day Pacific time.
|
27
|
+
define_attribute :redeem_by_date, String
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/recurly/resource.rb
CHANGED
@@ -1,1122 +1,35 @@
|
|
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 [String] The root key for this resource's xml document
|
202
|
-
def xml_root_key
|
203
|
-
self.member_name
|
204
|
-
end
|
205
|
-
|
206
|
-
# @return [Array] Per attribute, defines readers, writers, boolean and
|
207
|
-
# change-tracking methods.
|
208
|
-
# @param attribute_names [Array] An array of attribute names.
|
209
|
-
# @example
|
210
|
-
# class Account < Resource
|
211
|
-
# define_attribute_methods [:name]
|
212
|
-
# end
|
213
|
-
#
|
214
|
-
# a = Account.new
|
215
|
-
# a.name? # => false
|
216
|
-
# a.name # => nil
|
217
|
-
# a.name = "Stephen"
|
218
|
-
# a.name? # => true
|
219
|
-
# a.name # => "Stephen"
|
220
|
-
# a.name_changed? # => true
|
221
|
-
# a.name_was # => nil
|
222
|
-
# a.name_change # => [nil, "Stephen"]
|
223
|
-
def define_attribute_methods(attribute_names)
|
224
|
-
@attribute_names = attribute_names.map! { |m| m.to_s }.sort!.freeze
|
225
|
-
remove_const :AttributeMethods if constants.include? :AttributeMethods
|
226
|
-
include const_set :AttributeMethods, Module.new {
|
227
|
-
attribute_names.each do |name|
|
228
|
-
define_method(name) { self[name] } # Get.
|
229
|
-
define_method("#{name}=") { |value| self[name] = value } # Set.
|
230
|
-
define_method("#{name}?") { !!self[name] } # Present.
|
231
|
-
define_method("#{name}_change") { changes[name] } # Dirt...
|
232
|
-
define_method("#{name}_changed?") { changed_attributes.key? name }
|
233
|
-
define_method("#{name}_was") { changed_attributes[name] }
|
234
|
-
define_method("#{name}_previously_changed?") {
|
235
|
-
previous_changes.key? name
|
236
|
-
}
|
237
|
-
define_method("#{name}_previously_was") {
|
238
|
-
previous_changes[name].first if previous_changes.key? name
|
239
|
-
}
|
240
|
-
end
|
241
|
-
}
|
242
|
-
end
|
243
|
-
|
244
|
-
# @return [Array, nil] The list of attribute names defined for the
|
245
|
-
# resource class.
|
246
|
-
attr_reader :attribute_names
|
247
|
-
|
248
|
-
# @return [Pager] A pager with an iterable collection of records
|
249
|
-
# @param options [Hash] A hash of pagination options
|
250
|
-
# @option options [Integer] :per_page The number of records returned per
|
251
|
-
# page
|
252
|
-
# @option options [DateTime, Time, Integer] :cursor A timestamp that the
|
253
|
-
# pager will skim back to and return records created before it
|
254
|
-
# @option options [String] :etag When set, will raise
|
255
|
-
# {Recurly::API::NotModified} if the pager's loaded page content has
|
256
|
-
# not changed
|
257
|
-
# @example Fetch 50 records and iterate over them
|
258
|
-
# Recurly::Account.paginate(:per_page => 50).each { |a| p a }
|
259
|
-
# @example Fetch records before January 1, 2011
|
260
|
-
# Recurly::Account.paginate(:cursor => Time.new(2011, 1, 1))
|
261
|
-
def paginate(options = {})
|
262
|
-
Pager.new self, options
|
263
|
-
end
|
264
|
-
alias scoped paginate
|
265
|
-
alias where paginate
|
266
|
-
|
267
|
-
def all(options = {})
|
268
|
-
paginate(options).to_a
|
269
|
-
end
|
270
|
-
|
271
|
-
# @return [Hash] Defined scopes per resource.
|
272
|
-
def scopes
|
273
|
-
@scopes ||= Recurly::Helper.hash_with_indifferent_read_access
|
274
|
-
end
|
275
|
-
|
276
|
-
# @return [Module] Module of scopes methods.
|
277
|
-
def scopes_helper
|
278
|
-
@scopes_helper ||= Module.new.tap { |helper| extend helper }
|
279
|
-
end
|
280
|
-
|
281
|
-
# Defines a new resource scope.
|
282
|
-
#
|
283
|
-
# @return [Proc]
|
284
|
-
# @param [Symbol] name the scope name
|
285
|
-
# @param [Hash] params the scope params
|
286
|
-
def scope(name, params = {})
|
287
|
-
scopes[name = name.to_s] = params
|
288
|
-
scopes_helper.send(:define_method, name) { paginate scopes[name] }
|
289
|
-
end
|
290
|
-
|
291
|
-
# Iterates through every record by automatically paging.
|
292
|
-
#
|
293
|
-
# @option options [Hash] Optional hash to pass to Pager#paginate
|
294
|
-
#
|
295
|
-
# @return [nil]
|
296
|
-
# @param [Integer] per_page The number of records returned per request.
|
297
|
-
# @yield [record]
|
298
|
-
# @see Pager#paginate
|
299
|
-
# @example
|
300
|
-
# Recurly::Account.find_each { |a| p a }
|
301
|
-
# @example With sorting and filter
|
302
|
-
# opts = {
|
303
|
-
# begin_time: DateTime.new(2016,1,1),
|
304
|
-
# sort: :updated_at
|
305
|
-
# }
|
306
|
-
# Recurly::Account.find_each(opts) do |a|
|
307
|
-
# puts a.inspect
|
308
|
-
# end
|
309
|
-
def find_each(options = {}, &block)
|
310
|
-
paginate(options).find_each(&block)
|
311
|
-
end
|
312
|
-
|
313
|
-
# @return [Integer] The total record count of the resource in question.
|
314
|
-
# @see Pager#count
|
315
|
-
# @example
|
316
|
-
# Recurly::Account.count # => 42
|
317
|
-
def count
|
318
|
-
paginate.count
|
319
|
-
end
|
320
|
-
|
321
|
-
# @api internal
|
322
|
-
# @return [Resource, nil]
|
323
|
-
def first
|
324
|
-
paginate(:per_page => 1).first
|
325
|
-
end
|
326
|
-
|
327
|
-
# @return [Resource] A record matching the designated unique identifier.
|
328
|
-
# @param [String] uuid The unique identifier of the resource to be
|
329
|
-
# retrieved.
|
330
|
-
# @param [Hash] options A hash of options.
|
331
|
-
# @option options [String] :etag When set, will raise {API::NotModified}
|
332
|
-
# if the record content has not changed.
|
333
|
-
# @raise [Error] If the resource has no identifier (and thus cannot be
|
334
|
-
# retrieved).
|
335
|
-
# @raise [NotFound] If no resource can be found for the supplied
|
336
|
-
# identifier (or the supplied identifier is +nil+).
|
337
|
-
# @raise [API::NotModified] If the <tt>:etag</tt> option is set and
|
338
|
-
# matches the server's.
|
339
|
-
# @example
|
340
|
-
# Recurly::Account.find "heisenberg"
|
341
|
-
# # => #<Recurly::Account account_code: "heisenberg", ...>
|
342
|
-
# Use the following identifiers for these types of objects:
|
343
|
-
# for accounts use account_code
|
344
|
-
# for plans use plan_code
|
345
|
-
# for invoices use invoice_number
|
346
|
-
# for subscriptions use uuid
|
347
|
-
# for transactions use uuid
|
348
|
-
def find(uuid, options = {})
|
349
|
-
if uuid.nil? || uuid.to_s.empty?
|
350
|
-
raise NotFound, "can't find a record with nil identifier"
|
351
|
-
end
|
352
|
-
|
353
|
-
begin
|
354
|
-
from_response API.get(member_path(uuid), {}, options)
|
355
|
-
rescue API::NotFound => e
|
356
|
-
raise NotFound, e.description
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
# Instantiates and attempts to save a record.
|
361
|
-
#
|
362
|
-
# @return [Resource] The record.
|
363
|
-
# @raise [Transaction::Error] A monetary transaction failed.
|
364
|
-
# @see create!
|
365
|
-
def create(attributes = {})
|
366
|
-
new(attributes) { |record| record.save }
|
367
|
-
end
|
368
|
-
|
369
|
-
# Instantiates and attempts to save a record.
|
370
|
-
#
|
371
|
-
# @return [Resource] The saved record.
|
372
|
-
# @raise [Invalid] The record is invalid.
|
373
|
-
# @raise [Transaction::Error] A monetary transaction failed.
|
374
|
-
# @see create
|
375
|
-
def create!(attributes = {})
|
376
|
-
new(attributes) { |record| record.save! }
|
377
|
-
end
|
378
|
-
|
379
|
-
# Instantiates a record from an HTTP response, setting the record's
|
380
|
-
# response attribute in the process.
|
381
|
-
#
|
382
|
-
# @return [Resource]
|
383
|
-
# @param response [Net::HTTPResponse]
|
384
|
-
def from_response(response)
|
385
|
-
content_type = response['Content-Type']
|
386
|
-
|
387
|
-
case content_type
|
388
|
-
when %r{application/pdf}
|
389
|
-
response.body
|
390
|
-
when %r{application/xml}
|
391
|
-
record = from_xml response.body
|
392
|
-
record.instance_eval { @etag, @response = response['ETag'], response }
|
393
|
-
record
|
394
|
-
else
|
395
|
-
raise Recurly::Error, "Content-Type \"#{content_type}\" is not accepted"
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
# Instantiates a record from an XML blob: either a String or XML element.
|
400
|
-
#
|
401
|
-
# Assuming the record is from an API response, the record is flagged as
|
402
|
-
# persisted.
|
403
|
-
#
|
404
|
-
# @return [Resource]
|
405
|
-
# @param xml [String, REXML::Element, Nokogiri::XML::Node]
|
406
|
-
# @see from_response
|
407
|
-
def from_xml(xml)
|
408
|
-
xml = XML.new xml
|
409
|
-
record = new
|
410
|
-
|
411
|
-
xml.root.attributes.each do |name, value|
|
412
|
-
record.instance_variable_set "@#{name}", value.to_s
|
413
|
-
end
|
414
|
-
|
415
|
-
xml.each_element do |el|
|
416
|
-
# skip this element if it's an xml comment
|
417
|
-
next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.is_a?(Nokogiri::XML::Comment)
|
418
|
-
|
419
|
-
if el.name == 'a'
|
420
|
-
record.links[el.attribute('name').value] = {
|
421
|
-
:method => el.attribute('method').to_s,
|
422
|
-
:href => el.attribute('href').value
|
423
|
-
}
|
424
|
-
next
|
425
|
-
end
|
7
|
+
extend Schema::SchemaFactory
|
8
|
+
extend Schema::JsonDeserializer
|
9
|
+
include Schema::SchemaValidator
|
426
10
|
|
427
|
-
# Nokogiri on Jruby-1.7.19 likes to throw NullPointer exceptions
|
428
|
-
# if you try to run certian operations like el.attribute(''). Since
|
429
|
-
# we dont care about text nodes, let's just skip them
|
430
|
-
next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.node_type == Nokogiri::XML::Node::TEXT_NODE
|
431
|
-
|
432
|
-
if association = find_association(el.name)
|
433
|
-
class_name = association_class_name(association, el.name)
|
434
|
-
resource_class = Recurly.const_get(class_name)
|
435
|
-
is_many = association.relation == :has_many
|
436
|
-
|
437
|
-
# Is this a link, or is it embedded data?
|
438
|
-
if el.children.empty? && href = el.attribute('href')
|
439
|
-
if is_many
|
440
|
-
record[el.name] = Pager.new(
|
441
|
-
resource_class, :uri => href.value, :parent => record
|
442
|
-
)
|
443
|
-
else
|
444
|
-
record.links[el.name] = {
|
445
|
-
:resource_class => resource_class,
|
446
|
-
:method => :get,
|
447
|
-
:href => href.value
|
448
|
-
}
|
449
|
-
end
|
450
|
-
else
|
451
|
-
if is_many
|
452
|
-
resources = el.elements.map { |e| resource_class.from_xml(e) }
|
453
|
-
record[el.name] = resources
|
454
|
-
else
|
455
|
-
record[el.name] = resource_class.from_xml(el)
|
456
|
-
end
|
457
|
-
end
|
458
|
-
else
|
459
|
-
# TODO name tax_type conflicts with the TaxType
|
460
|
-
# class so if we get to this point was can assume
|
461
|
-
# it's the string. Will need to refactor this
|
462
|
-
if el.name == 'tax_type'
|
463
|
-
record[el.name] = el.text
|
464
|
-
else
|
465
|
-
val = XML.cast(el)
|
466
|
-
|
467
|
-
# TODO we have to clear changed attributes after
|
468
|
-
# parsing here or else it always serializes. Need
|
469
|
-
# a better way of handling changed attributes
|
470
|
-
if el.name == 'address' && val.kind_of?(Hash)
|
471
|
-
address = Address.new(val)
|
472
|
-
address.instance_variable_set(:@changed_attributes, {})
|
473
|
-
record[el.name] = address
|
474
|
-
else
|
475
|
-
record[el.name] = val
|
476
|
-
end
|
477
|
-
end
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
record.persist! if record.respond_to? :persist!
|
482
|
-
record
|
483
|
-
end
|
484
|
-
|
485
|
-
# @return [Array] A list of associations for the current class.
|
486
|
-
def associations
|
487
|
-
@associations ||= []
|
488
|
-
end
|
489
|
-
|
490
|
-
# @return [Array] A list of associated resource classes with
|
491
|
-
# the relation [:has_many, :has_one, :belongs_to] for the current class.
|
492
|
-
def associations_for_relation(relation)
|
493
|
-
associations.select{ |a| a.relation == relation }.map(&:resource_class)
|
494
|
-
end
|
495
|
-
|
496
|
-
def association_class_name(association, el_name)
|
497
|
-
return association.class_name if association.class_name
|
498
|
-
Helper.classify(el_name)
|
499
|
-
end
|
500
|
-
|
501
|
-
# @return [Association, nil] Find association for the current class
|
502
|
-
# with resource class name.
|
503
|
-
def find_association(resource_class)
|
504
|
-
associations.find{ |a| a.resource_class.to_s == resource_class.to_s }
|
505
|
-
end
|
506
|
-
|
507
|
-
def associations_helper
|
508
|
-
@associations_helper ||= Module.new.tap { |helper| include helper }
|
509
|
-
end
|
510
|
-
|
511
|
-
# Establishes a has_many association.
|
512
|
-
#
|
513
|
-
# @return [Proc, nil]
|
514
|
-
# @param collection_name [Symbol] Association name.
|
515
|
-
# @param options [Hash] A hash of association options.
|
516
|
-
# @option options [true, false] :readonly Define a setter when false, defaults to true
|
517
|
-
# [String] :class_name Actual associated resource class name
|
518
|
-
# if not same as collection_name.
|
519
|
-
def has_many(collection_name, options = {})
|
520
|
-
associations << Association.new(:has_many, collection_name.to_s, options)
|
521
|
-
associations_helper.module_eval do
|
522
|
-
define_method collection_name do
|
523
|
-
if self[collection_name]
|
524
|
-
self[collection_name]
|
525
|
-
else
|
526
|
-
attributes[collection_name.to_s] = []
|
527
|
-
end
|
528
|
-
end
|
529
|
-
if options.key?(:readonly) && options[:readonly] == false
|
530
|
-
define_method "#{collection_name}=" do |collection|
|
531
|
-
self[collection_name] = collection
|
532
|
-
end
|
533
|
-
end
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
|
-
# Establishes a has_one association.
|
538
|
-
#
|
539
|
-
# @return [Proc, nil]
|
540
|
-
# @param member_name [Symbol] Association name.
|
541
|
-
# @param options [Hash] A hash of association options.
|
542
|
-
# @option options [true, false] :readonly Don't define a setter.
|
543
|
-
# [String] :class_name Actual associated resource class name
|
544
|
-
# if not same as member_name.
|
545
|
-
def has_one(member_name, options = {})
|
546
|
-
associations << Association.new(:has_one, member_name.to_s, options)
|
547
|
-
associations_helper.module_eval do
|
548
|
-
define_method(member_name) { self[member_name] }
|
549
|
-
if options.key?(:readonly) && options[:readonly] == false
|
550
|
-
associated = Recurly.const_get Helper.classify(member_name), false
|
551
|
-
define_method "#{member_name}=" do |member|
|
552
|
-
associated_uri = "#{path}/#{member_name}"
|
553
|
-
self[member_name] = case member
|
554
|
-
when Hash
|
555
|
-
associated.send :new, member.merge(:uri => associated_uri)
|
556
|
-
when associated
|
557
|
-
member.uri = associated_uri and member
|
558
|
-
else
|
559
|
-
raise ArgumentError, "expected #{associated}"
|
560
|
-
end
|
561
|
-
end
|
562
|
-
define_method "build_#{member_name}" do |*args|
|
563
|
-
attributes = args.shift || {}
|
564
|
-
self[member_name] = associated.send(
|
565
|
-
:new, attributes.merge(:uri => "#{path}/#{associated.member_name}")
|
566
|
-
).tap { |child| child.attributes[self.class.member_name] = self }
|
567
|
-
end
|
568
|
-
define_method "create_#{member_name}" do |*args|
|
569
|
-
send("build_#{member_name}", *args).tap { |child| child.save }
|
570
|
-
end
|
571
|
-
end
|
572
|
-
end
|
573
|
-
end
|
574
|
-
|
575
|
-
# Establishes a belongs_to association.
|
576
|
-
#
|
577
|
-
# @return [Proc]
|
578
|
-
# @param parent_name [Symbol] Association name.
|
579
|
-
# @param options [Hash] A hash of association options.
|
580
|
-
# @option options [true, false] :readonly Don't define a setter.
|
581
|
-
# [String] :class_name Actual associated resource class name
|
582
|
-
# if not same as parent_name.
|
583
|
-
def belongs_to(parent_name, options = {})
|
584
|
-
associations << Association.new(:belongs_to, parent_name.to_s, options)
|
585
|
-
associations_helper.module_eval do
|
586
|
-
define_method(parent_name) { self[parent_name] }
|
587
|
-
if options.key?(:readonly) && options[:readonly] == false
|
588
|
-
define_method "#{parent_name}=" do |parent|
|
589
|
-
self[parent_name] = parent
|
590
|
-
end
|
591
|
-
end
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
# @return [:has_many, :has_one, :belongs_to, nil] An association type.
|
596
|
-
def reflect_on_association(name)
|
597
|
-
a = find_association(name)
|
598
|
-
a.relation if a
|
599
|
-
end
|
600
|
-
|
601
|
-
def embedded!(root_index = false)
|
602
|
-
protected :initialize
|
603
|
-
private_class_method(*%w(create create!))
|
604
|
-
unless root_index
|
605
|
-
private_class_method(*%w(all find_each first paginate scoped where))
|
606
|
-
end
|
607
|
-
end
|
608
|
-
|
609
|
-
def find_resource_class(name)
|
610
|
-
resource_name = Helper.classify(name)
|
611
|
-
if Recurly.const_defined?(resource_name, false)
|
612
|
-
Recurly.const_get(resource_name, false)
|
613
|
-
end
|
614
|
-
end
|
615
|
-
end
|
616
|
-
|
617
|
-
# @return [Hash] The raw hash of record attributes.
|
618
11
|
attr_reader :attributes
|
619
12
|
|
620
|
-
|
621
|
-
# record (updated during {#save} and {#destroy}).
|
622
|
-
attr_reader :response
|
623
|
-
|
624
|
-
# @return [String, nil] An ETag for the current record.
|
625
|
-
attr_reader :etag
|
626
|
-
|
627
|
-
# @return [String, nil] A writer to override the URI the record saves to.
|
628
|
-
attr_writer :uri
|
629
|
-
|
630
|
-
# @return [Resource] A new resource instance.
|
631
|
-
# @param attributes [Hash] A hash of attributes.
|
632
|
-
def initialize(attributes = {})
|
633
|
-
if instance_of? Resource
|
634
|
-
raise Error,
|
635
|
-
"#{self.class} is an abstract class and cannot be instantiated"
|
636
|
-
end
|
637
|
-
|
638
|
-
@attributes, @new_record, @destroyed, @uri, @href = {}, true, false
|
639
|
-
self.attributes = attributes
|
640
|
-
yield self if block_given?
|
641
|
-
end
|
642
|
-
|
643
|
-
# @return [self] Reloads the record from the server.
|
644
|
-
def reload(response = nil)
|
645
|
-
if response
|
646
|
-
return if response.body.to_s.length.zero?
|
647
|
-
fresh = self.class.from_response response
|
648
|
-
else
|
649
|
-
options = {:etag => (etag unless changed?)}
|
650
|
-
fresh = if @href
|
651
|
-
self.class.from_response API.get(@href, {}, options)
|
652
|
-
else
|
653
|
-
self.class.find(to_param, options)
|
654
|
-
end
|
655
|
-
end
|
656
|
-
fresh and copy_from fresh
|
657
|
-
persist! true
|
658
|
-
self
|
659
|
-
rescue API::NotModified
|
660
|
-
self
|
661
|
-
end
|
662
|
-
|
663
|
-
# @return [Hash] Hash of changed attributes.
|
664
|
-
# @see #changes
|
665
|
-
def changed_attributes
|
666
|
-
@changed_attributes ||= {}
|
667
|
-
end
|
668
|
-
|
669
|
-
# @return [Array] A list of changed attribute keys.
|
670
|
-
def changed
|
671
|
-
changed_attributes.keys
|
672
|
-
end
|
673
|
-
|
674
|
-
# Do any attributes have unsaved changes?
|
675
|
-
# @return [true, false]
|
676
|
-
def changed?
|
677
|
-
!changed_attributes.empty?
|
678
|
-
end
|
679
|
-
|
680
|
-
# @return [Hash] Map of changed attributes to original value and new value.
|
681
|
-
def changes
|
682
|
-
changed_attributes.inject({}) { |changes, (key, original_value)|
|
683
|
-
changes[key] = [original_value, self[key]] and changes
|
684
|
-
}
|
685
|
-
end
|
686
|
-
|
687
|
-
# @return [Hash] Previously-changed attributes.
|
688
|
-
# @see #changes
|
689
|
-
def previous_changes
|
690
|
-
@previous_changes ||= {}
|
691
|
-
end
|
692
|
-
|
693
|
-
# Is the record new (i.e., not saved on Recurly's servers)?
|
694
|
-
#
|
695
|
-
# @return [true, false]
|
696
|
-
# @see #persisted?
|
697
|
-
# @see #destroyed?
|
698
|
-
def new_record?
|
699
|
-
@new_record
|
700
|
-
end
|
701
|
-
|
702
|
-
# Has the record been destroyed? (Set +true+ after a successful destroy.)
|
703
|
-
# @return [true, false]
|
704
|
-
# @see #new_record?
|
705
|
-
# @see #persisted?
|
706
|
-
def destroyed?
|
707
|
-
@destroyed
|
708
|
-
end
|
709
|
-
|
710
|
-
# Has the record persisted (i.e., saved on Recurly's servers)?
|
711
|
-
#
|
712
|
-
# @return [true, false]
|
713
|
-
# @see #new_record?
|
714
|
-
# @see #destroyed?
|
715
|
-
def persisted?
|
716
|
-
!(new_record? || destroyed?)
|
717
|
-
end
|
718
|
-
|
719
|
-
# The value of a specified attribute, lazily fetching any defined
|
720
|
-
# association.
|
721
|
-
#
|
722
|
-
# @param key [Symbol, String] The name of the attribute to be fetched.
|
723
|
-
# @example
|
724
|
-
# account.read_attribute :first_name # => "Ted"
|
725
|
-
# account[:last_name] # => "Beneke"
|
726
|
-
# @see #write_attribute
|
727
|
-
def read_attribute(key)
|
728
|
-
key = key.to_s
|
729
|
-
if attributes.key? key
|
730
|
-
value = attributes[key]
|
731
|
-
elsif links.key?(key) && self.class.reflect_on_association(key)
|
732
|
-
value = attributes[key] = follow_link key
|
733
|
-
end
|
734
|
-
value
|
735
|
-
end
|
736
|
-
alias [] read_attribute
|
737
|
-
|
738
|
-
# Sets the value of a specified attribute.
|
739
|
-
#
|
740
|
-
# @param key [Symbol, String] The name of the attribute to be set.
|
741
|
-
# @param value [Object] The value the attribute will be set to.
|
742
|
-
# @example
|
743
|
-
# account.write_attribute :first_name, 'Gus'
|
744
|
-
# account[:company_name] = 'Los Pollos Hermanos'
|
745
|
-
# @see #read_attribute
|
746
|
-
def write_attribute(key, value)
|
747
|
-
if changed_attributes.key?(key = key.to_s)
|
748
|
-
changed_attributes.delete key if changed_attributes[key] == value
|
749
|
-
elsif self[key] != value
|
750
|
-
changed_attributes[key] = self[key]
|
751
|
-
end
|
752
|
-
|
753
|
-
association = self.class.find_association(key)
|
754
|
-
if association
|
755
|
-
value = fetch_associated(key, value)
|
756
|
-
# FIXME: More explicit; less magic.
|
757
|
-
elsif value && key.end_with?('_in_cents') && !respond_to?(:currency)
|
758
|
-
value = Money.new(value, self, key) unless value.is_a?(Money)
|
759
|
-
end
|
760
|
-
|
761
|
-
attributes[key] = value
|
762
|
-
end
|
763
|
-
alias []= write_attribute
|
764
|
-
|
765
|
-
# Apply a given hash of attributes to a record.
|
766
|
-
#
|
767
|
-
# @return [Hash]
|
768
|
-
# @param attributes [Hash] A hash of attributes.
|
769
|
-
def attributes=(attributes = {})
|
770
|
-
attributes.each_pair { |k, v|
|
771
|
-
respond_to?(name = "#{k}=") and send(name, v) or self[k] = v
|
772
|
-
}
|
773
|
-
end
|
774
|
-
|
775
|
-
def as_json(options = nil)
|
776
|
-
attributes.reject { |k, v| v.is_a?(Recurly::Resource::Pager) }
|
777
|
-
end
|
778
|
-
|
779
|
-
# @return [Hash] The raw hash of record href links.
|
780
|
-
def links
|
781
|
-
@links ||= {}
|
782
|
-
end
|
783
|
-
|
784
|
-
# Whether a record has a link with the given name.
|
785
|
-
#
|
786
|
-
# @param key [Symbol, String] The name of the link to check for.
|
787
|
-
# @example
|
788
|
-
# account.link? :billing_info # => true
|
789
|
-
def link?(key)
|
790
|
-
links.key?(key.to_s)
|
791
|
-
end
|
792
|
-
|
793
|
-
# Fetch the value of a link by following the associated href.
|
794
|
-
#
|
795
|
-
# @param key [Symbol, String] The name of the link to be followed.
|
796
|
-
# @param options [Hash] A hash of API options.
|
797
|
-
# @example
|
798
|
-
# account.read_link :billing_info # => <Recurly::BillingInfo>
|
799
|
-
def follow_link(key, options = {})
|
800
|
-
if link = links[key = key.to_s]
|
801
|
-
response = API.send link[:method], link[:href], options[:body], options
|
802
|
-
if resource_class = link[:resource_class]
|
803
|
-
response = resource_class.from_response response
|
804
|
-
response.attributes[self.class.member_name] = self
|
805
|
-
end
|
806
|
-
response
|
807
|
-
end
|
808
|
-
rescue Recurly::API::NotFound
|
809
|
-
raise unless resource_class
|
810
|
-
end
|
811
|
-
|
812
|
-
# Serializes the record to XML.
|
813
|
-
#
|
814
|
-
# @return [String] An XML string.
|
815
|
-
# @param options [Hash] A hash of XML options.
|
816
|
-
# @example
|
817
|
-
# Recurly::Account.new(:account_code => 'code').to_xml
|
818
|
-
# # => "<account><account_code>code</account_code></account>"
|
819
|
-
def to_xml(options = {})
|
820
|
-
builder = options[:builder] || XML.new("<#{self.class.xml_root_key}/>")
|
821
|
-
xml_keys.each { |key|
|
822
|
-
value = respond_to?(key) ? send(key) : self[key]
|
823
|
-
node = builder.add_element key
|
824
|
-
|
825
|
-
# Duck-typing here is problematic because of ActiveSupport's #to_xml.
|
826
|
-
case value
|
827
|
-
when Resource
|
828
|
-
value.to_xml options.merge(:builder => node)
|
829
|
-
when Array, Subscription::AddOns
|
830
|
-
value.each do |e|
|
831
|
-
if e.is_a? Recurly::Resource
|
832
|
-
# create a node to hold this resource
|
833
|
-
e_node = node.add_element Helper.singularize(key)
|
834
|
-
# serialize the resource into this node
|
835
|
-
e.to_xml(options.merge(builder: e_node))
|
836
|
-
else
|
837
|
-
# it's just a primitive value
|
838
|
-
node.add_element(Helper.singularize(key), e)
|
839
|
-
end
|
840
|
-
end
|
841
|
-
when Hash, Recurly::Money
|
842
|
-
value.each_pair { |k, v| node.add_element k.to_s, v }
|
843
|
-
else
|
844
|
-
node.text = value
|
845
|
-
end
|
846
|
-
}
|
847
|
-
builder.to_s
|
848
|
-
end
|
849
|
-
|
850
|
-
# Attempts to save the record, returning the success of the request.
|
851
|
-
#
|
852
|
-
# @return [true, false]
|
853
|
-
# @raise [Transaction::Error] A monetary transaction failed.
|
854
|
-
# @example
|
855
|
-
# account = Recurly::Account.new
|
856
|
-
# account.save # => false
|
857
|
-
# account.account_code = 'account_code'
|
858
|
-
# account.save # => true
|
859
|
-
# @see #save!
|
860
|
-
def save
|
861
|
-
if new_record? || changed?
|
862
|
-
clear_errors
|
863
|
-
@response = API.send(
|
864
|
-
persisted? ? :put : :post, path, to_xml
|
865
|
-
)
|
866
|
-
reload response
|
867
|
-
persist! true
|
868
|
-
end
|
869
|
-
true
|
870
|
-
rescue API::UnprocessableEntity => e
|
871
|
-
apply_errors e
|
872
|
-
Transaction::Error.validate! e, (self if is_a?(Transaction))
|
13
|
+
def requires_client?
|
873
14
|
false
|
874
15
|
end
|
875
16
|
|
876
|
-
|
877
|
-
|
878
|
-
#
|
879
|
-
# @return [true]
|
880
|
-
# @raise [Invalid] The record was invalid.
|
881
|
-
# @raise [Transaction::Error] A monetary transaction failed.
|
882
|
-
# @example
|
883
|
-
# account = Recurly::Account.new
|
884
|
-
# account.save! # raises Recurly::Resource::Invalid
|
885
|
-
# account.account_code = 'account_code'
|
886
|
-
# account.save! # => true
|
887
|
-
# @see #save
|
888
|
-
def save!
|
889
|
-
save || raise(Invalid.new(self))
|
890
|
-
end
|
891
|
-
|
892
|
-
# @return [true, false, nil] The validity of the record: +true+ if the
|
893
|
-
# record was successfully saved (or persisted and unchanged), +false+ if
|
894
|
-
# the record was not successfully saved, or +nil+ for a record with an
|
895
|
-
# unknown state (i.e. (i.e. new records that haven't been saved and
|
896
|
-
# persisted records with changed attributes).
|
897
|
-
# @example
|
898
|
-
# account = Recurly::Account.new
|
899
|
-
# account.valid? # => nil
|
900
|
-
# account.save # => false
|
901
|
-
# account.valid? # => false
|
902
|
-
# account.account_code = 'account_code'
|
903
|
-
# account.save # => true
|
904
|
-
# account.valid? # => true
|
905
|
-
def valid?
|
906
|
-
return true if persisted? && !changed?
|
907
|
-
errors_empty = errors.values.flatten.empty?
|
908
|
-
return if errors_empty && changed?
|
909
|
-
errors_empty
|
910
|
-
end
|
911
|
-
|
912
|
-
# Update a record with a given hash of attributes.
|
913
|
-
#
|
914
|
-
# @return [true, false] The success of the update.
|
915
|
-
# @param attributes [Hash] A hash of attributes.
|
916
|
-
# @raise [Transaction::Error] A monetary transaction failed.
|
917
|
-
# @example
|
918
|
-
# account = Account.find 'junior'
|
919
|
-
# account.update_attributes :account_code => 'flynn' # => true
|
920
|
-
# @see #update_attributes!
|
921
|
-
def update_attributes(attributes = {})
|
922
|
-
self.attributes = attributes and save
|
923
|
-
end
|
924
|
-
|
925
|
-
# Update a record with a given hash of attributes.
|
926
|
-
#
|
927
|
-
# @return [true] The update was successful.
|
928
|
-
# @param attributes [Hash] A hash of attributes.
|
929
|
-
# @raise [Invalid] The record was invalid.
|
930
|
-
# @raise [Transaction::Error] A monetary transaction failed.
|
931
|
-
# @example
|
932
|
-
# account = Account.find 'gale_boetticher'
|
933
|
-
# account.update_attributes! :account_code => nil # Raises an exception.
|
934
|
-
# @see #update_attributes
|
935
|
-
def update_attributes!(attributes = {})
|
936
|
-
self.attributes = attributes and save!
|
937
|
-
end
|
938
|
-
|
939
|
-
# @return [Hash] A hash with indifferent read access containing any
|
940
|
-
# validation errors where the key is the attribute name and the value is
|
941
|
-
# an array of error messages.
|
942
|
-
# @example
|
943
|
-
# account.errors # => {"account_code"=>["can't be blank"]}
|
944
|
-
# account.errors[:account_code] # => ["can't be blank"]
|
945
|
-
def errors
|
946
|
-
@errors ||= Errors.new { |h, k| h[k] = [] }
|
947
|
-
end
|
948
|
-
|
949
|
-
# Marks a record as persisted, i.e. not a new or deleted record, resetting
|
950
|
-
# any tracked attribute changes in the process. (This is an internal method
|
951
|
-
# and should probably not be called unless you know what you're doing.)
|
952
|
-
#
|
953
|
-
# @api internal
|
954
|
-
# @return [true]
|
955
|
-
def persist!(saved = false)
|
956
|
-
@new_record, @uri = false
|
957
|
-
if changed?
|
958
|
-
@previous_changes = changes if saved
|
959
|
-
changed_attributes.clear
|
960
|
-
end
|
961
|
-
true
|
962
|
-
end
|
963
|
-
|
964
|
-
# @return [String, nil] The unique resource identifier (URI) of the record
|
965
|
-
# (if persisted).
|
966
|
-
# @example
|
967
|
-
# Recurly::Account.new(:account_code => "account_code").uri # => nil
|
968
|
-
# Recurly::Account.find("account_code").uri
|
969
|
-
# # => "https://api.recurly.com/v2/accounts/account_code"
|
970
|
-
def uri
|
971
|
-
@href ||= ((API.base_uri + path).to_s if persisted?)
|
972
|
-
end
|
973
|
-
|
974
|
-
# Attempts to destroy the record.
|
975
|
-
#
|
976
|
-
# @return [true, false] +true+ if successful, +false+ if unable to destroy
|
977
|
-
# (if the record does not persist on Recurly).
|
978
|
-
# @raise [NotFound] The record cannot be found.
|
979
|
-
# @example
|
980
|
-
# account = Recurly::Account.find account_code
|
981
|
-
# race_condition = Recurly::Account.find account_code
|
982
|
-
# account.destroy # => true
|
983
|
-
# account.destroy # => false (already destroyed)
|
984
|
-
# race_condition.destroy # raises Recurly::Resource::NotFound
|
985
|
-
def destroy
|
986
|
-
return false unless persisted?
|
987
|
-
@response = API.delete uri
|
988
|
-
@destroyed = true
|
989
|
-
rescue API::NotFound => e
|
990
|
-
raise NotFound, e.description
|
991
|
-
end
|
992
|
-
|
993
|
-
def signable_attributes
|
994
|
-
Hash[xml_keys.map { |key| [key, self[key]] }]
|
995
|
-
end
|
996
|
-
|
997
|
-
def ==(other)
|
998
|
-
other.is_a?(self.class) && other.to_s == to_s
|
999
|
-
end
|
1000
|
-
|
1001
|
-
def marshal_dump
|
1002
|
-
[
|
1003
|
-
@attributes.reject { |k, v| v.is_a?(Proc) },
|
1004
|
-
@new_record,
|
1005
|
-
@destroyed,
|
1006
|
-
@uri,
|
1007
|
-
@href,
|
1008
|
-
changed_attributes,
|
1009
|
-
previous_changes,
|
1010
|
-
response,
|
1011
|
-
etag,
|
1012
|
-
links,
|
1013
|
-
@type
|
1014
|
-
]
|
1015
|
-
end
|
1016
|
-
|
1017
|
-
def marshal_load(serialization)
|
1018
|
-
@attributes,
|
1019
|
-
@new_record,
|
1020
|
-
@destroyed,
|
1021
|
-
@uri,
|
1022
|
-
@href,
|
1023
|
-
@changed_attributes,
|
1024
|
-
@previous_changes,
|
1025
|
-
@response,
|
1026
|
-
@etag,
|
1027
|
-
@links,
|
1028
|
-
@type = serialization
|
1029
|
-
end
|
1030
|
-
|
1031
|
-
# @return [String]
|
1032
|
-
def inspect(attributes = self.class.attribute_names.to_a)
|
1033
|
-
string = "#<#{self.class}"
|
1034
|
-
string << "##@type" if respond_to?(:type)
|
1035
|
-
attributes += %w(errors) if errors.any?
|
1036
|
-
string << " %s" % attributes.map { |k|
|
1037
|
-
"#{k}: #{self.send(k).inspect}"
|
1038
|
-
}.join(', ')
|
1039
|
-
string << '>'
|
1040
|
-
end
|
1041
|
-
alias to_s inspect
|
1042
|
-
|
1043
|
-
def apply_errors(exception)
|
1044
|
-
@response = exception.response
|
1045
|
-
document = XML.new exception.response.body
|
1046
|
-
|
1047
|
-
if document.root.name == 'error'
|
1048
|
-
# Single error is returned from the API
|
1049
|
-
attribute_path = document['symbol'].text.split '.'
|
1050
|
-
invalid! [attribute_path[1]], document['description'].text
|
1051
|
-
else
|
1052
|
-
# Array of errors was returned by the API
|
1053
|
-
document.each_element 'error' do |el|
|
1054
|
-
attribute_path = el.attribute('field').value.split '.'
|
1055
|
-
invalid! attribute_path[1, attribute_path.length], el.text
|
1056
|
-
end
|
1057
|
-
end
|
17
|
+
def ==(other_resource)
|
18
|
+
self.attributes == other_resource.attributes
|
1058
19
|
end
|
1059
20
|
|
1060
21
|
protected
|
1061
22
|
|
1062
|
-
def
|
1063
|
-
@
|
1064
|
-
self.class.member_path to_param
|
1065
|
-
else
|
1066
|
-
self.class.collection_path
|
1067
|
-
end
|
1068
|
-
end
|
1069
|
-
|
1070
|
-
def invalid!(attribute_path, error)
|
1071
|
-
if attribute_path.length == 1
|
1072
|
-
errors[attribute_path[0]] << error
|
1073
|
-
else
|
1074
|
-
child, k, v = attribute_path.shift.scan(/[^\[\]=]+/)
|
1075
|
-
if c = k ? self[child].find { |d| d[k] == v } : self[child]
|
1076
|
-
c.invalid! attribute_path, error if c.methods.include? :invalid!
|
1077
|
-
e = errors[child] << 'is invalid' and e.uniq!
|
1078
|
-
end
|
1079
|
-
end
|
1080
|
-
end
|
1081
|
-
|
1082
|
-
def clear_errors
|
1083
|
-
errors.clear
|
1084
|
-
self.class.associations do |association|
|
1085
|
-
next unless respond_to? "#{association}=" # Clear writable only.
|
1086
|
-
[*self[association]].each do |associated|
|
1087
|
-
associated.clear_errors if associated.respond_to? :clear_errors
|
1088
|
-
end
|
1089
|
-
end
|
1090
|
-
end
|
1091
|
-
|
1092
|
-
def copy_from(other)
|
1093
|
-
other.instance_variables.each do |ivar|
|
1094
|
-
instance_variable_set ivar, other.instance_variable_get(ivar)
|
1095
|
-
end
|
23
|
+
def initialize(attributes = {})
|
24
|
+
@attributes = attributes.clone
|
1096
25
|
end
|
1097
26
|
|
1098
|
-
|
1099
|
-
|
1100
|
-
def fetch_associated(name, value, options = {})
|
1101
|
-
case value
|
1102
|
-
when Array
|
1103
|
-
value.map do |v|
|
1104
|
-
fetch_associated(Helper.singularize(name), v, association_name: name)
|
1105
|
-
end
|
1106
|
-
when Hash
|
1107
|
-
association_name = options[:association_name] || name
|
1108
|
-
associated_class_name = self.class.find_association(association_name).class_name
|
1109
|
-
associated_class_name ||= Helper.classify(name)
|
1110
|
-
Recurly.const_get(associated_class_name, false).send(:new, value)
|
1111
|
-
when Proc, Resource, Resource::Pager, nil
|
1112
|
-
value
|
1113
|
-
else
|
1114
|
-
raise "unexpected association #{name.inspect}=#{value.inspect}"
|
1115
|
-
end
|
27
|
+
def to_s
|
28
|
+
self.inspect
|
1116
29
|
end
|
1117
30
|
|
1118
|
-
def
|
1119
|
-
|
31
|
+
def schema
|
32
|
+
self.class.schema
|
1120
33
|
end
|
1121
34
|
end
|
1122
35
|
end
|