Class: Recurly::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/recurly/resource.rb,
lib/recurly/resource/pager.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: 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:



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

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.



229
230
231
# File 'lib/recurly/resource.rb', line 229

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.



519
520
521
# File 'lib/recurly/resource.rb', line 519

def attributes
  @attributes
end

#etagString? (readonly)

Returns An ETag for the current record.

Returns:

  • (String, nil)

    An ETag for the current record.



526
527
528
# File 'lib/recurly/resource.rb', line 526

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



523
524
525
# File 'lib/recurly/resource.rb', line 523

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



821
822
823
# File 'lib/recurly/resource.rb', line 821

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

Class Method Details

.all(options = {}) ⇒ Object



250
251
252
# File 'lib/recurly/resource.rb', line 250

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

.associationsHash

Returns A list of association names for the current class.

Returns:

  • (Hash)

    A list of association names for the current class.



423
424
425
426
427
428
429
430
431
# File 'lib/recurly/resource.rb', line 423

def associations
  @associations ||= begin
    unless constants.include? :Associations
      include const_set :Associations, Module.new
    end

    { :has_many => [], :has_one => [], :belongs_to => [] }
  end
end

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

Establishes a belongs_to association.

Returns:

  • (Proc)


492
493
494
495
496
497
498
499
500
501
502
# File 'lib/recurly/resource.rb', line 492

def belongs_to parent_name, options = {}
  associations[:belongs_to] << parent_name.to_s
  self::Associations.module_eval {
    define_method(parent_name) { self[parent_name] }
    if options.key?(:readonly) && options[:readonly] == false
      define_method("#{parent_name}=") { |parent|
        self[parent_name] = parent
      }
    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.



167
168
169
# File 'lib/recurly/resource.rb', line 167

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:



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

def count
  paginate.count
end

.create(attributes = {}) ⇒ Resource

Instantiates and attempts to save a record.

Returns:

Raises:

See Also:



335
336
337
# File 'lib/recurly/resource.rb', line 335

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

.create!(attributes = {}) ⇒ Resource

Instantiates and attempts to save a record.

Returns:

Raises:

See Also:



345
346
347
# File 'lib/recurly/resource.rb', line 345

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.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/recurly/resource.rb', line 206

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



509
510
511
512
513
514
515
# File 'lib/recurly/resource.rb', line 509

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.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/recurly/resource.rb', line 311

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

  request_options = {}
  if etag = options[:etag]
    request_options[:head] = { 'If-None-Match' => etag }
  end

  uri = uuid =~ /^http/ ? uuid : member_path(uuid)
  begin
    from_response API.get(uri, {}, request_options)
  rescue API::NotFound => e
    raise NotFound, e.description
  end
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:



278
279
280
# File 'lib/recurly/resource.rb', line 278

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

.firstResource?

Returns:



292
293
294
# File 'lib/recurly/resource.rb', line 292

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:



354
355
356
357
358
# File 'lib/recurly/resource.rb', line 354

def from_response response
  record = from_xml response.body
  record.instance_eval { @etag, @response = response['ETag'], response }
  record
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:



368
369
370
371
372
373
374
375
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
# File 'lib/recurly/resource.rb', line 368

def from_xml xml
  xml = XML.new xml
  if xml.name == member_name
    record = new
  elsif Recurly.const_defined?(class_name = Helper.classify(xml.name))
    record = Recurly.const_get(class_name).new
  elsif root = xml.root and root.elements.empty?
    return XML.cast root
  else
    record = {}
  end

  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'
      name, uri = el.attribute('name').value, el.attribute('href').value
      record[name] = case el.attribute('method').to_s
        when 'get', '' then proc { |*opts| API.get uri, {}, *opts }
        when 'post'    then proc { |*opts| API.post uri, nil, *opts }
        when 'put'     then proc { |*opts| API.put uri, nil, *opts }
        when 'delete'  then proc { |*opts| API.delete uri, *opts }
      end
      next
    end

    if el.children.empty? && href = el.attribute('href')
      resource_class = Recurly.const_get(
        Helper.classify(el.attribute('type') || el.name)
      )
      record[el.name] = case el.name
      when *associations[:has_many]
        Pager.new resource_class, :uri => href.value, :parent => record
      when *(associations[:has_one] + associations[:belongs_to])
        lambda {
          begin
            relation = resource_class.from_response API.get(href.value)
            relation.attributes[member_name] = record
            relation
          rescue Recurly::API::NotFound
          end
        }
      end
    else
      record[el.name] = XML.cast el
    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.

Returns:

  • (Proc, nil)


439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/recurly/resource.rb', line 439

def has_many collection_name, options = {}
  associations[:has_many] << collection_name.to_s
  self::Associations.module_eval {
    define_method(collection_name) {
      self[collection_name] ||= []
    }
    if options.key?(:readonly) && options[:readonly] == false
      define_method("#{collection_name}=") { |collection|
        self[collection_name] = collection
      }
    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.

Returns:

  • (Proc, nil)


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/recurly/resource.rb', line 459

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



175
176
177
# File 'lib/recurly/resource.rb', line 175

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.



185
186
187
# File 'lib/recurly/resource.rb', line 185

def member_path 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



244
245
246
# File 'lib/recurly/resource.rb', line 244

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.



505
506
507
# File 'lib/recurly/resource.rb', line 505

def reflect_on_association name
  a = associations.find { |k, v| v.include? name.to_s } and a.first
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.



159
160
161
# File 'lib/recurly/resource.rb', line 159

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)


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

def scope name, params = {}
  scopes[name = name.to_s] = params
  extend const_set :Scopes, Module.new unless const_defined? :Scopes
  self::Scopes.send(:define_method, name) { paginate scopes[name] }
end

.scopesHash

Returns Defined scopes per resource.

Returns:

  • (Hash)

    Defined scopes per resource.



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

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

Instance Method Details

#==(other) ⇒ Object



844
845
846
# File 'lib/recurly/resource.rb', line 844

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.



572
573
574
# File 'lib/recurly/resource.rb', line 572

def changed
  changed_attributes.keys
end

#changed?true, false

Do any attributes have unsaved changes?

Returns:

  • (true, false)


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

def changed?
  !changed_attributes.empty?
end

#changed_attributesHash

Returns Hash of changed attributes.

Returns:

  • (Hash)

    Hash of changed attributes.

See Also:



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

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.



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

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.



836
837
838
839
840
841
842
# File 'lib/recurly/resource.rb', line 836

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:



608
609
610
# File 'lib/recurly/resource.rb', line 608

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.



796
797
798
# File 'lib/recurly/resource.rb', line 796

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

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

Returns:

  • (String)


875
876
877
878
879
880
881
882
883
# File 'lib/recurly/resource.rb', line 875

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

#marshal_dumpObject



848
849
850
851
852
853
854
855
856
857
858
859
860
# File 'lib/recurly/resource.rb', line 848

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

#marshal_load(serialization) ⇒ Object



862
863
864
865
866
867
868
869
870
871
872
# File 'lib/recurly/resource.rb', line 862

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

#new_record?true, false

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

Returns:

  • (true, false)

See Also:



600
601
602
# File 'lib/recurly/resource.rb', line 600

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)


806
807
808
809
810
811
812
813
# File 'lib/recurly/resource.rb', line 806

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:



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

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

#previous_changesHash

Returns Previously-changed attributes.

Returns:

  • (Hash)

    Previously-changed attributes.

See Also:



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

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:



629
630
631
632
633
634
635
# File 'lib/recurly/resource.rb', line 629

def read_attribute key
  value = attributes[key = key.to_s]
  if value.respond_to?(:call) && self.class.reflect_on_association(key)
    value = attributes[key] = value.call 
  end
  value
end

#reload(response = nil) ⇒ self

Returns Reloads the record from the server.

Returns:

  • (self)

    Reloads the record from the server.



549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/recurly/resource.rb', line 549

def reload response = nil
  if response
    return if response.body.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:



712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
# File 'lib/recurly/resource.rb', line 712

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:



740
741
742
# File 'lib/recurly/resource.rb', line 740

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

#to_paramObject



544
545
546
# File 'lib/recurly/resource.rb', line 544

def to_param
  self[self.class.param_name]
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.



681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
# File 'lib/recurly/resource.rb', line 681

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:



772
773
774
# File 'lib/recurly/resource.rb', line 772

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:



786
787
788
# File 'lib/recurly/resource.rb', line 786

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



757
758
759
760
761
# File 'lib/recurly/resource.rb', line 757

def valid?
  return true if persisted? && changed_attributes.empty?
  return if response.nil? || (errors.empty? && changed_attributes?)
  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:



646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/recurly/resource.rb', line 646

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.associations.values.flatten.include? key
    value = fetch_association key, value
  # FIXME: More explicit; less magic.
  elsif key.end_with?('_in_cents') && !respond_to?(:currency)
    value = Money.new value unless value.is_a? Money
  end

  attributes[key] = value
end