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
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