Blame view

lib/syspro/syspro_object.rb 3.35 KB
db76748d   Isaac Lewis   cop a bunch of St...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  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