frodata 0.9.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.
- checksums.yaml +7 -0
- data/.autotest +2 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +75 -0
- data/CHANGELOG.md +150 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +427 -0
- data/Rakefile +7 -0
- data/TODO.md +55 -0
- data/frodata.gemspec +34 -0
- data/lib/frodata.rb +36 -0
- data/lib/frodata/entity.rb +332 -0
- data/lib/frodata/entity_container.rb +75 -0
- data/lib/frodata/entity_set.rb +161 -0
- data/lib/frodata/errors.rb +68 -0
- data/lib/frodata/navigation_property.rb +29 -0
- data/lib/frodata/navigation_property/proxy.rb +80 -0
- data/lib/frodata/properties.rb +32 -0
- data/lib/frodata/properties/binary.rb +50 -0
- data/lib/frodata/properties/boolean.rb +37 -0
- data/lib/frodata/properties/collection.rb +50 -0
- data/lib/frodata/properties/complex.rb +114 -0
- data/lib/frodata/properties/date.rb +27 -0
- data/lib/frodata/properties/date_time.rb +83 -0
- data/lib/frodata/properties/date_time_offset.rb +17 -0
- data/lib/frodata/properties/decimal.rb +50 -0
- data/lib/frodata/properties/enum.rb +62 -0
- data/lib/frodata/properties/float.rb +67 -0
- data/lib/frodata/properties/geography.rb +13 -0
- data/lib/frodata/properties/geography/base.rb +162 -0
- data/lib/frodata/properties/geography/line_string.rb +33 -0
- data/lib/frodata/properties/geography/point.rb +31 -0
- data/lib/frodata/properties/geography/polygon.rb +38 -0
- data/lib/frodata/properties/guid.rb +17 -0
- data/lib/frodata/properties/integer.rb +107 -0
- data/lib/frodata/properties/number.rb +14 -0
- data/lib/frodata/properties/string.rb +72 -0
- data/lib/frodata/properties/time.rb +40 -0
- data/lib/frodata/properties/time_of_day.rb +27 -0
- data/lib/frodata/property.rb +139 -0
- data/lib/frodata/property_registry.rb +41 -0
- data/lib/frodata/query.rb +233 -0
- data/lib/frodata/query/criteria.rb +92 -0
- data/lib/frodata/query/criteria/comparison_operators.rb +49 -0
- data/lib/frodata/query/criteria/date_functions.rb +61 -0
- data/lib/frodata/query/criteria/geography_functions.rb +21 -0
- data/lib/frodata/query/criteria/lambda_operators.rb +27 -0
- data/lib/frodata/query/criteria/string_functions.rb +40 -0
- data/lib/frodata/query/in_batches.rb +58 -0
- data/lib/frodata/railtie.rb +19 -0
- data/lib/frodata/schema.rb +155 -0
- data/lib/frodata/schema/complex_type.rb +79 -0
- data/lib/frodata/schema/enum_type.rb +95 -0
- data/lib/frodata/service.rb +254 -0
- data/lib/frodata/service/request.rb +85 -0
- data/lib/frodata/service/response.rb +162 -0
- data/lib/frodata/service/response/atom.rb +40 -0
- data/lib/frodata/service/response/json.rb +41 -0
- data/lib/frodata/service/response/plain.rb +36 -0
- data/lib/frodata/service/response/xml.rb +40 -0
- data/lib/frodata/service_registry.rb +52 -0
- data/lib/frodata/version.rb +3 -0
- data/spec/fixtures/files/entity_to_xml.xml +17 -0
- data/spec/fixtures/files/error.xml +5 -0
- data/spec/fixtures/files/metadata.xml +150 -0
- data/spec/fixtures/files/product_0.json +10 -0
- data/spec/fixtures/files/product_0.xml +28 -0
- data/spec/fixtures/files/products.json +106 -0
- data/spec/fixtures/files/products.xml +308 -0
- data/spec/fixtures/files/supplier_0.json +26 -0
- data/spec/fixtures/files/supplier_0.xml +32 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs.yml +1635 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/bad_entry.yml +183 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/existing_entry.yml +256 -0
- data/spec/fixtures/vcr_cassettes/entity_set_specs/new_entry.yml +185 -0
- data/spec/fixtures/vcr_cassettes/entity_specs.yml +285 -0
- data/spec/fixtures/vcr_cassettes/navigation_property_proxy_specs.yml +346 -0
- data/spec/fixtures/vcr_cassettes/query/result_specs.yml +189 -0
- data/spec/fixtures/vcr_cassettes/query_specs.yml +1060 -0
- data/spec/fixtures/vcr_cassettes/schema/complex_type_specs.yml +127 -0
- data/spec/fixtures/vcr_cassettes/service/request_specs.yml +193 -0
- data/spec/fixtures/vcr_cassettes/service_registry_specs.yml +129 -0
- data/spec/fixtures/vcr_cassettes/service_specs.yml +127 -0
- data/spec/fixtures/vcr_cassettes/usage_example_specs.yml +1330 -0
- data/spec/frodata/entity/shared_examples.rb +82 -0
- data/spec/frodata/entity_container_spec.rb +38 -0
- data/spec/frodata/entity_set_spec.rb +168 -0
- data/spec/frodata/entity_spec.rb +151 -0
- data/spec/frodata/errors_spec.rb +48 -0
- data/spec/frodata/navigation_property/proxy_spec.rb +44 -0
- data/spec/frodata/navigation_property_spec.rb +55 -0
- data/spec/frodata/properties/binary_spec.rb +50 -0
- data/spec/frodata/properties/boolean_spec.rb +72 -0
- data/spec/frodata/properties/collection_spec.rb +44 -0
- data/spec/frodata/properties/date_spec.rb +23 -0
- data/spec/frodata/properties/date_time_offset_spec.rb +30 -0
- data/spec/frodata/properties/date_time_spec.rb +23 -0
- data/spec/frodata/properties/decimal_spec.rb +51 -0
- data/spec/frodata/properties/float_spec.rb +45 -0
- data/spec/frodata/properties/geography/line_string_spec.rb +33 -0
- data/spec/frodata/properties/geography/point_spec.rb +29 -0
- data/spec/frodata/properties/geography/polygon_spec.rb +55 -0
- data/spec/frodata/properties/geography/shared_examples.rb +72 -0
- data/spec/frodata/properties/guid_spec.rb +17 -0
- data/spec/frodata/properties/integer_spec.rb +58 -0
- data/spec/frodata/properties/string_spec.rb +46 -0
- data/spec/frodata/properties/time_of_day_spec.rb +23 -0
- data/spec/frodata/properties/time_spec.rb +15 -0
- data/spec/frodata/property_registry_spec.rb +16 -0
- data/spec/frodata/property_spec.rb +71 -0
- data/spec/frodata/query/criteria_spec.rb +229 -0
- data/spec/frodata/query_spec.rb +199 -0
- data/spec/frodata/schema/complex_type_spec.rb +96 -0
- data/spec/frodata/schema/enum_type_spec.rb +112 -0
- data/spec/frodata/schema_spec.rb +97 -0
- data/spec/frodata/service/request_spec.rb +49 -0
- data/spec/frodata/service/response_spec.rb +85 -0
- data/spec/frodata/service_registry_spec.rb +18 -0
- data/spec/frodata/service_spec.rb +191 -0
- data/spec/frodata/usage_example_spec.rb +188 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/coverage.rb +2 -0
- data/spec/support/vcr.rb +9 -0
- metadata +401 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines common behavior for FrOData numeric types.
|
|
4
|
+
module Number
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def validate(value)
|
|
8
|
+
if value > max_value || value < min_value
|
|
9
|
+
validation_error "Value is outside accepted range: #{min_value} to #{max_value}"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the String FrOData type.
|
|
4
|
+
class String < FrOData::Property
|
|
5
|
+
# Returns the property value, properly typecast
|
|
6
|
+
# @return [String,nil]
|
|
7
|
+
def value
|
|
8
|
+
if (@value.nil? || @value.empty?) && allows_nil?
|
|
9
|
+
nil
|
|
10
|
+
else
|
|
11
|
+
encode_value(@value)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Sets the property value
|
|
16
|
+
# @params new_value [to_s,nil]
|
|
17
|
+
def value=(new_value)
|
|
18
|
+
validate(new_value)
|
|
19
|
+
@value = new_value.nil? ? nil : encode_value(new_value.to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Value to be used in URLs.
|
|
23
|
+
# @return [String]
|
|
24
|
+
def url_value
|
|
25
|
+
"'#{value}'"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The FrOData type name
|
|
29
|
+
def type
|
|
30
|
+
'Edm.String'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Is the property value Unicode encoded
|
|
34
|
+
def is_unicode?
|
|
35
|
+
options[:unicode]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Does the property have a default value
|
|
39
|
+
def has_default_value?
|
|
40
|
+
not(options[:default_value].nil?)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The default value for the property
|
|
44
|
+
def default_value
|
|
45
|
+
options[:default_value]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def default_options
|
|
51
|
+
super.merge({
|
|
52
|
+
unicode: true,
|
|
53
|
+
default_value: nil
|
|
54
|
+
})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def validate(new_value)
|
|
58
|
+
if new_value.nil? && !allows_nil?
|
|
59
|
+
validation_error 'This property does not allow for nil values to be set'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def encode_value(new_value)
|
|
64
|
+
if options[:unicode]
|
|
65
|
+
new_value.encode(Encoding::UTF_8)
|
|
66
|
+
else
|
|
67
|
+
new_value.encode(Encoding::ASCII)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
module Properties
|
|
3
|
+
# Defines the Time FrOData type.
|
|
4
|
+
class Time < FrOData::Property
|
|
5
|
+
# Returns the property value, properly typecast
|
|
6
|
+
# @return [Time,nil]
|
|
7
|
+
def value
|
|
8
|
+
if (@value.nil? || @value.empty?) && allows_nil?
|
|
9
|
+
nil
|
|
10
|
+
else
|
|
11
|
+
::Time.strptime(@value, '%H:%M:%S%:z')
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Sets the property value
|
|
16
|
+
# @params new_value [Time]
|
|
17
|
+
def value=(new_value)
|
|
18
|
+
validate(new_value)
|
|
19
|
+
@value = parse_value(new_value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The FrOData type name
|
|
23
|
+
def type
|
|
24
|
+
'Edm.Time'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def validate(value)
|
|
30
|
+
unless value.is_a?(::Time)
|
|
31
|
+
validation_error 'Value is not a time object'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_value(value)
|
|
36
|
+
value.strftime('%H:%M:%S%:z')
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'frodata/properties/date_time'
|
|
2
|
+
|
|
3
|
+
module FrOData
|
|
4
|
+
module Properties
|
|
5
|
+
# Defines the Date FrOData type.
|
|
6
|
+
class TimeOfDay < FrOData::Properties::DateTime
|
|
7
|
+
# The FrOData type name
|
|
8
|
+
def type
|
|
9
|
+
'Edm.TimeOfDay'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def url_value
|
|
13
|
+
@value
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def date_class
|
|
19
|
+
::Time
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def strptime_format
|
|
23
|
+
'%H:%M:%S.%L'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module FrOData
|
|
2
|
+
# FrOData::Property represents an abstract property, defining the basic
|
|
3
|
+
# interface and required methods, with some default implementations. All
|
|
4
|
+
# supported property types should be implemented under the FrOData::Properties
|
|
5
|
+
# namespace.
|
|
6
|
+
class Property
|
|
7
|
+
# The property's name
|
|
8
|
+
attr_reader :name
|
|
9
|
+
# The property's value
|
|
10
|
+
attr_accessor :value
|
|
11
|
+
# The property's options
|
|
12
|
+
attr_reader :options
|
|
13
|
+
|
|
14
|
+
# Default intialization for a property with a name, value and options.
|
|
15
|
+
# @param name [to_s]
|
|
16
|
+
# @param value [to_s,nil]
|
|
17
|
+
# @param options [Hash]
|
|
18
|
+
def initialize(name, value, options = {})
|
|
19
|
+
@name = name.to_s
|
|
20
|
+
@value = value.nil? ? nil : value.to_s
|
|
21
|
+
@options = default_options.merge(options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Abstract implementation, should return property type, based on
|
|
25
|
+
# FrOData::Service metadata in proper implementation.
|
|
26
|
+
# @raise NotImplementedError
|
|
27
|
+
def type
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Provides for value-based equality checking.
|
|
32
|
+
# @param other [value] object for comparison
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def ==(other)
|
|
35
|
+
self.value == other.value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Whether the property permits a nil value.
|
|
39
|
+
# (Default=true)
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def allows_nil?
|
|
42
|
+
options[:allows_nil]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Whether the property uses strict validation.
|
|
46
|
+
# (Default=false)
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
def strict?
|
|
49
|
+
if options.key? :strict
|
|
50
|
+
options[:strict]
|
|
51
|
+
elsif service
|
|
52
|
+
service.options[:strict]
|
|
53
|
+
else
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The configured concurrency mode for the property.
|
|
59
|
+
# @return [String]
|
|
60
|
+
def concurrency_mode
|
|
61
|
+
@concurrency_mode ||= options[:concurrency_mode]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Value to be used in XML.
|
|
65
|
+
# @return [String]
|
|
66
|
+
def xml_value
|
|
67
|
+
@value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Value to be used in JSON.
|
|
71
|
+
# @return [*]
|
|
72
|
+
def json_value
|
|
73
|
+
value
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Value to be used in URLs.
|
|
77
|
+
# @return [String]
|
|
78
|
+
def url_value
|
|
79
|
+
@value
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the XML representation of the property to the supplied XML
|
|
83
|
+
# builder.
|
|
84
|
+
# @param xml_builder [Nokogiri::XML::Builder]
|
|
85
|
+
def to_xml(xml_builder)
|
|
86
|
+
attributes = {
|
|
87
|
+
'metadata:type' => type,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if value.nil?
|
|
91
|
+
attributes['metadata:null'] = 'true'
|
|
92
|
+
xml_builder['data'].send(name.to_sym, attributes)
|
|
93
|
+
else
|
|
94
|
+
xml_builder['data'].send(name.to_sym, attributes, xml_value)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Creates a new property instance from an XML element
|
|
99
|
+
# @param property_xml [Nokogiri::XML::Element]
|
|
100
|
+
# @param options [Hash]
|
|
101
|
+
# @return [FrOData::Property]
|
|
102
|
+
def self.from_xml(property_xml, options = {})
|
|
103
|
+
if property_xml.attributes['null'].andand.value == 'true'
|
|
104
|
+
content = nil
|
|
105
|
+
else
|
|
106
|
+
content = property_xml.content
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
new(property_xml.name, content, options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
protected
|
|
113
|
+
|
|
114
|
+
def default_options
|
|
115
|
+
{
|
|
116
|
+
allows_nil: true,
|
|
117
|
+
concurrency_mode: :none
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def service
|
|
122
|
+
options[:service]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def logger
|
|
126
|
+
# Use a dummy logger if service is not available (-> unit tests)
|
|
127
|
+
@logger ||= service.andand.logger || Logger.new('/dev/null')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def validation_error(message)
|
|
131
|
+
if strict?
|
|
132
|
+
raise ArgumentError, "#{name}: #{message}"
|
|
133
|
+
else
|
|
134
|
+
logger.warn "#{name}: #{message}"
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
|
|
3
|
+
module FrOData
|
|
4
|
+
# Provides a registry for keeping track of various property types used by
|
|
5
|
+
# FrOData.
|
|
6
|
+
class PropertyRegistry
|
|
7
|
+
include Singleton
|
|
8
|
+
|
|
9
|
+
# Add a property type to the registry
|
|
10
|
+
#
|
|
11
|
+
# @param type_name [String] property type name to register
|
|
12
|
+
# @param klass [Class] Ruby class to use for the specified type
|
|
13
|
+
def add(type_name, klass)
|
|
14
|
+
properties[type_name] = klass
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Lookup a property by name and get the Ruby class to use for its instances
|
|
18
|
+
#
|
|
19
|
+
# @param type_name [String] the type name to lookup
|
|
20
|
+
# @return [Class, nil] the proper class or nil
|
|
21
|
+
def [](type_name)
|
|
22
|
+
properties[type_name]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# (see #add)
|
|
26
|
+
def self.add(type_name, klass)
|
|
27
|
+
FrOData::PropertyRegistry.instance.add(type_name, klass)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# (see #[])
|
|
31
|
+
def self.[](type_name)
|
|
32
|
+
FrOData::PropertyRegistry.instance[type_name]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def properties
|
|
38
|
+
@properties ||= {}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
require 'frodata/query/criteria'
|
|
2
|
+
require 'frodata/query/in_batches'
|
|
3
|
+
|
|
4
|
+
module FrOData
|
|
5
|
+
# FrOData::Query provides the query interface for requesting Entities matching
|
|
6
|
+
# specific criteria from an FrOData::EntitySet. This class should not be
|
|
7
|
+
# instantiated directly, but can be. Normally you will access a Query by
|
|
8
|
+
# first asking for one from the FrOData::EntitySet you want to query.
|
|
9
|
+
class Query
|
|
10
|
+
attr_reader :options
|
|
11
|
+
|
|
12
|
+
include InBatches
|
|
13
|
+
|
|
14
|
+
# Create a new Query for the provided EntitySet
|
|
15
|
+
# @param entity_set [FrOData::EntitySet]
|
|
16
|
+
# @param options [Hash] Query options
|
|
17
|
+
def initialize(entity_set, options = {})
|
|
18
|
+
@entity_set = entity_set
|
|
19
|
+
@options = options
|
|
20
|
+
setup_empty_criteria_set
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Instantiates an FrOData::Query::Criteria for the named property.
|
|
24
|
+
# @param property [to_s]
|
|
25
|
+
def [](property)
|
|
26
|
+
property_instance = @entity_set.new_entity.get_property(property)
|
|
27
|
+
property_instance = property if property_instance.nil?
|
|
28
|
+
FrOData::Query::Criteria.new(property: property_instance)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Find the Entity with the supplied key value.
|
|
32
|
+
# @param key [to_s] primary key to lookup
|
|
33
|
+
# @return [FrOData::Entity,nil]
|
|
34
|
+
def find(key)
|
|
35
|
+
entity = @entity_set.new_entity
|
|
36
|
+
key_property = entity.get_property(entity.primary_key)
|
|
37
|
+
key_property.value = key
|
|
38
|
+
|
|
39
|
+
pathname = "#{entity_set.name}(#{key_property.url_value})"
|
|
40
|
+
query = [pathname, assemble_criteria].compact.join('?')
|
|
41
|
+
execute(query).first
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Adds a filter criteria to the query.
|
|
45
|
+
# For filter syntax see https://msdn.microsoft.com/en-us/library/gg309461.aspx
|
|
46
|
+
# Syntax:
|
|
47
|
+
# Property Operator Value
|
|
48
|
+
#
|
|
49
|
+
# For example:
|
|
50
|
+
# Name eq 'Customer Service'
|
|
51
|
+
#
|
|
52
|
+
# Operators:
|
|
53
|
+
# eq, ne, gt, ge, lt, le, and, or, not
|
|
54
|
+
#
|
|
55
|
+
# Value
|
|
56
|
+
# can be 'null', can use single quotes
|
|
57
|
+
# @param criteria
|
|
58
|
+
def where(criteria)
|
|
59
|
+
criteria_set[:filter] << criteria
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Adds a fulltext search term to the query
|
|
64
|
+
# NOTE: May not be implemented by the service
|
|
65
|
+
# @param term [String]
|
|
66
|
+
def search(term)
|
|
67
|
+
criteria_set[:search] << term
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Adds a filter criteria to the query with 'and' logical operator.
|
|
72
|
+
# @param criteria
|
|
73
|
+
#def and(criteria)
|
|
74
|
+
#
|
|
75
|
+
#end
|
|
76
|
+
|
|
77
|
+
# Adds a filter criteria to the query with 'or' logical operator.
|
|
78
|
+
# @param criteria
|
|
79
|
+
#def or(criteria)
|
|
80
|
+
#
|
|
81
|
+
#end
|
|
82
|
+
|
|
83
|
+
# Specify properties to order the result by.
|
|
84
|
+
# Can use 'desc' like 'Name desc'
|
|
85
|
+
# @param properties [Array<Symbol>]
|
|
86
|
+
# @return [self]
|
|
87
|
+
def order_by(*properties)
|
|
88
|
+
criteria_set[:orderby] += properties
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Specify associations to expand in the result.
|
|
93
|
+
# @param associations [Array<Symbol>]
|
|
94
|
+
# @return [self]
|
|
95
|
+
def expand(*associations)
|
|
96
|
+
criteria_set[:expand] += associations
|
|
97
|
+
self
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Specify properties to select within the result.
|
|
101
|
+
# @param properties [Array<Symbol>]
|
|
102
|
+
# @return [self]
|
|
103
|
+
def select(*properties)
|
|
104
|
+
criteria_set[:select] += properties
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Add skip criteria to query.
|
|
109
|
+
# @param value [to_i]
|
|
110
|
+
# @return [self]
|
|
111
|
+
def skip(value)
|
|
112
|
+
criteria_set[:skip] = value.to_i
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Add limit criteria to query.
|
|
117
|
+
# @param value [to_i]
|
|
118
|
+
# @return [self]
|
|
119
|
+
def limit(value)
|
|
120
|
+
criteria_set[:top] = value.to_i
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Add inline count criteria to query.
|
|
125
|
+
# Not Supported in CRM2011
|
|
126
|
+
# @return [self]
|
|
127
|
+
def include_count
|
|
128
|
+
criteria_set[:inline_count] = true
|
|
129
|
+
self
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Convert Query to string.
|
|
133
|
+
# @return [String]
|
|
134
|
+
def to_s
|
|
135
|
+
[entity_set.name, assemble_criteria].compact.join('?')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Execute the query.
|
|
139
|
+
# @return [FrOData::Service::Response]
|
|
140
|
+
def execute(url_chunk = self.to_s)
|
|
141
|
+
service.execute(url_chunk, options.merge(query: self))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Executes the query to get a count of entities.
|
|
145
|
+
# @return [Integer]
|
|
146
|
+
def count
|
|
147
|
+
url_chunk = ["#{entity_set.name}/$count", assemble_criteria].compact.join('?')
|
|
148
|
+
response = self.execute(url_chunk)
|
|
149
|
+
# Some servers (*cough* Microsoft *cough*) seem to
|
|
150
|
+
# return extraneous characters in the response.
|
|
151
|
+
response.body.scan(/\d+/).first.to_i
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Checks whether a query will return any results by calling #count
|
|
155
|
+
# @return [Boolean]
|
|
156
|
+
def empty?
|
|
157
|
+
self.count == 0
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# The EntitySet for this query.
|
|
161
|
+
# @return [FrOData::EntitySet]
|
|
162
|
+
# @api private
|
|
163
|
+
def entity_set
|
|
164
|
+
@entity_set
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The service for this query
|
|
168
|
+
# @return [FrOData::Service]
|
|
169
|
+
# @api private
|
|
170
|
+
def service
|
|
171
|
+
@service ||= entity_set.service
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def criteria_set
|
|
177
|
+
@criteria_set
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def setup_empty_criteria_set
|
|
181
|
+
@criteria_set = {
|
|
182
|
+
filter: [],
|
|
183
|
+
search: [],
|
|
184
|
+
select: [],
|
|
185
|
+
expand: [],
|
|
186
|
+
orderby: [],
|
|
187
|
+
skip: 0,
|
|
188
|
+
top: 0,
|
|
189
|
+
inline_count: false
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def assemble_criteria
|
|
194
|
+
criteria = [
|
|
195
|
+
filter_criteria,
|
|
196
|
+
search_criteria,
|
|
197
|
+
list_criteria(:orderby),
|
|
198
|
+
list_criteria(:expand),
|
|
199
|
+
list_criteria(:select),
|
|
200
|
+
inline_count_criteria,
|
|
201
|
+
paging_criteria(:skip),
|
|
202
|
+
paging_criteria(:top)
|
|
203
|
+
].compact!
|
|
204
|
+
|
|
205
|
+
criteria.empty? ? nil : criteria.join('&')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def filter_criteria
|
|
209
|
+
return nil if criteria_set[:filter].empty?
|
|
210
|
+
filters = criteria_set[:filter].collect(&:to_s)
|
|
211
|
+
"$filter=#{filters.join(' and ')}"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def search_criteria
|
|
215
|
+
return nil if criteria_set[:search].empty?
|
|
216
|
+
filters = criteria_set[:search].collect(&:to_s)
|
|
217
|
+
"$search=#{filters.join(' AND ')}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def list_criteria(name)
|
|
221
|
+
criteria_set[name].empty? ? nil : "$#{name}=#{criteria_set[name].join(',')}"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# inlinecount not supported by Microsoft CRM 2011
|
|
225
|
+
def inline_count_criteria
|
|
226
|
+
criteria_set[:inline_count] ? '$count=true' : nil
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def paging_criteria(name)
|
|
230
|
+
criteria_set[name] == 0 ? nil : "$#{name}=#{criteria_set[name]}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|