dynamics_crm 0.6.0 → 0.7.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -1
  3. data/CHANGELOG.md +10 -0
  4. data/dynamics_crm.gemspec +6 -5
  5. data/lib/dynamics_crm.rb +7 -0
  6. data/lib/dynamics_crm/client.rb +25 -14
  7. data/lib/dynamics_crm/fetch_xml/builder.rb +26 -27
  8. data/lib/dynamics_crm/metadata/attribute_metadata.rb +35 -2
  9. data/lib/dynamics_crm/metadata/attribute_query_expression.rb +25 -0
  10. data/lib/dynamics_crm/metadata/entity_metadata.rb +1 -1
  11. data/lib/dynamics_crm/metadata/entity_query_expression.rb +29 -0
  12. data/lib/dynamics_crm/metadata/filter_expression.rb +47 -0
  13. data/lib/dynamics_crm/metadata/properties_expression.rb +30 -0
  14. data/lib/dynamics_crm/metadata/retrieve_metadata_changes_response.rb +18 -0
  15. data/lib/dynamics_crm/version.rb +1 -1
  16. data/lib/dynamics_crm/xml/attributes.rb +28 -2
  17. data/lib/dynamics_crm/xml/entity_collection.rb +10 -0
  18. data/lib/dynamics_crm/xml/message_builder.rb +3 -3
  19. data/lib/dynamics_crm/xml/message_parser.rb +2 -0
  20. data/lib/dynamics_crm/xml/money.rb +16 -0
  21. data/spec/fixtures/retrieve_metadata_changes_response.xml +215 -0
  22. data/spec/lib/client_spec.rb +169 -70
  23. data/spec/lib/fetch_xml/builder_spec.rb +44 -0
  24. data/spec/lib/metadata/attribute_query_expression_spec.rb +29 -0
  25. data/spec/lib/metadata/entity_metadata_spec.rb +13 -13
  26. data/spec/lib/metadata/filter_expression_spec.rb +44 -0
  27. data/spec/lib/metadata/properties_expression_spec.rb +19 -0
  28. data/spec/lib/metadata/retrieve_all_entities_response_spec.rb +8 -8
  29. data/spec/lib/metadata/retrieve_attribute_response_spec.rb +23 -23
  30. data/spec/lib/metadata/retrieve_entity_response_spec.rb +7 -7
  31. data/spec/lib/metadata/retrieve_metadata_changes_response_spec.rb +52 -0
  32. data/spec/lib/model/opportunity_spec.rb +7 -7
  33. data/spec/lib/response/execute_result_spec.rb +4 -4
  34. data/spec/lib/response/retrieve_multiple_spec.rb +10 -10
  35. data/spec/lib/response/retrieve_result_spec.rb +26 -26
  36. data/spec/lib/xml/attributes_spec.rb +9 -9
  37. data/spec/lib/xml/column_set_spec.rb +4 -4
  38. data/spec/lib/xml/entity_reference_spec.rb +8 -8
  39. data/spec/lib/xml/entity_spec.rb +18 -18
  40. data/spec/lib/xml/fault_spec.rb +8 -8
  41. data/spec/lib/xml/money_spec.rb +41 -0
  42. data/spec/lib/xml/query_spec.rb +15 -15
  43. data/spec/spec_helper.rb +1 -0
  44. metadata +82 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2493af4ed2c1023379591be4833a321457200d8
4
- data.tar.gz: 5d48de1485d16926dc2731db51bccd8ce8deeccb
3
+ metadata.gz: 511b29b2b863fee746767bb6bf4781d043f13969
4
+ data.tar.gz: 9bffa8007f6f12332be94fb81e447b05c43c88e8
5
5
  SHA512:
6
- metadata.gz: 0cb157d1b9e89f0c64b6c8c19000728c8a977783434f31dc25af561826308a3deb532204ca6e96eb31fa9a6cd14523ec1c48f6b9586d47c18f8a2702ddf054ca
7
- data.tar.gz: a8af9693d74028ed6b8b5e7889e69a60adfe5560ece554f1f82d22ab70d13ecf8ee09c63278bfaf025b428ed4e6cca7957a0be3446ec1d1fbf8b3ba6440983b7
6
+ metadata.gz: d093920089243f8f13e586cb5815c5b4ff2ddaffa504bd5e41e9de1f9c4e15a3356126e47887373bac5fe29d2b837432c299d97350c3afdd2b938fb3edf28499
7
+ data.tar.gz: 45c230a953e78736440accdea82c61d77c440b1c5efdd7779a40131d11e9ea91e025ea2a0092889de1dcf8f2b49ab24dbeea483a5fce455079177d075b35e156
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
3
+ - 2.0.0
4
+ - 2.1.0
4
5
  notifications:
5
6
  email:
6
7
  recipients:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.7.0 (March 4, 2016)
2
+ * Add EntityCollection to xml attributes #24
3
+ * Fix illegal XML characters #25
4
+ * Dynamics optimize metadata queries #26
5
+ * Add Money attribute #31
6
+ * Use `request.bytesize` instead of `request.length` #32
7
+ * Rexml sanitization on username and password for ocp request builder #33
8
+ * Adding new regions to determine region set #34
9
+ * Improve region determination #37
10
+
1
11
  ## 0.6.0 (December 30, 2014)
2
12
 
3
13
  * Adds support for Relationship Metadata.
data/dynamics_crm.gemspec CHANGED
@@ -18,12 +18,13 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency 'curb', '~> 0.8', '>= 0.8.5'
22
- spec.add_runtime_dependency 'mimemagic', '~> 0.2', '>= 0.2.1'
21
+ spec.add_runtime_dependency 'curb', '>= 0.8', '< 1.0.0'
22
+ spec.add_runtime_dependency 'mimemagic', '>= 0.2', '< 4.0.0'
23
23
  spec.add_runtime_dependency 'builder', '>= 3.0.0', '< 4.0.0'
24
24
 
25
- spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "bundler", ">= 1.3", "< 2.0"
26
26
  spec.add_development_dependency 'rake', '~> 10.1'
27
- spec.add_development_dependency 'rspec', '~> 2.14'
28
- spec.add_development_dependency 'simplecov', '~> 0.7'
27
+ spec.add_development_dependency 'rspec', '>= 2.14', "< 4.0"
28
+ spec.add_development_dependency 'simplecov', '>= 0.7', "< 1.0"
29
+ spec.add_development_dependency 'pry', '~> 0.10.3'
29
30
  end
data/lib/dynamics_crm.rb CHANGED
@@ -11,6 +11,7 @@ require "dynamics_crm/xml/fetch_expression"
11
11
  require "dynamics_crm/xml/entity"
12
12
  require "dynamics_crm/xml/entity_reference"
13
13
  require "dynamics_crm/xml/entity_collection"
14
+ require "dynamics_crm/xml/money"
14
15
  require "dynamics_crm/response/result"
15
16
  require "dynamics_crm/response/retrieve_result"
16
17
  require "dynamics_crm/response/retrieve_multiple_result"
@@ -22,9 +23,14 @@ require "dynamics_crm/metadata/one_to_many_relationship"
22
23
  require "dynamics_crm/metadata/relationship_metadata"
23
24
  require "dynamics_crm/metadata/entity_metadata"
24
25
  require "dynamics_crm/metadata/attribute_metadata"
26
+ require "dynamics_crm/metadata/attribute_query_expression"
27
+ require "dynamics_crm/metadata/filter_expression"
28
+ require "dynamics_crm/metadata/properties_expression"
29
+ require "dynamics_crm/metadata/entity_query_expression"
25
30
  require "dynamics_crm/metadata/retrieve_all_entities_response"
26
31
  require "dynamics_crm/metadata/retrieve_entity_response"
27
32
  require "dynamics_crm/metadata/retrieve_attribute_response"
33
+ require "dynamics_crm/metadata/retrieve_metadata_changes_response"
28
34
  # Model
29
35
  require "dynamics_crm/model/entity"
30
36
  require "dynamics_crm/model/opportunity"
@@ -42,6 +48,7 @@ require 'mimemagic'
42
48
  require 'curl'
43
49
  require 'securerandom'
44
50
  require 'date'
51
+ require 'cgi'
45
52
 
46
53
  module DynamicsCRM
47
54
 
@@ -18,11 +18,21 @@ module DynamicsCRM
18
18
  extend Forwardable
19
19
  include XML::MessageBuilder
20
20
 
21
- attr_accessor :logger, :caller_id
21
+ attr_accessor :logger, :caller_id, :timeout
22
22
  attr_reader :hostname, :region, :organization_endpoint, :login_url
23
23
 
24
24
  OCP_LOGIN_URL = 'https://login.microsoftonline.com/RST2.srf'
25
25
 
26
+ REGION = {
27
+ "crm9.dynamics.com" => "urn:crmgcc:dynamics.com",
28
+ "crm7.dynamics.com" => "urn:crmjpn:dynamics.com",
29
+ "crm6.dynamics.com" => "urn:crmoce:dynamics.com",
30
+ "crm5.dynamics.com" => "urn:crmapac:dynamics.com",
31
+ "crm4.dynamics.com" => "urn:crmemea:dynamics.com",
32
+ "crm2.dynamics.com" => "urn:crmsam:dynamics.com",
33
+ "crm.dynamics.com" => "urn:crmna:dynamics.com",
34
+ }
35
+
26
36
  # Initializes Client instance.
27
37
  # Requires: organization_name
28
38
  # Optional: hostname
@@ -32,7 +42,9 @@ module DynamicsCRM
32
42
  @organization_name = config[:organization_name]
33
43
  @hostname = config[:hostname] || "#{@organization_name}.api.crm.dynamics.com"
34
44
  @organization_endpoint = "https://#{@hostname}/XRMServices/2011/Organization.svc"
45
+ REGION.default = @organization_endpoint
35
46
  @caller_id = config[:caller_id]
47
+ @timeout = config[:timeout] || 120
36
48
 
37
49
  # The Login URL and Region are located in the client's Organization WSDL.
38
50
  # https://tinderboxdev.api.crm.dynamics.com/XRMServices/2011/Organization.svc?wsdl=wsdl0
@@ -82,7 +94,7 @@ module DynamicsCRM
82
94
  @timestamp = '<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_0"><u:Created>' + @header_current_time + '</u:Created><u:Expires>' + @header_expires_time + '</u:Expires></u:Timestamp>'
83
95
  @digest_value = Digest::SHA1.base64digest @timestamp
84
96
  @signature = '<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"></SignatureMethod><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>' + @digest_value + '</DigestValue></Reference></SignedInfo>'
85
- @signature_value = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), Base64.decode64(@server_secret), @signature)).chomp
97
+ @signature_value = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), Base64.decode64(@server_secret), @signature)).chomp
86
98
  else
87
99
  cipher_values = document.get_elements("//CipherValue")
88
100
 
@@ -263,6 +275,13 @@ module DynamicsCRM
263
275
  Metadata::RetrieveAttributeResponse)
264
276
  end
265
277
 
278
+ def retrieve_metadata_changes(entity_query)
279
+ self.execute("RetrieveMetadataChanges", {
280
+ Query: entity_query
281
+ },
282
+ Metadata::RetrieveMetadataChangesResponse)
283
+ end
284
+
266
285
  def who_am_i
267
286
  self.execute('WhoAmI')
268
287
  end
@@ -296,16 +315,8 @@ module DynamicsCRM
296
315
  end
297
316
 
298
317
  def determine_region
299
- case hostname
300
- when /crm5\.dynamics\.com/
301
- "urn:crmapac:dynamics.com"
302
- when /crm4\.dynamics\.com/
303
- "urn:crmemea:dynamics.com"
304
- when /\.dynamics\.com/
305
- "urn:crmna:dynamics.com"
306
- else
307
- organization_endpoint
308
- end
318
+ hostname.match(/(crm\d?\.dynamics.com)/)
319
+ REGION[$1]
309
320
  end
310
321
 
311
322
  def post(url, request)
@@ -315,10 +326,10 @@ module DynamicsCRM
315
326
  # Set up headers.
316
327
  http.headers["Connection"] = "Keep-Alive"
317
328
  http.headers["Content-type"] = "application/soap+xml; charset=UTF-8"
318
- http.headers["Content-length"] = request.length
329
+ http.headers["Content-length"] = request.bytesize
319
330
 
320
331
  http.ssl_verify_peer = false
321
- http.timeout = 120
332
+ http.timeout = timeout
322
333
  http.follow_location = true
323
334
  http.ssl_version = 1
324
335
  # http.verbose = 1
@@ -46,60 +46,45 @@ module DynamicsCRM
46
46
 
47
47
  def to_xml
48
48
  @builder = ::Builder::XmlMarkup.new(:indent=>2)
49
-
50
49
  # <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
51
- @builder.fetch(version: @version, :"output-format" => @output_format, mapping: @mapping, distinct: @distinct) {
50
+ builder.fetch(version: @version, :"output-format" => @output_format, mapping: @mapping, distinct: @distinct) {
52
51
  @entities.each do |e|
53
52
  # <entity name="opportunityproduct">
54
- @builder.entity(name: e.logical_name) {
53
+ builder.entity(name: e.logical_name) {
55
54
  e.attributes.each do |field|
56
55
  # <attribute name="productid" />
57
- @builder.attribute(name: field)
56
+ builder.attribute(name: field)
58
57
  end
59
58
 
60
59
  if e.order_field
61
- @builder.order(attribute: e.order_field, descending: e.order_desc)
60
+ builder.order(attribute: e.order_field, descending: e.order_desc)
62
61
  end
63
62
 
64
63
  add_link_entities(e)
65
64
 
66
- if e.has_conditions?
67
- # <filter type="and">
68
- @builder.filter(type: 'and') {
69
- e.conditions.each do |c|
70
- # <condition attribute="opportunityid" operator="eq" value="02dd7344-d04a-e411-a9d3-9cb654950300" />
71
- @builder.condition(attribute: c[:attribute], operator: c[:operator], value: c[:value])
72
- end
73
- }
74
- end
65
+ add_filter_conditions(e) if e.has_conditions?
75
66
 
76
67
  # </entity>
77
68
  }
78
69
  end
79
70
  }
80
- @builder.target!
71
+ builder.target!
81
72
  end
82
73
 
83
74
  protected
84
75
 
76
+ attr_accessor :builder
77
+
85
78
  def add_link_entities(e)
86
79
  e.link_entities.each do |le|
87
80
  # <link-entity name="product" from="productid" to="productid" alias="product" link-type="outer">
88
81
  # NOTE: Use outer join in case related elements do not exist.
89
- @builder.tag!('link-entity', name: le.logical_name, from: le.from, to: le.to, :alias => le.alias, :"link-type" => le.link_type) {
82
+ builder.tag!('link-entity', name: le.logical_name, from: le.from, to: le.to, :alias => le.alias, :"link-type" => le.link_type) {
90
83
  le.attributes.each do |field|
91
84
  # <attribute name="name" />
92
- @builder.attribute(name: field)
93
- end
94
- if le.has_conditions?
95
- # <filter type="and">
96
- @builder.filter(type: 'and') {
97
- le.conditions.each do |c|
98
- # <condition attribute="opportunityid" operator="eq" value="02dd7344-d04a-e411-a9d3-9cb654950300" />
99
- @builder.condition(attribute: c[:attribute], operator: c[:operator], value: c[:value])
100
- end
101
- }
85
+ builder.attribute(name: field)
102
86
  end
87
+ add_filter_conditions(le) if le.has_conditions?
103
88
 
104
89
  # Support nested link-entity elements. Recursive.
105
90
  add_link_entities(le)
@@ -108,6 +93,20 @@ module DynamicsCRM
108
93
  end
109
94
  end
110
95
 
96
+ def add_filter_conditions(e)
97
+ builder.filter(type: 'and') {
98
+ e.conditions.each do |c|
99
+ if 'in' == c[:operator]
100
+ builder.condition(attribute: c[:attribute], operator: c[:operator]) do
101
+ c[:value].each {|v| builder.value v }
102
+ end
103
+ else
104
+ builder.condition(attribute: c[:attribute], operator: c[:operator], value: c[:value])
105
+ end
106
+ end
107
+ }
108
+ end
109
+
111
110
  end
112
111
  end
113
- end
112
+ end
@@ -36,7 +36,40 @@ module DynamicsCRM
36
36
  @picklist_options
37
37
  end
38
38
 
39
- end
39
+ def type
40
+ return @type if @type
41
+
42
+ type_metadata = "./d:AttributeType"
43
+ @type = @document.get_text(type_metadata).to_s
44
+ end
45
+
46
+ def logical_name
47
+ return @logical_name if @logical_name
40
48
 
49
+ logical_name_metadata = "./d:LogicalName"
50
+ @logical_name = @document.get_text(logical_name_metadata).to_s
51
+ end
52
+
53
+ def display_name
54
+ return @display_name if @display_name
55
+
56
+ display_name_metadata = "./d:DisplayName/b:LocalizedLabels/b:LocalizedLabel/b:Label"
57
+ @display_name = @document.get_text(display_name_metadata).to_s
58
+ end
59
+
60
+ def attribute_of
61
+ return @attribute_of if @attribute_of
62
+
63
+ attribute_of_metadata = "./d:AttributeOf"
64
+ @attribute_of = @document.get_text(attribute_of_metadata).to_s
65
+ end
66
+
67
+ def required_level
68
+ return @required_level if @required_level
69
+
70
+ required_level_metadata = "./d:RequiredLevel/b:Value"
71
+ @required_level = @document.get_text(required_level_metadata).to_s
72
+ end
73
+ end
41
74
  end
42
- end
75
+ end
@@ -0,0 +1,25 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ class AttributeQueryExpression
5
+ attr_accessor :criteria, :properties
6
+
7
+ def initialize(criteria, properties)
8
+ @criteria = criteria
9
+ @properties = properties
10
+ end
11
+
12
+ def to_xml(options={})
13
+ namespace = options[:namespace] ? options[:namespace] : 'b'
14
+
15
+ xml = %Q{<#{namespace}:AttributeQuery>}
16
+ xml << @criteria.to_xml({namespace: namespace}) if @criteria
17
+ xml << @properties.to_xml({namespace: namespace}) if @properties
18
+ xml << %Q{</#{namespace}:AttributeQuery>}
19
+
20
+ return xml
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -14,7 +14,7 @@ module DynamicsCRM
14
14
  def attributes
15
15
  return if @attributes.is_a?(Array)
16
16
 
17
- @attributes = document.get_elements("//d:Attributes/d:AttributeMetadata").collect do |attr_metadata|
17
+ @attributes = document.get_elements("d:Attributes/d:AttributeMetadata").collect do |attr_metadata|
18
18
  AttributeMetadata.new(attr_metadata)
19
19
  end
20
20
 
@@ -0,0 +1,29 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ class EntityQueryExpression
5
+
6
+ attr_accessor :criteria, :properties, :attribute_query
7
+
8
+ def initialize(options={})
9
+ @criteria = options[:criteria]
10
+ @properties = options[:properties]
11
+ @attribute_query = options[:attribute_query]
12
+ end
13
+
14
+ def to_xml(options={})
15
+ namespace = options[:namespace] ? options[:namespace] : "b"
16
+
17
+ xml = ""
18
+ xml << @criteria.to_xml({namespace: namespace}) if @criteria
19
+ xml << @properties.to_xml({namespace: namespace}) if @properties
20
+ xml << @attribute_query.to_xml({namespace: namespace}) if @attribute_query
21
+ xml << "<#{namespace}:ExtensionData i:nil='true' />"
22
+ xml << "<#{namespace}:LabelQuery i:nil='true' />"
23
+ xml << "<#{namespace}:RelationshipQuery i:nil='true' />"
24
+ xml
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,47 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ class FilterExpression
5
+ attr_accessor :operator, :conditions
6
+
7
+ def initialize(operator, conditions=[])
8
+ @operator = operator || 'And'
9
+ @conditions = conditions
10
+ end
11
+
12
+ def add_condition(condition)
13
+ @conditions << condition
14
+ end
15
+
16
+ def get_type(value)
17
+ type = value.class.to_s.downcase
18
+ type = "int" if type == "fixnum"
19
+ type = "boolean" if ["trueclass", "falseclass"].include?(type)
20
+ type
21
+ end
22
+
23
+ def to_xml(options={})
24
+ ns = options[:namespace] ? options[:namespace] : "a"
25
+
26
+ expressions = ""
27
+ @conditions.each do |condition|
28
+ attr_name, op, value = condition
29
+
30
+ expressions << %Q{
31
+ <#{ns}:MetadataConditionExpression>
32
+ <#{ns}:PropertyName>#{attr_name}</#{ns}:PropertyName>
33
+ <#{ns}:ConditionOperator>#{op}</#{ns}:ConditionOperator>
34
+ <#{ns}:Value i:type='e:#{get_type(value)}' xmlns:e='http://www.w3.org/2001/XMLSchema'>#{value}</#{ns}:Value>
35
+ </#{ns}:MetadataConditionExpression>}
36
+ end
37
+
38
+ %Q{<#{ns}:Criteria>
39
+ <#{ns}:Conditions>
40
+ #{expressions}
41
+ </#{ns}:Conditions>
42
+ <#{ns}:FilterOperator>#{@operator}</#{ns}:FilterOperator>
43
+ </#{ns}:Criteria>}
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ module DynamicsCRM
2
+ module Metadata
3
+
4
+ class PropertiesExpression
5
+ attr_accessor :properties
6
+
7
+ def initialize(properties=[])
8
+ @properties = properties
9
+ end
10
+
11
+ def to_xml(options={})
12
+ namespace = options[:namespace] ? options[:namespace] + ":" : ""
13
+
14
+ property_set = ''
15
+ if @properties.any?
16
+ property_set = %Q{<#{namespace}PropertyNames xmlns:e="http://schemas.microsoft.com/2003/10/Serialization/Arrays">}
17
+ @properties.each do |name|
18
+ property_set << "<e:string>#{name}</e:string>"
19
+ end
20
+ property_set << "</#{namespace}PropertyNames>"
21
+ end
22
+
23
+ %Q{<#{namespace}Properties>
24
+ <#{namespace}AllProperties>#{property_set.empty?}</#{namespace}AllProperties>
25
+ #{property_set}
26
+ </#{namespace}Properties>}
27
+ end
28
+ end
29
+ end
30
+ end