exchanger 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +53 -0
- data/lib/exchanger.rb +79 -0
- data/lib/exchanger/attributes.rb +61 -0
- data/lib/exchanger/boolean.rb +4 -0
- data/lib/exchanger/client.rb +19 -0
- data/lib/exchanger/config.rb +32 -0
- data/lib/exchanger/dirty.rb +239 -0
- data/lib/exchanger/element.rb +161 -0
- data/lib/exchanger/elements/attendee.rb +10 -0
- data/lib/exchanger/elements/base_folder.rb +61 -0
- data/lib/exchanger/elements/calendar_folder.rb +11 -0
- data/lib/exchanger/elements/calendar_item.rb +59 -0
- data/lib/exchanger/elements/complete_name.rb +16 -0
- data/lib/exchanger/elements/contact.rb +49 -0
- data/lib/exchanger/elements/contacts_folder.rb +17 -0
- data/lib/exchanger/elements/distribution_list.rb +8 -0
- data/lib/exchanger/elements/email_address.rb +16 -0
- data/lib/exchanger/elements/entry.rb +11 -0
- data/lib/exchanger/elements/folder.rb +9 -0
- data/lib/exchanger/elements/identifier.rb +7 -0
- data/lib/exchanger/elements/im_address.rb +20 -0
- data/lib/exchanger/elements/item.rb +86 -0
- data/lib/exchanger/elements/mailbox.rb +34 -0
- data/lib/exchanger/elements/meeting_cancellation.rb +4 -0
- data/lib/exchanger/elements/meeting_message.rb +16 -0
- data/lib/exchanger/elements/meeting_request.rb +6 -0
- data/lib/exchanger/elements/meeting_response.rb +4 -0
- data/lib/exchanger/elements/message.rb +24 -0
- data/lib/exchanger/elements/phone_number.rb +13 -0
- data/lib/exchanger/elements/physical_address.rb +15 -0
- data/lib/exchanger/elements/search_folder.rb +5 -0
- data/lib/exchanger/elements/single_recipient.rb +6 -0
- data/lib/exchanger/elements/task.rb +5 -0
- data/lib/exchanger/elements/tasks_folder.rb +4 -0
- data/lib/exchanger/field.rb +139 -0
- data/lib/exchanger/operation.rb +110 -0
- data/lib/exchanger/operations/create_item.rb +64 -0
- data/lib/exchanger/operations/expand_dl.rb +42 -0
- data/lib/exchanger/operations/find_folder.rb +55 -0
- data/lib/exchanger/operations/find_item.rb +54 -0
- data/lib/exchanger/operations/get_folder.rb +55 -0
- data/lib/exchanger/operations/get_item.rb +44 -0
- data/lib/exchanger/operations/resolve_names.rb +43 -0
- data/lib/exchanger/operations/update_item.rb +43 -0
- data/lib/exchanger/persistence.rb +27 -0
- data/spec/calendar_item_spec.rb +26 -0
- data/spec/client_spec.rb +11 -0
- data/spec/contact_spec.rb +68 -0
- data/spec/element_spec.rb +4 -0
- data/spec/field_spec.rb +118 -0
- data/spec/folder_spec.rb +13 -0
- data/spec/mailbox_spec.rb +9 -0
- data/spec/spec_helper.rb +6 -0
- 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.
|
data/README.md
ADDED
@@ -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)
|
data/lib/exchanger.rb
ADDED
@@ -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,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
|