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.
@@ -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