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:



576
577
578
579
580
581
582
583
584
585
# File 'lib/recurly/resource.rb', line 576

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.



233
234
235
# File 'lib/recurly/resource.rb', line 233

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.



562
563
564
# File 'lib/recurly/resource.rb', line 562

def attributes
  @attributes
end

#etagString? (readonly)

Returns An ETag for the current record.

Returns:

  • (String, nil)

    An ETag for the current record.



569
570
571
# File 'lib/recurly/resource.rb', line 569

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).



566
567
568
# File 'lib/recurly/resource.rb', line 566

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).



896
897
898
# File 'lib/recurly/resource.rb', line 896

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

Class Method Details

.all(options = {}) ⇒ Object



254
255
256
# File 'lib/recurly/resource.rb', line 254

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



451
452
453
454
# File 'lib/recurly/resource.rb', line 451

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.



438
439
440
# File 'lib/recurly/resource.rb', line 438

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



444
445
446
# File 'lib/recurly/resource.rb', line 444

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

.associations_helperObject



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

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

    :class_name Actual associated resource class name

    if not same as parent_name.

Returns:

  • (Proc)


534
535
536
537
538
539
540
541
542
543
544
# File 'lib/recurly/resource.rb', line 534

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.



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

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:



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

def count
  paginate.count
end

.create(attributes = {}) ⇒ Resource

Instantiates and attempts to save a record.

Returns:

Raises:

See Also:



338
339
340
# File 'lib/recurly/resource.rb', line 338

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

.create!(attributes = {}) ⇒ Resource

Instantiates and attempts to save a record.

Returns:

Raises:

See Also:



348
349
350
# File 'lib/recurly/resource.rb', line 348

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.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/recurly/resource.rb', line 210

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



552
553
554
555
556
557
558
# File 'lib/recurly/resource.rb', line 552

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", ...>

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.



319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/recurly/resource.rb', line 319

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.



458
459
460
# File 'lib/recurly/resource.rb', line 458

def find_association(resource_class)
  associations.find{ |a| a.resource_class == resource_class }
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:



286
287
288
# File 'lib/recurly/resource.rb', line 286

def find_each per_page = 50
  paginate(:per_page => per_page).find_each(&Proc.new)
end

.firstResource?

Returns:



300
301
302
# File 'lib/recurly/resource.rb', line 300

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:



357
358
359
360
361
362
363
364
365
366
# File 'lib/recurly/resource.rb', line 357

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:



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
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
# File 'lib/recurly/resource.rb', line 376

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
  associations = klass.associations

  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

    if el.children.empty? && href = el.attribute('href')
      resource_class = Recurly.const_get(
        Helper.classify(association_class_name(el.name) ||
          el.attribute('type') || el.name), false
      )
      case el.name
      when *associations_for_relation(:has_many)
        record[el.name] = Pager.new(
          resource_class, :uri => href.value, :parent => record
        )
      when *(associations_for_relation(:has_one) + 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)
        record[el.name] = Address.new val
      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

    :class_name Actual associated resource class name

    if not same as collection_name.

Returns:

  • (Proc, nil)


474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/recurly/resource.rb', line 474

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
      self[collection_name] ||= []
    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

    :class_name Actual associated resource class name

    if not same as member_name.

Returns:

  • (Proc, nil)


496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
# File 'lib/recurly/resource.rb', line 496

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.



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

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.



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

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



248
249
250
# File 'lib/recurly/resource.rb', line 248

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.



547
548
549
550
# File 'lib/recurly/resource.rb', line 547

def reflect_on_association name
  a = find_association name.to_s
  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.



162
163
164
# File 'lib/recurly/resource.rb', line 162

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)


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

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.



259
260
261
# File 'lib/recurly/resource.rb', line 259

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

.scopes_helperModule

Returns Module of scopes methods.

Returns:

  • (Module)

    Module of scopes methods.



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

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

Instance Method Details

#==(other) ⇒ Object



923
924
925
# File 'lib/recurly/resource.rb', line 923

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.



611
612
613
# File 'lib/recurly/resource.rb', line 611

def changed
  changed_attributes.keys
end

#changed?true, false

Do any attributes have unsaved changes?

Returns:

  • (true, false)


617
618
619
# File 'lib/recurly/resource.rb', line 617

def changed?
  !changed_attributes.empty?
end

#changed_attributesHash

Returns Hash of changed attributes.

Returns:

  • (Hash)

    Hash of changed attributes.

See Also:



606
607
608
# File 'lib/recurly/resource.rb', line 606

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.



622
623
624
625
626
# File 'lib/recurly/resource.rb', line 622

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.



911
912
913
914
915
916
917
# File 'lib/recurly/resource.rb', line 911

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:



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

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.



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

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.



735
736
737
738
739
740
741
742
743
744
745
746
# File 'lib/recurly/resource.rb', line 735

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)


956
957
958
959
960
961
962
963
964
# File 'lib/recurly/resource.rb', line 956

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)


725
726
727
# File 'lib/recurly/resource.rb', line 725

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.



716
717
718
# File 'lib/recurly/resource.rb', line 716

def links
  @links ||= {}
end

#marshal_dumpObject



927
928
929
930
931
932
933
934
935
936
937
938
939
940
# File 'lib/recurly/resource.rb', line 927

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



942
943
944
945
946
947
948
949
950
951
952
953
# File 'lib/recurly/resource.rb', line 942

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:



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

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)


881
882
883
884
885
886
887
888
# File 'lib/recurly/resource.rb', line 881

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:



656
657
658
# File 'lib/recurly/resource.rb', line 656

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

#previous_changesHash

Returns Previously-changed attributes.

Returns:

  • (Hash)

    Previously-changed attributes.

See Also:



630
631
632
# File 'lib/recurly/resource.rb', line 630

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:



668
669
670
671
672
673
674
675
676
# File 'lib/recurly/resource.rb', line 668

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.



588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/recurly/resource.rb', line 588

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:



786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
# File 'lib/recurly/resource.rb', line 786

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:



814
815
816
# File 'lib/recurly/resource.rb', line 814

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

#signable_attributesObject



919
920
921
# File 'lib/recurly/resource.rb', line 919

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.



755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
# File 'lib/recurly/resource.rb', line 755

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 { |e| node.add_element Helper.singularize(key), e }
    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:



847
848
849
# File 'lib/recurly/resource.rb', line 847

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:



861
862
863
# File 'lib/recurly/resource.rb', line 861

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).



831
832
833
834
835
836
# File 'lib/recurly/resource.rb', line 831

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:



687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
# File 'lib/recurly/resource.rb', line 687

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

  if self.class.find_association key
    value = fetch_association 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