Class: Recurly::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/recurly/resource.rb,
lib/recurly/resource/pager.rb,
lib/recurly/resource/errors.rb,
lib/recurly/resource/association.rb

Overview

The base class for all Recurly resources (e.g. Account, Subscription, Transaction).

Resources behave much like ActiveModel classes, especially like ActiveRecord.

Life Cycle

To take you through the typical life cycle of a resource, we'll use Account as an example.

Creating a Record

You can instantiate a record before attempting to save it.

 = Recurly::Account.new :first_name => 'Walter'

Once instantiated, you can assign and reassign any attribute.

.first_name = 'Walt'
.last_name = 'White'

When you're ready to save, do so.

.save # => false

If save returns false, validation likely failed. You can check the record for errors.

.errors # => {"account_code"=>["can't be blank"]}

Once the errors are fixed, you can try again.

. = 'heisenberg'
.save # => true

The object will be updated with any information provided by the server (including any UUIDs set).

.created_at # => 2011-04-30 07:13:35 -0700

You can also create accounts in one fell swoop.

Recurly::Account.create(
  :first_name   => 'Jesse'
  :last_name    => 'Pinkman'
  :account_code => 'capn_cook'
)
# => #<Recurly::Account account_code: "capn_cook" ...>

You can use alternative “bang” methods for exception control. If the record fails to save, a Recurly::Resource::Invalid exception will be raised.

begin
   = Recurly::Account.new :first_name => 'Junior'
  .save!
rescue Recurly::Resource::Invalid
  p .errors
end

You can access the invalid record from the exception itself (if, for example, you use the create! method).

begin
  Recurly::Account.create! :first_name => 'Skylar', :last_name => 'White'
rescue Recurly::Resource::Invalid => e
  p e.record.errors
end

Fetching a Record

Records are fetched by their unique identifiers.

 = Recurly::Account.find 'better_call_saul'
# => #<Recurly::Account account_code: "better_call_saul" ...>

If the record doesn't exist, a Recurly::Resource::NotFound exception will be raised.

Updating a Record

Once fetched, a record can be updated with a hash of attributes.

.update_attributes :first_name => 'Saul', :last_name => 'Goodman'
# => true

(A bang method, update_attributes!, will raise Recurly::Resource::Invalid.)

You can also update a record by setting attributes and calling save.

.last_name = 'McGill'
.save # Alternatively, call save!

Deleting a Record

To delete (deactivate, close, etc.) a fetched record, merely call destroy on it.

.destroy # => true

Fetching a List of Records

If you want to iterate over a list of accounts, you can use a Pager.

pager = Account.paginate :per_page => 50

If you want to iterate over every record, a convenience method will automatically paginate:

Account.find_each { || p  }

Defined Under Namespace

Classes: Association, Errors, Invalid, NotFound, Pager

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) {|_self| ... } ⇒ Resource

Returns A new resource instance.

Parameters:

  • attributes (Hash) (defaults to: {})

    A hash of attributes.

Yields:

  • (_self)

Yield Parameters:



605
606
607
608
609
610
611
612
613
614
# File 'lib/recurly/resource.rb', line 605

def initialize(attributes = {})
  if instance_of? Resource
    raise Error,
      "#{self.class} is an abstract class and cannot be instantiated"
  end

  @attributes, @new_record, @destroyed, @uri, @href = {}, true, false
  self.attributes = attributes
  yield self if block_given?
end

Class Attribute Details

.attribute_namesArray? (readonly)

Returns The list of attribute names defined for the resource class.

Returns:

  • (Array, nil)

    The list of attribute names defined for the resource class.



242
243
244
# File 'lib/recurly/resource.rb', line 242

def attribute_names
  @attribute_names
end

Instance Attribute Details

#attributesHash

Returns The raw hash of record attributes.

Returns:

  • (Hash)

    The raw hash of record attributes.



591
592
593
# File 'lib/recurly/resource.rb', line 591

def attributes
  @attributes
end

#etagString? (readonly)

Returns An ETag for the current record.

Returns:

  • (String, nil)

    An ETag for the current record.



598
599
600
# File 'lib/recurly/resource.rb', line 598

def etag
  @etag
end

#responseNet::HTTPResponse? (readonly)

Returns The most recent response object for the record (updated during #save and #destroy).

Returns:

  • (Net::HTTPResponse, nil)

    The most recent response object for the record (updated during #save and #destroy).



595
596
597
# File 'lib/recurly/resource.rb', line 595

def response
  @response
end

#uriString?

Returns The unique resource identifier (URI) of the record (if persisted).

Examples:

Recurly::Account.new(:account_code => "account_code").uri # => nil
Recurly::Account.find("account_code").uri
# => "https://api.recurly.com/v2/accounts/account_code"

Returns:

  • (String, nil)

    The unique resource identifier (URI) of the record (if persisted).



936
937
938
# File 'lib/recurly/resource.rb', line 936

def uri
  @href ||= ((API.base_uri + path).to_s if persisted?)
end

Class Method Details

.all(options = {}) ⇒ Object



263
264
265
# File 'lib/recurly/resource.rb', line 263

def all(options = {})
  paginate(options).to_a
end

.association_class_name(resource_class) ⇒ String?

for the current class if the resource class does not match the actual class.

Returns:

  • (String, nil)

    The actual associated resource class name



476
477
478
479
# File 'lib/recurly/resource.rb', line 476

def association_class_name(resource_class)
  association = find_association(resource_class)
  association.class_name if association
end

.associationsArray

Returns A list of associations for the current class.

Returns:

  • (Array)

    A list of associations for the current class.



463
464
465
# File 'lib/recurly/resource.rb', line 463

def associations
  @associations ||= []
end

.associations_for_relation(relation) ⇒ Array

the relation [:has_many, :has_one, :belongs_to] for the current class.

Returns:

  • (Array)

    A list of associated resource classes with



469
470
471
# File 'lib/recurly/resource.rb', line 469

def associations_for_relation(relation)
  associations.select{ |a| a.relation == relation }.map(&:resource_class)
end

.associations_helperObject



487
488
489
# File 'lib/recurly/resource.rb', line 487

def associations_helper
  @associations_helper ||= Module.new.tap { |helper| include helper }
end

.belongs_to(parent_name, options = {}) ⇒ Proc

Establishes a belongs_to association.

Parameters:

  • parent_name (Symbol)

    Association name.

  • options (Hash) (defaults to: {})

    A hash of association options.

Options Hash (options):

  • :readonly (true, false)

    Don't define a setter.

    String

    :resource_class Actual associated resource class name

    if not same as parent_name.

Returns:

  • (Proc)


563
564
565
566
567
568
569
570
571
572
573
# File 'lib/recurly/resource.rb', line 563

def belongs_to(parent_name, options = {})
  associations << Association.new(:belongs_to, parent_name.to_s, options)
  associations_helper.module_eval do
    define_method(parent_name) { self[parent_name] }
    if options.key?(:readonly) && options[:readonly] == false
      define_method "#{parent_name}=" do |parent|
        self[parent_name] = parent
      end
    end
  end
end

.collection_nameString Also known as: collection_path

Returns The underscored, pluralized name of the resource class.

Examples:

Recurly::Account.collection_name # => "accounts"

Returns:

  • (String)

    The underscored, pluralized name of the resource class.



179
180
181
# File 'lib/recurly/resource.rb', line 179

def collection_name
  Helper.pluralize Helper.underscore(resource_name)
end

.countInteger

Returns The total record count of the resource in question.

Examples:

Recurly::Account.count # => 42

Returns:

  • (Integer)

    The total record count of the resource in question.

See Also:



303
304
305
# File 'lib/recurly/resource.rb', line 303

def count
  paginate.count
end

.create(attributes = {}) ⇒ Resource

Instantiates and attempts to save a record.

Returns:

Raises:

See Also:



353
354
355
# File 'lib/recurly/resource.rb', line 353

def create(attributes = {})
  new(attributes) { |record| record.save }
end

.create!(attributes = {}) ⇒ Resource

Instantiates and attempts to save a record.

Returns:

Raises:

See Also:



363
364
365
# File 'lib/recurly/resource.rb', line 363

def create!(attributes = {})
  new(attributes) { |record| record.save! }
end

.define_attribute_methods(attribute_names) ⇒ Array

Returns Per attribute, defines readers, writers, boolean and change-tracking methods.

Examples:

class Account < Resource
  define_attribute_methods [:name]
end

a = Account.new
a.name?            # => false
a.name             # => nil
a.name = "Stephen"
a.name?            # => true
a.name             # => "Stephen"
a.name_changed?    # => true
a.name_was         # => nil
a.name_change      # => [nil, "Stephen"]

Parameters:

  • attribute_names (Array)

    An array of attribute names.

Returns:

  • (Array)

    Per attribute, defines readers, writers, boolean and change-tracking methods.



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/recurly/resource.rb', line 219

def define_attribute_methods(attribute_names)
  @attribute_names = attribute_names.map! { |m| m.to_s }.sort!.freeze
  remove_const :AttributeMethods if constants.include? :AttributeMethods
  include const_set :AttributeMethods, Module.new {
    attribute_names.each do |name|
      define_method(name) { self[name] }                       # Get.
      define_method("#{name}=") { |value| self[name] = value } # Set.
      define_method("#{name}?") { !!self[name] }               # Present.
      define_method("#{name}_change") { changes[name] }        # Dirt...
      define_method("#{name}_changed?") { changed_attributes.key? name }
      define_method("#{name}_was") { changed_attributes[name] }
      define_method("#{name}_previously_changed?") {
        previous_changes.key? name
      }
      define_method("#{name}_previously_was") {
        previous_changes[name].first if previous_changes.key? name
      }
    end
  }
end

.embedded!(root_index = false) ⇒ Object



581
582
583
584
585
586
587
# File 'lib/recurly/resource.rb', line 581

def embedded!(root_index = false)
  private :initialize
  private_class_method(*%w(new create create!))
  unless root_index
    private_class_method(*%w(all find_each first paginate scoped where))
  end
end

.find(uuid, options = {}) ⇒ Resource

Returns A record matching the designated unique identifier.

Examples:

Recurly::Account.find "heisenberg"
# => #<Recurly::Account account_code: "heisenberg", ...>
Use the following identifiers for these types of objects:
  for accounts use account_code
  for plans use plan_code
  for invoices use invoice_number
  for subscriptions use uuid
  for transactions use uuid

Parameters:

  • uuid (String)

    The unique identifier of the resource to be retrieved.

  • options (Hash) (defaults to: {})

    A hash of options.

Options Hash (options):

  • :etag (String)

    When set, will raise API::NotModified if the record content has not changed.

Returns:

  • (Resource)

    A record matching the designated unique identifier.

Raises:

  • (Error)

    If the resource has no identifier (and thus cannot be retrieved).

  • (NotFound)

    If no resource can be found for the supplied identifier (or the supplied identifier is nil).

  • (API::NotModified)

    If the :etag option is set and matches the server's.



334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/recurly/resource.rb', line 334

def find(uuid, options = {})
  if uuid.nil?
    # Should we raise an ArgumentError, instead?
    raise NotFound, "can't find a record with nil identifier"
  end

  uri = uuid =~ /^http/ ? uuid : member_path(uuid)
  begin
    from_response API.get(uri, {}, options)
  rescue API::NotFound => e
    raise NotFound, e.description
  end
end

.find_association(resource_class) ⇒ Association?

Returns Find association for the current class with resource class name.

Returns:

  • (Association, nil)

    Find association for the current class with resource class name.



483
484
485
# File 'lib/recurly/resource.rb', line 483

def find_association(resource_class)
  associations.find{ |a| a.resource_class.to_s == resource_class.to_s }
end

.find_each(per_page = 50) {|record| ... } ⇒ nil

Iterates through every record by automatically paging.

Examples:

Recurly::Account.find_each { |a| p a }

Parameters:

  • per_page (Integer) (defaults to: 50)

    The number of records returned per request.

Yields:

  • (record)

Returns:

  • (nil)

See Also:



295
296
297
# File 'lib/recurly/resource.rb', line 295

def find_each(per_page = 50, &block)
  paginate(:per_page => per_page).find_each(&block)
end

.firstResource?

Returns:



309
310
311
# File 'lib/recurly/resource.rb', line 309

def first
  paginate(:per_page => 1).first
end

.from_response(response) ⇒ Resource

Instantiates a record from an HTTP response, setting the record's response attribute in the process.

Parameters:

  • response (Net::HTTPResponse)

Returns:



372
373
374
375
376
377
378
379
380
381
# File 'lib/recurly/resource.rb', line 372

def from_response(response)
  case response['Content-Type']
  when %r{application/pdf}
    response.body
  else # when %r{application/xml}
    record = from_xml response.body
    record.instance_eval { @etag, @response = response['ETag'], response }
    record
  end
end

.from_xml(xml) ⇒ Resource

Instantiates a record from an XML blob: either a String or XML element.

Assuming the record is from an API response, the record is flagged as persisted.

Parameters:

  • xml (String, REXML::Element, Nokogiri::XML::Node)

Returns:

See Also:



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/recurly/resource.rb', line 391

def from_xml(xml)
  xml = XML.new xml
  if self != Resource || xml.name == member_name
    record = new
  elsif Recurly.const_defined?(
    class_name = Helper.classify(xml.name), false
  )
    klass = Recurly.const_get class_name, false
    record = klass.send :new
  elsif root = xml.root and root.elements.empty?
    return XML.cast root
  else
    record = {}
  end
  klass ||= self

  xml.root.attributes.each do |name, value|
    record.instance_variable_set "@#{name}", value.to_s
  end

  xml.each_element do |el|
    if el.name == 'a'
      record.links[el.attribute('name').value] = {
        :method => el.attribute('method').to_s,
        :href => el.attribute('href').value
      }
      next
    end

    # Nokogiri on Jruby-1.7.19 likes to throw NullPointer exceptions
    # if you try to run certian operations like el.attribute(''). Since
    # we dont care about text nodes, let's just skip them
    next if defined?(Nokogiri::XML::Node::TEXT_NODE) && el.node_type == Nokogiri::XML::Node::TEXT_NODE

    if el.children.empty? && href = el.attribute('href')
      klass_name = Helper.classify(klass.association_class_name(el.name) ||
                                   el.attribute('type') ||
                                   el.name)

      next unless Recurly.const_defined?(klass_name)

      resource_class = Recurly.const_get(klass_name, false)

      case el.name
      when *klass.associations_for_relation(:has_many)
        record[el.name] = Pager.new(
          resource_class, :uri => href.value, :parent => record
        )
      when *(klass.associations_for_relation(:has_one) + klass.associations_for_relation(:belongs_to))
        record.links[el.name] = {
          :resource_class => resource_class,
          :method => :get,
          :href => href.value
        }
      end
    else
      val = XML.cast(el)
      if 'address' == el.name && val.kind_of?(Hash)
        address = Address.new val
        address.instance_variable_set(:@changed_attributes, {})
        record[el.name] = address
      else
        record[el.name] = val
      end
    end
  end

  record.persist! if record.respond_to? :persist!
  record
end

.has_many(collection_name, options = {}) ⇒ Proc?

Establishes a has_many association.

Parameters:

  • collection_name (Symbol)

    Association name.

  • options (Hash) (defaults to: {})

    A hash of association options.

Options Hash (options):

  • :readonly (true, false)

    Don't define a setter.

    String

    :resource_class Actual associated resource class name

    if not same as collection_name.

Returns:

  • (Proc, nil)


499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
# File 'lib/recurly/resource.rb', line 499

def has_many(collection_name, options = {})
  associations << Association.new(:has_many, collection_name.to_s, options)
  associations_helper.module_eval do
    define_method collection_name do
      if self[collection_name]
        self[collection_name]
      else
        attributes[collection_name] = []
      end
    end
    if options.key?(:readonly) && options[:readonly] == false
      define_method "#{collection_name}=" do |collection|
        self[collection_name] = collection
      end
    end
  end
end

.has_one(member_name, options = {}) ⇒ Proc?

Establishes a has_one association.

Parameters:

  • member_name (Symbol)

    Association name.

  • options (Hash) (defaults to: {})

    A hash of association options.

Options Hash (options):

  • :readonly (true, false)

    Don't define a setter.

    String

    :resource_class Actual associated resource class name

    if not same as member_name.

Returns:

  • (Proc, nil)


525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
# File 'lib/recurly/resource.rb', line 525

def has_one(member_name, options = {})
  associations << Association.new(:has_one, member_name.to_s, options)
  associations_helper.module_eval do
    define_method(member_name) { self[member_name] }
    if options.key?(:readonly) && options[:readonly] == false
      associated = Recurly.const_get Helper.classify(member_name), false
      define_method "#{member_name}=" do |member|
        associated_uri = "#{path}/#{member_name}"
        self[member_name] = case member
        when Hash
          associated.send :new, member.merge(:uri => associated_uri)
        when associated
          member.uri = associated_uri and member
        else
          raise ArgumentError, "expected #{associated}"
        end
      end
      define_method "build_#{member_name}" do |*args|
        attributes = args.shift || {}
        self[member_name] = associated.send(
          :new, attributes.merge(:uri => "#{path}/#{member_name}")
        ).tap { |child| child.attributes[self.class.member_name] = self }
      end
      define_method "create_#{member_name}" do |*args|
        send("build_#{member_name}", *args).tap { |child| child.save }
      end
    end
  end
end

.member_nameString

Returns The underscored name of the resource class.

Examples:

Recurly::Account.member_name # => "account"

Returns:

  • (String)

    The underscored name of the resource class.



187
188
189
# File 'lib/recurly/resource.rb', line 187

def member_name
  Helper.underscore resource_name
end

.member_path(uuid) ⇒ String

Returns The relative path to a resource's identifier from the API's base URI.

Examples:

Recurly::Account.member_path "code" # => "accounts/code"
Recurly::Account.member_path nil    # => "accounts"

Parameters:

  • uuid (String, nil)

Returns:

  • (String)

    The relative path to a resource's identifier from the API's base URI.



197
198
199
200
# File 'lib/recurly/resource.rb', line 197

def member_path(uuid)
  uuid = ERB::Util.url_encode(uuid) if uuid
  [collection_path, uuid].compact.join '/'
end

.paginate(options = {}) ⇒ Pager Also known as: scoped, where

Returns A pager with an iterable collection of records

Examples:

Fetch 50 records and iterate over them

Recurly::Account.paginate(:per_page => 50).each { |a| p a }

Fetch records before January 1, 2011

Recurly::Account.paginate(:cursor => Time.new(2011, 1, 1))

Parameters:

  • options (Hash) (defaults to: {})

    A hash of pagination options

Options Hash (options):

  • :per_page (Integer)

    The number of records returned per page

  • :cursor (DateTime, Time, Integer)

    A timestamp that the pager will skim back to and return records created before it

  • :etag (String)

    When set, will raise API::NotModified if the pager's loaded page content has not changed

Returns:

  • (Pager)

    A pager with an iterable collection of records



257
258
259
# File 'lib/recurly/resource.rb', line 257

def paginate(options = {})
  Pager.new self, options
end

.reflect_on_association(name) ⇒ :has_many, ...

Returns An association type.

Returns:

  • (:has_many, :has_one, :belongs_to, nil)

    An association type.



576
577
578
579
# File 'lib/recurly/resource.rb', line 576

def reflect_on_association(name)
  a = find_association(name)
  a.relation if a
end

.resource_nameString

Returns The demodulized name of the resource class.

Examples:

Recurly::Account.name # => "Account"

Returns:

  • (String)

    The demodulized name of the resource class.



171
172
173
# File 'lib/recurly/resource.rb', line 171

def resource_name
  Helper.demodulize name
end

.scope(name, params = {}) ⇒ Proc

Defines a new resource scope.

Parameters:

  • name (Symbol)

    the scope name

  • params (Hash) (defaults to: {})

    the scope params

Returns:

  • (Proc)


282
283
284
285
# File 'lib/recurly/resource.rb', line 282

def scope(name, params = {})
  scopes[name = name.to_s] = params
  scopes_helper.send(:define_method, name) { paginate scopes[name] }
end

.scopesHash

Returns Defined scopes per resource.

Returns:

  • (Hash)

    Defined scopes per resource.



268
269
270
# File 'lib/recurly/resource.rb', line 268

def scopes
  @scopes ||= Recurly::Helper.hash_with_indifferent_read_access
end

.scopes_helperModule

Returns Module of scopes methods.

Returns:

  • (Module)

    Module of scopes methods.



273
274
275
# File 'lib/recurly/resource.rb', line 273

def scopes_helper
  @scopes_helper ||= Module.new.tap { |helper| extend helper }
end

Instance Method Details

#==(other) ⇒ Object



963
964
965
# File 'lib/recurly/resource.rb', line 963

def ==(other)
  other.is_a?(self.class) && other.to_s == to_s
end

#changedArray

Returns A list of changed attribute keys.

Returns:

  • (Array)

    A list of changed attribute keys.



640
641
642
# File 'lib/recurly/resource.rb', line 640

def changed
  changed_attributes.keys
end

#changed?true, false

Do any attributes have unsaved changes?

Returns:

  • (true, false)


646
647
648
# File 'lib/recurly/resource.rb', line 646

def changed?
  !changed_attributes.empty?
end

#changed_attributesHash

Returns Hash of changed attributes.

Returns:

  • (Hash)

    Hash of changed attributes.

See Also:



635
636
637
# File 'lib/recurly/resource.rb', line 635

def changed_attributes
  @changed_attributes ||= {}
end

#changesHash

Returns Map of changed attributes to original value and new value.

Returns:

  • (Hash)

    Map of changed attributes to original value and new value.



651
652
653
654
655
# File 'lib/recurly/resource.rb', line 651

def changes
  changed_attributes.inject({}) { |changes, (key, original_value)|
    changes[key] = [original_value, self[key]] and changes
  }
end

#destroytrue, false

Attempts to destroy the record.

(if the record does not persist on Recurly).

Examples:

 = Recurly::Account.find 
race_condition = Recurly::Account.find 
.destroy        # => true
.destroy        # => false (already destroyed)
race_condition.destroy # raises Recurly::Resource::NotFound

Returns:

  • (true, false)

    true if successful, false if unable to destroy

Raises:

  • (NotFound)

    The record cannot be found.



951
952
953
954
955
956
957
# File 'lib/recurly/resource.rb', line 951

def destroy
  return false unless persisted?
  @response = API.delete uri
  @destroyed = true
rescue API::NotFound => e
  raise NotFound, e.description
end

#destroyed?true, false

Has the record been destroyed? (Set true after a successful destroy.)

Returns:

  • (true, false)

See Also:



676
677
678
# File 'lib/recurly/resource.rb', line 676

def destroyed?
  @destroyed
end

#errorsHash

Returns A hash with indifferent read access containing any validation errors where the key is the attribute name and the value is an array of error messages.

Examples:

.errors                # => {"account_code"=>["can't be blank"]}
.errors[:account_code] # => ["can't be blank"]

Returns:

  • (Hash)

    A hash with indifferent read access containing any validation errors where the key is the attribute name and the value is an array of error messages.



911
912
913
# File 'lib/recurly/resource.rb', line 911

def errors
  @errors ||= Errors.new { |h, k| h[k] = [] }
end

Fetch the value of a link by following the associated href.

Examples:

.read_link :billing_info # => <Recurly::BillingInfo>

Parameters:

  • key (Symbol, String)

    The name of the link to be followed.

  • options (Hash) (defaults to: {})

    A hash of API options.



765
766
767
768
769
770
771
772
773
774
775
776
# File 'lib/recurly/resource.rb', line 765

def follow_link(key, options = {})
  if link = links[key = key.to_s]
    response = API.send link[:method], link[:href], options[:body], options
    if resource_class = link[:resource_class]
      response = resource_class.from_response response
      response.attributes[self.class.member_name] = self
    end
    response
  end
rescue Recurly::API::NotFound
  raise unless resource_class
end

#inspect(attributes = self.class.attribute_names.to_a) ⇒ String Also known as: to_s

Returns:

  • (String)


996
997
998
999
1000
1001
1002
1003
1004
# File 'lib/recurly/resource.rb', line 996

def inspect(attributes = self.class.attribute_names.to_a)
  string = "#<#{self.class}"
  string << "##@type" if instance_variable_defined? :@type
  attributes += %w(errors) if errors.any?
  string << " %s" % attributes.map { |k|
    "#{k}: #{self.send(k).inspect}"
  }.join(', ')
  string << '>'
end

#link?(key) ⇒ Boolean

Whether a record has a link with the given name.

Examples:

.link? :billing_info # => true

Parameters:

  • key (Symbol, String)

    The name of the link to check for.

Returns:

  • (Boolean)


755
756
757
# File 'lib/recurly/resource.rb', line 755

def link?(key)
  links.key?(key.to_s)
end

Returns The raw hash of record href links.

Returns:

  • (Hash)

    The raw hash of record href links.



746
747
748
# File 'lib/recurly/resource.rb', line 746

def links
  @links ||= {}
end

#marshal_dumpObject



967
968
969
970
971
972
973
974
975
976
977
978
979
980
# File 'lib/recurly/resource.rb', line 967

def marshal_dump
  [
    @attributes.reject { |k, v| v.is_a?(Proc) },
    @new_record,
    @destroyed,
    @uri,
    @href,
    changed_attributes,
    previous_changes,
    response,
    etag,
    links
  ]
end

#marshal_load(serialization) ⇒ Object



982
983
984
985
986
987
988
989
990
991
992
993
# File 'lib/recurly/resource.rb', line 982

def marshal_load(serialization)
  @attributes,
    @new_record,
    @destroyed,
    @uri,
    @href,
    @changed_attributes,
    @previous_changes,
    @response,
    @etag,
    @links = serialization
end

#new_record?true, false

Is the record new (i.e., not saved on Recurly's servers)?

Returns:

  • (true, false)

See Also:



668
669
670
# File 'lib/recurly/resource.rb', line 668

def new_record?
  @new_record
end

#persist!(saved = false) ⇒ true

Marks a record as persisted, i.e. not a new or deleted record, resetting any tracked attribute changes in the process. (This is an internal method and should probably not be called unless you know what you're doing.)

Returns:

  • (true)


921
922
923
924
925
926
927
928
# File 'lib/recurly/resource.rb', line 921

def persist!(saved = false)
  @new_record, @uri = false
  if changed?
    @previous_changes = changes if saved
    changed_attributes.clear
  end
  true
end

#persisted?true, false

Has the record persisted (i.e., saved on Recurly's servers)?

Returns:

  • (true, false)

See Also:



685
686
687
# File 'lib/recurly/resource.rb', line 685

def persisted?
  !(new_record? || destroyed?)
end

#previous_changesHash

Returns Previously-changed attributes.

Returns:

  • (Hash)

    Previously-changed attributes.

See Also:



659
660
661
# File 'lib/recurly/resource.rb', line 659

def previous_changes
  @previous_changes ||= {}
end

#read_attribute(key) ⇒ Object Also known as: []

The value of a specified attribute, lazily fetching any defined association.

Examples:

.read_attribute :first_name # => "Ted"
[:last_name]                # => "Beneke"

Parameters:

  • key (Symbol, String)

    The name of the attribute to be fetched.

See Also:



697
698
699
700
701
702
703
704
705
# File 'lib/recurly/resource.rb', line 697

def read_attribute(key)
  key = key.to_s
  if attributes.key? key
    value = attributes[key]
  elsif links.key?(key) && self.class.reflect_on_association(key)
    value = attributes[key] = follow_link key
  end
  value
end

#reload(response = nil) ⇒ self

Returns Reloads the record from the server.

Returns:

  • (self)

    Reloads the record from the server.



617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/recurly/resource.rb', line 617

def reload(response = nil)
  if response
    return if response.body.to_s.length.zero?
    fresh = self.class.from_response response
  else
    fresh = self.class.find(
      @href || to_param, :etag => (etag unless changed?)
    )
  end
  fresh and copy_from fresh
  persist! true
  self
rescue API::NotModified
  self
end

#savetrue, false

Attempts to save the record, returning the success of the request.

Examples:

 = Recurly::Account.new
.save # => false
. = 'account_code'
.save # => true

Returns:

  • (true, false)

Raises:

See Also:



826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
# File 'lib/recurly/resource.rb', line 826

def save
  if new_record? || changed?
    clear_errors
    @response = API.send(
      persisted? ? :put : :post, path, to_xml(:delta => true)
    )
    reload response
    persist! true
  end
  true
rescue API::UnprocessableEntity => e
  apply_errors e
  Transaction::Error.validate! e, (self if is_a?(Transaction))
  false
end

#save!true

Attempts to save the record, returning true if the record was saved and raising Invalid otherwise.

Examples:

 = Recurly::Account.new
.save! # raises Recurly::Resource::Invalid
. = 'account_code'
.save! # => true

Returns:

  • (true)

Raises:

See Also:



854
855
856
# File 'lib/recurly/resource.rb', line 854

def save!
  save || raise(Invalid.new(self))
end

#signable_attributesObject



959
960
961
# File 'lib/recurly/resource.rb', line 959

def signable_attributes
  Hash[xml_keys.map { |key| [key, self[key]] }]
end

#to_xml(options = {}) ⇒ String

Serializes the record to XML.

Examples:

Recurly::Account.new(:account_code => 'code').to_xml
# => "<account><account_code>code</account_code></account>"

Parameters:

  • options (Hash) (defaults to: {})

    A hash of XML options.

Returns:

  • (String)

    An XML string.



785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
# File 'lib/recurly/resource.rb', line 785

def to_xml(options = {})
  builder = options[:builder] || XML.new("<#{self.class.member_name}/>")
  xml_keys.each { |key|
    value = respond_to?(key) ? send(key) : self[key]
    node = builder.add_element key

    # Duck-typing here is problematic because of ActiveSupport's #to_xml.
    case value
    when Resource, Subscription::AddOns
      value.to_xml options.merge(:builder => node)
    when Array
      value.each do |e|
        if e.is_a? Recurly::Resource
          # create a node to hold this resource
          e_node = node.add_element Helper.singularize(key)
          # serialize the resource into this node
          e.to_xml(options.merge(builder: e_node))
        else
          # it's just a primitive value
          node.add_element(Helper.singularize(key), e)
        end
      end
    when Hash, Recurly::Money
      value.each_pair { |k, v| node.add_element k.to_s, v }
    else
      node.text = value
    end
  }
  builder.to_s
end

#update_attributes(attributes = {}) ⇒ true, false

Update a record with a given hash of attributes.

Examples:

 = Account.find 'junior'
.update_attributes :account_code => 'flynn' # => true

Parameters:

  • attributes (Hash) (defaults to: {})

    A hash of attributes.

Returns:

  • (true, false)

    The success of the update.

Raises:

See Also:



887
888
889
# File 'lib/recurly/resource.rb', line 887

def update_attributes(attributes = {})
  self.attributes = attributes and save
end

#update_attributes!(attributes = {}) ⇒ true

Update a record with a given hash of attributes.

Examples:

 = Account.find 'gale_boetticher'
.update_attributes! :account_code => nil # Raises an exception.

Parameters:

  • attributes (Hash) (defaults to: {})

    A hash of attributes.

Returns:

  • (true)

    The update was successful.

Raises:

See Also:



901
902
903
# File 'lib/recurly/resource.rb', line 901

def update_attributes!(attributes = {})
  self.attributes = attributes and save!
end

#valid?true, ...

Returns The validity of the record: true if the record was successfully saved (or persisted and unchanged), false if the record was not successfully saved, or nil for a record with an unknown state (i.e. (i.e. new records that haven't been saved and persisted records with changed attributes).

Examples:

 = Recurly::Account.new
.valid? # => nil
.save   # => false
.valid? # => false
. = 'account_code'
.save   # => true
.valid? # => true

Returns:

  • (true, false, nil)

    The validity of the record: true if the record was successfully saved (or persisted and unchanged), false if the record was not successfully saved, or nil for a record with an unknown state (i.e. (i.e. new records that haven't been saved and persisted records with changed attributes).



871
872
873
874
875
876
# File 'lib/recurly/resource.rb', line 871

def valid?
  return true if persisted? && !changed?
  errors_empty = errors.values.flatten.empty?
  return if errors_empty && changed?
  errors_empty
end

#write_attribute(key, value) ⇒ Object Also known as: []=

Sets the value of a specified attribute.

Examples:

.write_attribute :first_name, 'Gus'
[:company_name] = 'Los Pollos Hermanos'

Parameters:

  • key (Symbol, String)

    The name of the attribute to be set.

  • value (Object)

    The value the attribute will be set to.

See Also:



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/recurly/resource.rb', line 716

def write_attribute(key, value)
  if changed_attributes.key?(key = key.to_s)
    changed_attributes.delete key if changed_attributes[key] == value
  elsif self[key] != value
    changed_attributes[key] = self[key]
  end

  association = self.class.find_association(key)
  if association
    value = fetch_associated(key, value)
  # FIXME: More explicit; less magic.
  elsif value && key.end_with?('_in_cents') && !respond_to?(:currency)
    value = Money.new(value, self, key) unless value.is_a?(Money)
  end

  attributes[key] = value
end