ninjudd-active_document 0.0.2 → 0.0.3

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/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