odata_server 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +12 -0
- data/MIT-LICENSE +20 -0
- data/README +14 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/app/controllers/o_data_controller.rb +145 -0
- data/app/helpers/o_data_helper.rb +203 -0
- data/app/views/o_data/metadata.xml.builder +62 -0
- data/app/views/o_data/resource.atom.builder +8 -0
- data/app/views/o_data/resource.json.erb +1 -0
- data/app/views/o_data/service.xml.builder +11 -0
- data/config/routes.rb +11 -0
- data/init.rb +3 -0
- data/install.rb +1 -0
- data/lib/o_data/abstract_query.rb +27 -0
- data/lib/o_data/abstract_query/base.rb +133 -0
- data/lib/o_data/abstract_query/countable.rb +22 -0
- data/lib/o_data/abstract_query/errors.rb +117 -0
- data/lib/o_data/abstract_query/option.rb +58 -0
- data/lib/o_data/abstract_query/options/enumerated_option.rb +39 -0
- data/lib/o_data/abstract_query/options/expand_option.rb +81 -0
- data/lib/o_data/abstract_query/options/format_option.rb +19 -0
- data/lib/o_data/abstract_query/options/inlinecount_option.rb +20 -0
- data/lib/o_data/abstract_query/options/orderby_option.rb +79 -0
- data/lib/o_data/abstract_query/options/select_option.rb +74 -0
- data/lib/o_data/abstract_query/options/skip_option.rb +32 -0
- data/lib/o_data/abstract_query/options/top_option.rb +32 -0
- data/lib/o_data/abstract_query/parser.rb +77 -0
- data/lib/o_data/abstract_query/segment.rb +43 -0
- data/lib/o_data/abstract_query/segments/collection_segment.rb +24 -0
- data/lib/o_data/abstract_query/segments/count_segment.rb +38 -0
- data/lib/o_data/abstract_query/segments/entity_type_and_key_values_segment.rb +116 -0
- data/lib/o_data/abstract_query/segments/entity_type_segment.rb +31 -0
- data/lib/o_data/abstract_query/segments/links_segment.rb +49 -0
- data/lib/o_data/abstract_query/segments/navigation_property_segment.rb +82 -0
- data/lib/o_data/abstract_query/segments/property_segment.rb +50 -0
- data/lib/o_data/abstract_query/segments/value_segment.rb +40 -0
- data/lib/o_data/abstract_schema.rb +9 -0
- data/lib/o_data/abstract_schema/association.rb +29 -0
- data/lib/o_data/abstract_schema/base.rb +48 -0
- data/lib/o_data/abstract_schema/comparable.rb +42 -0
- data/lib/o_data/abstract_schema/end.rb +50 -0
- data/lib/o_data/abstract_schema/entity_type.rb +64 -0
- data/lib/o_data/abstract_schema/navigation_property.rb +37 -0
- data/lib/o_data/abstract_schema/property.rb +35 -0
- data/lib/o_data/abstract_schema/schema_object.rb +37 -0
- data/lib/o_data/abstract_schema/serializable.rb +79 -0
- data/lib/o_data/active_record_schema.rb +8 -0
- data/lib/o_data/active_record_schema/association.rb +130 -0
- data/lib/o_data/active_record_schema/base.rb +37 -0
- data/lib/o_data/active_record_schema/entity_type.rb +128 -0
- data/lib/o_data/active_record_schema/navigation_property.rb +45 -0
- data/lib/o_data/active_record_schema/property.rb +45 -0
- data/lib/o_data/active_record_schema/serializable.rb +36 -0
- data/lib/odata_server.rb +12 -0
- data/public/clientaccesspolicy.xml +13 -0
- data/tasks/o_data_server_tasks.rake +4 -0
- data/test/helper.rb +17 -0
- data/uninstall.rb +1 -0
- metadata +171 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
module OData
|
2
|
+
module ActiveRecordSchema
|
3
|
+
class Association < OData::AbstractSchema::Association
|
4
|
+
def self.name_for(reflection)
|
5
|
+
EntityType.name_for(reflection.active_record) + '#' + reflection.name.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.nullable?(active_record, association_columns)
|
9
|
+
association_columns.all? { |column_name|
|
10
|
+
column = active_record.columns.find { |c| c.name == column_name }
|
11
|
+
column.blank? ? true : column.null
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.active_record_for_from_end(reflection)
|
16
|
+
reflection.active_record
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.active_record_for_to_end(reflection)
|
20
|
+
return nil if reflection.options[:polymorphic]
|
21
|
+
begin
|
22
|
+
reflection.class_name.constantize
|
23
|
+
rescue => ex
|
24
|
+
raise "Failed to handle class <#{reflection.active_record}> #{reflection.macro} #{reflection.name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# def self.foreign_keys_for(reflection)
|
29
|
+
# [reflection.options[:foreign_key] || reflection.association_foreign_key, reflection.options[:foreign_type]].compact
|
30
|
+
# end
|
31
|
+
|
32
|
+
def self.polymorphic_column_name(reflection, column_name)
|
33
|
+
# self.polymorphic_namespace_name.to_s + '.' + (reflection.options[:as] ? reflection.options[:as].to_s.classify : reflection.class_name.to_s) + '#' + column_name.to_s
|
34
|
+
self.polymorphic_namespace_name.to_s + '#' + column_name.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.column_names_for_from_end(reflection)
|
38
|
+
out = []
|
39
|
+
|
40
|
+
case reflection.macro
|
41
|
+
when :belongs_to
|
42
|
+
out << reflection.primary_key_name
|
43
|
+
out << reflection.options[:foreign_type] if reflection.options[:polymorphic]
|
44
|
+
else
|
45
|
+
out << EntityType.primary_key_for(reflection.active_record)
|
46
|
+
out << polymorphic_column_name(reflection, 'ReturnType') if reflection.options[:as]
|
47
|
+
end
|
48
|
+
|
49
|
+
out
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.column_names_for_to_end(reflection)
|
53
|
+
out = []
|
54
|
+
|
55
|
+
case reflection.macro
|
56
|
+
when :belongs_to
|
57
|
+
if reflection.options[:polymorphic]
|
58
|
+
out << polymorphic_column_name(reflection, 'Key')
|
59
|
+
out << polymorphic_column_name(reflection, 'ReturnType')
|
60
|
+
else
|
61
|
+
out << EntityType.primary_key_for(reflection.class_name.constantize)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
out << reflection.primary_key_name
|
65
|
+
|
66
|
+
if reflection.options[:as]
|
67
|
+
out << reflection.options[:as].to_s + '_type'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
out
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.from_end_options_for(schema, reflection)
|
75
|
+
active_record = active_record_for_from_end(reflection)
|
76
|
+
|
77
|
+
entity_type = schema.find_entity_type(:active_record => active_record)
|
78
|
+
raise OData::AbstractQuery::Errors::EntityTypeNotFound.new(nil, active_record.class_name) if entity_type.blank?
|
79
|
+
|
80
|
+
polymorphic = false
|
81
|
+
|
82
|
+
# TODO: detect 'nullable' for FromEnd of Association
|
83
|
+
nullable = false
|
84
|
+
|
85
|
+
multiple = reflection.macro == :has_and_belongs_to_many
|
86
|
+
|
87
|
+
name = entity_type.name
|
88
|
+
name = name.pluralize if multiple
|
89
|
+
|
90
|
+
{ :name => name, :entity_type => entity_type, :return_type => entity_type.qualified_name, :multiple => multiple, :nullable => nullable, :polymorphic => polymorphic }
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.to_end_options_for(schema, reflection)
|
94
|
+
Rails.logger.info("Processing #{reflection.active_record}")
|
95
|
+
active_record = active_record_for_to_end(reflection)
|
96
|
+
entity_type = schema.find_entity_type(:active_record => active_record)
|
97
|
+
|
98
|
+
polymorphic = reflection.options[:polymorphic] # || reflection.options[:as]
|
99
|
+
|
100
|
+
multiple = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
101
|
+
|
102
|
+
nullable = begin
|
103
|
+
case reflection.macro
|
104
|
+
when :belongs_to
|
105
|
+
nullable?(active_record_for_from_end(reflection), column_names_for_from_end(reflection))
|
106
|
+
else
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
name = EntityType.name_for(reflection.class_name)
|
112
|
+
name = name.pluralize if multiple
|
113
|
+
|
114
|
+
unless active_record.blank? || entity_type.blank?
|
115
|
+
{ :name => name, :entity_type => entity_type, :return_type => entity_type.qualified_name, :multiple => multiple, :nullable => nullable, :polymorphic => polymorphic }
|
116
|
+
else
|
117
|
+
{ :name => name, :return_type => self.polymorphic_namespace_name, :multiple => multiple, :nullable => nullable, :polymorphic => polymorphic }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
attr_reader :reflection
|
122
|
+
|
123
|
+
def initialize(schema, reflection)
|
124
|
+
super(schema, self.class.name_for(reflection), self.class.from_end_options_for(schema, reflection), self.class.to_end_options_for(schema, reflection))
|
125
|
+
|
126
|
+
@reflection = reflection
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module OData
|
2
|
+
module ActiveRecordSchema
|
3
|
+
class Base < OData::AbstractSchema::Base
|
4
|
+
def find_entity_type(options = {})
|
5
|
+
if options[:active_record]
|
6
|
+
self.entity_types.find { |et| et.name == EntityType.name_for(options[:active_record]) }
|
7
|
+
else
|
8
|
+
super(options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
super(*args)
|
14
|
+
|
15
|
+
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
|
16
|
+
|
17
|
+
Object.subclasses_of(ActiveRecord::Base).collect { |active_record|
|
18
|
+
self.EntityType(active_record, :reflect_on_associations => false)
|
19
|
+
}.collect { |entity_type|
|
20
|
+
entity_type.active_record.reflect_on_all_associations.each do |reflection|
|
21
|
+
entity_type.NavigationProperty(reflection)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def Association(*args)
|
27
|
+
Association.new(self, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def EntityType(*args)
|
31
|
+
entity_type = EntityType.new(self, *args)
|
32
|
+
self.entity_types << entity_type
|
33
|
+
entity_type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module OData
|
2
|
+
module ActiveRecordSchema
|
3
|
+
class EntityType < OData::AbstractSchema::EntityType
|
4
|
+
def self.name_for(active_record_or_str)
|
5
|
+
name = active_record_or_str.is_a?(ActiveRecord::Base) ? active_record_or_str.name : active_record_or_str.to_s
|
6
|
+
name.gsub('::', '')
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.primary_key_for(active_record)
|
10
|
+
active_record.primary_key
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :active_record
|
14
|
+
|
15
|
+
def initialize(schema, active_record, options = {})
|
16
|
+
super(schema, self.class.name_for(active_record))
|
17
|
+
|
18
|
+
options.reverse_merge!(:reflect_on_associations => true)
|
19
|
+
|
20
|
+
@active_record = active_record
|
21
|
+
|
22
|
+
key_property_name = self.class.primary_key_for(@active_record).to_s
|
23
|
+
|
24
|
+
@active_record.columns.each do |column_adapter|
|
25
|
+
property = self.Property(column_adapter)
|
26
|
+
|
27
|
+
if key_property_name == property.name
|
28
|
+
self.key_property = property
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
OData::AbstractSchema::Serializable.atom_element_names.each do |atom_element_name|
|
33
|
+
o_data_active_record_method_name = :"o_data_atom_#{atom_element_name}"
|
34
|
+
o_data_entity_type_property_name = :"atom_#{atom_element_name}_property"
|
35
|
+
|
36
|
+
if @active_record.respond_to?(o_data_active_record_method_name)
|
37
|
+
result = @active_record.send(o_data_active_record_method_name)
|
38
|
+
next unless result.is_a?(Symbol)
|
39
|
+
|
40
|
+
property = self.properties.find { |p| p.name == result.to_s }
|
41
|
+
next if property.blank?
|
42
|
+
|
43
|
+
self.send(:"#{o_data_entity_type_property_name}=", property)
|
44
|
+
elsif !@active_record.instance_methods.include?(o_data_active_record_method_name.to_s) && @active_record.column_names.include?(atom_element_name.to_s)
|
45
|
+
property = self.properties.find { |p| p.name == atom_element_name.to_s }
|
46
|
+
next if property.blank?
|
47
|
+
|
48
|
+
self.send(:"#{o_data_entity_type_property_name}=", property)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if options[:reflect_on_associations]
|
53
|
+
@active_record.reflect_on_all_associations.each do |reflection|
|
54
|
+
self.NavigationProperty(reflection)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def Property(*args)
|
60
|
+
property = Property.new(self.schema, self, *args)
|
61
|
+
self.properties << property
|
62
|
+
property
|
63
|
+
end
|
64
|
+
|
65
|
+
def NavigationProperty(*args)
|
66
|
+
navigation_property = NavigationProperty.new(self.schema, self, *args)
|
67
|
+
self.navigation_properties << navigation_property
|
68
|
+
navigation_property
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_all(key_values = {})
|
72
|
+
if @active_record.respond_to?(:with_permissions_to)
|
73
|
+
@active_record.with_permissions_to(:read).find(:all, :conditions => conditions_for_find(key_values))
|
74
|
+
else
|
75
|
+
@active_record.find(:all, :conditions => conditions_for_find(key_values))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_one(key_value)
|
80
|
+
return nil if self.key_property.blank?
|
81
|
+
if @active_record.respond_to?(:with_permissions_to)
|
82
|
+
@active_record.with_permissions_to(:read).find(key_value)
|
83
|
+
else
|
84
|
+
@active_record.find(key_value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def conditions_for_find(key_values = {})
|
89
|
+
self.class.conditions_for_find(self, key_values)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.conditions_for_find(entity_type, key_values = {})
|
93
|
+
return "1=0" unless entity_type.is_a?(OData::ActiveRecordSchema::EntityType)
|
94
|
+
return "1=1" if key_values.blank?
|
95
|
+
|
96
|
+
key_values.collect { |pair|
|
97
|
+
property_or_str, value = pair
|
98
|
+
|
99
|
+
property = begin
|
100
|
+
if property_or_str.is_a?(Property)
|
101
|
+
property_or_str
|
102
|
+
else
|
103
|
+
property = entity_type.properties.find { |p| p.name == property_or_str.to_s }
|
104
|
+
raise OData::AbstractQuery::Errors::PropertyNotFound.new(nil, property_or_str) if property.blank?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
[property, value]
|
109
|
+
}.reject { |pair|
|
110
|
+
pair.first.blank?
|
111
|
+
}.inject({}) { |acc, pair|
|
112
|
+
property, value = pair
|
113
|
+
|
114
|
+
acc[property.column_adapter.name.to_sym] = value
|
115
|
+
acc
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.href_for(one)
|
120
|
+
one.class.name.pluralize + '(' + one.send(one.class.send(:primary_key)).to_s + ')'
|
121
|
+
end
|
122
|
+
|
123
|
+
def href_for(one)
|
124
|
+
self.class.href_for(one)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module OData
|
2
|
+
module ActiveRecordSchema
|
3
|
+
class NavigationProperty < OData::AbstractSchema::NavigationProperty
|
4
|
+
def self.name_for(reflection)
|
5
|
+
reflection.name.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.association_for(schema, reflection)
|
9
|
+
schema.Association(reflection)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(schema, entity_type, reflection)
|
13
|
+
super(schema, entity_type, self.class.name_for(reflection), self.class.association_for(schema, reflection), :source => true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_name
|
17
|
+
self.association.reflection.name.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_all(one, key_values = {})
|
21
|
+
results = one.send(method_name)
|
22
|
+
unless key_values.blank?
|
23
|
+
if results.respond_to?(:find)
|
24
|
+
results = results.find(:all, :conditions => self.entity_type.conditions_for_find(key_values))
|
25
|
+
else
|
26
|
+
# TODO: raise exception if key_values supplied for non-finder method
|
27
|
+
end
|
28
|
+
end
|
29
|
+
results
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_one(one, key_value = nil)
|
33
|
+
results = one.send(method_name)
|
34
|
+
unless key_value.blank?
|
35
|
+
if results.respond_to?(:find)
|
36
|
+
results = results.find(key_value)
|
37
|
+
else
|
38
|
+
# TODO: raise exception if key_value supplied for non-finder method
|
39
|
+
end
|
40
|
+
end
|
41
|
+
results
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module OData
|
2
|
+
module ActiveRecordSchema
|
3
|
+
class Property < OData::AbstractSchema::Property
|
4
|
+
cattr_reader :column_adapter_return_types
|
5
|
+
@@column_adapter_return_types = {
|
6
|
+
:binary => 'Edm.Binary',
|
7
|
+
:boolean => 'Edm.Boolean',
|
8
|
+
:byte => 'Edm.Byte',
|
9
|
+
:date => 'Edm.Date',
|
10
|
+
:datetime => 'Edm.DateTime',
|
11
|
+
:float => 'Edm.Decimal',
|
12
|
+
:integer => 'Edm.Int32',
|
13
|
+
:string => 'Edm.String',
|
14
|
+
:text => 'Edm.String',
|
15
|
+
:timestamp => 'Edm.DateTime',
|
16
|
+
:time => 'Edm.Time'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def self.return_type_for(column_adapter)
|
20
|
+
@@column_adapter_return_types[column_adapter.type]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.name_for(column_adapter)
|
24
|
+
column_adapter.name.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.nullable?(column_adapter)
|
28
|
+
column_adapter.null
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :column_adapter
|
32
|
+
|
33
|
+
def initialize(schema, entity_type, column_adapter)
|
34
|
+
super(schema, entity_type, self.class.name_for(column_adapter), self.class.return_type_for(column_adapter), self.class.nullable?(column_adapter))
|
35
|
+
|
36
|
+
@column_adapter = column_adapter
|
37
|
+
end
|
38
|
+
|
39
|
+
def value_for(one)
|
40
|
+
v = one.send(@column_adapter.name.to_sym)
|
41
|
+
v.respond_to?(:iso8601) ? v.send(:iso8601) : v
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module OData
|
2
|
+
module ActiveRecordSchema
|
3
|
+
module Serializable
|
4
|
+
# def self.atom_method_names
|
5
|
+
# OData::AbstractSchema::Serializable.atom_element_names.collect { |atom_element_name| "o_data_atom_#{atom_element_name}_column" }
|
6
|
+
# end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
OData::AbstractSchema::Serializable.atom_element_names.each do |atom_element_name|
|
12
|
+
o_data_active_record_method_name = :"o_data_atom_#{atom_element_name}"
|
13
|
+
o_data_entity_type_method_name = :"atom_#{atom_element_name}_for"
|
14
|
+
|
15
|
+
OData::ActiveRecordSchema::EntityType.instance_eval do
|
16
|
+
define_method(o_data_entity_type_method_name) do |one|
|
17
|
+
if one.class.respond_to?(o_data_active_record_method_name)
|
18
|
+
result = one.class.send(o_data_active_record_method_name)
|
19
|
+
|
20
|
+
if result.is_a?(Symbol)
|
21
|
+
one.send(result)
|
22
|
+
elsif result.is_a?(Proc)
|
23
|
+
result.call(one)
|
24
|
+
else
|
25
|
+
result
|
26
|
+
end
|
27
|
+
elsif one.respond_to?(o_data_active_record_method_name)
|
28
|
+
one.send(o_data_active_record_method_name)
|
29
|
+
elsif one.respond_to?(atom_element_name)
|
30
|
+
one.send(atom_element_name)
|
31
|
+
else
|
32
|
+
super(one)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/odata_server.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module OData
|
2
|
+
class ODataException < StandardError
|
3
|
+
def to_s
|
4
|
+
"An unknown #{self.class.name.demodulize.to_s} has occured."
|
5
|
+
end
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "o_data/abstract_schema"
|
10
|
+
require "o_data/abstract_query"
|
11
|
+
|
12
|
+
require "o_data/active_record_schema"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<access-policy>
|
3
|
+
<cross-domain-access>
|
4
|
+
<policy>
|
5
|
+
<allow-from http-request-headers="*">
|
6
|
+
<domain uri="*"/>
|
7
|
+
</allow-from>
|
8
|
+
<grant-to>
|
9
|
+
<resource path="/" include-subpaths="true"/>
|
10
|
+
</grant-to>
|
11
|
+
</policy>
|
12
|
+
</cross-domain-access>
|
13
|
+
</access-policy>
|