odata_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +12 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +14 -0
  4. data/Rakefile +53 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/o_data_controller.rb +145 -0
  7. data/app/helpers/o_data_helper.rb +203 -0
  8. data/app/views/o_data/metadata.xml.builder +62 -0
  9. data/app/views/o_data/resource.atom.builder +8 -0
  10. data/app/views/o_data/resource.json.erb +1 -0
  11. data/app/views/o_data/service.xml.builder +11 -0
  12. data/config/routes.rb +11 -0
  13. data/init.rb +3 -0
  14. data/install.rb +1 -0
  15. data/lib/o_data/abstract_query.rb +27 -0
  16. data/lib/o_data/abstract_query/base.rb +133 -0
  17. data/lib/o_data/abstract_query/countable.rb +22 -0
  18. data/lib/o_data/abstract_query/errors.rb +117 -0
  19. data/lib/o_data/abstract_query/option.rb +58 -0
  20. data/lib/o_data/abstract_query/options/enumerated_option.rb +39 -0
  21. data/lib/o_data/abstract_query/options/expand_option.rb +81 -0
  22. data/lib/o_data/abstract_query/options/format_option.rb +19 -0
  23. data/lib/o_data/abstract_query/options/inlinecount_option.rb +20 -0
  24. data/lib/o_data/abstract_query/options/orderby_option.rb +79 -0
  25. data/lib/o_data/abstract_query/options/select_option.rb +74 -0
  26. data/lib/o_data/abstract_query/options/skip_option.rb +32 -0
  27. data/lib/o_data/abstract_query/options/top_option.rb +32 -0
  28. data/lib/o_data/abstract_query/parser.rb +77 -0
  29. data/lib/o_data/abstract_query/segment.rb +43 -0
  30. data/lib/o_data/abstract_query/segments/collection_segment.rb +24 -0
  31. data/lib/o_data/abstract_query/segments/count_segment.rb +38 -0
  32. data/lib/o_data/abstract_query/segments/entity_type_and_key_values_segment.rb +116 -0
  33. data/lib/o_data/abstract_query/segments/entity_type_segment.rb +31 -0
  34. data/lib/o_data/abstract_query/segments/links_segment.rb +49 -0
  35. data/lib/o_data/abstract_query/segments/navigation_property_segment.rb +82 -0
  36. data/lib/o_data/abstract_query/segments/property_segment.rb +50 -0
  37. data/lib/o_data/abstract_query/segments/value_segment.rb +40 -0
  38. data/lib/o_data/abstract_schema.rb +9 -0
  39. data/lib/o_data/abstract_schema/association.rb +29 -0
  40. data/lib/o_data/abstract_schema/base.rb +48 -0
  41. data/lib/o_data/abstract_schema/comparable.rb +42 -0
  42. data/lib/o_data/abstract_schema/end.rb +50 -0
  43. data/lib/o_data/abstract_schema/entity_type.rb +64 -0
  44. data/lib/o_data/abstract_schema/navigation_property.rb +37 -0
  45. data/lib/o_data/abstract_schema/property.rb +35 -0
  46. data/lib/o_data/abstract_schema/schema_object.rb +37 -0
  47. data/lib/o_data/abstract_schema/serializable.rb +79 -0
  48. data/lib/o_data/active_record_schema.rb +8 -0
  49. data/lib/o_data/active_record_schema/association.rb +130 -0
  50. data/lib/o_data/active_record_schema/base.rb +37 -0
  51. data/lib/o_data/active_record_schema/entity_type.rb +128 -0
  52. data/lib/o_data/active_record_schema/navigation_property.rb +45 -0
  53. data/lib/o_data/active_record_schema/property.rb +45 -0
  54. data/lib/o_data/active_record_schema/serializable.rb +36 -0
  55. data/lib/odata_server.rb +12 -0
  56. data/public/clientaccesspolicy.xml +13 -0
  57. data/tasks/o_data_server_tasks.rake +4 -0
  58. data/test/helper.rb +17 -0
  59. data/uninstall.rb +1 -0
  60. metadata +171 -0
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.2"
11
+ gem "rcov", ">= 0"
12
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,14 @@
1
+ OData Server
2
+ ===========
3
+
4
+ This is a rails plugin/engine to embue your application with OData features (so its data can be readily consumed by
5
+ an OData client).
6
+
7
+ It was forked Mark Borkum's work at from http://rubyforge.org/scm/?group_id=9605 with only a few small additions
8
+ to support postgresql and declarative authorization and released as a gem.
9
+
10
+
11
+
12
+ The software iterates over your models exposing each as an OData source automatically.
13
+
14
+ Beware - this code is not heavily tested, it does work in my limited tests usint Tableau public to consume data.
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "odata_server"
16
+ gem.homepage = "http://github.com/bwlang/odata_server"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{OData server for Rails}
19
+ gem.description = %Q{This is a gem to enable OData consumers to pull data from your Rails application.}
20
+ gem.email = "langhorst@neb.com"
21
+ gem.authors = ["Mark Borkum", "Brad Langhorst"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "odata_server #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,145 @@
1
+ class ODataController < ApplicationController
2
+ cattr_reader :path_param
3
+ @@path_param = :__path__.freeze
4
+
5
+ cattr_reader :schema
6
+ @@schema = OData::ActiveRecordSchema::Base.new.freeze
7
+
8
+ cattr_reader :parser
9
+ @@parser = OData::AbstractQuery::Parser.new(@@schema).freeze
10
+
11
+ rescue_from OData::ODataException, :with => :handle_exception
12
+ rescue_from ActiveRecord::RecordNotFound, :with => :handle_exception
13
+
14
+ before_filter :extract_resource_path_and_query_string, :only => [:resource]
15
+ before_filter :parse_resource_path_and_query_string!, :only => [:resource]
16
+ before_filter :set_request_format!, :only => [:resource]
17
+
18
+ %w{service metadata resource}.each do |method_name|
19
+ define_method(:"redirect_to_#{method_name}") do
20
+ # redirect_to(send(:"o_data_#{method_name}_url"))
21
+ redirect_to(params.merge(:action => method_name.to_s))
22
+ end
23
+ end
24
+
25
+ def service
26
+ respond_to do |format|
27
+ format.xml # service.xml.builder
28
+ format.json { render :json => @@schema.to_json }
29
+ end
30
+ end
31
+
32
+ def metadata
33
+ respond_to do |format|
34
+ format.xml # metadata.xml.builder
35
+ end
36
+ end
37
+
38
+ def resource
39
+ @last_segment = @query.segments.last
40
+
41
+ @results = @query.execute!
42
+
43
+ case @last_segment.class.segment_name
44
+ when OData::AbstractQuery::Segments::CountSegment.segment_name
45
+ render :text => @results.to_i
46
+ when OData::AbstractQuery::Segments::LinksSegment.segment_name
47
+ request.format = :xml unless request.format == :json
48
+
49
+ respond_to do |format|
50
+ format.xml { render :inline => "xml.instruct!; @results.empty? ? xml.links('xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices') : xml.links('xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices') { @results.each { |r| xml.uri(o_data_resource_url(r[1])) } }", :type => :builder }
51
+ format.json { render :json => { "links" => @results.collect { |r| { "uri" => r } } }.to_json }
52
+ end
53
+ when OData::AbstractQuery::Segments::ValueSegment.segment_name
54
+ render :text => @results.to_s
55
+ when OData::AbstractQuery::Segments::PropertySegment.segment_name
56
+ request.format = :xml unless request.format == :json
57
+
58
+ respond_to do |format|
59
+ format.xml { render :inline => "xml.instruct!; value.blank? ? xml.tag!(key.to_sym, 'm:null' => true, 'xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices', 'xmlns:m' => 'http://schemas.microsoft.com/ado/2007/08/dataservices') : xml.tag!(key.to_sym, value, 'edm:Type' => type, 'xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices', 'xmlns:edm' => 'http://schemas.microsoft.com/ado/2007/05/edm')", :locals => { :key => @results.keys.first.name, :type => @results.keys.first.return_type, :value => @results.values.first }, :type => :builder }
60
+ format.json { render :json => { @results.keys.first.name => @results.values.first }.to_json }
61
+ end
62
+ when OData::AbstractQuery::Segments::NavigationPropertySegment.segment_name
63
+ @countable = @last_segment.countable?
64
+
65
+ @navigation_property = @last_segment.navigation_property
66
+ @polymorphic = @navigation_property.to_end.polymorphic?
67
+
68
+ if @polymorphic
69
+ @entity_type = nil
70
+ @entity_type_name = @navigation_property.to_end.name.singularize
71
+ else
72
+ @entity_type = @navigation_property.to_end.entity_type
73
+ @entity_type_name = @entity_type.name
74
+ end
75
+
76
+ @collection_name = @entity_type_name.pluralize
77
+
78
+ @expand_navigation_property_paths = {}
79
+ if expand_option = @query.options.find { |o| o.option_name == OData::AbstractQuery::Options::ExpandOption.option_name }
80
+ @expand_navigation_property_paths = expand_option.navigation_property_paths
81
+ end
82
+
83
+ respond_to do |format|
84
+ format.atom # resource.atom.builder
85
+ format.json # resource.json.erb
86
+ end
87
+ when OData::AbstractQuery::Segments::CollectionSegment.segment_name
88
+ @countable = @last_segment.countable?
89
+
90
+ @navigation_property = nil
91
+ @polymorphic = true
92
+
93
+ @entity_type = @last_segment.entity_type
94
+
95
+ @expand_navigation_property_paths = {}
96
+ if expand_option = @query.options.find { |o| o.option_name == OData::AbstractQuery::Options::ExpandOption.option_name }
97
+ @expand_navigation_property_paths = expand_option.navigation_property_paths
98
+ end
99
+
100
+ respond_to do |format|
101
+ format.atom # resource.atom.builder
102
+ format.json # resource.json.erb
103
+ end
104
+ else
105
+ # in theory, this branch is unreachable because the <tt>parse_resource_path_and_query_string!</tt>
106
+ # method will throw an exception if the <tt>OData::AbstractQuery::Parser</tt> fails to match any
107
+ # segment of the resource path.
108
+ raise OData::AbstractQuery::Errors::AbstractQueryException.new(@query)
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def extract_resource_path_and_query_string
115
+ @resource_path = params[@@path_param].join('/')
116
+
117
+ @query_string = params.inject({}) { |acc, pair|
118
+ key, value = pair
119
+ acc[key.to_sym] = value unless [@@path_param, :controller, :action].include?(key.to_sym)
120
+ acc
121
+ }.collect { |key, value|
122
+ key.to_s + '=' + value.to_s
123
+ }.join('&')
124
+ end
125
+
126
+ def parse_resource_path_and_query_string!
127
+ @query = @@parser.parse!([@resource_path, @query_string].compact.join('?'))
128
+ end
129
+
130
+ def set_request_format!
131
+ if format_option = @query.options.find { |o| o.option_name == OData::AbstractQuery::Options::FormatOption.option_name }
132
+ if format_value = format_option.value
133
+ request.format = format_value.to_sym
134
+ end
135
+ end
136
+ end
137
+
138
+ def handle_exception(ex)
139
+ request.format = :xml
140
+
141
+ respond_to do |format|
142
+ format.xml { render :inline => "xml.instruct!; xml.error('xmlns' => 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata') { xml.code(code.to_s); xml.message(message); xml.uri(uri) }", :type => :builder, :locals => { :code => nil, :message => ex.message, :uri => request.url } }
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,203 @@
1
+ module ODataHelper
2
+ @@o_data_atom_xmlns = {
3
+ "xmlns" => "http://www.w3.org/2005/Atom",
4
+ "xmlns:d" => "http://schemas.microsoft.com/ado/2007/08/dataservices",
5
+ "xmlns:m" => "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
6
+ }.freeze
7
+
8
+ def o_data_atom_feed(xml, query, results, options = {})
9
+ results_href, results_url = begin
10
+ if base_href = options.delete(:href)
11
+ [base_href.to_s, o_data_resource_url(base_href.to_s)]
12
+ else
13
+ [query.resource_path, o_data_resource_url(query.to_uri)]
14
+ end
15
+ end
16
+
17
+ results_title = options.delete(:title) || results_href
18
+
19
+ xml.tag!(:feed, { "xml:base" => o_data_service_url }.merge(options[:hide_xmlns] ? {} : @@o_data_atom_xmlns)) do
20
+ xml.tag!(:title, results_title)
21
+ xml.tag!(:id, results_url)
22
+ xml.tag!(:link, :rel => "self", :title => results_title, :href => results_href)
23
+
24
+ unless results.empty?
25
+ if last_result = results.last
26
+ if atom_updated_at = query.schema.atom_updated_at_for(last_result)
27
+ xml.tag!(:updated, atom_updated_at.iso8601)
28
+ end
29
+ end
30
+
31
+ results.each do |result|
32
+ o_data_atom_entry(xml, query, result, options.merge(:hide_xmlns => true, :href => results_href))
33
+ end
34
+ end
35
+
36
+ if inlinecount_option = query.options.find { |o| o.option_name == OData::AbstractQuery::Options::InlinecountOption.option_name }
37
+ if inlinecount_option.value == 'allpages'
38
+ xml.m(:count, results.length)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def o_data_atom_entry(xml, query, result, options = {})
45
+ entity_type = options[:entity_type] || query.schema.find_entity_type(:active_record => result.class)
46
+ raise OData::AbstractQuery::Errors::EntityTypeNotFound.new(query, result.class.name) if entity_type.blank?
47
+
48
+ result_href = entity_type.href_for(result)
49
+ result_url = o_data_resource_url(result_href)
50
+
51
+ result_title = entity_type.atom_title_for(result)
52
+ result_summary = entity_type.atom_summary_for(result)
53
+ result_updated_at = entity_type.atom_updated_at_for(result)
54
+
55
+ xml.tag!(:entry, {}.merge(options[:hide_xmlns] ? {} : @@o_data_atom_xmlns)) do
56
+ xml.tag!(:id, result_url) unless result_href.blank?
57
+ xml.tag!(:title, result_title, :type => "text") unless result_title.blank?
58
+ xml.tag!(:summary, result_summary, :type => "text") unless result_summary.blank?
59
+ xml.tag!(:updated, result_updated_at.iso8601) unless result_updated_at.blank?
60
+
61
+ xml.tag!(:author) do
62
+ xml.tag!(:name)
63
+ end
64
+
65
+ xml.tag!(:link, :rel => "edit", :title => result_title, :href => result_href) unless result_title.blank? || result_href.blank?
66
+
67
+ unless entity_type.navigation_properties.empty?
68
+ entity_type.navigation_properties.sort_by(&:name).each do |navigation_property|
69
+ navigation_property_href = result_href + '/' + navigation_property.name
70
+
71
+ navigation_property_attrs = { :rel => "http://schemas.microsoft.com/ado/2007/08/dataservices/related/" + navigation_property.name, :type => "application/atom+xml;type=#{navigation_property.to_end.multiple? ? 'feed' : 'entry'}", :title => navigation_property.name, :href => navigation_property_href }
72
+
73
+ if (options[:expand] || {}).keys.include?(navigation_property)
74
+ xml.tag!(:link, navigation_property_attrs) do
75
+ xml.m(:inline, :type => navigation_property_attrs[:type]) do
76
+ if navigation_property.to_end.multiple?
77
+ o_data_atom_feed(xml, query, navigation_property.find_all(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property]))
78
+ else
79
+ o_data_atom_entry(xml, query, navigation_property.find_one(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property]))
80
+ end
81
+ end
82
+ end
83
+ else
84
+ xml.tag!(:link, navigation_property_attrs)
85
+ end
86
+ end
87
+ end
88
+
89
+ xml.tag!(:category, :term => entity_type.qualified_name, :scheme => "http://schemas.microsoft.com/ado/2007/08/dataservices/scheme")
90
+
91
+ unless (properties = get_selected_properties_for(query, entity_type)).empty?
92
+ xml.tag!(:content, :type => "application/xml") do
93
+ xml.m(:properties) do
94
+ properties.each do |property|
95
+ property_attrs = { "m:type" => property.return_type }
96
+
97
+ unless (value = property.value_for(result)).blank?
98
+ xml.d(property.name.to_sym, value, property_attrs)
99
+ else
100
+ xml.d(property.name.to_sym, property_attrs.merge("m:null" => true))
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def o_data_json_feed(query, results, options = {})
110
+ results_json = results.collect { |result| o_data_json_entry(query, result, options.merge(:d => false, :deferred => false)) }
111
+
112
+ if inlinecount_option = query.options.find { |o| o.option_name == OData::AbstractQuery::Options::InlinecountOption.option_name }
113
+ if inlinecount_option.value == 'allpages'
114
+ _json = {
115
+ "results" => results_json,
116
+ "__count" => results.length
117
+ }
118
+
119
+ return options[:d] ? { "d" => _json } : _json
120
+ end
121
+ end
122
+
123
+ options[:d] ? { "d" => results_json } : results_json
124
+ end
125
+
126
+ def o_data_json_entry(query, result, options = {})
127
+ entity_type = options[:entity_type] || query.schema.find_entity_type(:active_record => result.class)
128
+ raise OData::AbstractQuery::Errors::EntityTypeNotFound.new(query, result.class.name) if entity_type.blank?
129
+
130
+ resource_uri = o_data_resource_url(entity_type.href_for(result))
131
+ resource_type = entity_type.qualified_name
132
+
133
+ json = begin
134
+ if options[:deferred]
135
+ {
136
+ "__deferred" => {
137
+ "uri" => resource_uri
138
+ }
139
+ }
140
+ else
141
+ _json = {
142
+ "__metadata" => {
143
+ "uri" => resource_uri,
144
+ "type" => resource_type
145
+ }
146
+ }
147
+
148
+ get_selected_properties_for(query, entity_type).each do |property|
149
+ unless %w{__deferred __metadata}.include?(property.name.to_s)
150
+ _json[property.name.to_s] = property.value_for(result)
151
+ else
152
+ # TODO: raise JSONException (property with reserved name)
153
+ end
154
+ end
155
+
156
+ entity_type.navigation_properties.sort_by(&:name).each do |navigation_property|
157
+ unless %w{__deferred __metadata}.include?(navigation_property.name.to_s)
158
+ navigation_property_uri = resource_uri + '/' + navigation_property.name.to_s
159
+
160
+ _json[navigation_property.name.to_s] = begin
161
+ if (options[:expand] || {}).keys.include?(navigation_property)
162
+ if navigation_property.to_end.multiple?
163
+ o_data_json_feed(query, navigation_property.find_all(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property], :d => false))
164
+ else
165
+ o_data_json_entry(query, navigation_property.find_one(result), options.merge(:entity_type => navigation_property.to_end.entity_type, :expand => options[:expand][navigation_property], :d => false))
166
+ end
167
+ else
168
+ {
169
+ "__deferred" => {
170
+ "uri" => navigation_property_uri
171
+ }
172
+ }
173
+ end
174
+ end
175
+ else
176
+ # TODO: raise JSONException (navigation property with reserved name)
177
+ end
178
+ end
179
+
180
+ _json
181
+ end
182
+ end
183
+
184
+ options[:d] ? { "d" => json } : json
185
+ end
186
+
187
+ protected
188
+
189
+ def get_selected_properties_for(query, entity_type)
190
+ if select_option = query.options.find { |o| o.option_name == OData::AbstractQuery::Options::SelectOption.option_name }
191
+ if select_option.entity_type == entity_type
192
+ # entity_type is the $select'ed collection/navigation property
193
+ return select_option.properties
194
+ else
195
+ # entity_type is an $expand'ed navigation property
196
+ return []
197
+ end
198
+ end
199
+
200
+ # $select option not supplied
201
+ entity_type.properties
202
+ end
203
+ end