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
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDYyNmExNWY0NTlhOGZmZTA2NzMyYWUzM2U2MjFlNjM2MWIxZGRiNQ==
5
+ data.tar.gz: !binary |-
6
+ NmNiMjM2OGMxNGYxMTcyNmE2MTg5YjUxMjkwZmQ2Yzc5ZWFkNGY4OQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZDJkMTRhM2YxZjgxYmJmZWRkODlhZjE3YzZhOTQzNjZhNTJhNzgwZTk5NzE5
10
+ Mzk1MjJmZTIxZThjYjQyN2MwYTAwZmI2YTVhMTQyYzVkNjU0M2FlOTAyMjAw
11
+ MTc4NjQ5MjM1YzE1YTg2ZmFhODYwN2M1MzBjODZiNzdmNmM2NjQ=
12
+ data.tar.gz: !binary |-
13
+ MjgyOGVjMzA5ZDI2YzI3MGFkYzdkN2EwZjg5OTJiZjJhMzAwMmVmYTBjM2U1
14
+ ZTAwMjM3Mjg2MThiMzRmMjk1N2NlZWFhOTkwY2Q5MmE5N2YzZjliMjA1MWVj
15
+ OTYwOGUwNjc2OTdmMjQ3MjIxZGI1YjA4MTM2NTYxZDlhNWYyNmI=
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ notifications:
5
+ email:
6
+ recipients:
7
+ - joeheth@gmail.com
8
+ on_success: never
9
+ on_failure: always
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dynamics_crm.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Joe Heth
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # DynamicsCRM
2
+
3
+ [![Build Status](https://travis-ci.org/TinderBox/dynamics_crm.png)](https://travis-ci.org/TinderBox/dynamics_crm)
4
+
5
+ Ruby library for accessing Microsoft Dynamics CRM Online 2011/2013 via their SOAP API.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'dynamics_crm'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install dynamics_crm
20
+
21
+ ## Usage
22
+
23
+
24
+ #### Username/Password authentication
25
+
26
+ ```ruby
27
+ client = DynamicsCRM::Client.new
28
+ client.authenticate('user@orgname.onmicrosoft.com', 'password')
29
+ ```
30
+
31
+ ### retrieve
32
+
33
+ ```ruby
34
+ client.retrieve('account', '53291AAB-4A9A-E311-B097-6C3BE5A8DD60')
35
+ # => #<DynamicsCRM::XML::Entity ... >
36
+ ```
37
+
38
+ ### retrieve_multiple
39
+
40
+ ```ruby
41
+ client.retrieve_multiple('account', ["name", "Equal", "Test Account"])
42
+ # => [#<DynamicsCRM::XML::Entity ... >]
43
+
44
+ client.retrieve_multiple('account', ["name", "Equal", "Test Account"], ["Name, "CreatedBy"])
45
+ # => [#<DynamicsCRM::XML::Entity ... >]
46
+ ```
47
+
48
+
49
+ ### create
50
+
51
+ ```ruby
52
+ # Add a new account
53
+ client.create('Account', name: 'Foobar Inc.')
54
+ # => {id: '53291AAB-4A9A-E311-B097-6C3BE5A8DD60'}
55
+ ```
56
+
57
+ ### update
58
+
59
+ ```ruby
60
+ # Update the Account with id '53291AAB-4A9A-E311-B097-6C3BE5A8DD60'
61
+ client.update('account', '53291AAB-4A9A-E311-B097-6C3BE5A8DD60', name: 'Whizbang Corp')
62
+ # => {}
63
+ ```
64
+
65
+ ### delete
66
+
67
+ ```ruby
68
+ # Delete the Account with id '53291AAB-4A9A-E311-B097-6C3BE5A8DD60'
69
+ client.delete('account', '53291AAB-4A9A-E311-B097-6C3BE5A8DD60')
70
+ # => {}
71
+ ```
72
+
73
+ ### retrieve_all_entities
74
+
75
+ ```ruby
76
+ # get the list of organization entities
77
+ client.retrieve_all_entities
78
+ # => [#<DynamicsCRM::Metadata::EntityMetadata>, ...]
79
+ ```
80
+
81
+ ### retrieve_entity
82
+
83
+ ```ruby
84
+ # get the entity metadata for the account object
85
+ client.retrieve_entity('account')
86
+ # => DynamicsCRM::Metadata::EntityMetadata
87
+ ```
88
+
89
+ ### retrieve_attribute
90
+
91
+ ```ruby
92
+ # get AttributeMetadata for 'name' field on the account object
93
+ client.retrieve_attribute('account', 'name')
94
+ # => [#<DynamicsCRM::Metadata::AttributeMetadata>, ...]
95
+ ```
96
+
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => [:spec]
4
+
5
+ require 'rspec/core/rake_task'
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new do |t|
8
+ t.pattern = 'spec/**/*_spec.rb'
9
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dynamics_crm/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dynamics_crm"
8
+ spec.version = DynamicsCRM::VERSION
9
+ spec.authors = ["Joe Heth"]
10
+ spec.email = ["joeheth@gmail.com"]
11
+ spec.description = %q{Ruby API for integrating with MS Dynamics 2011/2013 SOAP API}
12
+ spec.summary = %q{Ruby gem for integrating with MS Dynamics 2011/2013 SOAP API}
13
+ spec.homepage = "https://github.com/TinderBox/dynamics_crm"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'curb', '~> 0.8', '>= 0.8.5'
22
+ spec.add_runtime_dependency 'mimemagic', '~> 0.2', '>= 0.2.1'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency 'rake', '~> 10.1'
26
+ spec.add_development_dependency 'rspec', '~> 2.14'
27
+ spec.add_development_dependency 'simplecov', '~> 0.7'
28
+ end
@@ -0,0 +1,295 @@
1
+ # SOAP Only Walk through
2
+ # http://code.msdn.microsoft.com/CRM-Online-2011-WebServices-14913a16
3
+ #
4
+ # PHP starting point:
5
+ # http://crmtroubleshoot.blogspot.com.au/2013/07/dynamics-crm-2011-php-and-soap-using.html
6
+ #
7
+ # OCP: Open Commerce Platform
8
+ # https://community.dynamics.com/crm/b/crmgirishraja/archive/2012/09/04/authentication-with-dynamics-crm-online-on-ocp-office-365.aspx
9
+ module DynamicsCRM
10
+
11
+ class Client
12
+ extend Forwardable
13
+ include XML::MessageBuilder
14
+
15
+ # The Login URL and Region are located in the client's Organization WSDL.
16
+ # https://tinderboxdev.api.crm.dynamics.com/XRMServices/2011/Organization.svc?wsdl=wsdl0
17
+ #
18
+ # Login URL: Policy -> Issuer -> Address
19
+ # Region: SecureTokenService -> AppliesTo
20
+ LOGIN_URL = "https://login.microsoftonline.com/RST2.srf"
21
+ REGION = 'urn:crmna:dynamics.com'
22
+
23
+ attr_accessor :logger, :caller_id
24
+
25
+ # Initializes Client instance.
26
+ # Requires: organization_name
27
+ # Optional: hostname
28
+ def initialize(config={organization_name: nil, hostname: nil, caller_id: nil})
29
+ raise RuntimeError.new("organization_name is required") if config[:organization_name].nil?
30
+
31
+ @organization_name = config[:organization_name]
32
+ @hostname = config[:hostname] || "#{@organization_name}.api.crm.dynamics.com"
33
+ @organization_endpoint = "https://#{@hostname}/XRMServices/2011/Organization.svc"
34
+ @caller_id = config[:caller_id]
35
+ end
36
+
37
+ # Public: Authenticate User
38
+ #
39
+ # Examples
40
+ #
41
+ # client.authenticate('test@orgnam.onmicrosoft.com', 'password')
42
+ # # => true || raised Fault
43
+ #
44
+ # Returns true on success or raises Fault
45
+ def authenticate(username, password)
46
+
47
+ @username = username
48
+ @password = password
49
+
50
+ soap_response = post(LOGIN_URL, build_ocp_request(username, password))
51
+
52
+ document = REXML::Document.new(soap_response)
53
+ # Check for Fault
54
+ fault_xml = document.get_elements("//[local-name() = 'Fault']")
55
+ raise XML::Fault.new(fault_xml) if fault_xml.any?
56
+
57
+ cipher_values = document.get_elements("//CipherValue")
58
+
59
+ if cipher_values && cipher_values.length > 0
60
+ @security_token0 = cipher_values[0].text
61
+ @security_token1 = cipher_values[1].text
62
+ # Use local-name() to ignore namespace.
63
+ @key_identifier = document.get_elements("//[local-name() = 'KeyIdentifier']").first.text
64
+ else
65
+ raise RuntimeError.new(soap_response)
66
+ end
67
+
68
+ true
69
+ end
70
+
71
+ # These are all the operations defined by the Dynamics WSDL.
72
+ # Tag names are case-sensitive.
73
+ def create(entity_name, attributes)
74
+
75
+ entity = XML::Entity.new(entity_name)
76
+ entity.attributes = XML::Attributes.new(attributes)
77
+
78
+ xml_response = post(@organization_endpoint, create_request(entity))
79
+ return Response::CreateResult.new(xml_response)
80
+ end
81
+
82
+ # http://crmtroubleshoot.blogspot.com.au/2013/07/dynamics-crm-2011-php-and-soap-calls.html
83
+ def retrieve(entity_name, guid, columns=[])
84
+
85
+ column_set = XML::ColumnSet.new(columns)
86
+ request = retrieve_request(entity_name, guid, column_set)
87
+
88
+ xml_response = post(@organization_endpoint, request)
89
+ return Response::RetrieveResult.new(xml_response)
90
+ end
91
+
92
+ def rollup(target_entity, query, rollup_type="Related")
93
+ self.execute("Rollup", {
94
+ Target: target_entity,
95
+ Query: query,
96
+ RollupType: rollup_type
97
+ })
98
+ end
99
+
100
+ def retrieve_multiple(entity_name, criteria=[], columns=[])
101
+
102
+ query = XML::Query.new(entity_name)
103
+ query.columns = columns
104
+ query.criteria = XML::Criteria.new(criteria)
105
+
106
+ request = retrieve_multiple_request(query)
107
+ xml_response = post(@organization_endpoint, request)
108
+ return Response::RetrieveMultipleResult.new(xml_response)
109
+ end
110
+
111
+ def fetch(fetchxml)
112
+ response = self.execute("RetrieveMultiple", {
113
+ Query: XML::FetchExpression.new(fetchxml)
114
+ })
115
+ end
116
+
117
+ # Update entity attributes
118
+ def update(entity_name, guid, attributes)
119
+
120
+ entity = XML::Entity.new(entity_name)
121
+ entity.id = guid
122
+ entity.attributes = XML::Attributes.new(attributes)
123
+
124
+ request = update_request(entity)
125
+ xml_response = post(@organization_endpoint, request)
126
+ return Response::UpdateResponse.new(xml_response)
127
+ end
128
+
129
+ def delete(entity_name, guid)
130
+ request = delete_request(entity_name, guid)
131
+
132
+ xml_response = post(@organization_endpoint, request)
133
+ return Response::DeleteResponse.new(xml_response)
134
+ end
135
+
136
+ def execute(action, parameters={}, response_class=nil)
137
+ request = execute_request(action, parameters)
138
+ xml_response = post(@organization_endpoint, request)
139
+
140
+ response_class ||= Response::ExecuteResult
141
+ return response_class.new(xml_response)
142
+ end
143
+
144
+ def associate(entity_name, guid, relationship, related_entities)
145
+ request = associate_request(entity_name, guid, relationship, related_entities)
146
+ xml_response = post(@organization_endpoint, request)
147
+ return Response::AssociateResponse.new(xml_response)
148
+ end
149
+
150
+ def disassociate(entity_name, guid, relationship, related_entities)
151
+ request = disassociate_request(entity_name, guid, relationship, related_entities)
152
+ xml_response = post(@organization_endpoint, request)
153
+ return Response::DisassociateResponse.new(xml_response)
154
+ end
155
+
156
+ def create_attachment(entity_name, entity_id, options={})
157
+ raise "options must contain a document entry" unless options[:document]
158
+
159
+ file_name = options[:filename]
160
+ document = options[:document]
161
+ subject = options[:subject]
162
+ text = options[:text] || ""
163
+
164
+ if document.is_a?(String) && File.exists?(document)
165
+ file = File.new(document)
166
+ elsif document.is_a?(String) && document.start_with?("http")
167
+ require 'open-uri'
168
+ file = open(document)
169
+ else
170
+ file = document
171
+ end
172
+
173
+ if file.respond_to?(:base_uri)
174
+ file_name ||= File.basename(file.base_uri.path)
175
+ mime_type = MimeMagic.by_path(file.base_uri.path)
176
+ elsif file.respond_to?(:path)
177
+ file_name ||= File.basename(file.path)
178
+ mime_type = MimeMagic.by_path(file.path)
179
+ else
180
+ raise "file must be a valid File object, file path or URL"
181
+ end
182
+
183
+ documentbody = file.read
184
+ attributes = {
185
+ objectid: {id: entity_id, logical_name: entity_name},
186
+ subject: subject || file_name,
187
+ notetext: text || "",
188
+ filename: file_name,
189
+ isdocument: true,
190
+ documentbody: ::Base64.encode64(documentbody),
191
+ filesize: documentbody.length,
192
+ mimetype: mime_type
193
+ }
194
+
195
+ self.create("annotation", attributes)
196
+ end
197
+
198
+ def retrieve_attachments(entity_id, columns=["filename", "documentbody", "mimetype"])
199
+ self.retrieve_multiple("annotation", [["objectid", "Equal", entity_id], ["isdocument", "Equal", true]], columns)
200
+ end
201
+
202
+ # Metadata Calls
203
+ # EntityFilters Enum: Default, Entity, Attributes, Privileges, Relationships, All
204
+ def retrieve_all_entities
205
+ response = self.execute("RetrieveAllEntities", {
206
+ EntityFilters: "Entity",
207
+ RetrieveAsIfPublished: true
208
+ },
209
+ Metadata::RetrieveAllEntitiesResponse)
210
+ end
211
+
212
+ # EntityFilters Enum: Default, Entity, Attributes, Privileges, Relationships, All
213
+ def retrieve_entity(logical_name, entity_filter="Attributes")
214
+ self.execute("RetrieveEntity", {
215
+ LogicalName: logical_name,
216
+ MetadataId: "00000000-0000-0000-0000-000000000000",
217
+ EntityFilters: entity_filter,
218
+ RetrieveAsIfPublished: true
219
+ },
220
+ Metadata::RetrieveEntityResponse)
221
+ end
222
+
223
+ def retrieve_attribute(entity_logical_name, logical_name)
224
+ self.execute("RetrieveAttribute", {
225
+ EntityLogicalName: entity_logical_name,
226
+ LogicalName: logical_name,
227
+ MetadataId: "00000000-0000-0000-0000-000000000000",
228
+ RetrieveAsIfPublished: true
229
+ },
230
+ Metadata::RetrieveAttributeResponse)
231
+ end
232
+
233
+ def who_am_i
234
+ self.execute('WhoAmI')
235
+ end
236
+
237
+ def load_entity(logical_name, id)
238
+ case logical_name
239
+ when "opportunity"
240
+ Model::Opportunity.new(id, self)
241
+ else
242
+ Model::Entity.new(logical_name, id, self)
243
+ end
244
+ end
245
+
246
+ protected
247
+
248
+ def post(url, request)
249
+
250
+ log_xml("REQUEST", request)
251
+
252
+ c = Curl::Easy.new(url) do |http|
253
+ # Set up headers.
254
+ http.headers["Connection"] = "Keep-Alive"
255
+ http.headers["Content-type"] = "application/soap+xml; charset=UTF-8"
256
+ http.headers["Content-length"] = request.length
257
+
258
+ http.ssl_verify_peer = false
259
+ http.timeout = 60
260
+ http.follow_location = true
261
+ http.ssl_version = 3
262
+ # http.verbose = 1
263
+ end
264
+
265
+ if c.http_post(request)
266
+ response = c.body_str
267
+ else
268
+
269
+ end
270
+
271
+ log_xml("RESPONSE", response)
272
+
273
+ response
274
+ end
275
+
276
+ def log_xml(title, xml)
277
+ return unless logger
278
+
279
+ logger.puts(title)
280
+ doc = REXML::Document.new(xml)
281
+ formatter.write(doc.root, logger)
282
+ logger.puts
283
+ end
284
+
285
+ def formatter
286
+ unless @formatter
287
+ @formatter = REXML::Formatters::Pretty.new(2)
288
+ @formatter.compact = true # This is the magic line that does what you need!
289
+ end
290
+ @formatter
291
+ end
292
+
293
+ end
294
+
295
+ end
@@ -0,0 +1,42 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ # AttributeMetadata
5
+ # ManagedPropertyAttributeMetadata
6
+ # IntegerAttributeMetadata
7
+ # BooleanAttributeMetadata
8
+ # DateTimeAttributeMetadata
9
+ # DecimalAttributeMetadata
10
+ # DoubleAttributeMetadata
11
+ # EntityNameAttributeMetadata
12
+ # MoneyAttributeMetadata
13
+ # StringAttributeMetadata
14
+ # LookupAttributeMetadata
15
+ # MemoAttributeMetadata
16
+ # BigIntAttributeMetadata
17
+ # PicklistAttributeMetadata
18
+ # StateAttributeMetadata
19
+ # StatusAttributeMetadata
20
+ #
21
+ # http://msdn.microsoft.com/en-us/library/microsoft.xrm.sdk.metadata.attributemetadata.aspx
22
+ class AttributeMetadata < XmlDocument
23
+
24
+ # Only applicable to PicklistAttributeMetadata
25
+ def picklist_options
26
+ return @picklist_options if @picklist_options
27
+
28
+ @picklist_options = {}
29
+ option_metadata = "./d:OptionSet/d:Options/d:OptionMetadata"
30
+ @document.get_elements(option_metadata).each do |option|
31
+ numeric_value = option.elements["d:Value"].text
32
+ label = option.elements["d:Label/b:UserLocalizedLabel/b:Label"].text
33
+ @picklist_options[numeric_value.to_i] = label
34
+ end
35
+
36
+ @picklist_options
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+ # Represents EntityMetdata XML fragment.
4
+ # Optionally contains list of AttributeMetadata
5
+ class EntityMetadata < XmlDocument
6
+ attr_reader :attributes
7
+
8
+ def initialize(document)
9
+ super
10
+ end
11
+
12
+ def attributes
13
+ return if @attributes.is_a?(Array)
14
+
15
+ @attributes = document.get_elements("//d:Attributes/d:AttributeMetadata").collect do |attr_metadata|
16
+ AttributeMetadata.new(attr_metadata)
17
+ end
18
+
19
+ return @attributes
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ # Retrieve All Entities returns a list of EntityMetadata
5
+ class RetrieveAllEntitiesResponse < DynamicsCRM::Response::ExecuteResult
6
+ attr_reader :entities
7
+
8
+ def initialize(xml)
9
+ super
10
+ @entities = []
11
+ # Single KeyValuePair of EntityMetadata -> [EntityMetdata,...]
12
+ self.delete("EntityMetadata").each do |em_xml|
13
+ @entities << EntityMetadata.new(em_xml)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+ # Retrieve Attribute returns a single AttributeMetadata.
4
+ class RetrieveAttributeResponse < DynamicsCRM::Response::ExecuteResult
5
+ attr_reader :attribute
6
+
7
+ def initialize(xml)
8
+ super
9
+
10
+ # Single KeyValuePair containing 1 value type of AttributeMetadata
11
+ @attribute = AttributeMetadata.new(self.delete("AttributeMetadata"))
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ # RetrieveEntity returns a single EntityMetadata element.
5
+ class RetrieveEntityResponse < DynamicsCRM::Response::ExecuteResult
6
+ attr_reader :entity, :attributes
7
+
8
+ def initialize(xml)
9
+ super
10
+
11
+ # Single KeyValuePair containing 1 value type of EntityMetadata
12
+ @entity = EntityMetadata.new(self["EntityMetadata"])
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ class XmlDocument
5
+
6
+ attr_reader :document
7
+ def initialize(document)
8
+ @document = document
9
+ end
10
+
11
+ # Allows access to attributes in underlying XML document.
12
+ def method_missing(method, *args, &block)
13
+ value = nil
14
+ return value if @document.nil?
15
+
16
+ camel_name = method.to_s
17
+ element = @document.get_elements("./[local-name() = '#{camel_name}']").first
18
+
19
+ if element && element.children.size == 1 && element.children.first.is_a?(REXML::Text)
20
+ value = element.text
21
+ elsif element
22
+ value = XmlDocument.new(element)
23
+ else
24
+ # This returns if no XML element was found to avoid nil errors.
25
+ value = OpenStruct.new
26
+ end
27
+
28
+ # Return wrapper to support method_method missing.
29
+ return value
30
+ end
31
+
32
+ def respond_to_missing?(method_name, include_private = false)
33
+ camel_name = method_name.to_s
34
+ @document.get_elements("d:#{camel_name}").any? || super
35
+ end
36
+ end
37
+
38
+ end
39
+ end