timfel-active_cmis 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +26 -0
- data/README.md +34 -0
- data/Rakefile +36 -0
- data/TODO +7 -0
- data/active_cmis.gemspec +77 -0
- data/lib/active_cmis/acl.rb +181 -0
- data/lib/active_cmis/acl_entry.rb +26 -0
- data/lib/active_cmis/active_cmis.rb +87 -0
- data/lib/active_cmis/atomic_types.rb +245 -0
- data/lib/active_cmis/attribute_prefix.rb +35 -0
- data/lib/active_cmis/collection.rb +206 -0
- data/lib/active_cmis/document.rb +299 -0
- data/lib/active_cmis/exceptions.rb +82 -0
- data/lib/active_cmis/folder.rb +36 -0
- data/lib/active_cmis/internal/caching.rb +86 -0
- data/lib/active_cmis/internal/connection.rb +222 -0
- data/lib/active_cmis/internal/utils.rb +82 -0
- data/lib/active_cmis/ns.rb +18 -0
- data/lib/active_cmis/object.rb +563 -0
- data/lib/active_cmis/policy.rb +13 -0
- data/lib/active_cmis/property_definition.rb +179 -0
- data/lib/active_cmis/query_result.rb +40 -0
- data/lib/active_cmis/rel.rb +17 -0
- data/lib/active_cmis/relationship.rb +49 -0
- data/lib/active_cmis/rendition.rb +80 -0
- data/lib/active_cmis/repository.rb +327 -0
- data/lib/active_cmis/server.rb +113 -0
- data/lib/active_cmis/type.rb +200 -0
- data/lib/active_cmis/version.rb +8 -0
- data/lib/active_cmis.rb +31 -0
- metadata +111 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
class Document < ActiveCMIS::Object
|
3
|
+
# Returns an ActiveCMIS::Rendition to the content stream or nil if there is none
|
4
|
+
# @return [Rendition]
|
5
|
+
def content_stream
|
6
|
+
if content = data.xpath("at:content", NS::COMBINED).first
|
7
|
+
if content['src']
|
8
|
+
ActiveCMIS::Rendition.new(repository, self, "href" => content['src'], "type" => content["type"])
|
9
|
+
else
|
10
|
+
if content['type'] =~ /\+xml$/
|
11
|
+
content_data = content.to_xml # FIXME: this may not preserve whitespace
|
12
|
+
else
|
13
|
+
content_data = data.unpack("m*").first
|
14
|
+
end
|
15
|
+
ActiveCMIS::Rendition.new(repository, self, "data" => content_data, "type" => content["type"])
|
16
|
+
end
|
17
|
+
elsif content = data.xpath("cra:content", NS::COMBINED).first
|
18
|
+
content.children.each do |node|
|
19
|
+
next unless node.namespace and node.namespace.href == NS::CMIS_REST
|
20
|
+
content_data = node.text if node.name == "base64"
|
21
|
+
content_type = node.text if node.name == "mediaType"
|
22
|
+
end
|
23
|
+
data = content_data.unpack("m*").first
|
24
|
+
ActiveCMIS::Rendition.new(repository, self, "data" => content_data, "type" => content_type)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
cache :content_stream
|
28
|
+
|
29
|
+
# Will reload if renditionFilter was not set or cmis:none, but not in other circumstances
|
30
|
+
# @return [Array<Rendition>]
|
31
|
+
def renditions
|
32
|
+
filter = used_parameters["renditionFilter"]
|
33
|
+
if filter.nil? || filter == "cmis:none"
|
34
|
+
reload
|
35
|
+
end
|
36
|
+
|
37
|
+
links = data.xpath("at:link[@rel = 'alternate']", NS::COMBINED)
|
38
|
+
links.map do |link|
|
39
|
+
ActiveCMIS::Rendition.new(repository, self, link)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
cache :renditions
|
43
|
+
|
44
|
+
# Sets new content to be uploaded, does not alter values you will get from content_stream (for the moment)
|
45
|
+
# @param [Hash] options A hash containing exactly one of :file or :data
|
46
|
+
# @option options [String] :file The name of a file to upload
|
47
|
+
# @option options [#read] :data Data you want to upload (if #length is defined it should give the total length that can be read)
|
48
|
+
# @option options [Boolean] :overwrite (true) Whether the contents should be overwritten (ignored in case of checkin)
|
49
|
+
# @option options [String] :mime_type
|
50
|
+
#
|
51
|
+
# @return [void]
|
52
|
+
def set_content_stream(options)
|
53
|
+
if key.nil?
|
54
|
+
if self.class.content_stream_allowed == "notallowed"
|
55
|
+
raise Error::StreamNotSupported.new("Documents of this type can't have content")
|
56
|
+
end
|
57
|
+
else
|
58
|
+
updatability = repository.capabilities["ContentStreamUpdatability"]
|
59
|
+
if updatability == "none"
|
60
|
+
raise Error::NotSupported.new("Content can't be updated in this repository")
|
61
|
+
elsif updatability == "pwconly" && !working_copy?
|
62
|
+
raise Error::Constraint.new("Content can only be updated for working copies in this repository")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
@updated_contents = options
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns all documents in the version series of this document.
|
69
|
+
# Uses self to represent the version of this document
|
70
|
+
# @return [Collection<Document>, Array(self)]
|
71
|
+
def versions
|
72
|
+
link = data.xpath("at:link[@rel = 'version-history']/@href", NS::COMBINED)
|
73
|
+
if link = link.first
|
74
|
+
Collection.new(repository, link) # Problem: does not in fact use self
|
75
|
+
else
|
76
|
+
# The document is not versionable
|
77
|
+
[self]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
cache :versions
|
81
|
+
|
82
|
+
# Returns self if this is the latest version
|
83
|
+
# Note: There will allways be a latest version in a version series
|
84
|
+
# @return [Document]
|
85
|
+
def latest_version
|
86
|
+
link = data.xpath("at:link[@rel = 'current-version']/@href", NS::COMBINED)
|
87
|
+
if link.first
|
88
|
+
entry = conn.get_atom_entry(link.first.text)
|
89
|
+
self_or_new(entry)
|
90
|
+
else
|
91
|
+
# FIXME: should somehow return the current version even for opencmis
|
92
|
+
self
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns self if this is the working copy
|
97
|
+
# Returns nil if there is no working copy
|
98
|
+
# @return [Document]
|
99
|
+
def working_copy
|
100
|
+
link = data.xpath("at:link[@rel = 'working-copy']/@href", NS::COMBINED)
|
101
|
+
if link.first
|
102
|
+
entry = conn.get_atom_entry(link.first.text)
|
103
|
+
self_or_new(entry)
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def latest?
|
110
|
+
attributes["cmis:isLatestVersion"]
|
111
|
+
end
|
112
|
+
def major?
|
113
|
+
attributes["cmis:isMajorVersion"]
|
114
|
+
end
|
115
|
+
def latest_major?
|
116
|
+
attributes["cmis:isLatestMajorVersion"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def working_copy?
|
120
|
+
return false if key.nil?
|
121
|
+
|
122
|
+
# NOTE: This may not be a sufficient condition, but according to the spec it should be
|
123
|
+
!data.xpath("at:link[@rel = 'via']", NS::COMBINED).empty?
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns information about the checked out status of this document
|
127
|
+
#
|
128
|
+
# @return [Hash,nil] Keys are :by for the owner of the PWC and :id for the CMIS ID, both can be unset according to the spec
|
129
|
+
def version_series_checked_out
|
130
|
+
attributes = self.attributes
|
131
|
+
if attributes["cmis:isVersionSeriesCheckedOut"]
|
132
|
+
result = {}
|
133
|
+
if attributes.has_key? "cmis:versionSeriesCheckedOutBy"
|
134
|
+
result[:by] = attributes["cmis:versionSeriesCheckedOutBy"]
|
135
|
+
end
|
136
|
+
if attributes.has_key? "cmis:versionSeriesCheckedOutId"
|
137
|
+
result[:id] = attributes["cmis:versionSeriesCheckedOutId"]
|
138
|
+
end
|
139
|
+
result
|
140
|
+
else
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# The checkout operation results in a Private Working Copy
|
146
|
+
#
|
147
|
+
# Most properties should be the same as for the document that was checked out,
|
148
|
+
# certain properties may differ such as cmis:objectId and cmis:creationDate.
|
149
|
+
#
|
150
|
+
# The content stream of the PWC may be identical to that of the document
|
151
|
+
# that was checked out, or it may be unset.
|
152
|
+
# @return [Document] The checked out version of this document
|
153
|
+
def checkout
|
154
|
+
body = render_atom_entry(self.class.attributes.reject {|k,v| k != "cmis:objectId"})
|
155
|
+
|
156
|
+
response = conn.post_response(repository.checkedout.url, body)
|
157
|
+
if 200 <= response.code.to_i && response.code.to_i < 300
|
158
|
+
entry = Nokogiri::XML.parse(response.body, nil, nil, Nokogiri::XML::ParseOptions::STRICT).xpath("/at:entry", NS::COMBINED)
|
159
|
+
result = self_or_new(entry)
|
160
|
+
if result.working_copy? # Work around a bug in OpenCMIS where result returned is the version checked out not the PWC
|
161
|
+
result
|
162
|
+
else
|
163
|
+
conn.logger.warn "Repository did not return working copy for checkout operation"
|
164
|
+
result.working_copy
|
165
|
+
end
|
166
|
+
else
|
167
|
+
raise response.body
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# This action may not be permitted (query allowable_actions to see whether it is permitted)
|
172
|
+
# @return [void]
|
173
|
+
def cancel_checkout
|
174
|
+
if !self.class.versionable
|
175
|
+
raise Error::Constraint.new("Object is not versionable, can't cancel checkout")
|
176
|
+
elsif working_copy?
|
177
|
+
conn.delete(self_link)
|
178
|
+
else
|
179
|
+
raise Error::InvalidArgument.new("Not a working copy")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# You can specify whether the new version should be major (defaults to true)
|
184
|
+
# You can optionally give a list of attributes that need to be set.
|
185
|
+
#
|
186
|
+
# This operation exists only for Private Working Copies
|
187
|
+
# @return [Document] The final version that results from the checkin
|
188
|
+
def checkin(major = true, comment = "", updated_attributes = {})
|
189
|
+
if working_copy?
|
190
|
+
update(updated_attributes)
|
191
|
+
result = self
|
192
|
+
updated_aspects([true, major, comment]).each do |hash|
|
193
|
+
result = result.send(hash[:message], *hash[:parameters])
|
194
|
+
end
|
195
|
+
result
|
196
|
+
else
|
197
|
+
raise "Not a working copy"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @return [void]
|
202
|
+
def reload
|
203
|
+
@updated_contents = nil
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
attr_reader :updated_contents
|
209
|
+
|
210
|
+
def render_atom_entry(properties = self.class.attributes, attributes = self.attributes, options = {})
|
211
|
+
super(properties, attributes, options) do |entry|
|
212
|
+
if updated_contents && (options[:create] || options[:checkin])
|
213
|
+
entry["cra"].content do
|
214
|
+
entry["cra"].mediatype(updated_contents[:mime_type] || "application/binary")
|
215
|
+
data = updated_contents[:data] || File.read(updated_contents[:file])
|
216
|
+
entry["cra"].base64 [data].pack("m")
|
217
|
+
end
|
218
|
+
end
|
219
|
+
if block_given?
|
220
|
+
yield(entry)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def updated_aspects(checkin = nil)
|
227
|
+
if working_copy? && !(checkin || repository.pwc_ubdatable)
|
228
|
+
raise Error::NotSupported.new("Updating a PWC without checking in is not supported by repository")
|
229
|
+
end
|
230
|
+
unless working_copy? || checkin.nil?
|
231
|
+
raise Error::NotSupported.new("Can't check in when not checked out")
|
232
|
+
end
|
233
|
+
|
234
|
+
result = super
|
235
|
+
|
236
|
+
unless checkin || key.nil? || updated_contents.nil?
|
237
|
+
# Don't set content_stream separately if it can be done by setting the content during create
|
238
|
+
#
|
239
|
+
# TODO: For checkin we could try to see if we can save it via puts *before* we checkin,
|
240
|
+
# If not checking in we should also try to see if we can actually save it
|
241
|
+
result << {:message => :save_content_stream, :parameters => [updated_contents]}
|
242
|
+
end
|
243
|
+
|
244
|
+
result
|
245
|
+
end
|
246
|
+
|
247
|
+
def self_or_new(entry)
|
248
|
+
if entry.nil?
|
249
|
+
nil
|
250
|
+
elsif entry.xpath("cra:object/c:properties/c:propertyId[@propertyDefinitionId = 'cmis:objectId']/c:value", NS::COMBINED).text == id
|
251
|
+
reload
|
252
|
+
@data = entry
|
253
|
+
self
|
254
|
+
else
|
255
|
+
ActiveCMIS::Object.from_atom_entry(repository, entry)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def create_url
|
260
|
+
if f = parent_folders.first
|
261
|
+
url = f.items.url
|
262
|
+
if self.class.versionable # Necessary in OpenCMIS at least
|
263
|
+
url
|
264
|
+
else
|
265
|
+
Internal::Utils.append_parameters(url, "versioningState" => "none")
|
266
|
+
end
|
267
|
+
else
|
268
|
+
raise Error::NotSupported.new("Creating an unfiled document is not supported by CMIS")
|
269
|
+
# Can't create documents that are unfiled, CMIS does not support it (note this means exceptions should not actually be NotSupported)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def save_content_stream(stream)
|
274
|
+
# Should never occur (is private method)
|
275
|
+
raise "no content to save" if stream.nil?
|
276
|
+
|
277
|
+
# put to link with rel 'edit-media' if it's there
|
278
|
+
# NOTE: cmislib uses the src link of atom:content instead, that might be correct
|
279
|
+
edit_links = Internal::Utils.extract_links(data, "edit-media")
|
280
|
+
if edit_links.length == 1
|
281
|
+
link = edit_links.first
|
282
|
+
elsif edit_links.empty?
|
283
|
+
raise Error.new("No edit-media link, can't save content")
|
284
|
+
else
|
285
|
+
raise Error.new("Too many edit-media links, don't know how to choose")
|
286
|
+
end
|
287
|
+
data = stream[:data] || File.open(stream[:file])
|
288
|
+
content_type = stream[:mime_type] || "application/octet-stream"
|
289
|
+
|
290
|
+
if stream.has_key?(:overwrite)
|
291
|
+
url = Internal::Utils.append_parameters(link, "overwrite" => stream[:overwrite])
|
292
|
+
else
|
293
|
+
url = link
|
294
|
+
end
|
295
|
+
conn.put(url, data, "Content-Type" => content_type)
|
296
|
+
self
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
# The base class for all CMIS exceptions,
|
3
|
+
# HTTP communication errors and the like are not catched by this
|
4
|
+
class Error < StandardError
|
5
|
+
# === Cause
|
6
|
+
# One or more of the input parameters to the service method is missing or invalid
|
7
|
+
class InvalidArgument < Error; end
|
8
|
+
|
9
|
+
# === Cause
|
10
|
+
# The service call has specified an object that does not exist in the Repository
|
11
|
+
class ObjectNotFound < Error; end
|
12
|
+
|
13
|
+
# === Cause
|
14
|
+
# The service method invoked requires an optional capability not supported by the repository
|
15
|
+
class NotSupported < Error; end
|
16
|
+
|
17
|
+
# === Cause
|
18
|
+
# The caller of the service method does not have sufficient permissions to perform the operation
|
19
|
+
class PermissionDenied < Error; end
|
20
|
+
|
21
|
+
# === Cause
|
22
|
+
# Any cause not expressible by another CMIS exception
|
23
|
+
class Runtime < Error; end
|
24
|
+
|
25
|
+
# === Intent
|
26
|
+
# The operation violates a Repository- or Object-level constraint defined in the CMIS domain model
|
27
|
+
#
|
28
|
+
# === Methods
|
29
|
+
# see the CMIS specification
|
30
|
+
class Constraint < Error; end
|
31
|
+
# === Intent
|
32
|
+
# The operation attempts to set the content stream for a Document
|
33
|
+
# that already has a content stream without explicitly specifying the
|
34
|
+
# "overwriteFlag" parameter
|
35
|
+
#
|
36
|
+
# === Methods
|
37
|
+
# see the CMIS specification
|
38
|
+
class ContentAlreadyExists < Error; end
|
39
|
+
# === Intent
|
40
|
+
# The property filter or rendition filter input to the operation is not valid
|
41
|
+
#
|
42
|
+
# === Methods
|
43
|
+
# see the CMIS specification
|
44
|
+
class FilterNotValid < Error; end
|
45
|
+
# === Intent
|
46
|
+
# The repository is not able to store the object that the user is creating/updating due to a name constraint violation
|
47
|
+
#
|
48
|
+
# === Methods
|
49
|
+
# see the CMIS specification
|
50
|
+
class NameConstraintViolation < Error; end
|
51
|
+
# === Intent
|
52
|
+
# The repository is not able to store the object that the user is creating/updating due to an internal storage problam
|
53
|
+
#
|
54
|
+
# === Methods
|
55
|
+
# see the CMIS specification
|
56
|
+
class Storage < Error; end
|
57
|
+
# === Intent
|
58
|
+
#
|
59
|
+
#
|
60
|
+
# === Methods
|
61
|
+
# see the CMIS specification
|
62
|
+
class StreamNotSupported < Error; end
|
63
|
+
# === Intent
|
64
|
+
#
|
65
|
+
#
|
66
|
+
# === Methods
|
67
|
+
# see the CMIS specification
|
68
|
+
class UpdateConflict < Error; end
|
69
|
+
# === Intent
|
70
|
+
#
|
71
|
+
#
|
72
|
+
# === Methods
|
73
|
+
# see the CMIS specification
|
74
|
+
class Versioning < Error; end
|
75
|
+
end
|
76
|
+
|
77
|
+
class HTTPError < StandardError
|
78
|
+
class ServerError < HTTPError; end
|
79
|
+
class ClientError < HTTPError; end
|
80
|
+
class AuthenticationError < HTTPError; end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
class Folder < ActiveCMIS::Object
|
3
|
+
# Returns a collection of all items contained in this folder (1 level deep)
|
4
|
+
# @return [Collection<Document,Folder,Policy>]
|
5
|
+
def items
|
6
|
+
item_feed = Internal::Utils.extract_links(data, 'down', 'application/atom+xml','type' => 'feed')
|
7
|
+
raise "No child feed link for folder" if item_feed.empty?
|
8
|
+
Collection.new(repository, item_feed.first)
|
9
|
+
end
|
10
|
+
cache :items
|
11
|
+
|
12
|
+
def allowed_object_types
|
13
|
+
if attributes["cmis:allowedChildObjectTypeIds"].empty?
|
14
|
+
repository.types.select { |type| type.fileable }
|
15
|
+
else
|
16
|
+
# TODO: it is repository specific if subtypes of the allowed types MAY be filed (line 976)
|
17
|
+
#
|
18
|
+
# There is as far as I can see no other mention of this possibility in the spec, no way to
|
19
|
+
# check if this is so for any specific repository. In addition there is in a few places a
|
20
|
+
# requirement that an error is thrown if the cmis:objectTypeId is not in the list of allowed
|
21
|
+
# values. So for now this is not supported at all.
|
22
|
+
attributes["cmis:allowedChildObjectTypeIds"].map { |type_id| repository.type_by_id(type_id) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
cache :allowed_object_types
|
26
|
+
|
27
|
+
private
|
28
|
+
def create_url
|
29
|
+
if f = parent_folders.first
|
30
|
+
f.items.url
|
31
|
+
else
|
32
|
+
raise "Not possible to create folder without parent folder"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
module Internal
|
3
|
+
module Caching
|
4
|
+
def self.included(cl)
|
5
|
+
cl.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# A module for internal use only.
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# Creates a proxy method for the given method names that caches the result.
|
12
|
+
#
|
13
|
+
# Parameters are passed and ignored, cached values will be returned regardless of the parameters.
|
14
|
+
# @param [Symbol, <Symbol>] Names of methods that will be cached
|
15
|
+
# @return [void]
|
16
|
+
def cache(*names)
|
17
|
+
(@cached_methods ||= []).concat(names).uniq!
|
18
|
+
names.each do |name|
|
19
|
+
alias_method("#{name}__uncached", name)
|
20
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
21
|
+
if private_method_defined? :"#{name}"
|
22
|
+
private_method = true
|
23
|
+
end
|
24
|
+
def #{name}(*a, &b)
|
25
|
+
if defined? @#{name}
|
26
|
+
@#{name}
|
27
|
+
else
|
28
|
+
@#{name} = #{name}__uncached(*a, &b)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
if private_method
|
32
|
+
private :"#{name}__uncached"
|
33
|
+
private :"#{name}"
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
end
|
37
|
+
reloadable
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates methods to retrieve attributes with the given names.
|
41
|
+
#
|
42
|
+
# If the given attribute does not yet exist the method #load_from_data will be called
|
43
|
+
#
|
44
|
+
# @param [Symbol, <Symbol>] Names of desired attributes
|
45
|
+
# @return [void]
|
46
|
+
def cached_reader(*names)
|
47
|
+
(@cached_methods ||= []).concat(names).uniq!
|
48
|
+
names.each do |name|
|
49
|
+
define_method "#{name}" do
|
50
|
+
if instance_variable_defined? "@#{name}"
|
51
|
+
instance_variable_get("@#{name}")
|
52
|
+
else
|
53
|
+
load_from_data # FIXME: make flexible?
|
54
|
+
instance_variable_get("@#{name}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
reloadable
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def reloadable
|
63
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
64
|
+
def __reload
|
65
|
+
#{@cached_methods.inspect}.map do |method|
|
66
|
+
:"@\#{method}"
|
67
|
+
end.select do |iv|
|
68
|
+
instance_variable_defined? iv
|
69
|
+
end.each do |iv|
|
70
|
+
remove_instance_variable iv
|
71
|
+
end + (defined?(super) ? super : [])
|
72
|
+
end
|
73
|
+
private :__reload
|
74
|
+
RUBY
|
75
|
+
unless instance_methods.include? "reload"
|
76
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
77
|
+
def reload
|
78
|
+
__reload
|
79
|
+
end
|
80
|
+
RUBY
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|