exchanger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +53 -0
  3. data/lib/exchanger.rb +79 -0
  4. data/lib/exchanger/attributes.rb +61 -0
  5. data/lib/exchanger/boolean.rb +4 -0
  6. data/lib/exchanger/client.rb +19 -0
  7. data/lib/exchanger/config.rb +32 -0
  8. data/lib/exchanger/dirty.rb +239 -0
  9. data/lib/exchanger/element.rb +161 -0
  10. data/lib/exchanger/elements/attendee.rb +10 -0
  11. data/lib/exchanger/elements/base_folder.rb +61 -0
  12. data/lib/exchanger/elements/calendar_folder.rb +11 -0
  13. data/lib/exchanger/elements/calendar_item.rb +59 -0
  14. data/lib/exchanger/elements/complete_name.rb +16 -0
  15. data/lib/exchanger/elements/contact.rb +49 -0
  16. data/lib/exchanger/elements/contacts_folder.rb +17 -0
  17. data/lib/exchanger/elements/distribution_list.rb +8 -0
  18. data/lib/exchanger/elements/email_address.rb +16 -0
  19. data/lib/exchanger/elements/entry.rb +11 -0
  20. data/lib/exchanger/elements/folder.rb +9 -0
  21. data/lib/exchanger/elements/identifier.rb +7 -0
  22. data/lib/exchanger/elements/im_address.rb +20 -0
  23. data/lib/exchanger/elements/item.rb +86 -0
  24. data/lib/exchanger/elements/mailbox.rb +34 -0
  25. data/lib/exchanger/elements/meeting_cancellation.rb +4 -0
  26. data/lib/exchanger/elements/meeting_message.rb +16 -0
  27. data/lib/exchanger/elements/meeting_request.rb +6 -0
  28. data/lib/exchanger/elements/meeting_response.rb +4 -0
  29. data/lib/exchanger/elements/message.rb +24 -0
  30. data/lib/exchanger/elements/phone_number.rb +13 -0
  31. data/lib/exchanger/elements/physical_address.rb +15 -0
  32. data/lib/exchanger/elements/search_folder.rb +5 -0
  33. data/lib/exchanger/elements/single_recipient.rb +6 -0
  34. data/lib/exchanger/elements/task.rb +5 -0
  35. data/lib/exchanger/elements/tasks_folder.rb +4 -0
  36. data/lib/exchanger/field.rb +139 -0
  37. data/lib/exchanger/operation.rb +110 -0
  38. data/lib/exchanger/operations/create_item.rb +64 -0
  39. data/lib/exchanger/operations/expand_dl.rb +42 -0
  40. data/lib/exchanger/operations/find_folder.rb +55 -0
  41. data/lib/exchanger/operations/find_item.rb +54 -0
  42. data/lib/exchanger/operations/get_folder.rb +55 -0
  43. data/lib/exchanger/operations/get_item.rb +44 -0
  44. data/lib/exchanger/operations/resolve_names.rb +43 -0
  45. data/lib/exchanger/operations/update_item.rb +43 -0
  46. data/lib/exchanger/persistence.rb +27 -0
  47. data/spec/calendar_item_spec.rb +26 -0
  48. data/spec/client_spec.rb +11 -0
  49. data/spec/contact_spec.rb +68 -0
  50. data/spec/element_spec.rb +4 -0
  51. data/spec/field_spec.rb +118 -0
  52. data/spec/folder_spec.rb +13 -0
  53. data/spec/mailbox_spec.rb +9 -0
  54. data/spec/spec_helper.rb +6 -0
  55. metadata +208 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Edgars Beigarts
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ Exchanger
2
+ =========
3
+
4
+ Ruby library for accessing Microsoft Exchange using Exchange Web Services.
5
+ This library tries to make creating and updating items as easy as possible.
6
+ It will keep track of changed properties and will update only them.
7
+
8
+ Supported operations
9
+ ====================
10
+
11
+ * FindItem, GetItem, CreateItem, UpdateItem
12
+ * FindFolder, GetFolder
13
+
14
+
15
+ Installing
16
+ ==========
17
+
18
+ gem install exchanger
19
+
20
+ Configuration
21
+ =============
22
+
23
+ Exchanger.configure do |config|
24
+ config.endpoint = "https://domain.com/EWS/Exchanger.asmx"
25
+ config.username = "username"
26
+ config.password = "password"
27
+ end
28
+
29
+ or configure from YAML
30
+
31
+ Exchanger::Config.instance.from_hash(YAML.load_file("#{Rails.root}/config/exchanger.yml")[Rails.env])
32
+
33
+ Examples
34
+ ========
35
+
36
+ Creating and updating contacts
37
+ ------------------------------
38
+
39
+ folder = Exchanger::Folder.find(:contacts)
40
+ contact = folder.new_contact
41
+ contact.given_name = "Edgars"
42
+ contact.surname = "Beigarts"
43
+ contact.email_addresses = [ Exchanger::EmailAddress.new(:key => "EmailAddress1", :text => "me@domain.com") ]
44
+ contact.phone_numbers = [ Exchanger::PhoneNumber.new(:key => "MobilePhone", :text => "+371 80000000") ]
45
+ contact.save # CreateItem operation
46
+ contact.company_name = "Tieto"
47
+ contact.save # UpdateItem operation
48
+
49
+ Alternatives
50
+ ============
51
+
52
+ * [github.com/jrun/ews-api](http://github.com/jrun/ews-api)
53
+ * [github.com/zenchild/Viewpoint](http://github.com/zenchild/Viewpoint)
@@ -0,0 +1,79 @@
1
+ require "singleton"
2
+ require "delegate"
3
+ require "base64"
4
+
5
+ require "active_support/core_ext"
6
+ require "nokogiri"
7
+ require "httpclient"
8
+ require "net/ntlm"
9
+
10
+ require "exchanger/config"
11
+ require "exchanger/client"
12
+ require "exchanger/boolean"
13
+ require "exchanger/field"
14
+ require "exchanger/dirty"
15
+ require "exchanger/attributes"
16
+ require "exchanger/persistence"
17
+
18
+ # Elements
19
+ require "exchanger/element"
20
+ require "exchanger/elements/identifier"
21
+ require "exchanger/elements/mailbox"
22
+ require "exchanger/elements/single_recipient"
23
+ require "exchanger/elements/attendee"
24
+ require "exchanger/elements/complete_name"
25
+ # Entry elements
26
+ require "exchanger/elements/entry"
27
+ require "exchanger/elements/email_address"
28
+ require "exchanger/elements/phone_number"
29
+ require "exchanger/elements/physical_address"
30
+ require "exchanger/elements/im_address"
31
+ # Folder elements
32
+ require "exchanger/elements/base_folder"
33
+ require "exchanger/elements/folder"
34
+ require "exchanger/elements/calendar_folder"
35
+ require "exchanger/elements/contacts_folder"
36
+ require "exchanger/elements/tasks_folder"
37
+ require "exchanger/elements/search_folder"
38
+ # Item elements
39
+ require "exchanger/elements/item"
40
+ require "exchanger/elements/message"
41
+ require "exchanger/elements/calendar_item"
42
+ require "exchanger/elements/contact"
43
+ require "exchanger/elements/meeting_message"
44
+ require "exchanger/elements/meeting_request"
45
+ require "exchanger/elements/meeting_response"
46
+ require "exchanger/elements/meeting_cancellation"
47
+ require "exchanger/elements/task"
48
+ require "exchanger/elements/distribution_list"
49
+
50
+ # Operations
51
+ require "exchanger/operation"
52
+ require "exchanger/operations/get_folder"
53
+ require "exchanger/operations/find_folder"
54
+ require "exchanger/operations/get_item"
55
+ require "exchanger/operations/find_item"
56
+ require "exchanger/operations/create_item"
57
+ require "exchanger/operations/update_item"
58
+ require "exchanger/operations/resolve_names"
59
+ require "exchanger/operations/expand_dl"
60
+
61
+ module Exchanger
62
+ NS = {
63
+ "xsi" => "http://www.w3.org/2001/XMLSchema-instance",
64
+ "xsd" => "http://www.w3.org/2001/XMLSchema",
65
+ "soap" => "http://schemas.xmlsoap.org/soap/envelope/",
66
+ "m" => "http://schemas.microsoft.com/exchange/services/2006/messages",
67
+ "t" => "http://schemas.microsoft.com/exchange/services/2006/types"
68
+ }
69
+
70
+ class << self
71
+ # The Exchanger +Config+ singleton instance.
72
+ def configure
73
+ config = Config.instance
74
+ block_given? ? yield(config) : config
75
+ end
76
+
77
+ alias :config :configure
78
+ end
79
+ end
@@ -0,0 +1,61 @@
1
+ module Exchanger
2
+ module Attributes
3
+ def attributes=(values = {})
4
+ values.each do |name, value|
5
+ write_attribute(name, value)
6
+ end
7
+ end
8
+
9
+ # Return the attributes hash with indifferent access.
10
+ def attributes
11
+ @attributes.with_indifferent_access
12
+ end
13
+
14
+ # TODO: Add typecasting
15
+ # Read a value from the +Document+ attributes. If the value does not exist
16
+ # it will return nil.
17
+ def read_attribute(name)
18
+ name = name.to_s
19
+ value = @attributes[name]
20
+ accessed(name, value)
21
+ end
22
+
23
+ # TODO: Add typecasting
24
+ def write_attribute(name, value)
25
+ name = name.to_s
26
+ modify(name, @attributes[name], value)
27
+ end
28
+
29
+ def identifier
30
+ if self.class.identifier_name
31
+ @identifier ||= self.send(self.class.identifier_name)
32
+ @identifier.tag_name = self.class.identifier_name.to_s.camelize if @identifier
33
+ @identifier
34
+ end
35
+ end
36
+
37
+ def id
38
+ identifier && identifier.id
39
+ end
40
+
41
+ def change_key
42
+ identifier && identifier.change_key
43
+ end
44
+
45
+ # Override respond_to? so it responds properly for dynamic attributes
46
+ def respond_to?(name)
47
+ (@attributes && @attributes.has_key?(name.to_s)) || super
48
+ end
49
+
50
+ # Used for allowing accessor methods for dynamic attributes
51
+ def method_missing(name, *args)
52
+ attr = name.to_s.sub("=", "")
53
+ return super unless attributes.has_key?(attr)
54
+ if name.to_s.ends_with?("=")
55
+ write_attribute(attr, *args)
56
+ else
57
+ read_attribute(attr)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,4 @@
1
+ module Exchanger
2
+ class Boolean
3
+ end
4
+ end
@@ -0,0 +1,19 @@
1
+ module Exchanger
2
+ # SOAP Client for Exhange Web Services
3
+ class Client
4
+ delegate :endpoint, :timeout, :username, :password, :debug, :to => "Exchanger.config"
5
+
6
+ def initialize
7
+ @client = HTTPClient.new
8
+ @client.debug_dev = STDERR if debug
9
+ @client.set_auth nil, username, password if username
10
+ end
11
+
12
+ # Does the actual HTTP level interaction.
13
+ def request(post_body, headers)
14
+ # @client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
15
+ response = @client.post(endpoint, post_body, headers)
16
+ return { :status => response.status, :body => response.content, :content_type => response.contenttype }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ module Exchanger
2
+ class Config
3
+ include Singleton
4
+
5
+ attr_accessor :endpoint, :timeout, :username, :password, :debug
6
+
7
+ def initialize
8
+ reset
9
+ end
10
+
11
+ # Reset the configuration options to the defaults.
12
+ def reset
13
+ @endpoint = nil
14
+ @timeout = 5
15
+ @username = nil
16
+ @password = nil
17
+ @debug = false
18
+ end
19
+
20
+ # Configure Exchanger client from a hash. This is usually called after parsing a
21
+ # yaml config file such as exchanger.yml.
22
+ #
23
+ # Example:
24
+ #
25
+ # <tt>Exchanger::Config.instance.from_hash({})</tt>
26
+ def from_hash(settings)
27
+ settings.each do |name, value|
28
+ send("#{name}=", value) if respond_to?("#{name}=")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,239 @@
1
+ module Exchanger
2
+ module Dirty
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ # Gets the changes for a specific field.
8
+ #
9
+ # Example:
10
+ #
11
+ # person = Person.new(:title => "Sir")
12
+ # person.title = "Madam"
13
+ # person.attribute_change("title") # [ "Sir", "Madam" ]
14
+ #
15
+ # Returns:
16
+ #
17
+ # An +Array+ containing the old and new values.
18
+ def attribute_change(name)
19
+ modifications[name]
20
+ end
21
+
22
+ # Determines if a specific field has chaged.
23
+ #
24
+ # Example:
25
+ #
26
+ # person = Person.new(:title => "Sir")
27
+ # person.title = "Madam"
28
+ # person.attribute_changed?("title") # true
29
+ #
30
+ # Returns:
31
+ #
32
+ # +true+ if changed, +false+ if not.
33
+ def attribute_changed?(name)
34
+ modifications.include?(name)
35
+ end
36
+
37
+ # Gets the old value for a specific field.
38
+ #
39
+ # Example:
40
+ #
41
+ # person = Person.new(:title => "Sir")
42
+ # person.title = "Madam"
43
+ # person.attribute_was("title") # "Sir"
44
+ #
45
+ # Returns:
46
+ #
47
+ # The old field value.
48
+ def attribute_was(name)
49
+ change = modifications[name]
50
+ change ? change[0] : nil
51
+ end
52
+
53
+ # Gets the names of all the fields that have changed in the document.
54
+ #
55
+ # Example:
56
+ #
57
+ # person = Person.new(:title => "Sir")
58
+ # person.title = "Madam"
59
+ # person.changed # returns [ "title" ]
60
+ #
61
+ # Returns:
62
+ #
63
+ # An +Array+ of changed field names.
64
+ def changed
65
+ modifications.keys
66
+ end
67
+
68
+ # Alerts to whether the document has been modified or not.
69
+ #
70
+ # Example:
71
+ #
72
+ # person = Person.new(:title => "Sir")
73
+ # person.title = "Madam"
74
+ # person.changed? # returns true
75
+ #
76
+ # Returns:
77
+ #
78
+ # +true+ if changed, +false+ if not.
79
+ def changed?
80
+ !modifications.empty?
81
+ end
82
+
83
+ # Gets all the modifications that have happened to the object as a +Hash+
84
+ # with the keys being the names of the fields, and the values being an
85
+ # +Array+ with the old value and new value.
86
+ #
87
+ # Example:
88
+ #
89
+ # person = Person.new(:title => "Sir")
90
+ # person.title = "Madam"
91
+ # person.changes # returns { "title" => [ "Sir", "Madam" ] }
92
+ #
93
+ # Returns:
94
+ #
95
+ # A +Hash+ of changes.
96
+ def changes
97
+ modifications
98
+ end
99
+
100
+ # Call this method after save, so the changes can be properly switched.
101
+ #
102
+ # Example:
103
+ #
104
+ # <tt>person.move_changes</tt>
105
+ def move_changes
106
+ @previous_modifications = modifications.dup
107
+ @modifications = {}
108
+ end
109
+
110
+ # Gets all the modifications that have happened to the object before the
111
+ # object was saved.
112
+ #
113
+ # Example:
114
+ #
115
+ # person = Person.new(:title => "Sir")
116
+ # person.title = "Madam"
117
+ # person.save!
118
+ # person.previous_changes # returns { "title" => [ "Sir", "Madam" ] }
119
+ #
120
+ # Returns:
121
+ #
122
+ # A +Hash+ of changes before save.
123
+ def previous_changes
124
+ @previous_modifications
125
+ end
126
+
127
+ # Resets a changed field back to its old value.
128
+ #
129
+ # Example:
130
+ #
131
+ # person = Person.new(:title => "Sir")
132
+ # person.title = "Madam"
133
+ # person.reset_attribute!("title")
134
+ # person.title # "Sir"
135
+ #
136
+ # Returns:
137
+ #
138
+ # The old field value.
139
+ def reset_attribute!(name)
140
+ value = attribute_was(name)
141
+ if value
142
+ @attributes[name] = value
143
+ modifications.delete(name)
144
+ end
145
+ end
146
+
147
+ # Sets up the modifications hash. This occurs just after the document is
148
+ # instantiated.
149
+ #
150
+ # Example:
151
+ #
152
+ # <tt>document.setup_notifications</tt>
153
+ def setup_modifications
154
+ @accessed ||= {}
155
+ @modifications ||= {}
156
+ @previous_modifications ||= {}
157
+ end
158
+
159
+ # Reset all modifications for the document. This will wipe all the marked
160
+ # changes, but not reset the values.
161
+ #
162
+ # Example:
163
+ #
164
+ # <tt>document.reset_modifications</tt>
165
+ def reset_modifications
166
+ @accessed = {}
167
+ @modifications = {}
168
+ end
169
+
170
+ protected
171
+
172
+ # Audit the original value for a field that can be modified in place.
173
+ #
174
+ # Example:
175
+ #
176
+ # <tt>person.accessed("aliases", [ "007" ])</tt>
177
+ def accessed(name, value)
178
+ @accessed ||= {}
179
+ @accessed[name] = value.dup if (value.is_a?(Array) || value.is_a?(Hash)) && !@accessed.has_key?(name)
180
+ value
181
+ end
182
+
183
+ # Get all normal modifications plus in place potential changes.
184
+ # Also checks changes in array attributes with +Element+ objects.
185
+ #
186
+ # Example:
187
+ #
188
+ # <tt>person.modifications</tt>
189
+ #
190
+ # Returns:
191
+ #
192
+ # All changes to the document.
193
+ def modifications
194
+ @accessed.each_pair do |field, value|
195
+ current = @attributes[field]
196
+ if current != value || (current.is_a?(Array) &&
197
+ current.any? { |v| v.respond_to?(:changed?) && v.changed? })
198
+ @modifications[field] = [ value, current ]
199
+ end
200
+ end
201
+ @accessed.clear
202
+ @modifications
203
+ end
204
+
205
+ # Audit the change of a field's value.
206
+ #
207
+ # Example:
208
+ #
209
+ # <tt>person.modify("name", "Jack", "John")</tt>
210
+ def modify(name, old_value, new_value)
211
+ @attributes[name] = new_value
212
+ if @modifications && (old_value != new_value)
213
+ original = @modifications[name].first if @modifications[name]
214
+ @modifications[name] = [ (original || old_value), new_value ]
215
+ end
216
+ end
217
+
218
+ module ClassMethods #:nodoc:
219
+ # Add the dynamic dirty methods. These are custom methods defined on a
220
+ # field by field basis that wrap the dirty attribute methods.
221
+ #
222
+ # Example:
223
+ #
224
+ # person = Person.new(:title => "Sir")
225
+ # person.title = "Madam"
226
+ # person.title_change # [ "Sir", "Madam" ]
227
+ # person.title_changed? # true
228
+ # person.title_was # "Sir"
229
+ # person.reset_title!
230
+ def add_dirty_methods(name)
231
+ name = name.to_s
232
+ define_method("#{name}_change") { attribute_change(name) } unless instance_methods.include?("#{name}_change")
233
+ define_method("#{name}_changed?") { attribute_changed?(name) } unless instance_methods.include?("#{name}_changed?")
234
+ define_method("#{name}_was") { attribute_was(name) } unless instance_methods.include?("#{name}_was")
235
+ define_method("reset_#{name}!") { reset_attribute!(name) } unless instance_methods.include?("reset_#{name}!")
236
+ end
237
+ end
238
+ end
239
+ end