dynamics_crm 0.1.0
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 +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
|