ninjudd-active_document 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,43 @@
1
+ = ActiveDocument
2
+
3
+ ActiveDocument is a persistent Model store built on Berkeley DB. It was inspired by
4
+ ActiveRecord, and in some cases, it can be used as a drop-in replacement. The performance
5
+ of ActiveDocument can exceed a traditional ORM for many applications, because the database
6
+ is stored locally and all lookups must use a predefined index. Also, attributes do not
7
+ have to be cast after they are read from the database like in ActiveRecord. Instead, Ruby
8
+ objects are stored directly in Berkeley DB and loaded using Marshal, which makes loading
9
+ objects much faster. For more information on the diffences between Berkeley DB and a
10
+ relational Database, see (http://www.oracle.com/database/docs/Berkeley-DB-v-Relational.pdf).
11
+
12
+ == Usage:
13
+
14
+ require 'active_document'
15
+
16
+ class User < ActiveDocument::Base
17
+ path '/data/bdb'
18
+ accessor :first_name, :last_name, :username, :email_address
19
+
20
+ primary_key :username
21
+ index_by [:last_name, :first_name]
22
+ index_by :email_address, :unique => true
23
+ end
24
+
25
+ User.create(
26
+ :first_name => 'John',
27
+ :last_name => 'Stewart',
28
+ :username => 'lefty',
29
+ :email_address => 'john@thedailyshow.com'
30
+ )
31
+
32
+ User.find('lefty').attributes
33
+ => {:first_name=>"John", :last_name=>"Stewart", :username=>"lefty", :email_address=>"john@thedailyshow.com"}
34
+
35
+ == Install:
36
+
37
+ sudo gem install ninjudd-bdb -s http://gems.github.com
38
+ sudo gem install ninjudd-tuple -s http://gems.github.com
39
+ sudo gem install ninjudd-active_document -s http://gems.github.com
40
+
41
+ == License:
42
+
43
+ Copyright (c) 2009 Justin Balthrop, Geni.com; Published under The MIT License, see LICENSE
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :minor: 0
3
- :patch: 2
3
+ :patch: 3
4
4
  :major: 0
@@ -3,7 +3,7 @@ class ActiveDocument::Base
3
3
  if path
4
4
  @path = path
5
5
  else
6
- @path || (base_class? ? DEFAULT_PATH : super)
6
+ @path || (base_class? ? ActiveDocument::DEFAULT_PATH : super)
7
7
  end
8
8
  end
9
9
 
@@ -13,7 +13,7 @@ class ActiveDocument::Base
13
13
  @database_name = database_name
14
14
  else
15
15
  return nil if self == ActiveDocument::Base
16
- @database_name ||= base_class? ? name.underscore.pluralize : super
16
+ @database_name ||= base_class? ? name.underscore.gsub('/', '-').pluralize : super
17
17
  end
18
18
  end
19
19
 
@@ -38,58 +38,118 @@ class ActiveDocument::Base
38
38
  environment.transaction(&block)
39
39
  end
40
40
 
41
+ def transaction(&block)
42
+ self.class.transaction(&block)
43
+ end
44
+
41
45
  def self.create(*args)
42
46
  model = new(*args)
43
47
  model.save
44
48
  model
45
49
  end
46
50
 
47
- def self.index_by(field, opts = {})
48
- field = field.to_sym
51
+ def self.primary_key(field_or_fields)
52
+ field = define_field_accessor(field_or_fields)
53
+ define_find_methods(field, :field => :primary_key) # find_by_field1_and_field2
54
+
55
+ define_field_accessor(field_or_fields, :primary_key)
56
+ define_find_methods(:primary_key) # find_by_primary_key
57
+
58
+ # Define shortcuts for partial keys.
59
+ if field_or_fields.kind_of?(Array) and not respond_to?(field_or_fields.first)
60
+ define_find_methods(field_or_fields.first, :field => :primary_key, :partial => true) # find_by_field1
61
+ end
62
+ end
63
+
64
+ def self.index_by(field_or_fields, opts = {})
65
+ field = define_field_accessor(field_or_fields)
49
66
  raise "index on #{field} already exists" if databases[field]
50
67
  databases[field] = ActiveDocument::Database.new(opts.merge(:field => field, :model_class => self))
68
+ define_find_methods(field) # find_by_field1_and_field2
69
+
70
+ # Define shortcuts for partial keys.
71
+ if field_or_fields.kind_of?(Array) and not respond_to?(field_or_fields.first)
72
+ define_find_methods(field_or_fields.first, :field => field, :partial => true) # find_by_field1
73
+ end
51
74
  end
52
-
75
+
53
76
  def self.databases
54
- @databases ||= { :id => ActiveDocument::Database.new(:model_class => self, :unique => true) }
77
+ @databases ||= { :primary_key => ActiveDocument::Database.new(:model_class => self, :unique => true) }
55
78
  end
56
79
 
57
80
  def self.open_database
58
- environment.open
59
- databases[:id].open # Must be opened first for associate to work.
60
- databases.values.each {|database| database.open}
81
+ unless @database_open
82
+ environment.open
83
+ databases[:primary_key].open # Must be opened first for associate to work.
84
+ databases.values.each {|database| database.open}
85
+ @database_open = true
86
+ at_exit { close_database }
87
+ end
61
88
  end
62
89
 
63
90
  def self.close_database
64
- databases.values.each {|database| database.close}
65
- environment.close
91
+ if @database_open
92
+ databases.values.each {|database| database.close}
93
+ environment.close
94
+ @database_open = false
95
+ end
66
96
  end
67
97
 
68
- def self.database(field = :id)
98
+ def self.database(field = nil)
99
+ open_database # Make sure the database is open.
100
+ field ||= :primary_key
69
101
  field = field.to_sym
70
102
  database = databases[field]
71
103
  database ||= base_class.database(field) unless base_class?
72
104
  database
73
105
  end
74
106
 
107
+ def database(field = nil)
108
+ self.class.database(field)
109
+ end
110
+
75
111
  def self.find_by(field, *keys)
76
- opts = keys.last.kind_of?(Hash) ? keys.pop : {}
112
+ opts = extract_opts(keys)
113
+ keys << :all if keys.empty?
77
114
  database(field).find(keys, opts)
78
115
  end
79
116
 
80
117
  def self.find(key, opts = {})
81
118
  doc = database.find([key], opts).first
82
- raise ActiveDocument::DocumentNotFound, "Couldn't find #{name} with key #{key}" unless doc
119
+ raise ActiveDocument::DocumentNotFound, "Couldn't find #{name} with id #{key.inspect}" unless doc
83
120
  doc
84
121
  end
122
+
123
+ def self.define_field_accessor(field_or_fields, field = nil)
124
+ if field_or_fields.kind_of?(Array)
125
+ field ||= field_or_fields.join('_and_').to_sym
126
+ define_method(field) do
127
+ field_or_fields.collect {|f| self.send(f)}.flatten
128
+ end
129
+ elsif field
130
+ define_method(field) do
131
+ self.send(field_or_fields)
132
+ end
133
+ else
134
+ field = field_or_fields.to_sym
135
+ end
136
+ field
137
+ end
85
138
 
86
- def self.method_missing(method_name, *args)
87
- method_name = method_name.to_s
88
- if method_name =~ /^find_by_(\w+)$/
89
- field = $1.to_sym
90
- return find_by(field, *args) if databases[field]
139
+ def self.define_find_methods(name, opts = {})
140
+ field = opts[:field] || name
141
+
142
+ (class << self; self; end).instance_eval do
143
+ define_method("find_by_#{name}") do |*args|
144
+ merge_opts(args, :limit => 1, :partial => opts[:partial])
145
+ find_by(field, *args).first
146
+ end
147
+
148
+ define_method("find_all_by_#{name}") do |*args|
149
+ merge_opts(args, :partial => opts[:partial])
150
+ find_by(field, *args)
151
+ end
91
152
  end
92
- raise NoMethodError, "undefined method `#{method_name}' for #{self}"
93
153
  end
94
154
 
95
155
  def self.timestamps
@@ -107,7 +167,6 @@ class ActiveDocument::Base
107
167
  def self.writer(*attrs)
108
168
  attrs.each do |attr|
109
169
  define_method("#{attr}=") do |value|
110
- raise 'cannot modify readonly document' if readonly?
111
170
  attributes[attr] = value
112
171
  end
113
172
  end
@@ -117,7 +176,15 @@ class ActiveDocument::Base
117
176
  reader(*attrs)
118
177
  writer(*attrs)
119
178
  end
120
-
179
+
180
+ def self.save_method(method_name)
181
+ define_method("#{method_name}!") do |*args|
182
+ value = send(method_name, *args)
183
+ save
184
+ value
185
+ end
186
+ end
187
+
121
188
  def initialize(attributes = {})
122
189
  if attributes.kind_of?(String)
123
190
  @attributes, @saved_attributes = Marshal.load(attributes)
@@ -145,20 +212,37 @@ class ActiveDocument::Base
145
212
  return false unless @attributes and @saved_attributes
146
213
 
147
214
  if field
148
- attributes[field] != saved_attributes[field]
215
+ send(field) != saved.send(field)
149
216
  else
150
217
  attributes != saved_attributes
151
218
  end
152
219
  end
153
220
 
221
+ def saved
222
+ raise 'no saved attributes for new record' if new_record?
223
+ @saved ||= self.class.new(saved_attributes)
224
+ end
225
+
154
226
  def save
155
227
  attributes[:updated_at] = Time.now if respond_to?(:updated_at)
156
228
  attributes[:created_at] = Time.now if respond_to?(:created_at) and new_record?
229
+
230
+ opts = {}
231
+ if changed?(:primary_key)
232
+ opts[:create] = true
233
+ saved.destroy
234
+ else
235
+ opts[:create] = new_record?
236
+ end
237
+
157
238
  @saved_attributes = attributes
158
239
  @attributes = nil
159
- self.class.database.save(self)
240
+ @saved = nil
241
+ database.save(self, opts)
242
+ end
160
243
 
161
- true
244
+ def destroy
245
+ database.delete(self)
162
246
  end
163
247
 
164
248
  def _dump(ignored)
@@ -168,4 +252,14 @@ class ActiveDocument::Base
168
252
  def self._load(data)
169
253
  new(data)
170
254
  end
255
+
256
+ private
257
+
258
+ def self.extract_opts(args)
259
+ args.last.kind_of?(Hash) ? args.pop : {}
260
+ end
261
+
262
+ def self.merge_opts(args, opts)
263
+ args << extract_opts(args).merge(opts)
264
+ end
171
265
  end
@@ -4,7 +4,6 @@ class ActiveDocument::Database
4
4
  @field = opts[:field]
5
5
  @unique = opts[:unique]
6
6
  @suffix = opts[:suffix] || (@field ? "by_#{@field}" : nil)
7
- at_exit { close }
8
7
  end
9
8
 
10
9
  attr_accessor :model_class, :field, :db, :suffix
@@ -31,34 +30,73 @@ class ActiveDocument::Database
31
30
 
32
31
  def find(keys, opts = {}, &block)
33
32
  models = block_given? ? BlockArray.new(block) : []
33
+ flags = opts[:modify] ? Bdb::DB_RMW : 0
34
34
 
35
35
  keys.uniq.each do |key|
36
- if key.kind_of?(Range)
36
+ if opts[:partial] and not key.kind_of?(Range)
37
+ first = [*key]
38
+ last = first + [true]
39
+ key = first..last
40
+ end
41
+
42
+ if key == :all
43
+ cursor = db.cursor(transaction, 0)
44
+ if opts[:reverse]
45
+ k,v = cursor.get(nil, nil, Bdb::DB_LAST | flags) # Start at the last item.
46
+ iter = lambda {cursor.get(nil, nil, Bdb::DB_PREV | flags)} # Move backward.
47
+ else
48
+ k,v = cursor.get(nil, nil, Bdb::DB_FIRST | flags) # Start at the first item.
49
+ iter = lambda {cursor.get(nil, nil, Bdb::DB_NEXT | flags)} # Move forward.
50
+ end
51
+
52
+ while k
53
+ models << Marshal.load(v)
54
+ break if opts[:limit] and models.size == opts[:limit]
55
+ k,v = iter.call
56
+ end
57
+ cursor.close
58
+ elsif key.kind_of?(Range)
37
59
  # Fetch a range of keys.
38
60
  cursor = db.cursor(transaction, 0)
39
61
  first = Tuple.dump(key.first)
40
- last = Tuple.dump(key.last)
41
- k,v = cursor.get(first, nil, Bdb::DB_SET_RANGE)
42
- while key.exclude_end? ? k < last : k <= last
62
+ last = Tuple.dump(key.last)
63
+
64
+ # Return false once we pass the end of the range.
65
+ cond = key.exclude_end? ? lambda {|k| k < last} : lambda {|k| k <= last}
66
+
67
+ if opts[:reverse]
68
+ # Position the cursor at the end of the range.
69
+ k,v = cursor.get(last, nil, Bdb::DB_SET_RANGE | flags)
70
+ while k and not cond.call(k)
71
+ k,v = iter.call
72
+ end
73
+
74
+ iter = lambda {cursor.get(nil, nil, Bdb::DB_PREV | flags)} # Move backward.
75
+ cond = lambda {|k| k >= first} # Change the condition to stop when we move past the start.
76
+ else
77
+ k,v = cursor.get(first, nil, Bdb::DB_SET_RANGE | flags) # Start at the beginning of the range.
78
+ iter = lambda {cursor.get(nil, nil, Bdb::DB_NEXT | flags)} # Move forward.
79
+ end
80
+
81
+ while k and cond.call(k)
43
82
  models << Marshal.load(v)
44
83
  break if opts[:limit] and models.size == opts[:limit]
45
- k, v = cursor.get(nil, nil, Bdb::DB_NEXT)
46
- break unless k
84
+ k,v = iter.call
47
85
  end
48
86
  cursor.close
49
87
  else
50
88
  if unique?
51
89
  # There can only be one item for each key.
52
- data = db.get(transaction, Tuple.dump(key), nil, 0)
90
+ data = db.get(transaction, Tuple.dump(key), nil, flags)
53
91
  models << Marshal.load(data) if data
54
92
  else
55
93
  # Have to use a cursor because there may be multiple items with each key.
56
94
  cursor = db.cursor(transaction, 0)
57
- k,v = cursor.get(Tuple.dump(key), nil, Bdb::DB_SET)
95
+ k,v = cursor.get(Tuple.dump(key), nil, Bdb::DB_SET | flags)
58
96
  while k
59
97
  models << Marshal.load(v)
60
98
  break if opts[:limit] and models.size == opts[:limit]
61
- k,v = cursor.get(nil, nil, Bdb::DB_NEXT_DUP)
99
+ k,v = cursor.get(nil, nil, Bdb::DB_NEXT_DUP | flags)
62
100
  end
63
101
  cursor.close
64
102
  end
@@ -69,10 +107,18 @@ class ActiveDocument::Database
69
107
  block_given? ? nil : models
70
108
  end
71
109
 
72
- def save(model)
73
- id = Tuple.dump(model.id)
74
- data = Marshal.dump(model)
75
- db.put(nil, id, data, 0)
110
+ def save(model, opts = {})
111
+ key = Tuple.dump(model.primary_key)
112
+ data = Marshal.dump(model)
113
+ flags = opts[:create] ? Bdb::DB_NOOVERWRITE : 0
114
+ db.put(transaction, key, data, flags)
115
+ rescue Bdb::DbError => e
116
+ raise ActiveDocument::DuplicatePrimaryKey, "primary key #{model.primary_key.inspect} already exists"
117
+ end
118
+
119
+ def delete(model)
120
+ key = Tuple.dump(model.primary_key)
121
+ db.del(transaction, key, 0)
76
122
  end
77
123
 
78
124
  def open
@@ -1,7 +1,6 @@
1
1
  class ActiveDocument::Environment
2
2
  def initialize(path)
3
3
  @path = path
4
- at_exit { close }
5
4
  end
6
5
 
7
6
  attr_reader :path, :env
@@ -13,12 +12,14 @@ class ActiveDocument::Environment
13
12
  def open
14
13
  if @env.nil?
15
14
  @env = Bdb::Env.new(0)
16
- env_flags = Bdb::DB_CREATE | # Create the environment if it does not already exist.
17
- Bdb::DB_INIT_TXN | # Initialize transactions
18
- Bdb::DB_INIT_LOCK | # Initialize locking.
19
- Bdb::DB_INIT_LOG | # Initialize logging
20
- Bdb::DB_INIT_MPOOL # Initialize the in-memory cache.
21
- @env.open(path, env_flags, 0);
15
+ env_flags = Bdb::DB_CREATE | # Create the environment if it does not already exist.
16
+ Bdb::DB_INIT_TXN | # Initialize transactions
17
+ Bdb::DB_INIT_LOCK | # Initialize locking.
18
+ Bdb::DB_INIT_LOG | # Initialize logging
19
+ Bdb::DB_INIT_MPOOL # Initialize the in-memory cache.
20
+ @env.cachesize = 10 * 1024 * 1024
21
+ @env.flags_on = Bdb::DB_TXN_WRITE_NOSYNC
22
+ @env.open(path, env_flags, 0)
22
23
  end
23
24
  end
24
25
 
@@ -34,7 +35,7 @@ class ActiveDocument::Environment
34
35
  parent = @transaction
35
36
  @transaction = env.txn_begin(nil, 0)
36
37
  begin
37
- yield
38
+ value = yield
38
39
  @transaction.commit(0)
39
40
  rescue Exception => e
40
41
  @transaction.abort
@@ -42,6 +43,7 @@ class ActiveDocument::Environment
42
43
  ensure
43
44
  @transaction = parent
44
45
  end
46
+ value
45
47
  else
46
48
  @transaction
47
49
  end
@@ -1,6 +1,7 @@
1
1
  module ActiveDocument
2
2
  class ActiveDocumentError < StandardError; end
3
3
  class DocumentNotFound < ActiveDocumentError; end
4
+ class DuplicatePrimaryKey < ActiveDocumentError; end
4
5
  end
5
6
 
6
7
  require 'bdb'
@@ -6,20 +6,29 @@ class Foo < ActiveDocument::Base
6
6
  path BDB_PATH
7
7
  accessor :foo, :bar, :id
8
8
 
9
+ primary_key :id
9
10
  index_by :foo
10
11
  index_by :bar, :unique => true
11
12
  end
12
13
 
14
+ class Bar < ActiveDocument::Base
15
+ path BDB_PATH
16
+ accessor :foo, :bar
17
+
18
+ primary_key [:foo, :bar]
19
+ index_by :bar
20
+ end
21
+
13
22
  class ActiveDocumentTest < Test::Unit::TestCase
14
- context 'with db open' do
23
+ context 'with foo db open' do
15
24
  setup do
16
25
  FileUtils.mkdir BDB_PATH
17
26
  Foo.open_database
18
27
  end
19
-
28
+
20
29
  teardown do
21
30
  Foo.close_database
22
- FileUtils.rmtree BDB_PATH
31
+ FileUtils.rmtree BDB_PATH
23
32
  end
24
33
 
25
34
  should 'find in database after save' do
@@ -35,11 +44,46 @@ class ActiveDocumentTest < Test::Unit::TestCase
35
44
  end
36
45
  end
37
46
 
38
- should 'find_by_id' do
47
+ should 'find_by_primary_key' do
39
48
  f = Foo.new(:foo => 'BAR', :id => 1)
40
49
  f.save
41
50
 
42
- assert_equal f, Foo.find_by_id(1).first
51
+ assert_equal f, Foo.find_by_primary_key(1)
52
+ assert_equal f, Foo.find_by_id(1)
53
+ end
54
+
55
+ should 'destroy' do
56
+ f = Foo.new(:foo => 'BAR', :id => 1)
57
+ f.save
58
+
59
+ assert_equal f, Foo.find_by_id(1)
60
+
61
+ f.destroy
62
+
63
+ assert_equal nil, Foo.find_by_id(1)
64
+ end
65
+
66
+ should 'change primary key' do
67
+ f = Foo.new(:foo => 'BAR', :id => 1)
68
+ f.save
69
+
70
+ assert_equal f, Foo.find_by_id(1)
71
+
72
+ f.id = 2
73
+ f.save
74
+
75
+ assert_equal nil, Foo.find_by_id(1)
76
+ assert_equal 2, Foo.find_by_id(2).id
77
+ end
78
+
79
+ should 'not overwrite existing model' do
80
+ b1 = Bar.new(:foo => 'foo', :bar => 'bar')
81
+ b1.save
82
+
83
+ assert_raises(ActiveDocument::DuplicatePrimaryKey) do
84
+ b2 = Bar.new(:foo => 'foo', :bar => 'bar')
85
+ b2.save
86
+ end
43
87
  end
44
88
 
45
89
  should 'find by secondary indexes' do
@@ -49,9 +93,9 @@ class ActiveDocumentTest < Test::Unit::TestCase
49
93
  f2 = Foo.new(:foo => 'BAR', :bar => 'FU', :id => 2)
50
94
  f2.save
51
95
 
52
- assert_equal f1, Foo.find_by_bar('FOO').first
53
- assert_equal f2, Foo.find_by_bar('FU').first
54
- assert_equal [f1,f2], Foo.find_by_foo('BAR')
96
+ assert_equal f1, Foo.find_by_bar('FOO')
97
+ assert_equal f2, Foo.find_by_bar('FU')
98
+ assert_equal [f1,f2], Foo.find_all_by_foo('BAR')
55
99
  end
56
100
 
57
101
  should 'find by range' do
@@ -59,11 +103,57 @@ class ActiveDocumentTest < Test::Unit::TestCase
59
103
  Foo.new(:id => i, :foo => "foo-#{i}").save
60
104
  end
61
105
 
62
- assert_equal (5..17).to_a, Foo.find_by_id(5..17).collect {|f| f.id}
63
- assert_equal (5..14).to_a, Foo.find_by_id(5..17, :limit => 10).collect {|f| f.id}
106
+ assert_equal (5..17).to_a, Foo.find_all_by_id(5..17).collect {|f| f.id}
107
+ assert_equal (5..14).to_a, Foo.find_all_by_id(5..17, :limit => 10).collect {|f| f.id}
64
108
 
65
109
  # Mixed keys and ranges.
66
- assert_equal (1..4).to_a + (16..20).to_a, Foo.find_by_id(1..3, 4, 16..20).collect {|f| f.id}
110
+ assert_equal (1..4).to_a + (16..20).to_a, Foo.find_all_by_id(1..3, 4, 16..20).collect {|f| f.id}
111
+ end
112
+
113
+ should 'find all' do
114
+ (1..20).each do |i|
115
+ Foo.new(:id => i, :foo => "foo-#{i}").save
116
+ end
117
+
118
+ assert_equal (1..20).to_a, Foo.find_all_by_id.collect {|f| f.id}
119
+ assert_equal 1, Foo.find_by_id.id # First
120
+ end
121
+
122
+ should 'find with reverse' do
123
+ (1..20).each do |i|
124
+ Foo.new(:id => i, :foo => "foo-#{i}").save
125
+ end
126
+
127
+ assert_equal (1..20).to_a.reverse, Foo.find_all_by_id(:reverse => true).collect {|f| f.id}
128
+ assert_equal (5..17).to_a.reverse, Foo.find_all_by_id(5..17, :reverse => true).collect {|f| f.id}
129
+ assert_equal 20, Foo.find_by_id(:reverse => true).id # Last
67
130
  end
68
131
  end
132
+
133
+ context 'with bar db open' do
134
+ setup do
135
+ FileUtils.mkdir BDB_PATH
136
+ Bar.open_database
137
+ end
138
+
139
+ teardown do
140
+ Bar.close_database
141
+ FileUtils.rmtree BDB_PATH
142
+ end
143
+
144
+ should 'find_by_primary_key and find by id fields' do
145
+ 100.times do |i|
146
+ 100.times do |j|
147
+ b = Bar.new(:foo => i, :bar => j)
148
+ b.save
149
+ end
150
+ end
151
+
152
+ assert_equal [5, 5], Bar.find_by_primary_key([5, 5]).primary_key
153
+ assert_equal [52, 52], Bar.find_by_foo_and_bar([52, 52]).foo_and_bar
154
+ assert_equal (0..99).collect {|i| [42, i]}, Bar.find_all_by_foo(42).collect {|b| b.primary_key}
155
+ assert_equal (0..99).collect {|i| [i, 52]}, Bar.find_all_by_bar(52).collect {|b| b.primary_key}
156
+ end
157
+ end
158
+
69
159
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ninjudd-active_document
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Balthrop
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-19 00:00:00 -07:00
12
+ date: 2009-08-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -22,6 +22,7 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
+ - README.rdoc
25
26
  - VERSION.yml
26
27
  - lib/active_document
27
28
  - lib/active_document/base.rb