odata-model 0.0.1 → 0.0.2
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.
- 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
|