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