dm-xml-adapter 0.2 → 0.52

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- resources.each do |resource|
34
- model = resource.model
35
- id = find_free_id_for(resource.class.to_s)
36
- # find name of key attribute
37
- key = model.key.first.name
38
- resource.attributes[key] = id
39
- resource.instance_variable_set("@" + key.to_s, id)
40
- save(resource)
41
- end
42
- return resources.size
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
- @last_used_id[result.class.to_s] = result.key.first
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
- return collection
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
- private
144
+ private
76
145
 
77
146
  def xml_destroy(resource)
78
- # take an objects class and ID and figure
79
- # out what file it's in
80
- # and then remove that file
81
- class_name = resource.class.to_s
82
- id = resource.key.first
83
- file = class_name_to_file(class_name, id)
84
- File.unlink(file)
85
- end
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
- def filter_result_set(objects, query)
88
- #puts "before filter: #{objects.inspect}"
89
- result_set = objects.clone
90
- result_set = query.filter_records(result_set)
91
- #puts "After filter: #{result_set.inspect}"
92
- return query.filter_records(result_set)
93
- end
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
- objects = Array.new
97
- directory = classname_to_dir(model.to_s)
98
- if ! File.exists?(directory)
99
- return objects
100
- end
101
- Dir.glob(directory / "*.xml").each do |entry|
102
- objects << file_to_object(entry, model)
103
- end
104
- return objects
105
- end
106
-
107
- def is_integer?(number)
108
- if number.class == String and number.match(/^\-*[0-9]+$/)
109
- return true
110
- end
111
- return false
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
- def file_to_object(file, model)
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
- # allocate new object to receive these fields
117
- if (model.to_s.match(/\:\:/))
118
- modname, clazz = model.to_s.split("::")
119
- new_obj = Kernel.const_get(modname).const_get(clazz).new
120
- else
121
- clazz = model.to_s
122
- new_obj = Kernel.const_get(clazz).new
123
- end
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
- # file is our XML file
126
- string = IO.read(file)
127
- parser = XML::Parser.string(string)
128
- doc = parser.parse
129
- root_node = doc.root
130
- # iterate over children
131
- root_node.children.each do |child|
132
- child_class = child.attributes["class"]
133
- if child_class != nil
134
- # this means we have an attribute
135
- if (child_class == "Integer")
136
- new_obj.instance_variable_set("@#{child.name}", child.content.to_i)
137
- elsif (child_class == "String")
138
- new_obj.instance_variable_set("@#{child.name}", child.content)
139
- elsif (child_class == "TrueClass")
140
- new_obj.instance_variable_set("@#{child.name}", true)
141
- elsif (child_class == "FalseClass")
142
- new_obj.instance_variable_set("@#{child.name}", false)
143
- elsif (child_class == "DateTime")
144
- new_obj.instance_variable_set("@#{child.name}", DateTime.parse(child.content))
145
- end
146
- end
147
- end
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
- return new_obj
150
- end
240
+ return new_obj
241
+ end
151
242
 
152
243
  def find_free_id_for(class_name)
153
- # if there are no entries in the directory
154
- # or the directory doesn't exist
155
- # we need to create it...
156
- if ! File.exists?(classname_to_dir(class_name))
157
- # default ID
158
- return 1
159
- end
160
- directory = Dir.new(classname_to_dir(class_name))
161
- if directory.entries.size == 0
162
- return 1
163
- end
164
- id = @last_used_id[class_name] || 0
165
- while true do
166
- if ! File.exists?(File.join(directory.path, id.to_s + ".xml"))
167
- @last_used_id[class_name] = id
168
- return id
169
- end
170
- id += 1
171
- end
172
- end
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
- def save(resource)
175
- file = File.join(class_name_to_file(resource.class.to_s, resource.key.first))
176
- # see if the directory exists, if it doesn't, we need to create it
177
- if ! File.exists?(classname_to_dir(resource.class.to_s))
178
- FileUtils.mkdir_p(classname_to_dir(resource.class.to_s))
179
- end
180
- xmlfile = File.new(class_name_to_file(resource.class.to_s, resource.key.first), "w")
181
- # puts resource.model.properties.inspect
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
- # set up builder
184
- outString = ""
185
- xml = Builder::XmlMarkup.new(:target => outString, :indent => 1)
274
+ # set up builder
275
+ out_string = ""
276
+ xml = Builder::XmlMarkup.new(:target => out_string, :indent => 1)
186
277
 
187
- # iterate over the resource and figure out the fields
188
- class_name = resource.model.to_s.gsub(/\:/, "_")
189
- xml.tag!("RubyObject", :class => resource.model.to_s) do
190
- resource.model.properties.each do |property|
191
- #puts "saving prop: #{property.name}"
192
- #puts "primitive: #{property.primitive}"
193
- value = resource.instance_variable_get("@" + property.name.to_s)
194
- # special case for false
195
- if property.primitive == TrueClass and value == false
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
- elsif (value != nil)
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
- end
200
- end
201
- end
202
- xmlfile.puts outString
203
- xmlfile.close
204
- end
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
- def classname_to_dir(class_name)
207
- return File.join(@options[:directory],
208
- class_name.gsub("/", "_").gsub(":", "_"))
209
- end
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
 
@@ -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(:all) do
7
+ before(:each) do
8
8
  @adapter = DataMapper.setup(:default, {:adapter => 'xml', :directory => 'db'})
9
9
  end
10
10
 
11
- after(:all) do
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
@@ -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.2"
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-10-23 00:00:00 -04:00
12
+ date: 2009-12-14 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency