dynamics_crm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +9 -0
- data/dynamics_crm.gemspec +28 -0
- data/lib/dynamics_crm/client.rb +295 -0
- data/lib/dynamics_crm/metadata/attribute_metadata.rb +42 -0
- data/lib/dynamics_crm/metadata/entity_metadata.rb +24 -0
- data/lib/dynamics_crm/metadata/retrieve_all_entities_response.rb +21 -0
- data/lib/dynamics_crm/metadata/retrieve_attribute_response.rb +16 -0
- data/lib/dynamics_crm/metadata/retrieve_entity_response.rb +17 -0
- data/lib/dynamics_crm/metadata/xml_document.rb +39 -0
- data/lib/dynamics_crm/response/create_result.rb +15 -0
- data/lib/dynamics_crm/response/execute_result.rb +26 -0
- data/lib/dynamics_crm/response/result.rb +78 -0
- data/lib/dynamics_crm/response/retrieve_multiple_result.rb +38 -0
- data/lib/dynamics_crm/response/retrieve_result.rb +20 -0
- data/lib/dynamics_crm/version.rb +3 -0
- data/lib/dynamics_crm/xml/attributes.rb +163 -0
- data/lib/dynamics_crm/xml/column_set.rb +34 -0
- data/lib/dynamics_crm/xml/criteria.rb +54 -0
- data/lib/dynamics_crm/xml/entity.rb +61 -0
- data/lib/dynamics_crm/xml/entity_reference.rb +56 -0
- data/lib/dynamics_crm/xml/fault.rb +42 -0
- data/lib/dynamics_crm/xml/fetch_expression.rb +27 -0
- data/lib/dynamics_crm/xml/message_builder.rb +222 -0
- data/lib/dynamics_crm/xml/message_parser.rb +68 -0
- data/lib/dynamics_crm/xml/orders.rb +36 -0
- data/lib/dynamics_crm/xml/page_info.rb +38 -0
- data/lib/dynamics_crm/xml/query.rb +38 -0
- data/lib/dynamics_crm.rb +46 -0
- data/spec/fixtures/associate_response.xml +17 -0
- data/spec/fixtures/create_response.xml +19 -0
- data/spec/fixtures/delete_response.xml +16 -0
- data/spec/fixtures/disassociate_response.xml +17 -0
- data/spec/fixtures/fetch_xml_response.xml +120 -0
- data/spec/fixtures/receiver_fault.xml +27 -0
- data/spec/fixtures/request_security_token_response.xml +62 -0
- data/spec/fixtures/retrieve_account_all_columns.xml +402 -0
- data/spec/fixtures/retrieve_all_entities.xml +614 -0
- data/spec/fixtures/retrieve_attribute_identifier_response.xml +117 -0
- data/spec/fixtures/retrieve_attribute_picklist_response.xml +1097 -0
- data/spec/fixtures/retrieve_attribute_response.xml +126 -0
- data/spec/fixtures/retrieve_entity_response.xml +671 -0
- data/spec/fixtures/retrieve_multiple_result.xml +67 -0
- data/spec/fixtures/sender_fault.xml +34 -0
- data/spec/fixtures/update_response.xml +16 -0
- data/spec/fixtures/who_am_i_result.xml +35 -0
- data/spec/lib/client_spec.rb +230 -0
- data/spec/lib/metadata/entity_metadata_spec.rb +24 -0
- data/spec/lib/metadata/retrieve_all_entities_response_spec.rb +26 -0
- data/spec/lib/metadata/retrieve_attribute_response_spec.rb +65 -0
- data/spec/lib/metadata/retrieve_entity_response_spec.rb +24 -0
- data/spec/lib/response/execute_result_spec.rb +20 -0
- data/spec/lib/response/retrieve_multiple_spec.rb +34 -0
- data/spec/lib/response/retrieve_result_spec.rb +67 -0
- data/spec/lib/xml/attributes_spec.rb +39 -0
- data/spec/lib/xml/column_set_spec.rb +19 -0
- data/spec/lib/xml/entity_reference_spec.rb +47 -0
- data/spec/lib/xml/entity_spec.rb +63 -0
- data/spec/lib/xml/fault_spec.rb +41 -0
- data/spec/lib/xml/query_spec.rb +43 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixture_helpers.rb +14 -0
- metadata +240 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module Response
|
3
|
+
# Base response class for all Execute requests.
|
4
|
+
# Pulls out the ResponseName and parses the Results element of key/value pairs.
|
5
|
+
class ExecuteResult < Result
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
# Returns base element of the response document to parse.
|
10
|
+
def response_element
|
11
|
+
class_name = 'ExecuteResult' if self.is_a?(ExecuteResult)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Invoked by Result constructor
|
15
|
+
def parse_result_response(result)
|
16
|
+
h = {}
|
17
|
+
h["ResponseName"] = result.elements["b:ResponseName"].text
|
18
|
+
|
19
|
+
attributes = XML::MessageParser.parse_key_value_pairs(result.elements["b:Results"])
|
20
|
+
h.merge(attributes)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module Response
|
3
|
+
|
4
|
+
class Result < Hash
|
5
|
+
|
6
|
+
attr_reader :document
|
7
|
+
attr_reader :result_response
|
8
|
+
|
9
|
+
def initialize(xml)
|
10
|
+
|
11
|
+
@document = REXML::Document.new(xml)
|
12
|
+
|
13
|
+
fault_xml = @document.get_elements("//[local-name() = 'Fault']")
|
14
|
+
raise XML::Fault.new(fault_xml) if fault_xml.any?
|
15
|
+
|
16
|
+
@result_response = @document.get_elements("//#{response_element}").first
|
17
|
+
|
18
|
+
# Child classes should override this method.
|
19
|
+
h = parse_result_response(@result_response)
|
20
|
+
|
21
|
+
# Calling super causes undesired behavior so just merge.
|
22
|
+
self.merge!(h)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns base element of the response document to parse.
|
26
|
+
def response_element
|
27
|
+
class_name = self.class.to_s.split("::").last
|
28
|
+
end
|
29
|
+
|
30
|
+
# Invoked by constructor, should be implemented in sub-classes.
|
31
|
+
def parse_result_response(result)
|
32
|
+
# do nothing here
|
33
|
+
{}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Allows method-like access to the hash using camelcase field names.
|
37
|
+
def method_missing(method, *args, &block)
|
38
|
+
|
39
|
+
# First return local hash entry for symbol or string.
|
40
|
+
return self[method] if self.has_key?(method)
|
41
|
+
|
42
|
+
string_method = method.to_s
|
43
|
+
return self[string_method] if self.has_key?(string_method)
|
44
|
+
|
45
|
+
value = nil
|
46
|
+
# Then check if string converted to underscore finds a match.
|
47
|
+
if string_method =~ /[A-Z+]/
|
48
|
+
string_method = ::DynamicsCRM::StringUtil.underscore(string_method)
|
49
|
+
value = self[string_method] || self[string_method.to_sym]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Finally return nil.
|
53
|
+
return value
|
54
|
+
end
|
55
|
+
|
56
|
+
def respond_to_missing?(method_name, include_private = false)
|
57
|
+
self.has_key?(method_name.to_s) || self.has_key?(method_name) || super
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
# There's nothing to parse in the UpdateResult
|
63
|
+
class UpdateResponse < Result
|
64
|
+
end
|
65
|
+
|
66
|
+
# There's nothing to parse in the DeleteResult
|
67
|
+
class DeleteResponse < Result
|
68
|
+
end
|
69
|
+
|
70
|
+
# There's nothing to parse in the Associate/DisassociateResponse
|
71
|
+
class AssociateResponse < Result
|
72
|
+
end
|
73
|
+
|
74
|
+
class DisassociateResponse < Result
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module Response
|
3
|
+
# Retrieve Multiple returns a list of Entities.
|
4
|
+
class RetrieveMultipleResult < Result
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Invoked by Result constructor
|
9
|
+
def parse_result_response(result)
|
10
|
+
|
11
|
+
h = {}
|
12
|
+
result.elements.each do |el|
|
13
|
+
next if el.name == "Entities"
|
14
|
+
|
15
|
+
# Convert text to actual data types.
|
16
|
+
value = el.text
|
17
|
+
if value == "true" || value == "false"
|
18
|
+
value = (value == "true")
|
19
|
+
elsif value =~ /^[-?]\d+$/
|
20
|
+
value = value.to_i
|
21
|
+
elsif value =~ /^[-?]\d+\.\d+$/
|
22
|
+
value = value.to_f
|
23
|
+
end
|
24
|
+
h[el.name] = value
|
25
|
+
end
|
26
|
+
|
27
|
+
h[:entities] = []
|
28
|
+
result.elements["b:Entities"].elements.each do |entity_xml|
|
29
|
+
h[:entities] << XML::Entity.from_xml(entity_xml)
|
30
|
+
end
|
31
|
+
|
32
|
+
h
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
# RetrieveMultipleResult
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module Response
|
3
|
+
class RetrieveResult < Result
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
# Invoked by Result constructor
|
8
|
+
def parse_result_response(result)
|
9
|
+
h = {}
|
10
|
+
h["LogicalName"] = h["type"] = result.elements["b:LogicalName"].text
|
11
|
+
h["Id"] = h["id"] = result.elements["b:Id"].text
|
12
|
+
|
13
|
+
attributes = XML::MessageParser.parse_key_value_pairs(result.elements["b:Attributes"])
|
14
|
+
h.merge(attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
# RetrieveResult
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
|
4
|
+
class Attributes < Hash
|
5
|
+
|
6
|
+
def initialize(attrs)
|
7
|
+
super
|
8
|
+
self.merge!(attrs)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_type(key, value)
|
12
|
+
type = "string"
|
13
|
+
case value
|
14
|
+
when ::Fixnum
|
15
|
+
type = "int"
|
16
|
+
when ::BigDecimal, ::Float
|
17
|
+
type = "decimal"
|
18
|
+
when ::TrueClass, ::FalseClass
|
19
|
+
type = "boolean"
|
20
|
+
when ::Time, ::DateTime
|
21
|
+
type = "dateTime"
|
22
|
+
when ::Hash, EntityReference
|
23
|
+
type = "EntityReference"
|
24
|
+
when Query
|
25
|
+
type = "QueryExpression"
|
26
|
+
when FetchExpression
|
27
|
+
type = "FetchExpression"
|
28
|
+
else
|
29
|
+
if key.to_s == "EntityFilters"
|
30
|
+
type = "EntityFilters"
|
31
|
+
elsif key.to_s == "RollupType"
|
32
|
+
type = "RollupType"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if type == "string" && value =~ /\w+{8}-\w+{4}-\w+{4}-\w+{4}-\w+{12}/
|
37
|
+
type = "guid"
|
38
|
+
end
|
39
|
+
|
40
|
+
type
|
41
|
+
end
|
42
|
+
|
43
|
+
# Removes Attributes class wrapper.
|
44
|
+
def to_hash
|
45
|
+
raw_hash = {}
|
46
|
+
self.each do |key, value|
|
47
|
+
raw_hash[key] = value
|
48
|
+
end
|
49
|
+
raw_hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_xml
|
53
|
+
xml = %Q{<a:#{self.class_name} xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic">}
|
54
|
+
|
55
|
+
self.each do |key,value|
|
56
|
+
|
57
|
+
# Temporary hack to handle types I cannot infer (OptionSetValue or Money).
|
58
|
+
if value.is_a?(Hash) && !value[:type].nil?
|
59
|
+
type = value[:type]
|
60
|
+
value = value[:value]
|
61
|
+
else
|
62
|
+
type = get_type(key, value)
|
63
|
+
end
|
64
|
+
|
65
|
+
xml << build_xml(key, value, type)
|
66
|
+
end
|
67
|
+
|
68
|
+
xml << %Q{\n</a:#{self.class_name}>}
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
self.to_xml
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_xml(key, value, type)
|
76
|
+
|
77
|
+
xml = %Q{
|
78
|
+
<a:KeyValuePairOfstringanyType>
|
79
|
+
<c:key>#{key}</c:key>
|
80
|
+
}
|
81
|
+
|
82
|
+
# If we have an object that can convert itself, use it.
|
83
|
+
if (value.respond_to?(:to_xml) && value.class.to_s.include?("DynamicsCRM"))
|
84
|
+
xml << "<c:value i:type=\"a:#{type}\">\n" << value.to_xml({exclude_root: true, namespace: 'a'}) << "</c:value>"
|
85
|
+
else
|
86
|
+
xml << render_value_xml(type, value)
|
87
|
+
end
|
88
|
+
|
89
|
+
xml << "\n</a:KeyValuePairOfstringanyType>"
|
90
|
+
|
91
|
+
xml
|
92
|
+
end
|
93
|
+
|
94
|
+
def render_value_xml(type, value)
|
95
|
+
xml = ""
|
96
|
+
case type
|
97
|
+
when "EntityReference"
|
98
|
+
xml << %Q{
|
99
|
+
<c:value i:type="a:EntityReference">
|
100
|
+
<a:Id>#{value[:id]}</a:Id>
|
101
|
+
<a:LogicalName>#{value[:logical_name]}</a:LogicalName>
|
102
|
+
<a:Name #{value[:name] ? '' : 'i:nil="true"'}>#{value[:name]}</a:Name>
|
103
|
+
</c:value>
|
104
|
+
}
|
105
|
+
when "OptionSetValue", "Money"
|
106
|
+
xml << %Q{
|
107
|
+
<c:value i:type="a:#{type}">
|
108
|
+
<a:Value>#{value}</a:Value>
|
109
|
+
</c:value>
|
110
|
+
}
|
111
|
+
else
|
112
|
+
s_namespace = "http://www.w3.org/2001/XMLSchema"
|
113
|
+
if ["EntityFilters"].include?(type)
|
114
|
+
s_namespace = "http://schemas.microsoft.com/xrm/2011/Metadata"
|
115
|
+
end
|
116
|
+
|
117
|
+
if type == "guid"
|
118
|
+
xml << %Q{
|
119
|
+
<c:value xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/" i:type="d:guid">#{value}</c:value>
|
120
|
+
}
|
121
|
+
elsif type == "RollupType"
|
122
|
+
xml << %Q{
|
123
|
+
<c:value i:type="a:RollupType">#{value}</c:value>
|
124
|
+
}
|
125
|
+
elsif type == "dateTime"
|
126
|
+
xml << %Q{
|
127
|
+
<c:value i:type="s:#{type}" xmlns:s="http://www.w3.org/2001/XMLSchema">#{value.utc.strftime('%Y-%m-%dT%H:%M:%SZ')}</c:value>
|
128
|
+
}
|
129
|
+
else
|
130
|
+
xml << %Q{
|
131
|
+
<c:value i:type="s:#{type}" xmlns:s="#{s_namespace}">#{value}</c:value>
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
xml
|
137
|
+
end
|
138
|
+
|
139
|
+
def class_name
|
140
|
+
self.class.to_s.split("::").last
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.from_xml(xml_document)
|
144
|
+
hash = MessageParser.parse_key_value_pairs(xml_document)
|
145
|
+
return Attributes.new(hash)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Allows method-like access to the hash (OpenStruct)
|
149
|
+
def method_missing(method_name, *args, &block)
|
150
|
+
# Return local hash entry if any.
|
151
|
+
return self[method_name.to_s]
|
152
|
+
end
|
153
|
+
|
154
|
+
def respond_to_missing?(method_name, include_private = false)
|
155
|
+
self.has_key?(method_name.to_s) || super
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
class Parameters < Attributes; end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
|
4
|
+
class ColumnSet < Array
|
5
|
+
|
6
|
+
def initialize(column_names=[])
|
7
|
+
super(column_names || [])
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_xml(options={})
|
11
|
+
namespace = options[:namespace] ? options[:namespace] + ":" : ""
|
12
|
+
|
13
|
+
column_set = ''
|
14
|
+
if self.any?
|
15
|
+
column_set = "<b:Columns xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">"
|
16
|
+
self.each do |name|
|
17
|
+
column_set << "\n<d:string>#{name}</d:string>"
|
18
|
+
end
|
19
|
+
column_set << "\n</b:Columns>"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Really annoying that Retrieve uses columnSet
|
23
|
+
# while QueryExpression uses ColumnSet.
|
24
|
+
tag_name = options[:camel_case] ? "ColumnSet" : "columnSet"
|
25
|
+
%Q{<#{namespace}#{tag_name} xmlns:b="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
26
|
+
<b:AllColumns>#{self.empty?}</b:AllColumns>
|
27
|
+
#{column_set}
|
28
|
+
</#{namespace}#{tag_name}>}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# ColumnSet
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
|
4
|
+
class Criteria < Array
|
5
|
+
|
6
|
+
attr_accessor :filter_operator
|
7
|
+
def initialize(tuples=[])
|
8
|
+
super
|
9
|
+
@filter_operator = 'And'
|
10
|
+
end
|
11
|
+
|
12
|
+
# ConditionExpression can be repeated multiple times
|
13
|
+
# Operator: can be lots of values such as: eq (Equals), neq (Not Equals), gt (Greater Than)
|
14
|
+
# get the values from a fetch xml query
|
15
|
+
# Values -> Value can be repeated multiple times
|
16
|
+
# FilterOperator: and OR or depending on the filter requirements
|
17
|
+
def to_xml(options={})
|
18
|
+
ns = options[:namespace] ? options[:namespace] : "a"
|
19
|
+
|
20
|
+
expressions = ""
|
21
|
+
self.each do |tuple|
|
22
|
+
attr_name = tuple[0]
|
23
|
+
operator = tuple[1]
|
24
|
+
values = tuple[2].is_a?(Array) ? tuple[2] : [tuple[2]]
|
25
|
+
# TODO: Improve type detection
|
26
|
+
type = (tuple[3] || values.first.class).to_s.downcase
|
27
|
+
type = "int" if type == "fixnum"
|
28
|
+
type = "boolean" if ["trueclass", "falseclass"].include?(type)
|
29
|
+
|
30
|
+
expressions << %Q{<#{ns}:ConditionExpression>
|
31
|
+
<#{ns}:AttributeName>#{attr_name}</#{ns}:AttributeName>
|
32
|
+
<#{ns}:Operator>#{operator}</#{ns}:Operator>
|
33
|
+
<#{ns}:Values xmlns:d="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
|
34
|
+
}
|
35
|
+
values.each do |v|
|
36
|
+
expressions << %Q{<d:anyType i:type="s:#{type}" xmlns:s="http://www.w3.org/2001/XMLSchema">#{v}</d:anyType>}
|
37
|
+
end
|
38
|
+
|
39
|
+
expressions << %Q{
|
40
|
+
</#{ns}:Values>
|
41
|
+
</#{ns}:ConditionExpression>}
|
42
|
+
end
|
43
|
+
|
44
|
+
%Q{<#{ns}:Criteria>
|
45
|
+
<#{ns}:Conditions>
|
46
|
+
#{expressions}
|
47
|
+
</#{ns}:Conditions>
|
48
|
+
<#{ns}:FilterOperator>#{@filter_operator}</#{ns}:FilterOperator>
|
49
|
+
</#{ns}:Criteria>}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
# Criteria
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
|
4
|
+
class Entity
|
5
|
+
|
6
|
+
attr_accessor :attributes, :entity_state, :formatted_values, :id, :logical_name, :related_entities
|
7
|
+
|
8
|
+
def initialize(logical_name)
|
9
|
+
@logical_name = logical_name
|
10
|
+
@id = "00000000-0000-0000-0000-000000000000"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Using Entity vs entity causes the error: Value cannot be null.
|
14
|
+
def to_xml(options={})
|
15
|
+
%Q{
|
16
|
+
<entity xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
|
17
|
+
#{@attributes}
|
18
|
+
<a:EntityState i:nil="true" />
|
19
|
+
<a:FormattedValues xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
|
20
|
+
<a:Id>#{@id}</a:Id>
|
21
|
+
<a:LogicalName>#{@logical_name}</a:LogicalName>
|
22
|
+
<a:RelatedEntities xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
|
23
|
+
</entity>
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{
|
29
|
+
:attributes => attributes.to_hash,
|
30
|
+
:entity_state => entity_state,
|
31
|
+
:formatted_values => formatted_values,
|
32
|
+
:id => @id,
|
33
|
+
:logical_name => @logical_name,
|
34
|
+
:related_entities => related_entities
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_xml(xml_document)
|
39
|
+
entity = Entity.new('')
|
40
|
+
|
41
|
+
if xml_document
|
42
|
+
xml_document.elements.each do |node|
|
43
|
+
|
44
|
+
attr_name = DynamicsCRM::StringUtil.underscore(node.name).to_sym
|
45
|
+
if entity.respond_to?(attr_name)
|
46
|
+
if node.name == "Attributes"
|
47
|
+
entity.attributes = XML::Attributes.from_xml(node)
|
48
|
+
else
|
49
|
+
entity.send("#{attr_name}=", node.text ? node.text.strip : nil)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return entity
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
# Entity
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
|
4
|
+
class EntityReference
|
5
|
+
|
6
|
+
attr_accessor :id, :logical_name, :name, :namespace
|
7
|
+
|
8
|
+
def initialize(logical_name, id)
|
9
|
+
@logical_name = logical_name
|
10
|
+
@id = id || "00000000-0000-0000-0000-000000000000"
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_xml(options={})
|
14
|
+
namespace = options[:namespace] ? "#{options[:namespace]}:" : ''
|
15
|
+
|
16
|
+
xml = %Q{
|
17
|
+
<#{namespace}LogicalName>#{@logical_name}</#{namespace}LogicalName>
|
18
|
+
<#{namespace}Id>#{@id}</#{namespace}Id>
|
19
|
+
<#{namespace}Name #{@name ? '' : 'nil="true"'}>#{@name}</#{namespace}Name>
|
20
|
+
}
|
21
|
+
|
22
|
+
if options[:exclude_root].nil?
|
23
|
+
xml = %Q{
|
24
|
+
<#{namespace}entityReference>#{xml}</#{namespace}entityReference>
|
25
|
+
}
|
26
|
+
end
|
27
|
+
return xml
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
{
|
32
|
+
:logical_name => @logical_name,
|
33
|
+
:id => @id,
|
34
|
+
:name => @name,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.from_xml(xml_document)
|
39
|
+
entity_ref = EntityReference.new('unknown', nil)
|
40
|
+
|
41
|
+
if xml_document
|
42
|
+
xml_document.each_element do |node|
|
43
|
+
attr_name = ::DynamicsCRM::StringUtil.underscore(node.name).to_sym
|
44
|
+
if entity_ref.respond_to?(attr_name)
|
45
|
+
entity_ref.send("#{attr_name}=", node.text ? node.text.strip : nil)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
return entity_ref
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
# EntityReference
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
|
4
|
+
# Represents a SOAP Fault
|
5
|
+
# Resposible for parsing each element
|
6
|
+
class Fault < RuntimeError
|
7
|
+
|
8
|
+
attr_reader :code, :subcode, :reason, :detail
|
9
|
+
|
10
|
+
def initialize(fault_xml)
|
11
|
+
if fault_xml.is_a?(Array)
|
12
|
+
fault_xml = fault_xml.first
|
13
|
+
end
|
14
|
+
# REXL::Element
|
15
|
+
@code = fault_xml.get_text("//[local-name() = 'Code']/[local-name() = 'Value']")
|
16
|
+
@subcode = fault_xml.get_text("//[local-name() = 'Code']/[local-name() = 'Subcode']/[local-name() = 'Value']")
|
17
|
+
@reason = fault_xml.get_text("//[local-name() = 'Reason']/[local-name() = 'Text']")
|
18
|
+
|
19
|
+
@detail = {}
|
20
|
+
detail_fragment = fault_xml.get_elements("//[local-name() = 'Detail']").first
|
21
|
+
if detail_fragment
|
22
|
+
fault_type = detail_fragment.elements.first
|
23
|
+
@detail[:type] = fault_type.name
|
24
|
+
detail_fragment.elements.first.each_element do |node|
|
25
|
+
|
26
|
+
@detail[node.name.to_sym] = node.text
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def message
|
32
|
+
if @detail.empty?
|
33
|
+
"%s[%s] %s" % [@code, @subcode, @reason]
|
34
|
+
else
|
35
|
+
"%s[%s] %s (Detail => %s)" % [@code, @subcode, @reason, @detail]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
# Fault
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DynamicsCRM
|
2
|
+
module XML
|
3
|
+
class FetchExpression
|
4
|
+
|
5
|
+
def initialize(fetch_xml)
|
6
|
+
@fetch = fetch_xml
|
7
|
+
end
|
8
|
+
|
9
|
+
# Using Entity vs entity causes the error: Value cannot be null.
|
10
|
+
# <Order> can be repeated multiple times
|
11
|
+
# orderType: 0 (Ascending) or 1 (Descending)
|
12
|
+
def to_xml(options={})
|
13
|
+
%Q{
|
14
|
+
<a:Query>
|
15
|
+
#{CGI.escapeHTML(@fetch)}
|
16
|
+
</a:Query>
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash
|
21
|
+
{:fetch_xml => @fetch}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|