dynamics_crm 0.6.0 → 0.7.1

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