odata_server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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>