dynamics_crm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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