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
@@ -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
@@ -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>