lib/rest_adapter.rb in dm-rest-adapter-0.9.2 vs lib/rest_adapter.rb in dm-rest-adapter-0.9.3

- old
+ new

@@ -1,50 +1,41 @@ require 'rubygems' -gem 'dm-core', '=0.9.2' +require 'pathname' +require Pathname(__FILE__).dirname + 'rest_adapter/version' +gem 'dm-core', DataMapper::More::RestAdapter::VERSION require 'dm-core' require 'extlib' require 'dm-serializer' -require 'pathname' require 'net/http' require 'rexml/document' # TODO: Abstract XML support out from the protocol # TODO: Build JSON support module DataMapper module Adapters class RestAdapter < AbstractAdapter include Extlib - - # def read_one(query) - # raise NotImplementedError - # end - # - # def update(attributes, query) - # raise NotImplementedError - # end - # - # def delete(query) - # raise NotImplementedError - # end - + # Creates a new resource in the specified repository. def create(resources) - success = true + count = 0 resources.each do |resource| - resource_name = Inflection.underscore(resource.class.name.downcase) + resource_name = Inflection.underscore(resource.class.name) result = http_post("/#{resource_name.pluralize}.xml", resource.to_xml) # TODO: Raise error if cannot reach server - success = success && result.instance_of?(Net::HTTPCreated) + success = result.instance_of?(Net::HTTPCreated) if success - updated_resource = parse_resource(result.body, resource.class) - resource.id = updated_resource.id + count += 1 + # TODO: Fix commented out code below to work through the identity_map of the repository + # values = parse_resource(result.body, resource.class) + # resource.id = updated_resource.id end # TODO: We're not using the response to update the DataMapper::Resource with the newly acquired ID!!! end - success + count end - + # read_set # # Examples of query string: # A. [] # GET /books/ @@ -53,138 +44,187 @@ # GET /books/4200 # # IN PROGRESS # TODO: Need to account for query.conditions (i.e., [[:eql, #<Property:Book:id>, 1]] for books/1) def read_many(query) - resource_name = Inflection.underscore(query.model.name.downcase) - case query.conditions - when [] - read_set_all(repository, query, resource_name) - else - read_set_for_condition(repository, query, resource) + resource_name = Inflection.underscore(query.model.name) + Collection.new(query) do |collection| + case query.conditions + when [] + resources_meta = read_set_all(repository, query, resource_name) + else + resources_meta = read_set_for_condition(repository, query, resource_name) + end + resources_meta.each do |resource_meta| + if resource_meta.has_key?(:associations) + load_nested_resources_from resource_meta[:associations], query + end + collection.load(resource_meta[:values]) + end end end - + def read_one(query) - # puts "---------------- QUERY: #{query} #{query.inspect}" - id = query.conditions.first[2] - # KLUGE: Again, we're assuming below that we're dealing with a pluralized resource mapping + resource = nil resource_name = resource_name_from_query(query) - response = http_get("/#{resource_name.pluralize}/#{id}.xml") - - # KLUGE: Rails returns HTML if it can't find a resource. A properly RESTful app would return a 404, right? - return nil if response.is_a? Net::HTTPNotFound || response.content_type == "text/html" - - data = response.body - res = parse_resource(data, query.model) - res + resources_meta = nil + if query.conditions.empty? && query.limit == 1 + results = read_set_all(repository, query, resource_name) + resource_meta = results.first unless results.empty? + else + id = query.conditions.first[2] + # KLUGE: Again, we're assuming below that we're dealing with a pluralized resource mapping + + response = http_get("/#{resource_name.pluralize}/#{id}.xml") + + # KLUGE: Rails returns HTML if it can't find a resource. A properly RESTful app would return a 404, right? + return nil if response.is_a? Net::HTTPNotFound || response.content_type == "text/html" + + data = response.body + resource_meta = parse_resource(data, query.model, query) + end + if resource_meta + if resource_meta.has_key?(:associations) + load_nested_resources_from resource_meta[:associations], query + end + resource = query.model.load(resource_meta[:values], query) + end + resource end - + def update(attributes, query) - # TODO update for v0.9.2 + # TODO What if we have a compound key? raise NotImplementedError.new unless is_single_resource_query? query id = query.conditions.first[2] - resource = query.model.new + resource = nil + query.repository.scope do + resource = query.model.get(id) + end attributes.each do |attr, val| resource.send("#{attr.name}=", val) end # KLUGE: Again, we're assuming below that we're dealing with a pluralized resource mapping - http_put("/#{resource_name_from_query(query).pluralize}/#{id}.xml", resource.to_xml) + res = http_put("/#{resource_name_from_query(query).pluralize}/#{id}.xml", resource.to_xml) # TODO: Raise error if cannot reach server + res.kind_of?(Net::HTTPSuccess) ? 1 : 0 end - + def delete(query) - #puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> QUERY: #{query} #{query.inspect}" - # TODO update for v0.9.2 raise NotImplementedError.new unless is_single_resource_query? query id = query.conditions.first[2] - http_delete("/#{resource_name_from_query(query).pluralize}/#{id}.xml") + res = http_delete("/#{resource_name_from_query(query).pluralize}/#{id}.xml") + res.kind_of?(Net::HTTPSuccess) ? 1 : 0 end - - protected + + protected + def load_nested_resources_from(nested_resources, query) + nested_resources.each do |resource_meta| + # TODO: Houston, we have a problem. Model#load expects a Query. When we're nested, we don't have a query yet... + #resource_meta[:model].load(resource_meta[:values]) + #if resource_meta.has_key? :associations + # load_nested_resources_from resource_meta, query + #end + end + end + def read_set_all(repository, query, resource_name) # TODO: how do we know whether the resource we're talking to is singular or plural? res = http_get("/#{resource_name.pluralize}.xml") data = res.body - parse_resources(data, query.model) + parse_resources(data, query.model, query) # TODO: Raise error if cannot reach server end - + # GET /books/4200 def read_set_for_condition(repository, query, resource_name) # More complex conditions raise NotImplementedError.new - end - + end + # query.conditions like [[:eql, #<Property:Book:id>, 4200]] def is_single_resource_query?(query) query.conditions.length == 1 && query.conditions.first.first == :eql && query.conditions.first[1].name == :id end - + def http_put(uri, data = nil) - request { |http| http.put(uri, data) } + request { |http| http.put(uri, data, {"Content-Type", "application/xml"}) } end def http_post(uri, data) request { |http| http.post(uri, data, {"Content-Type", "application/xml"}) } end def http_get(uri) - request { |http| http.get(uri) } + request { |http| http.get(uri, {"Content-Type", "application/xml"}) } end def http_delete(uri) - request { |http| http.delete(uri) } + request { |http| http.delete(uri, {"Content-Type", "application/xml"}) } end def request(&block) res = nil Net::HTTP.start(@uri[:host], @uri[:port].to_i) do |http| res = yield(http) end res - end + end - def resource_from_rexml(entity_element, dm_model_class) - resource = dm_model_class.new + def values_from_rexml(entity_element, dm_model_class) + resource = {} + resource[:values] = [] entity_element.elements.each do |field_element| - attribute = resource.attributes.find do |name, val| + attribute = dm_model_class.properties(repository.name).find do |property| # *MUST* use Inflection.underscore on the XML as Rails converts '_' to '-' in the XML - name.to_s == Inflection.underscore(field_element.name.to_s) + property.name.to_s == Inflection.underscore(field_element.name.to_s) end - resource.send("#{Inflection.underscore(attribute[0])}=", field_element.text) if attribute + if attribute + resource[:values] << field_element.text + next + end + association = dm_model_class.relationships.find do |name, dm_relationship| + field_element.name.to_s == Inflection.pluralize(Inflection.underscore(dm_relationship.child_model.to_s)) + end + if association + field_element.each_element do |associated_element| + model = association[1].child_model + (resource[:associations] ||= []) << { + :model => model, + :value => values_from_rexml(associated_element, association[1].child_model) + } + end + end end - resource.instance_eval { @new_record= false } resource end - def parse_resource(xml, dm_model_class) + def parse_resource(xml, dm_model_class, query = nil) doc = REXML::Document::new(xml) # TODO: handle singular resource case as well.... entity_element = REXML::XPath.first(doc, "/#{resource_name_from_model(dm_model_class)}") return nil unless entity_element - resource_from_rexml(entity_element, dm_model_class) + values_from_rexml(entity_element, dm_model_class) end - - def parse_resources(xml, dm_model_class) + + def parse_resources(xml, dm_model_class, query = nil) doc = REXML::Document::new(xml) # # TODO: handle singular resource case as well.... # array = XPath(doc, "/*[@type='array']") # if array # parse_resources() # else resource_name = resource_name_from_model dm_model_class doc.elements.collect("#{resource_name.pluralize}/#{resource_name}") do |entity_element| - resource_from_rexml(entity_element, dm_model_class) + values_from_rexml(entity_element, dm_model_class) end - end - + end + def resource_name_from_model(model) - Inflection.underscore(model.name.downcase) + Inflection.underscore(model.name) end - + def resource_name_from_query(query) resource_name_from_model(query.model) end end end -end +end \ No newline at end of file