odata-model 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/odata/model/active_model.rb +48 -0
- data/lib/odata/model/attributes.rb +121 -0
- data/lib/odata/model/configuration.rb +104 -0
- data/lib/odata/model/persistence.rb +43 -0
- data/lib/odata/model/query.rb +29 -0
- data/lib/odata/model/version.rb +1 -1
- data/lib/odata/model.rb +17 -44
- data/lib/odata/query/builder.rb +210 -0
- data/odata-model.gemspec +1 -1
- data/spec/example_models/bare_model.rb +3 -0
- data/spec/example_models/odatademo_product.rb +13 -0
- data/spec/{active_model_lint_spec.rb → model/active_model_lint_spec.rb} +3 -7
- data/spec/model/property_defaults_spec.rb +22 -0
- data/spec/model/query_interface_spec.rb +20 -0
- data/spec/model/service_integration_spec.rb +14 -0
- data/spec/query/builder/filters_spec.rb +41 -0
- data/spec/query/builder/order_by_spec.rb +22 -0
- data/spec/query/builder/pagination_spec.rb +19 -0
- data/spec/query/builder/select_spec.rb +12 -0
- data/spec/query/builder_spec.rb +25 -0
- data/spec/spec_helper.rb +11 -0
- metadata +32 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 332cae2c88d7bec95fd2099befabbbecc227585a
|
4
|
+
data.tar.gz: 9c7c7fb9ad675081918ddbd85397425ec086c9a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7d906d5dbf1970f0391133f3daf252e193cdfb452f00a0fda0532357f7c8fb955524579338f1648d93c1025d2664ee746dff72d67f90f03344fa30510431294
|
7
|
+
data.tar.gz: 2de6ebdf03dd5b12d4870e3c39f338d873459be5a1f48f2f9b7f90c1ea973b530fba95d8b003c89aa6a4c86db92d7d8b5aa810be7274816e12575b29fb087e41
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module OData
|
4
|
+
module Model
|
5
|
+
# The OData::Model::ActiveModel module encapsulates all the functionality
|
6
|
+
# specifically needed for OData::Model to work with Rails via the
|
7
|
+
# ActiveModel conventions.
|
8
|
+
module ActiveModel
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
extend ::ActiveModel::Naming
|
12
|
+
include ::ActiveModel::Validations
|
13
|
+
include ::ActiveModel::Conversion
|
14
|
+
include ::ActiveModel::Dirty
|
15
|
+
|
16
|
+
included do
|
17
|
+
# ...
|
18
|
+
end
|
19
|
+
|
20
|
+
# Integrates ActiveModel's error handling capabilities
|
21
|
+
# @return [ActiveModel::Errors]
|
22
|
+
def errors
|
23
|
+
@errors ||= ::ActiveModel::Errors.new(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Used for ActiveModel validations
|
27
|
+
# @api private
|
28
|
+
def read_attribute_for_validation(attr)
|
29
|
+
send(attr)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Methods mixed in at the class level.
|
33
|
+
module ClassMethods
|
34
|
+
# Used for ActiveModel validations
|
35
|
+
# @api private
|
36
|
+
def human_attribute_name(attr, options = {})
|
37
|
+
attr
|
38
|
+
end
|
39
|
+
|
40
|
+
# Used for ActiveModel validations
|
41
|
+
# @api private
|
42
|
+
def lookup_ancestors
|
43
|
+
[self]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module OData
|
2
|
+
module Model
|
3
|
+
# The OData::Model::Attributes module encapsulates all the functionality
|
4
|
+
# specifically needed for OData::Model to support the mapping of
|
5
|
+
# OData::Entity properties to attributes on the class that includes
|
6
|
+
# OData::Model.
|
7
|
+
module Attributes
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
# ...
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns an array of registered attributes.
|
15
|
+
# @return [Array]
|
16
|
+
# @api private
|
17
|
+
def attributes
|
18
|
+
self.class.class_variable_get(:@@attributes)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the hash for the attribute to property mapping.
|
22
|
+
# @return [Hash]
|
23
|
+
# @api private
|
24
|
+
def property_map
|
25
|
+
self.class.class_variable_get(:@@property_map)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Methods mixed in at the class level.
|
29
|
+
module ClassMethods
|
30
|
+
# Defines a property from this model's related OData::Entity you want
|
31
|
+
# mapped to an attribute.
|
32
|
+
#
|
33
|
+
# @param literal_name [to_s] the literal Entity property name
|
34
|
+
# @param options [Hash] hash of options
|
35
|
+
# @return nil
|
36
|
+
def property(literal_name, options = {})
|
37
|
+
attribute_name = (options[:as] || literal_name.to_s.underscore).to_sym
|
38
|
+
|
39
|
+
register_attribute(attribute_name, literal_name, options)
|
40
|
+
create_accessors(attribute_name)
|
41
|
+
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an array of all registered attributes
|
46
|
+
# @return [Array]
|
47
|
+
# @api private
|
48
|
+
def attributes
|
49
|
+
if self.class_variable_defined?(:@@attributes)
|
50
|
+
class_variable_get(:@@attributes)
|
51
|
+
else
|
52
|
+
class_variable_set(:@@attributes, [])
|
53
|
+
class_variable_get(:@@attributes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns a hash keyed to the attribute name of passed options from the
|
58
|
+
# property definitions.
|
59
|
+
# @return [Hash]
|
60
|
+
# @api private
|
61
|
+
def attribute_options
|
62
|
+
if self.class_variable_defined?(:@@attribute_options)
|
63
|
+
class_variable_get(:@@attribute_options)
|
64
|
+
else
|
65
|
+
class_variable_set(:@@attribute_options, {})
|
66
|
+
class_variable_get(:@@attribute_options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a hash keyed to the attribute name with the values being the
|
71
|
+
# literal OData property name to use when accessing entity data.
|
72
|
+
# @return [Hash]
|
73
|
+
# @api private
|
74
|
+
def property_map
|
75
|
+
if self.class_variable_defined?(:@@property_map)
|
76
|
+
class_variable_get(:@@property_map)
|
77
|
+
else
|
78
|
+
class_variable_set(:@@property_map, {})
|
79
|
+
class_variable_get(:@@property_map)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Registers a supplied attribute with its literal property name and any
|
84
|
+
# provided options.
|
85
|
+
# @return [nil]
|
86
|
+
# @api private
|
87
|
+
def register_attribute(attribute_name, literal_name, options)
|
88
|
+
attributes << attribute_name
|
89
|
+
attribute_options[attribute_name] = options
|
90
|
+
property_map[attribute_name] = literal_name
|
91
|
+
|
92
|
+
# Ties into ActiveModel::Dirty
|
93
|
+
define_attribute_methods attribute_name
|
94
|
+
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Create attribute accessors for the supplied attribute name.
|
99
|
+
# @return [nil]
|
100
|
+
# @api private
|
101
|
+
def create_accessors(attribute_name)
|
102
|
+
class_eval do
|
103
|
+
define_method(attribute_name) do
|
104
|
+
odata_entity[property_map[attribute_name]]
|
105
|
+
end
|
106
|
+
|
107
|
+
define_method("#{attribute_name}=") do |value|
|
108
|
+
unless entity[property_map[attribute_name]] == value
|
109
|
+
send("#{attribute_name}_will_change!")
|
110
|
+
end
|
111
|
+
|
112
|
+
odata_entity[property_map[attribute_name]] = value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module OData
|
2
|
+
module Model
|
3
|
+
# The OData::Model::Configuration module encapsulates all the functionality
|
4
|
+
# specifically needed for OData::Model to maintain internal configuration
|
5
|
+
# details about it's relationship to the OData gem.
|
6
|
+
module Configuration
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
# ...
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the entity the current model is associated with, or a fresh
|
14
|
+
# entity.
|
15
|
+
# @return [OData::Entity]
|
16
|
+
# @api private
|
17
|
+
def odata_entity
|
18
|
+
@odata_entity ||= self.class.odata_service[odata_entity_set_name].new_entity
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the name of the OData::EntitySet the current model is related
|
22
|
+
# to.
|
23
|
+
# @return [String]
|
24
|
+
# @api private
|
25
|
+
def odata_entity_set_name
|
26
|
+
self.class.odata_entity_set_name
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the OData::EntitySet the current model is related to.
|
30
|
+
# @return [OData::EntitySet]
|
31
|
+
# @api private
|
32
|
+
def odata_entity_set
|
33
|
+
self.class.odata_entity_set
|
34
|
+
end
|
35
|
+
|
36
|
+
# Methods mixed in at the class level.
|
37
|
+
module ClassMethods
|
38
|
+
# Define the service to use for the current OData::Model. This method
|
39
|
+
# will cause the service to be looked up in the OData::ServiceRegistry
|
40
|
+
# by the supplied key, so it can accept either the service's URL or its
|
41
|
+
# namespace.
|
42
|
+
#
|
43
|
+
# @param service_key [to_s] service URL or namespace
|
44
|
+
# @return [nil]
|
45
|
+
def use_service(service_key)
|
46
|
+
odata_config[:service] = OData::ServiceRegistry[service_key.to_s]
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Define the entity set to use for the current OData::Model. This
|
51
|
+
# method will record in the OData configuration the supplied name so
|
52
|
+
# that it can be used to communicate properly with the underlying
|
53
|
+
# OData::Service.
|
54
|
+
#
|
55
|
+
# @param set_name [to_s] name of EntitySet used by OData service
|
56
|
+
# @return [nil]
|
57
|
+
def use_entity_set(set_name)
|
58
|
+
odata_config[:entity_set_name] = set_name.to_s
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Get the OData::Service
|
63
|
+
# @return [OData::Service]
|
64
|
+
# @api private
|
65
|
+
def odata_service
|
66
|
+
odata_config[:service]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get the OData::Service's namespace
|
70
|
+
# @return [String] OData Service's namespace
|
71
|
+
# @api private
|
72
|
+
def odata_namespace
|
73
|
+
odata_service.try(:namespace)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the configuration for working with the OData gem.
|
77
|
+
# @return [Hash]
|
78
|
+
# @api private
|
79
|
+
def odata_config
|
80
|
+
if class_variable_defined?(:@@odata_config)
|
81
|
+
class_variable_get(:@@odata_config)
|
82
|
+
else
|
83
|
+
class_variable_set(:@@odata_config, {})
|
84
|
+
class_variable_get(:@@odata_config)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the entity set name this model is related to.
|
89
|
+
# @return [String]
|
90
|
+
# @api private
|
91
|
+
def odata_entity_set_name
|
92
|
+
odata_config[:entity_set_name] ||= self.name.pluralize
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the OData::EntitySet the current model is related to.
|
96
|
+
# @return [OData::EntitySet]
|
97
|
+
# @api private
|
98
|
+
def odata_entity_set
|
99
|
+
odata_config[:entity_set] ||= odata_service[odata_entity_set_name]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module OData
|
2
|
+
module Model
|
3
|
+
# The OData::Model::Persistence module encapsulates all the functionality
|
4
|
+
# specifically needed for OData::Model to persist data to and from the
|
5
|
+
# OData gem.
|
6
|
+
module Persistence
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
# ...
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns whether the current instance has been persisted.
|
14
|
+
# @return [Boolean]
|
15
|
+
def persisted?
|
16
|
+
if instance_variable_defined?(:@persisted)
|
17
|
+
instance_variable_get(:@persisted)
|
18
|
+
else
|
19
|
+
instance_variable_set(:@persisted, false)
|
20
|
+
instance_variable_get(:@persisted)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Save the current model.
|
25
|
+
def save
|
26
|
+
odata_service[odata_entity_set_name] << odata_entity
|
27
|
+
instance_variable_set(:@persisted, true)
|
28
|
+
changes_applied
|
29
|
+
end
|
30
|
+
|
31
|
+
# Reload the model from OData
|
32
|
+
def reload!
|
33
|
+
# TODO reload OData entity
|
34
|
+
reset_changes
|
35
|
+
end
|
36
|
+
|
37
|
+
# Methods mixed in at the class level.
|
38
|
+
module ClassMethods
|
39
|
+
# ...
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module OData
|
2
|
+
module Model
|
3
|
+
module Query
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# ...
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Starts a chain for building up an OData query.
|
12
|
+
# @return [OData::Query::Builder]
|
13
|
+
def find
|
14
|
+
OData::Query::Builder.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Enables lookup of model entities by their primary key.
|
18
|
+
# @param [to_s] primary key value to lookup
|
19
|
+
# @return [OData::Model,nil]
|
20
|
+
def [](primary_key_value)
|
21
|
+
entity = odata_entity_set[primary_key_value]
|
22
|
+
model = self.new
|
23
|
+
model.instance_variable_set(:@odata_entity, entity)
|
24
|
+
model
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/odata/model/version.rb
CHANGED
data/lib/odata/model.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
1
5
|
require 'odata'
|
2
|
-
require 'odata/model/version'
|
3
6
|
|
4
|
-
require '
|
5
|
-
|
7
|
+
require 'odata/query/builder'
|
8
|
+
|
9
|
+
require 'odata/model/version'
|
10
|
+
require 'odata/model/active_model'
|
11
|
+
require 'odata/model/configuration'
|
12
|
+
require 'odata/model/attributes'
|
13
|
+
require 'odata/model/persistence'
|
14
|
+
require 'odata/model/query'
|
6
15
|
|
7
16
|
# OData is the parent namespace for the OData::Model project.
|
8
17
|
module OData
|
@@ -12,46 +21,10 @@ module OData
|
|
12
21
|
module Model
|
13
22
|
extend ActiveSupport::Concern
|
14
23
|
|
15
|
-
|
16
|
-
include
|
17
|
-
include
|
18
|
-
|
19
|
-
|
20
|
-
# ...
|
21
|
-
end
|
22
|
-
|
23
|
-
# Integrates ActiveModel's error handling capabilities
|
24
|
-
# @return [ActiveModel::Errors]
|
25
|
-
def errors
|
26
|
-
@errors ||= ActiveModel::Errors.new(self)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Returns whether the current instance has been persisted.
|
30
|
-
# @return [Boolean]
|
31
|
-
def persisted?
|
32
|
-
false
|
33
|
-
end
|
34
|
-
|
35
|
-
# Used for ActiveModel validations
|
36
|
-
# @api private
|
37
|
-
def read_attribute_for_validation(attr)
|
38
|
-
send(attr)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Methods integrated at the class level when OData::Model is included into
|
42
|
-
# a given class.
|
43
|
-
module ClassMethods
|
44
|
-
# Used for ActiveModel validations
|
45
|
-
# @api private
|
46
|
-
def human_attribute_name(attr, options = {})
|
47
|
-
attr
|
48
|
-
end
|
49
|
-
|
50
|
-
# Used for ActiveModel validations
|
51
|
-
# @api private
|
52
|
-
def lookup_ancestors
|
53
|
-
[self]
|
54
|
-
end
|
55
|
-
end
|
24
|
+
include OData::Model::ActiveModel
|
25
|
+
include OData::Model::Configuration
|
26
|
+
include OData::Model::Attributes
|
27
|
+
include OData::Model::Persistence
|
28
|
+
include OData::Model::Query
|
56
29
|
end
|
57
30
|
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module OData
|
2
|
+
class Query
|
3
|
+
class Builder
|
4
|
+
SUPPORTED_OPERATIONS = [:eq, :ne, :gt, :ge, :lt, :le, :not]
|
5
|
+
|
6
|
+
# Sets up a new Query Builder.
|
7
|
+
# @param entity_set [OData::EntitySet] the EntitySet to query
|
8
|
+
def initialize(model)
|
9
|
+
@entity_set = model.odata_entity_set
|
10
|
+
@property_map = model.property_map
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set the number of entities to skip for the final query.
|
14
|
+
# @param value [to_i,nil]
|
15
|
+
# @return [self]
|
16
|
+
def skip(value)
|
17
|
+
value = nil if value == 0
|
18
|
+
query_structure[:skip] = value.try(:to_i)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set the number of entities to return in the final query.
|
23
|
+
# @param value [to_i,nil]
|
24
|
+
# @return [self]
|
25
|
+
def limit(value)
|
26
|
+
value = nil if value == 0
|
27
|
+
query_structure[:top] = value.try(:to_i)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set the options for ordering the final query.
|
32
|
+
# @param args [Symbol,Hash]
|
33
|
+
# @return [self]
|
34
|
+
def order_by(*args)
|
35
|
+
validate_order_by_arguments(args)
|
36
|
+
query_structure[:orderby] += process_order_by_arguments(args)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the properties to select with the final query.
|
41
|
+
# @param args [Array<Symbol>]
|
42
|
+
# @return self
|
43
|
+
def select(*args)
|
44
|
+
args.each {|arg| query_structure[:select] << arg.to_sym}
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sets up a new filter condition on the provided property mapping.
|
49
|
+
# @param value [to_s]
|
50
|
+
# @return self
|
51
|
+
def where(value)
|
52
|
+
last_filter = query_structure[:filter].last
|
53
|
+
clause = {
|
54
|
+
property: lookup_property_name(value),
|
55
|
+
opeartion: nil,
|
56
|
+
argument: nil
|
57
|
+
}
|
58
|
+
|
59
|
+
if last_filter.is_a?(Array)
|
60
|
+
last_filter << clause
|
61
|
+
else
|
62
|
+
query_structure[:filter] << [clause]
|
63
|
+
end
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Sets the operation and argument for the last filter condition started
|
68
|
+
# by #where, #and or #or.
|
69
|
+
# @param value [Hash]
|
70
|
+
# @return self
|
71
|
+
def is(value)
|
72
|
+
value = value.first
|
73
|
+
validate_is_argument(value)
|
74
|
+
last_filter = query_structure[:filter].last.last
|
75
|
+
if last_filter[:operation].nil? && last_filter[:argument].nil?
|
76
|
+
last_filter[:operation] = value[0].to_sym
|
77
|
+
last_filter[:argument] = value[1]
|
78
|
+
else
|
79
|
+
query_structure[:filter].last << {
|
80
|
+
property: last_filter[:property],
|
81
|
+
operation: value[0].to_sym,
|
82
|
+
argument: value[1]
|
83
|
+
}
|
84
|
+
end
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
# Adds another filter to the last supplied clause.
|
89
|
+
# @param value [to_s]
|
90
|
+
# @return self
|
91
|
+
def and(value)
|
92
|
+
query_structure[:filter].last << {
|
93
|
+
property: lookup_property_name(value),
|
94
|
+
opeartion: nil,
|
95
|
+
argument: nil
|
96
|
+
}
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# Adds an alternate filter after the last supplied clause.
|
101
|
+
# @param value [to_s]
|
102
|
+
# @return self
|
103
|
+
def or(value)
|
104
|
+
query_structure[:filter] << [{
|
105
|
+
property: lookup_property_name(value),
|
106
|
+
opeartion: nil,
|
107
|
+
argument: nil
|
108
|
+
}]
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the current query structure as a valid query string.
|
113
|
+
def to_s
|
114
|
+
# TODO validate the actual query structure
|
115
|
+
|
116
|
+
query_array = []
|
117
|
+
query_array << filters_to_query
|
118
|
+
query_array << orderby_to_query
|
119
|
+
query_array << select_to_query
|
120
|
+
query_array << top_to_query
|
121
|
+
query_array << skip_to_query
|
122
|
+
query_array.compact!
|
123
|
+
|
124
|
+
query_string = query_array.join('&')
|
125
|
+
"#{entity_set.name}?#{query_string}"
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def query_structure
|
131
|
+
@query_structure ||= {
|
132
|
+
top: nil,
|
133
|
+
skip: nil,
|
134
|
+
orderby: [],
|
135
|
+
select: [],
|
136
|
+
filter: []
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def entity_set
|
141
|
+
@entity_set
|
142
|
+
end
|
143
|
+
|
144
|
+
def skip_to_query
|
145
|
+
return nil if query_structure[:skip].nil?
|
146
|
+
"$skip=#{query_structure[:skip]}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def top_to_query
|
150
|
+
return nil if query_structure[:top].nil?
|
151
|
+
"$top=#{query_structure[:top]}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def orderby_to_query
|
155
|
+
return nil if query_structure[:orderby].empty?
|
156
|
+
"$orderby=#{query_structure[:orderby].join(',')}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def select_to_query
|
160
|
+
return nil if query_structure[:select].empty?
|
161
|
+
"$select=#{query_structure[:select].join(',')}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def filters_to_query
|
165
|
+
str = "$filter="
|
166
|
+
clauses = []
|
167
|
+
query_structure[:filter].each do |clause|
|
168
|
+
clause_filters = []
|
169
|
+
clause.each do |filter|
|
170
|
+
clause_filters << "#{filter[:property]} #{filter[:operation]} #{filter[:argument]}"
|
171
|
+
end
|
172
|
+
clauses << clause_filters.join(' and ')
|
173
|
+
end
|
174
|
+
clauses.collect! {|clause| "(#{clause})"}
|
175
|
+
str << clauses.join(' or ')
|
176
|
+
str
|
177
|
+
end
|
178
|
+
|
179
|
+
def validate_order_by_arguments(args)
|
180
|
+
args.each do |arg|
|
181
|
+
unless arg.is_a?(Hash) || arg.is_a?(Symbol)
|
182
|
+
raise ArgumentError, 'must be a Hash or Symbol'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def validate_is_argument(value)
|
188
|
+
raise ArgumentError, 'argument must be a hash' unless value.size == 2
|
189
|
+
raise ArgumentError, "unsupported operation: #{value[0]}" unless SUPPORTED_OPERATIONS.include?(value[0].to_sym)
|
190
|
+
end
|
191
|
+
|
192
|
+
def process_order_by_arguments(args)
|
193
|
+
args.collect do |arg|
|
194
|
+
case arg
|
195
|
+
when is_a?(Symbol)
|
196
|
+
lookup_property_name(arg)
|
197
|
+
when is_a?(Hash)
|
198
|
+
arg.each do |key, value|
|
199
|
+
"#{lookup_property_name(key)} #{value}"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def lookup_property_name(mapping)
|
206
|
+
@property_map[mapping.to_s.to_sym]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
data/odata-model.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency 'codeclimate-test-reporter'
|
25
25
|
spec.add_development_dependency 'rspec', '~> 3.0.0'
|
26
26
|
|
27
|
-
spec.add_dependency 'odata', '~> 0.
|
27
|
+
spec.add_dependency 'odata', '~> 0.2.0'
|
28
28
|
spec.add_dependency 'activesupport', '>= 3.0.0'
|
29
29
|
spec.add_dependency 'activemodel', '>= 3.0.0'
|
30
30
|
end
|
@@ -1,13 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
class BareModel
|
4
|
-
include OData::Model
|
5
|
-
end
|
6
|
-
|
7
3
|
describe OData::Model do
|
8
|
-
let(:subject) {
|
4
|
+
let(:subject) { Product.new }
|
9
5
|
|
10
|
-
it { expect(
|
6
|
+
it { expect(Product).to respond_to(:model_name) }
|
11
7
|
|
12
8
|
it { expect(subject).to respond_to(:to_key) }
|
13
9
|
it { expect(subject).to respond_to(:to_param) }
|
@@ -46,7 +42,7 @@ describe OData::Model do
|
|
46
42
|
end
|
47
43
|
|
48
44
|
describe '.model_name' do
|
49
|
-
let(:model_name) {
|
45
|
+
let(:model_name) { Product.model_name }
|
50
46
|
|
51
47
|
it { expect(model_name).to respond_to(:to_str) }
|
52
48
|
it { expect(model_name.human).to respond_to(:to_str) }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Model do
|
4
|
+
describe 'property defaults' do
|
5
|
+
properties = %w{ID Name Description Rating Price ReleaseDate DiscontinuedDate}
|
6
|
+
attributes = properties.collect {|k| k.underscore.to_sym}
|
7
|
+
|
8
|
+
attributes.each do |attr_name|
|
9
|
+
it { expect(Product.new).to respond_to(attr_name) }
|
10
|
+
end
|
11
|
+
|
12
|
+
attributes.each do |attr_name|
|
13
|
+
it { expect(Product.new).to respond_to("#{attr_name}=") }
|
14
|
+
end
|
15
|
+
|
16
|
+
it { expect(Product).to respond_to(:attributes)}
|
17
|
+
it { expect(Product.attributes).to eq(attributes) }
|
18
|
+
|
19
|
+
it { expect(Product.new).to respond_to(:attributes) }
|
20
|
+
it { expect(Product.new.attributes).to eq(attributes) }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Model do
|
4
|
+
describe 'query interface' do
|
5
|
+
it { expect(Product).to respond_to(:[]) }
|
6
|
+
it { expect(Product).to respond_to(:find) }
|
7
|
+
|
8
|
+
describe 'Product[]' do
|
9
|
+
it 'finds a model instance' do
|
10
|
+
expect(Product[0]).to be_a(Product)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'Product.find' do
|
15
|
+
it 'returns an OData::Query::Builder' do
|
16
|
+
expect(Product.find).to be_a(OData::Query::Builder)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Model do
|
4
|
+
describe 'service integration' do
|
5
|
+
let(:service) { OData::ServiceRegistry['ODataDemo'] }
|
6
|
+
|
7
|
+
it { expect(Product).to respond_to(:odata_service) }
|
8
|
+
it { expect(Product.odata_service).to be_a(OData::Service) }
|
9
|
+
it { expect(Product.odata_service).to eq(service)}
|
10
|
+
|
11
|
+
it { expect(Product).to respond_to(:odata_namespace) }
|
12
|
+
it { expect(Product.odata_namespace).to eq('ODataDemo') }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Query::Builder do
|
4
|
+
let(:subject) { OData::Query::Builder.new(Product) }
|
5
|
+
|
6
|
+
describe 'filter method' do
|
7
|
+
describe '#where' do
|
8
|
+
it { expect(subject).to respond_to(:where) }
|
9
|
+
|
10
|
+
it { expect(subject.where(:name)).to be_a(OData::Query::Builder) }
|
11
|
+
it { expect(subject.where(:name)).to eq(subject) }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#is' do
|
15
|
+
let(:subject) { OData::Query::Builder.new(Product).where(:name) }
|
16
|
+
|
17
|
+
it { expect(subject).to respond_to(:is) }
|
18
|
+
|
19
|
+
it { expect(subject.is(eq: 'Bread')).to be_a(OData::Query::Builder) }
|
20
|
+
it { expect(subject.is(eq: 'Bread')).to eq(subject) }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#and' do
|
24
|
+
let(:subject) { OData::Query::Builder.new(Product).where(:name).is(eq: 'Bread') }
|
25
|
+
|
26
|
+
it { expect(subject).to respond_to(:and) }
|
27
|
+
|
28
|
+
it { expect(subject.and(:name)).to be_a(OData::Query::Builder) }
|
29
|
+
it { expect(subject.and(:name)).to eq(subject) }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#or' do
|
33
|
+
let(:subject) { OData::Query::Builder.new(Product).where(:name).is(eq: 'Bread') }
|
34
|
+
|
35
|
+
it { expect(subject).to respond_to(:or) }
|
36
|
+
|
37
|
+
it { expect(subject.or(:name)).to be_a(OData::Query::Builder) }
|
38
|
+
it { expect(subject.or(:name)).to eq(subject) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Query::Builder do
|
4
|
+
let(:subject) { OData::Query::Builder.new(Product) }
|
5
|
+
|
6
|
+
describe '#order_by' do
|
7
|
+
it { expect(subject).to respond_to(:order_by) }
|
8
|
+
|
9
|
+
describe 'accepts Symbols' do
|
10
|
+
it { expect(subject.order_by(:name)).to be_a(OData::Query::Builder) }
|
11
|
+
it { expect(subject.order_by(:name)).to eq(subject) }
|
12
|
+
|
13
|
+
it { expect(subject.order_by(:price, :rating)).to be_a(OData::Query::Builder) }
|
14
|
+
it { expect(subject.order_by(:price, :rating)).to eq(subject) }
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'accepts a Hash' do
|
18
|
+
it { expect(subject.order_by(name: :asc)).to be_a(OData::Query::Builder) }
|
19
|
+
it { expect(subject.order_by(name: :asc)).to eq(subject) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Query::Builder do
|
4
|
+
let(:subject) { OData::Query::Builder.new(Product) }
|
5
|
+
|
6
|
+
describe 'pagination method' do
|
7
|
+
describe '#skip' do
|
8
|
+
it { expect(subject).to respond_to(:skip) }
|
9
|
+
it { expect(subject.skip(5)).to be_a(OData::Query::Builder) }
|
10
|
+
it { expect(subject.skip(5)).to eq(subject) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#limit' do
|
14
|
+
it { expect(subject).to respond_to(:limit) }
|
15
|
+
it { expect(subject.limit(5)).to be_a(OData::Query::Builder) }
|
16
|
+
it { expect(subject.limit(5)).to eq(subject) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Query::Builder do
|
4
|
+
let(:subject) { OData::Query::Builder.new(Product) }
|
5
|
+
|
6
|
+
describe '#select' do
|
7
|
+
it { expect(subject).to respond_to(:select) }
|
8
|
+
|
9
|
+
it { expect(subject.select(:name, :description)).to be_a(OData::Query::Builder) }
|
10
|
+
it { expect(subject.select(:name, :description)).to eq(subject) }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe OData::Query::Builder do
|
4
|
+
let(:subject) { OData::Query::Builder.new(Product) }
|
5
|
+
|
6
|
+
describe '#to_s' do
|
7
|
+
it 'properly builds queries' do
|
8
|
+
query_string = "Products?$filter=(Name eq 'Bread')"
|
9
|
+
query = subject.where(:name).is(eq: "'Bread'")
|
10
|
+
expect(query.to_s).to eq(query_string)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'properly builds queries' do
|
14
|
+
query_string = "Products?$filter=(Name eq 'Bread') or (Name eq 'Milk')"
|
15
|
+
query = subject.where(:name).is(eq: "'Bread'").or(:name).is(eq: "'Milk'")
|
16
|
+
expect(query.to_s).to eq(query_string)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'properly builds queries' do
|
20
|
+
query_string = 'Products?$filter=(Price lt 15 and Rating gt 3)'
|
21
|
+
query = subject.where(:price).is(lt: 15).and(:rating).is(gt: 3)
|
22
|
+
expect(query.to_s).to eq(query_string)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
1
4
|
if ENV['CI']
|
2
5
|
require 'codeclimate-test-reporter'
|
3
6
|
CodeClimate::TestReporter.start
|
@@ -10,6 +13,14 @@ end
|
|
10
13
|
|
11
14
|
require 'odata/model'
|
12
15
|
|
16
|
+
# Suppressing a deprecation warning
|
17
|
+
I18n.enforce_available_locales = false
|
18
|
+
|
19
|
+
OData::Service.open('http://services.odata.org/OData/OData.svc')
|
20
|
+
|
21
|
+
require 'example_models/bare_model'
|
22
|
+
require 'example_models/odatademo_product'
|
23
|
+
|
13
24
|
RSpec::Matchers.define :be_boolean do
|
14
25
|
match do |actual|
|
15
26
|
expect(actual).to satisfy { |x| x == true || x == false }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: odata-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Thompson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: 0.2.0
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: 0.2.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: activesupport
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,9 +138,25 @@ files:
|
|
138
138
|
- README.md
|
139
139
|
- Rakefile
|
140
140
|
- lib/odata/model.rb
|
141
|
+
- lib/odata/model/active_model.rb
|
142
|
+
- lib/odata/model/attributes.rb
|
143
|
+
- lib/odata/model/configuration.rb
|
144
|
+
- lib/odata/model/persistence.rb
|
145
|
+
- lib/odata/model/query.rb
|
141
146
|
- lib/odata/model/version.rb
|
147
|
+
- lib/odata/query/builder.rb
|
142
148
|
- odata-model.gemspec
|
143
|
-
- spec/
|
149
|
+
- spec/example_models/bare_model.rb
|
150
|
+
- spec/example_models/odatademo_product.rb
|
151
|
+
- spec/model/active_model_lint_spec.rb
|
152
|
+
- spec/model/property_defaults_spec.rb
|
153
|
+
- spec/model/query_interface_spec.rb
|
154
|
+
- spec/model/service_integration_spec.rb
|
155
|
+
- spec/query/builder/filters_spec.rb
|
156
|
+
- spec/query/builder/order_by_spec.rb
|
157
|
+
- spec/query/builder/pagination_spec.rb
|
158
|
+
- spec/query/builder/select_spec.rb
|
159
|
+
- spec/query/builder_spec.rb
|
144
160
|
- spec/spec_helper.rb
|
145
161
|
homepage: https://github.com/ruby-odata/odata-model
|
146
162
|
licenses:
|
@@ -167,5 +183,15 @@ signing_key:
|
|
167
183
|
specification_version: 4
|
168
184
|
summary: An ActiveModel mapping layer for the OData gem
|
169
185
|
test_files:
|
170
|
-
- spec/
|
186
|
+
- spec/example_models/bare_model.rb
|
187
|
+
- spec/example_models/odatademo_product.rb
|
188
|
+
- spec/model/active_model_lint_spec.rb
|
189
|
+
- spec/model/property_defaults_spec.rb
|
190
|
+
- spec/model/query_interface_spec.rb
|
191
|
+
- spec/model/service_integration_spec.rb
|
192
|
+
- spec/query/builder/filters_spec.rb
|
193
|
+
- spec/query/builder/order_by_spec.rb
|
194
|
+
- spec/query/builder/pagination_spec.rb
|
195
|
+
- spec/query/builder/select_spec.rb
|
196
|
+
- spec/query/builder_spec.rb
|
171
197
|
- spec/spec_helper.rb
|