Skip to content

Commit

Permalink
Merge branch 'release/0.1.5'
Browse files Browse the repository at this point in the history
  • Loading branch information
visoft committed Nov 19, 2013
2 parents 763d054 + aeb8344 commit 8d9992f
Show file tree
Hide file tree
Showing 21 changed files with 827 additions and 38 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
matrix:
include:
- rvm: 1.8.7
gemfile: gemfiles/Gemfile.ruby187
# - rvm: 1.8.7
# gemfile: gemfiles/Gemfile.ruby187
- rvm: 1.9.3
gemfile: Gemfile
- rvm: 2.0.0
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,11 @@
* Updated the [VCR](https://github.com/myronmarston/vcr) and [WebMock](https://github.com/bblimke/webmock) gems to the latest versions (used for testing)
* Specified activesupport ~> 3.0 (in gemfiles/ruby187) for Ruby 1.8.7 as activesupport 4 doesn't support Ruby < 1.9.3

## 0.1.5
* **BREAKING CHANGES**
* Previously if the OData service threw an exception, ruby_odata threw a generic error with the message that would start with "HTTP Error XXX: ". Instead of the message, the Error that is thrown is an `OData::ServiceError`. It has an `http_code` property on it, thus, the message is now just the text from the OData error without the "HTTP Error XXX: " prefix. This could potentially cause you problems if you were sniffing error messages for the HTTP error code.

* New Features
* Added the ability to query the OData service using the [$select system query option](http://www.odata.org/documentation/odata-v2-documentation/uri-conventions/#48_Select_System_Query_Option_select)
* Support for Int64 keys ([issue 39](https://github.com/visoft/ruby_odata/issues/39) and [issue 40](https://github.com/visoft/ruby_odata/pull/40), thanks [@nasali](https://github.com/nasali))
* New property `is_key` added to `PropertyMetadata` in order to determine the key properties for the class (found in the service's `class_metadata` collection)
3 changes: 1 addition & 2 deletions features/error_handling.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ Background:

Scenario: Violate a data type conversion (empty string to decimal)
Given I call "AddToProducts" on the service with a new "Product" object with Price: ""
When I save changes it should throw an exception with message containing "HTTP Error 400"

When I save changes it should throw an exception with message containing "Error encountered in converting the value"
18 changes: 11 additions & 7 deletions lib/ruby_odata/class_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def add_initializer(klass)
end
end
end

def add_methods(klass)
# Add metadata methods
klass.send :define_method, :__metadata do
Expand Down Expand Up @@ -158,20 +158,20 @@ def add_methods(klass)
instance_variable_set("@#{method_name}", value)
end
end

# Add an id method pulling out the id from the uri (mainly for Pickle support)
klass.send :define_method, :id do
metadata = self.__metadata
id = nil
if metadata && metadata[:uri] =~ /\((\d+)\)$/
if metadata && metadata[:uri] =~ /\((\d+)L?\)$/
id = $~[1]
end
return (true if Integer(id) rescue false) ? id.to_i : id
end

# Override equals
klass.send :define_method, :== do |other|
self.class == other.class &&
self.class == other.class &&
self.id == other.id &&
self.__metadata == other.__metadata
end
Expand All @@ -195,14 +195,18 @@ def add_class_methods(klass)
klass.send :define_singleton_method, 'properties' do
context.class_metadata[klass.to_s] || {}
end

# Finds a single model by ID
klass.send :define_singleton_method, 'first' do |id|
return nil if id.nil?
# TODO: Instead of just pluralizing the klass name, use the actual collection name
collection = klass.to_s.pluralize
context.send "#{collection}", id
results = context.execute
begin
results = context.execute
rescue OData::ServiceError => e
return nil
end
results.count == 0 ? nil : results.first
end
end
Expand Down
7 changes: 7 additions & 0 deletions lib/ruby_odata/exceptions.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
module OData
# Raised when a user attempts to do something that is not supported
class NotSupportedError < StandardError; end
# Raised when the service returns an error
class ServiceError < StandardError
attr_reader :http_code
def initialize(code)
@http_code = code
end
end
end
2 changes: 2 additions & 0 deletions lib/ruby_odata/property_metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class PropertyMetadata
attr_reader :nav_prop
# Applies only to navigation properties; the association corresponding to the property
attr_accessor :association
# Applies to the primary key(s)
attr_accessor :is_key

# Creates a new instance of the Class Property class
#
Expand Down
27 changes: 27 additions & 0 deletions lib/ruby_odata/query_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def initialize(root, additional_params = {})
@filters = []
@order_bys = []
@navigation_paths = []
@select = []
@skip = nil
@top = nil
@count = nil
Expand Down Expand Up @@ -102,6 +103,7 @@ def top(num)
# product_links = svc.execute # => returns URIs for the products under the Category with an ID of 1
def links(navigation_property)
raise OData::NotSupportedError.new("You cannot call both the `links` method and the `count` method in the same query.") if @count
raise OData::NotSupportedError.new("You cannot call both the `links` method and the `select` method in the same query.") unless @select.empty?
@links_navigation_property = navigation_property
self
end
Expand All @@ -116,6 +118,8 @@ def links(navigation_property)
# product_count = svc.execute
def count
raise OData::NotSupportedError.new("You cannot call both the `links` method and the `count` method in the same query.") if @links_navigation_property
raise OData::NotSupportedError.new("You cannot call both the `select` method and the `count` method in the same query.") unless @select.empty?

@count = true
self
end
Expand All @@ -131,6 +135,28 @@ def navigate(navigation_property)
self
end

# Used to customize the properties that are returned for "ad-hoc" queries
#
# @param [Array<String>] properties to return
#
# @example
# svc.Products.select('Price', 'Rating')
def select(*fields)
raise OData::NotSupportedError.new("You cannot call both the `links` method and the `select` method in the same query.") if @links_navigation_property
raise OData::NotSupportedError.new("You cannot call both the `count` method and the `select` method in the same query.") if @count

@select |= fields

expands = fields.find_all { |f| /\// =~ f }
expands.each do |e|
parts = e.split '/'
@expands |= [parts[0...-1].join('/')]
end


self
end

# Builds the query URI (path, not including root) incorporating expands, filters, etc.
# This is used internally when the execute method is called on the service
def query
Expand All @@ -150,6 +176,7 @@ def query
end

query_options = []
query_options << "$select=#{@select.join(',')}" unless @select.empty?
query_options << "$expand=#{@expands.join(',')}" unless @expands.empty?
query_options << "$filter=#{@filters.join('+and+')}" unless @filters.empty?
query_options << "$orderby=#{@order_bys.join(',')}" unless @order_bys.empty?
Expand Down
66 changes: 59 additions & 7 deletions lib/ruby_odata/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ def initialize(service_uri, options = {})
def method_missing(name, *args)
# Queries
if @collections.include?(name.to_s)
root = "/#{name.to_s}"
root << "(#{args.join(',')})" unless args.empty?
@query = QueryBuilder.new(root, @additional_params)
@query = build_collection_query_object(name,@additional_params, *args)
return @query
# Adds
elsif name.to_s =~ /^AddTo(.*)/
Expand Down Expand Up @@ -96,8 +94,13 @@ def save_changes

# Performs query operations (Read) against the server.
# Typically this returns an array of record instances, except in the case of count queries
# @raise [ServiceError] if there is an error when talking to the service
def execute
@response = RestClient::Resource.new(build_query_uri, @rest_options).get
begin
@response = RestClient::Resource.new(build_query_uri, @rest_options).get
rescue Exception => e
handle_exception(e)
end
return Integer(@response) if @response =~ /^\d+$/
handle_collection_result(@response)
end
Expand Down Expand Up @@ -167,6 +170,51 @@ def add_link(parent, nav_prop, child)

private

# Constructs a QueryBuilder instance for a collection using the arguments provided.
#
# @param [String] name the name of the collection
# @param [Hash] additional_parameters the additional parameters
# @param [Array] args the arguments to use for query
def build_collection_query_object(name, additional_parameters, *args)
root = "/#{name.to_s}"
if args.empty?
#nothing to add
elsif args.size == 1
if args.first.to_s =~ /\d+/
id_metadata = find_id_metadata(name.to_s)
root << build_id_path(args.first, id_metadata)
else
root << "(#{args.first})"
end
else
root << "(#{args.join(',')})"
end
QueryBuilder.new(root, additional_parameters)
end

# Finds the metadata associated with the given collection's first id property
# Remarks: This is used for single item lookup queries using the ID, e.g. Products(1), not complex primary keys
#
# @param [String] collection_name the name of the collection
def find_id_metadata(collection_name)
collection_data = @collections.fetch(collection_name)
class_metadata = @class_metadata.fetch(collection_data[:type].to_s)
key = class_metadata.select{|k,h| h.is_key }.collect{|k,h| h.name }[0]
class_metadata[key]
end

# Builds the ID expression of a given id for query
#
# @param [Object] id_value the actual value to be used
# @param [PropertyMetadata] id_metadata the property metadata object for the id
def build_id_path(id_value, id_metadata)
if id_metadata.type == "Edm.Int64"
"(#{id_value}L)"
else
"(#{id_value})"
end
end

def set_options!(options)
@options = options
if @options[:eager_partial].nil?
Expand Down Expand Up @@ -291,10 +339,12 @@ def qualify_class_name(klass_name)
end

# Builds the metadata need for each property for things like feed customizations and navigation properties
def build_property_metadata(props)
def build_property_metadata(props, keys=[])
metadata = {}
props.each do |property_element|
prop_meta = PropertyMetadata.new(property_element)
prop_meta.is_key = keys.include?(prop_meta.name)

# If this is a navigation property, we need to add the association to the property metadata
prop_meta.association = Association.new(property_element, @edmx) if prop_meta.nav_prop
metadata[prop_meta.name] = prop_meta
Expand All @@ -319,13 +369,15 @@ def handle_exception(e)
error = Nokogiri::XML(e.response)

message = error.xpath("m:error/m:message", @ds_namespaces).first.content
raise "HTTP Error #{code}: #{message}"
raise ServiceError.new(code), message
end

# Loops through the standard properties (non-navigation) for a given class and returns the appropriate list of methods
def collect_properties(klass_name, element, doc)
props = element.xpath(".//edm:Property", @ds_namespaces)
@class_metadata[klass_name] = build_property_metadata(props)
key_elemnts = element.xpath(".//edm:Key//edm:PropertyRef", @ds_namespaces)
keys = key_elemnts.collect { |k| k['Name'] }
@class_metadata[klass_name] = build_property_metadata(props, keys)
methods = props.collect { |p| p['Name'] }
unless element["BaseType"].nil?
base = element["BaseType"].split(".").last()
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_odata/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The ruby_odata namespace
module OData
# The current version of ruby_odata
VERSION = "0.1.4"
VERSION = "0.1.5"
end
19 changes: 19 additions & 0 deletions spec/fixtures/int64_ids/edmx_boat_service.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="1.0" m:MaxDataServiceVersion="2.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<Schema Namespace="Model" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="Boat">
<Key>
<PropertyRef Name="KeyId" />
</Key>
<Property Name="KeyId" Type="Edm.Int64" Nullable="false" p6:StoreGeneratedPattern="Identity" xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/annotation" />
<Property Name="color" Type="Edm.String" Nullable="false" MaxLength="50" FixedLength="false" Unicode="false" />
</EntityType>
</Schema>
<Schema Namespace="WebApp" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="adoEntities" m:IsDefaultEntityContainer="true" p6:LazyLoadingEnabled="true" xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
<EntitySet Name="Boats" EntityType="Model.Boat" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
21 changes: 21 additions & 0 deletions spec/fixtures/int64_ids/edmx_car_service.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="1.0" m:MaxDataServiceVersion="2.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<Schema Namespace="Model" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="Car">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.Int64" Nullable="false" p6:StoreGeneratedPattern="Identity" xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/annotation" />
<Property Name="color" Type="Edm.String" Nullable="false" MaxLength="50" FixedLength="false" Unicode="false" />
<Property Name="num_spots" Type="Edm.Int32" />
<Property Name="striped" Type="Edm.Byte" />
</EntityType>
</Schema>
<Schema Namespace="WebApp" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="adoEntities" m:IsDefaultEntityContainer="true" p6:LazyLoadingEnabled="true" xmlns:p6="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
<EntitySet Name="Cars" EntityType="Model.Car" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
26 changes: 26 additions & 0 deletions spec/fixtures/int64_ids/result_boats.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://test.com/test.svc/"
xmlns="http://www.w3.org/2005/Atom"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<id>http://test.com/test.svc/Boats</id>
<title type="text">Boats</title>
<updated>2013-10-28T19:59:15Z</updated>
<link rel="self" title="Boats" href="Boats" />
<entry>
<id>http://test.com/test.svc/Boats(213L)</id>
<category term="Model.Boat" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="edit" title="Boat" href="Boats(213L)" />
<title />
<updated>2013-10-28T19:59:15Z</updated>
<author>
<name />
</author>
<content type="application/xml">
<m:properties>
<d:KeyId m:type="Edm.Int64">213</d:KeyId>
<d:color>blue</d:color>
</m:properties>
</content>
</entry>
</feed>
28 changes: 28 additions & 0 deletions spec/fixtures/int64_ids/result_cars.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://test.com/test.svc/"
xmlns="http://www.w3.org/2005/Atom"
xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<id>http://test.com/test.svc/Cars</id>
<title type="text">Cars</title>
<updated>2013-10-28T19:59:15Z</updated>
<link rel="self" title="Cars" href="Cars" />
<entry>
<id>http://test.com/test.svc/Cars(213L)</id>
<category term="Model.Car" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<link rel="edit" title="Car" href="Cars(213L)" />
<title />
<updated>2013-10-28T19:59:15Z</updated>
<author>
<name />
</author>
<content type="application/xml">
<m:properties>
<d:id m:type="Edm.Int64">213</d:id>
<d:color>peach</d:color>
<d:num_spots m:type="Edm.Int32" m:null="true" />
<d:striped m:type="Edm.Byte" m:null="true" />
</m:properties>
</content>
</entry>
</feed>
Loading

0 comments on commit 8d9992f

Please sign in to comment.