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.
Files changed (69) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +104 -0
  8. data/Rakefile +9 -0
  9. data/dynamics_crm.gemspec +28 -0
  10. data/lib/dynamics_crm/client.rb +295 -0
  11. data/lib/dynamics_crm/metadata/attribute_metadata.rb +42 -0
  12. data/lib/dynamics_crm/metadata/entity_metadata.rb +24 -0
  13. data/lib/dynamics_crm/metadata/retrieve_all_entities_response.rb +21 -0
  14. data/lib/dynamics_crm/metadata/retrieve_attribute_response.rb +16 -0
  15. data/lib/dynamics_crm/metadata/retrieve_entity_response.rb +17 -0
  16. data/lib/dynamics_crm/metadata/xml_document.rb +39 -0
  17. data/lib/dynamics_crm/response/create_result.rb +15 -0
  18. data/lib/dynamics_crm/response/execute_result.rb +26 -0
  19. data/lib/dynamics_crm/response/result.rb +78 -0
  20. data/lib/dynamics_crm/response/retrieve_multiple_result.rb +38 -0
  21. data/lib/dynamics_crm/response/retrieve_result.rb +20 -0
  22. data/lib/dynamics_crm/version.rb +3 -0
  23. data/lib/dynamics_crm/xml/attributes.rb +163 -0
  24. data/lib/dynamics_crm/xml/column_set.rb +34 -0
  25. data/lib/dynamics_crm/xml/criteria.rb +54 -0
  26. data/lib/dynamics_crm/xml/entity.rb +61 -0
  27. data/lib/dynamics_crm/xml/entity_reference.rb +56 -0
  28. data/lib/dynamics_crm/xml/fault.rb +42 -0
  29. data/lib/dynamics_crm/xml/fetch_expression.rb +27 -0
  30. data/lib/dynamics_crm/xml/message_builder.rb +222 -0
  31. data/lib/dynamics_crm/xml/message_parser.rb +68 -0
  32. data/lib/dynamics_crm/xml/orders.rb +36 -0
  33. data/lib/dynamics_crm/xml/page_info.rb +38 -0
  34. data/lib/dynamics_crm/xml/query.rb +38 -0
  35. data/lib/dynamics_crm.rb +46 -0
  36. data/spec/fixtures/associate_response.xml +17 -0
  37. data/spec/fixtures/create_response.xml +19 -0
  38. data/spec/fixtures/delete_response.xml +16 -0
  39. data/spec/fixtures/disassociate_response.xml +17 -0
  40. data/spec/fixtures/fetch_xml_response.xml +120 -0
  41. data/spec/fixtures/receiver_fault.xml +27 -0
  42. data/spec/fixtures/request_security_token_response.xml +62 -0
  43. data/spec/fixtures/retrieve_account_all_columns.xml +402 -0
  44. data/spec/fixtures/retrieve_all_entities.xml +614 -0
  45. data/spec/fixtures/retrieve_attribute_identifier_response.xml +117 -0
  46. data/spec/fixtures/retrieve_attribute_picklist_response.xml +1097 -0
  47. data/spec/fixtures/retrieve_attribute_response.xml +126 -0
  48. data/spec/fixtures/retrieve_entity_response.xml +671 -0
  49. data/spec/fixtures/retrieve_multiple_result.xml +67 -0
  50. data/spec/fixtures/sender_fault.xml +34 -0
  51. data/spec/fixtures/update_response.xml +16 -0
  52. data/spec/fixtures/who_am_i_result.xml +35 -0
  53. data/spec/lib/client_spec.rb +230 -0
  54. data/spec/lib/metadata/entity_metadata_spec.rb +24 -0
  55. data/spec/lib/metadata/retrieve_all_entities_response_spec.rb +26 -0
  56. data/spec/lib/metadata/retrieve_attribute_response_spec.rb +65 -0
  57. data/spec/lib/metadata/retrieve_entity_response_spec.rb +24 -0
  58. data/spec/lib/response/execute_result_spec.rb +20 -0
  59. data/spec/lib/response/retrieve_multiple_spec.rb +34 -0
  60. data/spec/lib/response/retrieve_result_spec.rb +67 -0
  61. data/spec/lib/xml/attributes_spec.rb +39 -0
  62. data/spec/lib/xml/column_set_spec.rb +19 -0
  63. data/spec/lib/xml/entity_reference_spec.rb +47 -0
  64. data/spec/lib/xml/entity_spec.rb +63 -0
  65. data/spec/lib/xml/fault_spec.rb +41 -0
  66. data/spec/lib/xml/query_spec.rb +43 -0
  67. data/spec/spec_helper.rb +17 -0
  68. data/spec/support/fixture_helpers.rb +14 -0
  69. metadata +240 -0
@@ -0,0 +1,15 @@
1
+ module DynamicsCRM
2
+ module Response
3
+
4
+ class CreateResult < Result
5
+
6
+ protected
7
+
8
+ # Invoked by Result constructor
9
+ def parse_result_response(result)
10
+ {"Id" => result.text, "id" => result.text}
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -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,3 @@
1
+ module DynamicsCRM
2
+ VERSION = "0.1.0"
3
+ 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