dm-xml-adapter 0.2 → 0.52
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.
- data/lib/dm-xml-adapter.rb +233 -135
- data/spec/dm-xml-adapter_spec.rb +11 -2
- data/spec/spec_helper.rb +14 -0
- metadata +2 -2
data/lib/dm-xml-adapter.rb
CHANGED
@@ -5,8 +5,70 @@ require 'dm-core/adapters/abstract_adapter'
|
|
5
5
|
require 'pp'
|
6
6
|
gem 'libxml-ruby', '>= 0.8.3'
|
7
7
|
require 'xml'
|
8
|
+
require 'tempfile'
|
9
|
+
|
10
|
+
require 'log4r'
|
11
|
+
|
8
12
|
|
9
13
|
module DataMapper::Adapters
|
14
|
+
|
15
|
+
class XmlAdapterCache
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
# @@logger = Log4r::Logger.new 'cache'
|
19
|
+
# @@logger.outputters = Log4r::Outputter.stdout
|
20
|
+
@mtimes = Hash.new()
|
21
|
+
@classes = Hash.new()
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_mtime(file, mtime)
|
25
|
+
# @@logger.info("Saved mtime, #{file} -> #{mtime}")
|
26
|
+
@mtimes[file] = mtime
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_mtime(file)
|
30
|
+
# @@logger.info("deleting the following mtime: #{file}")
|
31
|
+
@mtimes.delete(file)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_mtime(file)
|
35
|
+
return @mtimes[file]
|
36
|
+
end
|
37
|
+
|
38
|
+
def put(classname, id, object)
|
39
|
+
# @@logger.info("first one: #{@classes.inspect}")
|
40
|
+
# @@logger.info("CACHE INSERT: #{classname} #{id} #{object.inspect}")
|
41
|
+
|
42
|
+
# check to see if we have a classname entry
|
43
|
+
if (@classes[classname] == nil)
|
44
|
+
@classes[classname] = Hash.new
|
45
|
+
end
|
46
|
+
|
47
|
+
hash = @classes[classname]
|
48
|
+
hash[id.to_s] = object
|
49
|
+
# @@logger.info("the hash is: #{hash.inspect}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(classname, id)
|
53
|
+
# @@logger.info("deleted the following from cache: #{classname} #{id.to_s}")
|
54
|
+
hash = @classes[classname]
|
55
|
+
# @@logger.info("the hash is: #{hash.inspect}")
|
56
|
+
unless hash == nil
|
57
|
+
hash.delete(id.to_s)
|
58
|
+
end
|
59
|
+
# @@logger.info("the hash is: #{hash.inspect}")
|
60
|
+
end
|
61
|
+
|
62
|
+
def get(classname, id)
|
63
|
+
# @@logger.info("CACHE FETCH: #{classname} #{id}")
|
64
|
+
# @@logger.info("Master classes: #{@classes.inspect}")
|
65
|
+
hash = @classes[classname]
|
66
|
+
# @@logger.info("the hash is: #{hash.inspect}")
|
67
|
+
object = hash[id]
|
68
|
+
# @@logger.info("The object is: #{object}")
|
69
|
+
return object
|
70
|
+
end
|
71
|
+
end
|
10
72
|
|
11
73
|
class XmlAdapter < AbstractAdapter
|
12
74
|
|
@@ -18,6 +80,11 @@ module DataMapper::Adapters
|
|
18
80
|
@options[:directory] ||= './db'
|
19
81
|
|
20
82
|
@last_used_id = Hash.new
|
83
|
+
|
84
|
+
@cache = XmlAdapterCache.new
|
85
|
+
|
86
|
+
# @@logger = Log4r::Logger.new 'adapter'
|
87
|
+
# @@logger.outputters = Log4r::Outputter.stdout
|
21
88
|
|
22
89
|
end
|
23
90
|
|
@@ -30,22 +97,26 @@ module DataMapper::Adapters
|
|
30
97
|
end
|
31
98
|
|
32
99
|
def create(resources)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
100
|
+
resources.each do |resource|
|
101
|
+
model = resource.model
|
102
|
+
id = find_free_id_for(resource.class.to_s)
|
103
|
+
# find name of key attribute
|
104
|
+
key = model.key.first.name
|
105
|
+
resource.attributes[key] = id
|
106
|
+
resource.instance_variable_set("@" + key.to_s, id)
|
107
|
+
save(resource)
|
108
|
+
end
|
109
|
+
return resources.size
|
43
110
|
end
|
44
111
|
|
45
112
|
def delete(collection)
|
46
113
|
collection.each do |result|
|
47
|
-
|
114
|
+
@last_used_id[result.class.to_s] = result.key.first
|
48
115
|
xml_destroy(result)
|
116
|
+
# also remove from cache
|
117
|
+
@cache.delete(result.model.to_s, result.key.first)
|
118
|
+
# also remove from mtimes
|
119
|
+
@cache.delete_mtime(class_name_to_file(result.model.to_s, result.key.first))
|
49
120
|
end
|
50
121
|
return collection.size
|
51
122
|
end
|
@@ -63,155 +134,182 @@ module DataMapper::Adapters
|
|
63
134
|
end
|
64
135
|
save(obj)
|
65
136
|
end
|
66
|
-
|
137
|
+
return collection
|
67
138
|
end
|
68
139
|
|
69
140
|
def read(query)
|
70
|
-
#puts "READING #{query.conditions}"
|
71
|
-
#puts query.conditions.operands.inspect
|
72
141
|
return filter_result_set(get_all(query.model), query)
|
73
142
|
end
|
74
143
|
|
75
|
-
|
144
|
+
private
|
76
145
|
|
77
146
|
def xml_destroy(resource)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
147
|
+
# take an objects class and ID and figure
|
148
|
+
# out what file it's in
|
149
|
+
# and then remove that file
|
150
|
+
class_name = resource.class.to_s
|
151
|
+
id = resource.key.first
|
152
|
+
file = class_name_to_file(class_name, id)
|
153
|
+
File.unlink(file)
|
154
|
+
end
|
86
155
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
156
|
+
def filter_result_set(objects, query)
|
157
|
+
#puts "before filter: #{objects.inspect}"
|
158
|
+
result_set = objects.clone
|
159
|
+
result_set = query.filter_records(result_set)
|
160
|
+
#puts "After filter: #{result_set.inspect}"
|
161
|
+
return query.filter_records(result_set)
|
162
|
+
end
|
94
163
|
|
95
164
|
def get_all(model)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
165
|
+
objects = Array.new
|
166
|
+
directory = classname_to_dir(model.to_s)
|
167
|
+
if ! File.exists?(directory)
|
168
|
+
return objects
|
169
|
+
end
|
170
|
+
Dir.glob(directory / "*.xml").each do |filename|
|
171
|
+
# TODO: caching at this point, based of mtime of files
|
172
|
+
file = File.new(filename)
|
173
|
+
# see if we have a nice cached version
|
174
|
+
if (@cache.get_mtime(filename) == nil or file.mtime > @cache.get_mtime(filename))
|
175
|
+
#@@logger.info("CACHE MISS")
|
176
|
+
# the file's newer, so we gotta load it up
|
177
|
+
object = file_to_object(filename, model)
|
178
|
+
objects << object
|
179
|
+
@cache.put(model.to_s, object.key.first, object)
|
180
|
+
@cache.set_mtime(filename, file.mtime)
|
181
|
+
else
|
182
|
+
# @@logger.info("YES !!!")
|
183
|
+
#@@logger.info("CACHE HIT")
|
184
|
+
#@@logger.info("the file is: #{file}")
|
185
|
+
# the file was older than what we had so cache is fine
|
186
|
+
objects << @cache.get(model.to_s, file_to_id(filename))
|
112
187
|
end
|
188
|
+
end
|
189
|
+
return objects
|
190
|
+
end
|
113
191
|
|
114
|
-
|
192
|
+
def file_to_id(file)
|
193
|
+
return file.match(/([0-9]+)\.xml/)[1]
|
194
|
+
end
|
195
|
+
|
196
|
+
def is_integer?(number)
|
197
|
+
if number.class == String and number.match(/^\-*[0-9]+$/)
|
198
|
+
return true
|
199
|
+
end
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
|
203
|
+
def file_to_object(file, model)
|
115
204
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
205
|
+
# allocate new object to receive these fields
|
206
|
+
if (model.to_s.match(/\:\:/))
|
207
|
+
modname, clazz = model.to_s.split("::")
|
208
|
+
new_obj = Kernel.const_get(modname).const_get(clazz).new
|
209
|
+
else
|
210
|
+
clazz = model.to_s
|
211
|
+
new_obj = Kernel.const_get(clazz).new
|
212
|
+
end
|
124
213
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
214
|
+
# file is our XML file
|
215
|
+
string = IO.read(file)
|
216
|
+
parser = XML::Parser.string(string)
|
217
|
+
doc = parser.parse
|
218
|
+
root_node = doc.root
|
219
|
+
# iterate over children
|
220
|
+
root_node.children.each do |child|
|
221
|
+
child_class = child.attributes["class"]
|
222
|
+
if child_class != nil
|
223
|
+
# this means we have an attribute
|
224
|
+
if (child_class == "Integer")
|
225
|
+
new_obj.instance_variable_set("@#{child.name}", child.content.to_i)
|
226
|
+
elsif (child_class == "String")
|
227
|
+
new_obj.instance_variable_set("@#{child.name}", child.content)
|
228
|
+
elsif (child_class == "TrueClass")
|
229
|
+
new_obj.instance_variable_set("@#{child.name}", true)
|
230
|
+
elsif (child_class == "FalseClass")
|
231
|
+
new_obj.instance_variable_set("@#{child.name}", false)
|
232
|
+
elsif (child_class == "BigDecimal")
|
233
|
+
new_obj.instance_variable_set("@#{child.name}", BigDecimal.new(child.content))
|
234
|
+
elsif (child_class == "DateTime")
|
235
|
+
new_obj.instance_variable_set("@#{child.name}", DateTime.parse(child.content))
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
148
239
|
|
149
|
-
|
150
|
-
|
240
|
+
return new_obj
|
241
|
+
end
|
151
242
|
|
152
243
|
def find_free_id_for(class_name)
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
244
|
+
# if there are no entries in the directory
|
245
|
+
# or the directory doesn't exist
|
246
|
+
# we need to create it...
|
247
|
+
if ! File.exists?(classname_to_dir(class_name))
|
248
|
+
# default ID
|
249
|
+
return 0
|
250
|
+
end
|
251
|
+
directory = Dir.new(classname_to_dir(class_name))
|
252
|
+
if directory.entries.size == 0
|
253
|
+
return 0
|
254
|
+
end
|
255
|
+
id = @last_used_id[class_name] || 0
|
256
|
+
while true do
|
257
|
+
if ! File.exists?(File.join(directory.path, id.to_s + ".xml"))
|
258
|
+
@last_used_id[class_name] = id
|
259
|
+
return id
|
260
|
+
end
|
261
|
+
id += 1
|
262
|
+
end
|
263
|
+
end
|
173
264
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
265
|
+
def save(resource)
|
266
|
+
# file = File.join(class_name_to_file(resource.class.to_s, resource.key.first))
|
267
|
+
# see if the directory exists, if it doesn't, we need to create it
|
268
|
+
if ! File.exists?(classname_to_dir(resource.class.to_s))
|
269
|
+
FileUtils.mkdir_p(classname_to_dir(resource.class.to_s))
|
270
|
+
end
|
271
|
+
|
272
|
+
# puts resource.model.properties.inspect
|
182
273
|
|
183
|
-
|
184
|
-
|
185
|
-
|
274
|
+
# set up builder
|
275
|
+
out_string = ""
|
276
|
+
xml = Builder::XmlMarkup.new(:target => out_string, :indent => 1)
|
186
277
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
278
|
+
# iterate over the resource and figure out the fields
|
279
|
+
# class_name = resource.model.to_s.gsub(/\:/, "_")
|
280
|
+
xml.tag!("RubyObject", :class => resource.model.to_s) do
|
281
|
+
resource.model.properties.each do |property|
|
282
|
+
#puts "saving prop: #{property.name}"
|
283
|
+
#puts "primitive: #{property.primitive}"
|
284
|
+
value = resource.instance_variable_get("@" + property.name.to_s)
|
285
|
+
# special case for false
|
286
|
+
if property.primitive == TrueClass and value == false
|
196
287
|
xml.tag!(property.name.to_s, false, :class => FalseClass.to_s)
|
197
|
-
|
288
|
+
elsif (value != nil and property.primitive == BigDecimal)
|
289
|
+
xml.tag!(property.name.to_s, value.to_s("F"), :class => BigDecimal)
|
290
|
+
elsif (value != nil)
|
198
291
|
xml.tag!(property.name.to_s, value, :class => property.primitive)
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
xmlfile = File.new(class_name_to_file(resource.class.to_s, resource.key.first), "w")
|
296
|
+
|
297
|
+
tempfile = Tempfile.new("dm-xml-adapter", "/tmp")
|
298
|
+
tempfile.puts out_string
|
299
|
+
tempfile.close
|
300
|
+
FileUtils.mv(tempfile.path,xmlfile.path)
|
301
|
+
return xmlfile
|
302
|
+
end
|
205
303
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
def class_name_to_file(class_name, id)
|
212
|
-
return File.join(classname_to_dir(class_name), id.to_s + ".xml")
|
213
|
-
end
|
304
|
+
def classname_to_dir(class_name)
|
305
|
+
return File.join(@options[:directory],
|
306
|
+
class_name.gsub("/", "_").gsub(":", "_"))
|
307
|
+
end
|
214
308
|
|
309
|
+
def class_name_to_file(class_name, id)
|
310
|
+
return File.join(classname_to_dir(class_name), id.to_s + ".xml")
|
215
311
|
end
|
312
|
+
|
313
|
+
end
|
216
314
|
end
|
217
315
|
|
data/spec/dm-xml-adapter_spec.rb
CHANGED
@@ -4,11 +4,11 @@ require 'pathname'
|
|
4
4
|
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'
|
5
5
|
|
6
6
|
describe DataMapper::Adapters::XmlAdapter do
|
7
|
-
before(:
|
7
|
+
before(:each) do
|
8
8
|
@adapter = DataMapper.setup(:default, {:adapter => 'xml', :directory => 'db'})
|
9
9
|
end
|
10
10
|
|
11
|
-
after(:
|
11
|
+
after(:each) do
|
12
12
|
XMLTest::User.all.each {|u| u.destroy }
|
13
13
|
XMLTest::Post.all.each {|p| p.destroy }
|
14
14
|
AutoQuote.all.each {|aq| aq.destroy }
|
@@ -112,6 +112,15 @@ describe DataMapper::Adapters::XmlAdapter do
|
|
112
112
|
user.destroy.should == true
|
113
113
|
end
|
114
114
|
end
|
115
|
+
|
116
|
+
describe "BigDecimal Support" do
|
117
|
+
it "should support bigdecimals" do
|
118
|
+
user1 = XMLTest::User.create(:name => "Richie Rich", :money => BigDecimal.new("100000000000000.51"))
|
119
|
+
user1.save
|
120
|
+
user1.reload
|
121
|
+
user1.money.class.should == BigDecimal
|
122
|
+
end
|
123
|
+
end
|
115
124
|
|
116
125
|
describe "find not" do
|
117
126
|
it "should find someone with a not clause" do
|
data/spec/spec_helper.rb
CHANGED
@@ -3,6 +3,19 @@ require 'dm-core'
|
|
3
3
|
require 'pathname'
|
4
4
|
require Pathname(__FILE__).dirname.parent.expand_path + 'lib/dm-xml-adapter'
|
5
5
|
|
6
|
+
class Food
|
7
|
+
include DataMapper::Resource
|
8
|
+
|
9
|
+
property :food_id, Serial
|
10
|
+
property :name, String
|
11
|
+
end
|
12
|
+
|
13
|
+
class Drink
|
14
|
+
include DataMapper::Resource
|
15
|
+
|
16
|
+
property :drink_id, Serial
|
17
|
+
property :name, String
|
18
|
+
end
|
6
19
|
|
7
20
|
class AutoQuote
|
8
21
|
include DataMapper::Resource
|
@@ -28,6 +41,7 @@ class User
|
|
28
41
|
property :user_id, Serial
|
29
42
|
property :name, String
|
30
43
|
property :age, Integer
|
44
|
+
property :money, BigDecimal
|
31
45
|
property :created, DateTime
|
32
46
|
property :cool, Boolean
|
33
47
|
property :content, String
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dm-xml-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.52"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Harding
|
@@ -9,7 +9,7 @@ autorequire: dm-xml-adapter
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-14 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|