base.rb 6.1 KB
Newer Older
1 2 3 4
require 'active_resource/connection'

module ActiveResource
  class Base
5 6 7 8
    # The logger for logging diagnostic and trace information during ARes
    # calls.
    cattr_accessor :logger

9
    class << self
10
      attr_reader :site
J
Jeremy Kemper 已提交
11

12 13
      def site=(site)
        @site = site.is_a?(URI) ? site : URI.parse(site)
14 15
        @connection = nil
        @site
16 17 18 19 20 21
      end

      def connection(refresh = false)
        @connection = Connection.new(site) if refresh || @connection.nil?
        @connection
      end
22

23 24 25 26 27 28 29
      def element_name
        self.to_s.underscore
      end

      def collection_name
        element_name.pluralize
      end
30 31 32 33

      def prefix(options={})
        default = site.path
        default << '/' unless default[-1..-1] == '/'
34
        self.prefix = default
35 36
        prefix(options)
      end
37

38
      def prefix=(value = '/')
39 40 41 42
        prefix_call = value.gsub(/:\w+/) { |s| "\#{options[#{s}]}" }
        method_decl = %(def self.prefix(options={}) "#{prefix_call}" end)
        eval method_decl
      end
43
      alias_method :set_prefix, :prefix=
44

45
      def element_name=(value)
46 47 48
        class << self ; attr_reader :element_name ; end
        @element_name = value
      end
49
      alias_method :set_element_name, :element_name=
50

51
      def collection_name=(value)
52 53 54
        class << self ; attr_reader :collection_name ; end
        @collection_name = value
      end
55
      alias_method :set_collection_name, :collection_name=
56 57 58 59

      def element_path(id, options = {})
        "#{prefix(options)}#{collection_name}/#{id}.xml"
      end
60

61 62 63
      def collection_path(options = {})
        "#{prefix(options)}#{collection_name}.xml"
      end
64

65
      def primary_key
66
        self.primary_key = 'id'
67
      end
68

69
      def primary_key=(value)
70 71
        class << self ; attr_reader :primary_key ; end
        @primary_key = value
72
      end
73
      alias_method :set_primary_key, :primary_key=
74

75 76
      # Person.find(1) # => GET /people/1.xml
      # StreetAddress.find(1, :person_id => 1) # => GET /people/1/street_addresses/1.xml
77
      def find(*arguments)
78 79
        scope   = arguments.slice!(0)
        options = arguments.slice!(0) || {}
80 81

        case scope
82 83 84
          when :all   then find_every(options)
          when :first then find_every(options).first
          else             find_single(scope, options)
85 86
        end
      end
87

88 89 90 91
      def delete(id)
        connection.delete(element_path(id))
      end

92 93 94 95 96
      private
        # { :people => { :person => [ person1, person2 ] } }
        def find_every(options)
          connection.get(collection_path(options)).values.first.values.first.collect { |element| new(element, options) }
        end
97

98 99 100 101
        # { :person => person1 }
        def find_single(scope, options)
          new(connection.get(element_path(scope, options)).values.first, options)
        end
102 103 104
    end

    attr_accessor :attributes
105
    attr_accessor :prefix_options
106

107
    def initialize(attributes = {}, prefix_options = {})
108 109
      @attributes = {}
      self.load attributes
110
      @prefix_options = prefix_options
111
    end
112

113
    def new?
114 115 116
      id.nil?
    end

117
    def id
118
      attributes[self.class.primary_key]
119
    end
120

121
    def id=(id)
122
      attributes[self.class.primary_key] = id
123
    end
124

125
    def save
126
      new? ? create : update
127 128 129
    end

    def destroy
130
      connection.delete(self.class.element_path(id, prefix_options))
131
    end
132

133 134 135
    def to_xml
      attributes.to_xml(:root => self.class.element_name)
    end
136 137 138

    # Reloads the attributes of this object from the remote web service.
    def reload
139 140 141 142 143 144
      self.load self.class.find(id, @prefix_options).attributes
    end

    # Manually load attributes from a hash. Recursively loads collections of
    # resources.
    def load(attributes)
J
Jeremy Kemper 已提交
145
      raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
146 147 148 149 150 151 152
      attributes.each do |key, value|
        @attributes[key.to_s] =
          case value
            when Array
              resource = find_or_create_resource_for_collection(key)
              value.map { |attrs| resource.new(attrs) }
            when Hash
153 154 155 156 157 158 159 160 161 162 163
              # Workaround collections loaded as Hash
              #   :persons => { :person => [
              #     { :id => 1, :name => 'a' },
              #     { :id => 2, :name => 'b' } ]}
              if value.keys.size == 1 and value.values.first.is_a?(Array)
                resource = find_or_create_resource_for(value.keys.first)
                value.values.first.map { |attrs| resource.new(attrs) }
              else
                resource = find_or_create_resource_for(key)
                resource.new(value)
              end
164 165 166 167 168 169
            when ActiveResource::Base
              value.class.new(value.attributes)
            else
              value.dup rescue value
          end
      end
170 171 172
      self
    end

173 174 175 176
    protected
      def connection(refresh = false)
        self.class.connection(refresh)
      end
177

178
      def update
179
        connection.put(self.class.element_path(id, prefix_options), to_xml)
180
        true
181
      end
182 183

      def create
184 185 186
        resp = connection.post(self.class.collection_path(prefix_options), to_xml)
        self.id = id_from_response(resp)
        true
187 188
      end

189 190 191 192 193
      # takes a response from a typical create post and pulls the ID out
      def id_from_response(response)
        response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1]
      end

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
    private
      def find_or_create_resource_for_collection(name)
        find_or_create_resource_for(name.to_s.singularize)
      end

      def find_or_create_resource_for(name)
        resource_name = name.to_s.camelize
        resource_name.constantize
      rescue NameError
        resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
        resource.prefix = self.class.prefix
        resource.site = self.class.site
        resource
      end

209 210
      def method_missing(method_symbol, *arguments)
        method_name = method_symbol.to_s
211

212 213 214 215
        case method_name.last
          when "="
            attributes[method_name.first(-1)] = arguments.first
          when "?"
216
            attributes[method_name.first(-1)] == true
217
          else
218
            attributes.has_key?(method_name) ? attributes[method_name] : super
219 220 221
        end
      end
  end
J
Jeremy Kemper 已提交
222
end