lib/ruby_odata/service.rb in ruby_odata-0.0.5 vs lib/ruby_odata/service.rb in ruby_odata-0.0.6

- old
+ new

@@ -1,5 +1,7 @@ +require 'logger' + module OData class Service attr_reader :classes # Creates a new instance of the Service class @@ -7,10 +9,11 @@ # ==== Required Attributes # - service_uri: The root URI of the OData service def initialize(service_uri) @uri = service_uri @collections = get_collections + @save_operations = [] build_classes end # Handles the dynamic AddTo<EntityName> methods as well as the collections on the service def method_missing(name, *args) @@ -22,11 +25,11 @@ return @query # Adds elsif name.to_s =~ /^AddTo(.*)/ type = $1 if @collections.include?(type) - @save_operation = Operation.new("Add", $1, args[0]) + @save_operations << Operation.new("Add", $1, args[0]) else super end else super @@ -41,11 +44,11 @@ # # Note: This method will throw an exception if the +obj+ isn't a tracked entity def delete_object(obj) type = obj.class.to_s if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil? - @save_operation = Operation.new("Delete", type, obj) + @save_operations << Operation.new("Delete", type, obj) else raise "You cannot delete a non-tracked entity" end end @@ -56,39 +59,32 @@ # # Note: This method will throw an exception if the +obj+ isn't a tracked entity def update_object(obj) type = obj.class.to_s if obj.respond_to?(:__metadata) && !obj.send(:__metadata).nil? - @save_operation = Operation.new("Update", type, obj) + @save_operations << Operation.new("Update", type, obj) else raise "You cannot update a non-tracked entity" end end # Performs save operations (Create/Update/Delete) against the server def save_changes - return nil if @save_operation.nil? + return nil if @save_operations.empty? result = nil - - if @save_operation.kind == "Add" - save_uri = "#{@uri}/#{@save_operation.klass_name}" - json_klass = @save_operation.klass.to_json(:type => :add) - post_result = RestClient.post save_uri, json_klass, :content_type => :json - result = build_classes_from_result(post_result) - elsif @save_operation.kind == "Update" - update_uri = @save_operation.klass.send(:__metadata)[:uri] - json_klass = @save_operation.klass.to_json - update_result = RestClient.put update_uri, json_klass, :content_type => :json - return (update_result.code == 204) - elsif @save_operation.kind == "Delete" - delete_uri = @save_operation.klass.send(:__metadata)[:uri] - delete_result = RestClient.delete delete_uri - return (delete_result.code == 204) + + if @save_operations.length == 1 + result = single_save(@save_operations[0]) + else + result = batch_save(@save_operations) end - - @save_operation = nil # Clear out the last operation + + # TODO: We should probably perform a check here + # to make sure everything worked before clearing it out + @save_operations.clear + return result end # Performs query operations (Read) against the server def execute @@ -199,9 +195,93 @@ # Build the class inline_klass = entry_to_class(entry) # Add the property klass.send "#{property_name}=", inline_klass + end + def single_save(operation) + if operation.kind == "Add" + save_uri = "#{@uri}/#{operation.klass_name}" + json_klass = operation.klass.to_json(:type => :add) + post_result = RestClient.post save_uri, json_klass, :content_type => :json + return build_classes_from_result(post_result) + elsif operation.kind == "Update" + update_uri = operation.klass.send(:__metadata)[:uri] + json_klass = operation.klass.to_json + update_result = RestClient.put update_uri, json_klass, :content_type => :json + return (update_result.code == 204) + elsif operation.kind == "Delete" + delete_uri = operation.klass.send(:__metadata)[:uri] + delete_result = RestClient.delete delete_uri + return (delete_result.code == 204) + end + end + def batch_save(operations) + batch_num = generate_guid + changeset_num = generate_guid + batch_uri = "#{@uri}/$batch" + + body = build_batch_body(operations, batch_num, changeset_num) + + result = RestClient.post batch_uri, body, :content_type => "multipart/mixed; boundary=batch_#{batch_num}" + + # TODO: More result validation needs to be done. + # The result returns HTTP 202 even if there is an error in the batch + return (result.code == 202) + end + def build_batch_body(operations, batch_num, changeset_num) + # Header + body = "--batch_#{batch_num}\n" + body << "Content-Type: multipart/mixed;boundary=changeset_#{changeset_num}\n\n" + + # Operations + operations.each do |operation| + body << build_batch_operation(operation, changeset_num) + body << "\n" + end + + # Footer + body << "\n\n--changeset_#{changeset_num}--\n" + body << "--batch_#{batch_num}--" + + return body + end + + def build_batch_operation(operation, changeset_num) + accept_headers = "Accept-Charset: utf-8\n" + accept_headers << "Content-Type: application/json;charset=utf-8\n" unless operation.kind == "Delete" + accept_headers << "\n" + + content = "--changeset_#{changeset_num}\n" + content << "Content-Type: application/http\n" + content << "Content-Transfer-Encoding: binary\n\n" + + if operation.kind == "Add" + save_uri = "#{@uri}/#{operation.klass_name}" + json_klass = operation.klass.to_json(:type => :add) + + content << "POST #{save_uri} HTTP/1.1\n" + content << accept_headers + content << json_klass + elsif operation.kind == "Update" + update_uri = operation.klass.send(:__metadata)[:uri] + json_klass = operation.klass.to_json + + content << "PUT #{update_uri} HTTP/1.1\n" + content << accept_headers + content << json_klass + elsif operation.kind == "Delete" + delete_uri = operation.klass.send(:__metadata)[:uri] + + content << "DELETE #{delete_uri} HTTP/1.1\n" + content << accept_headers + end + + return content + end + + def generate_guid + rand(36**12).to_s(36).insert(4, "-").insert(9, "-") end end end # module OData \ No newline at end of file