module Syspro class SysproObject # Re-initializes the object based on a hash of values (usually one that's # come back from an API call). Adds or removes value accessors as necessary # and updates the state of internal data. # # Protected on purpose! Please do not expose. # # ==== Options # # * +:values:+ Hash used to update accessors and values. # * +:opts:+ Options for SysproObject like an API key. # * +:partial:+ Indicates that the re-initialization should not attempt to # remove accessors. def initialize_from(values, opts, partial = false) @opts = Util.normalize_opts(opts) # the `#send` is here so that we can keep this method private @original_values = self.class.send(:deep_copy, values) removed = partial ? Set.new : Set.new(@values.keys - values.keys) added = Set.new(values.keys - @values.keys) # Wipe old state before setting new. This is useful for e.g. updating a # customer, where there is no persistent card parameter. Mark those values # which don't persist as transient remove_accessors(removed) add_accessors(added, values) removed.each do |k| @values.delete(k) @transient_values.add(k) @unsaved_values.delete(k) end update_attributes(values, opts, dirty: false) values.each_key do |k| @transient_values.delete(k) @unsaved_values.delete(k) end self end def remove_accessors(keys) # not available in the #instance_eval below protected_fields = self.class.protected_fields metaclass.instance_eval do keys.each do |k| next if protected_fields.include?(k) next if @@permanent_attributes.include?(k) # Remove methods for the accessor's reader and writer. [k, :"#{k}=", :"#{k}?"].each do |method_name| remove_method(method_name) if method_defined?(method_name) end end end end def add_accessors(keys, values) # not available in the #instance_eval below protected_fields = self.class.protected_fields metaclass.instance_eval do keys.each do |k| next if protected_fields.include?(k) next if @@permanent_attributes.include?(k) if k == :method # Object#method is a built-in Ruby method that accepts a symbol # and returns the corresponding Method object. Because the API may # also use `method` as a field name, we check the arity of *args # to decide whether to act as a getter or call the parent method. define_method(k) { |*args| args.empty? ? @values[k] : super(*args) } else define_method(k) { @values[k] } end define_method(:"#{k}=") do |v| if v == "" raise ArgumentError, "You cannot set #{k} to an empty string. " \ "We interpret empty strings as nil in requests. " \ "You may set (object).#{k} = nil to delete the property." end @values[k] = Util.convert_to_stripe_object(v, @opts) dirty_value!(@values[k]) @unsaved_values.add(k) end if [FalseClass, TrueClass].include?(values[k].class) define_method(:"#{k}?") { @values[k] } end end end end end end