lib/recurly/resource.rb in recurly-2.1.12 vs lib/recurly/resource.rb in recurly-2.2.0

- old
+ new

@@ -1,7 +1,6 @@ require 'date' -require 'erb' module Recurly # The base class for all Recurly resources (e.g. {Account}, {Subscription}, # {Transaction}). # @@ -183,11 +182,10 @@ # @param uuid [String, nil] # @example # Recurly::Account.member_path "code" # => "accounts/code" # Recurly::Account.member_path nil # => "accounts" def member_path uuid - uuid = ERB::Util.url_encode(uuid) if uuid [collection_path, uuid].compact.join '/' end # @return [Array] Per attribute, defines readers, writers, boolean and # change-tracking methods. @@ -319,12 +317,13 @@ 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(member_path(uuid), {}, options) + from_response API.get(uri, {}, options) rescue API::NotFound => e raise NotFound, e.description end end @@ -392,35 +391,31 @@ 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 + 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(el.attribute('type') || el.name), false ) - record[el.name] = case el.name + case el.name when *associations[:has_many] - Pager.new resource_class, :uri => href.value, :parent => record + record[el.name] = 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 + record.links[el.name] = { + :resource_class => resource_class, + :method => :get, + :href => href.value } end else record[el.name] = XML.cast el end @@ -550,26 +545,19 @@ @attributes, @new_record, @destroyed, @uri, @href = {}, true, false self.attributes = attributes yield self if block_given? end - def to_param - self[self.class.param_name] - end - # @return [self] Reloads the record from the server. def reload response = nil if response return if response.body.to_s.length.zero? fresh = self.class.from_response response else - options = {:etag => (etag unless changed?)} - fresh = if @href - self.class.from_response API.get(@href, {}, options) - else - self.class.find(to_param, options) - end + fresh = self.class.find( + @href || to_param, :etag => (etag unless changed?) + ) end fresh and copy_from fresh persist! true self rescue API::NotModified @@ -639,13 +627,15 @@ # @example # account.read_attribute :first_name # => "Ted" # account[:last_name] # => "Beneke" # @see #write_attribute 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 + 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 alias [] read_attribute @@ -683,10 +673,43 @@ attributes.each_pair { |k, v| respond_to?(name = "#{k}=") and send(name, v) or self[k] = v } end + # @return [Hash] The raw hash of record href links. + def links + @links ||= {} + end + + # Whether a record has a link with the given name. + # + # @param key [Symbol, String] The name of the link to check for. + # @example + # account.link? :billing_info # => true + def link? key + links.key?(key.to_s) + end + + # Fetch the value of a link by following the associated href. + # + # @param key [Symbol, String] The name of the link to be followed. + # @param options [Hash] A hash of API options. + # @example + # account.read_link :billing_info # => <Recurly::BillingInfo> + def follow_link key, options = {} + if link = links[key = key.to_s] + response = API.send link[:method], link[:href], nil, 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 + # Serializes the record to XML. # # @return [String] An XML string. # @param options [Hash] A hash of XML options. # @example @@ -767,13 +790,14 @@ # account.valid? # => false # account.account_code = 'account_code' # account.save # => true # account.valid? # => true def valid? - return true if persisted? && changed_attributes.empty? - return if errors.empty? && changed_attributes? - errors.empty? + return true if persisted? && !changed? + errors_empty = errors.values.flatten.empty? + return if errors_empty && changed? + errors_empty end # Update a record with a given hash of attributes. # # @return [true, false] The success of the update. @@ -806,11 +830,11 @@ # an array of error messages. # @example # account.errors # => {"account_code"=>["can't be blank"]} # account.errors[:account_code] # => ["can't be blank"] def errors - @errors ||= Errors.new + @errors ||= Errors.new { |h, k| h[k] = [] } end # 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.) @@ -870,12 +894,13 @@ @destroyed, @uri, @href, changed_attributes, previous_changes, + response, etag, - response + links ] end def marshal_load serialization @attributes, @@ -884,11 +909,12 @@ @uri, @href, @changed_attributes, @previous_changes, @response, - @etag = serialization + @etag, + @links = serialization end # @return [String] def inspect attributes = self.class.attribute_names.to_a string = "#<#{self.class}" @@ -911,15 +937,15 @@ end end def invalid! attribute_path, error if attribute_path.length == 1 - (errors[attribute_path[0]] ||= []) << error + errors[attribute_path[0]] << error else child, k, v = attribute_path.shift.scan(/[^\[\]=]+/) if c = k ? self[child].find { |d| d[k] == v } : self[child] c.invalid! attribute_path, error - (e = errors[child] ||= []) << 'is invalid' and e.uniq! + e = errors[child] << 'is invalid' and e.uniq! end end end def clear_errors