Class: ActiveDocument::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/active_document/base.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}, saved_attributes = nil) ⇒ Base

Returns a new instance of Base.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/active_document/base.rb', line 241

def initialize(attributes = {}, saved_attributes = nil)
  @attributes       = HashWithIndifferentAccess.new(attributes)       if attributes
  @saved_attributes = HashWithIndifferentAccess.new(saved_attributes) if saved_attributes

  # Initialize defaults if this is a new record.
  if @saved_attributes.nil?
    self.class.defaults.each do |attr, default|
      next if @attributes.has_key?(attr)
      @attributes[attr] = default.is_a?(Proc) ? default.bind(self).call : default.dup
    end
  end

  # Set the partition field in case we are in a with_partition block.
  if partition_by and partition.nil?
    set_method = "#{partition_by}="
    self.send(set_method, database.partition) if respond_to?(set_method)
  end
end

Instance Attribute Details

#saved_attributesObject (readonly)

Returns the value of attribute saved_attributes.



260
261
262
# File 'lib/active_document/base.rb', line 260

def saved_attributes
  @saved_attributes
end

Class Method Details

._load(data) ⇒ Object



375
376
377
# File 'lib/active_document/base.rb', line 375

def self._load(data)
  new(*Marshal.load(data))
end

.accessor(*attrs) ⇒ Object



221
222
223
224
# File 'lib/active_document/base.rb', line 221

def self.accessor(*attrs)
  reader(*attrs)
  writer(*attrs)
end

.bool_accessor(*attrs) ⇒ Object



226
227
228
229
# File 'lib/active_document/base.rb', line 226

def self.bool_accessor(*attrs)
  bool_reader(*attrs)
  writer(*attrs)
end

.bool_reader(*attrs) ⇒ Object



201
202
203
204
205
206
207
208
209
210
211
# File 'lib/active_document/base.rb', line 201

def self.bool_reader(*attrs)
  attrs.each do |attr|
    define_method(attr) do
      !!read_attribute(attr)
    end

    define_method("#{attr}?") do
      !!read_attribute(attr)
    end
  end
end

.checkpoint(opts = {}) ⇒ Object



37
38
39
# File 'lib/active_document/base.rb', line 37

def self.checkpoint(opts = {})
  database.checkpoint(opts)
end

.close_environmentObject



110
111
112
113
# File 'lib/active_document/base.rb', line 110

def self.close_environment
  # Will close all databases in the environment.
  environment.close
end

.count(field, key) ⇒ Object



129
130
131
# File 'lib/active_document/base.rb', line 129

def self.count(field, key)
  database.count(field, key)
end

.create(*args) ⇒ Object



41
42
43
44
45
# File 'lib/active_document/base.rb', line 41

def self.create(*args)
  model = new(*args)
  model.save
  model
end

.databaseObject



21
22
23
# File 'lib/active_document/base.rb', line 21

def self.database
  @database
end

.database_name(database_name = nil) ⇒ Object



11
12
13
14
15
16
17
18
19
# File 'lib/active_document/base.rb', line 11

def self.database_name(database_name = nil)
  if database_name
    raise 'cannot modify database_name after db has been initialized' if @database_name
    @database_name = database_name
  else
    return if self == ActiveDocument::Base 
    @database_name ||= name.underscore.gsub('/', '-').pluralize
  end
end

.default(attr, default) ⇒ Object



189
190
191
# File 'lib/active_document/base.rb', line 189

def self.default(attr, default)
  defaults[attr] = default
end

.defaults(defaults = {}) ⇒ Object



184
185
186
187
# File 'lib/active_document/base.rb', line 184

def self.defaults(defaults = {})
  @defaults ||= {}
  @defaults.merge!(defaults)
end

.define_field_accessor(field_or_fields, field = nil) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/active_document/base.rb', line 133

def self.define_field_accessor(field_or_fields, field = nil)    
  if field_or_fields.kind_of?(Array)
    field ||= field_or_fields.join('_and_').to_sym
    define_method(field) do
      field_or_fields.collect {|f| self.send(f)}.flatten
    end
  elsif field
    define_method(field) do
      self.send(field_or_fields)
    end
  else
    field = field_or_fields.to_sym
  end
  field
end

.define_find_methods(name, config = {}) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/active_document/base.rb', line 149

def self.define_find_methods(name, config = {})
  field = config[:field] || name

  (class << self; self; end).instance_eval do
    define_method("find_by_#{name}") do |*args|
      modify_opts(args) do |opts|
        opts[:limit] = 1
        opts[:partial] ||= config[:partial]
      end
      find_by(field, *args).first
    end

    define_method("find_all_by_#{name}") do |*args|
      modify_opts(args) do |opts|
        opts[:partial] ||= config[:partial]
      end
      find_by(field, *args)
    end
  end
end

.define_partial_shortcuts(fields, primary_field) ⇒ Object



170
171
172
173
174
175
176
177
178
# File 'lib/active_document/base.rb', line 170

def self.define_partial_shortcuts(fields, primary_field)
  return unless fields.kind_of?(Array)

  (fields.size - 1).times do |i|
    name = fields[0..i].join('_and_')
    next if respond_to?("find_by_#{name}")
    define_find_methods(name, :field => primary_field, :partial => true)
  end
end

.find(key, opts = {}) ⇒ Object



123
124
125
126
127
# File 'lib/active_document/base.rb', line 123

def self.find(key, opts = {})
  doc = database.get(key, opts).first
  raise ActiveDocument::DocumentNotFound, "Couldn't find #{name} with id #{key.inspect}" unless doc
  doc
end

.find_by(field, *args) ⇒ Object



115
116
117
118
119
120
121
# File 'lib/active_document/base.rb', line 115

def self.find_by(field, *args)
  opts = extract_opts(args)
  opts[:field] = field
  args << :all if args.empty?
  args << opts
  database.get(*args)
end

.index_by(field_or_fields, opts = {}) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/active_document/base.rb', line 97

def self.index_by(field_or_fields, opts = {})
  raise "cannot have a multi_key index on an aggregate key" if opts[:multi_key] and field_or_fields.kind_of?(Array)

  field = define_field_accessor(field_or_fields)
  database.index_by(field, opts)

  field_name = opts[:multi_key] ? field.to_s.singularize : field
  define_find_methods(field_name, :field => field) # find_by_field1_and_field2

  # Define shortcuts for partial keys.
  define_partial_shortcuts(field_or_fields, field)
end

.partition_byObject



85
86
87
# File 'lib/active_document/base.rb', line 85

def self.partition_by
  @partition_by
end

.partitionsObject



71
72
73
# File 'lib/active_document/base.rb', line 71

def self.partitions
  database.partitions
end

.path(path = nil) ⇒ Object



2
3
4
5
# File 'lib/active_document/base.rb', line 2

def self.path(path = nil)    
  @path = path if path
  @path ||= (self == ActiveDocument::Base ? nil : ActiveDocument::Base.path)
end

.path=(path) ⇒ Object



7
8
9
# File 'lib/active_document/base.rb', line 7

def self.path=(path)
  @path = path
end

.primary_key(field_or_fields, opts = {}) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/active_document/base.rb', line 47

def self.primary_key(field_or_fields, opts = {})
  raise 'primary key already defined' if @database

  if @partition_by = opts[:partition_by]
    @database = Bdb::PartitionedDatabase.new(database_name, :path  => path, :partition_by => @partition_by)
    (class << self; self; end).instance_eval do
      alias_method opts[:partition_by].to_s.pluralize, :partitions
      alias_method "with_#{opts[:partition_by]}", :with_partition
      alias_method "with_each_#{opts[:partition_by]}", :with_each_partition
    end
  else
    @database = Bdb::Database.new(database_name, :path => path)
  end

  field = define_field_accessor(field_or_fields)
  define_find_methods(field, :field => :primary_key) # find_by_field1_and_field2
  
  define_field_accessor(field_or_fields, :primary_key)
  define_find_methods(:primary_key) # find_by_primary_key

  # Define shortcuts for partial keys.
  define_partial_shortcuts(field_or_fields, :primary_key)
end

.reader(*attrs) ⇒ Object



193
194
195
196
197
198
199
# File 'lib/active_document/base.rb', line 193

def self.reader(*attrs)
  attrs.each do |attr|
    define_method(attr) do
      read_attribute(attr)
    end
  end
end

.save_method(method_name) ⇒ Object



231
232
233
234
235
236
237
238
239
# File 'lib/active_document/base.rb', line 231

def self.save_method(method_name)
  define_method("#{method_name}!") do |*args|
    transaction do 
      value = send(method_name, *args)
      save
      value
    end
  end
end

.timestampsObject



180
181
182
# File 'lib/active_document/base.rb', line 180

def self.timestamps
  reader(:created_at, :updated_at, :deleted_at)
end

.transaction(&block) ⇒ Object



29
30
31
# File 'lib/active_document/base.rb', line 29

def self.transaction(&block)
  database.transaction(&block)
end

.uncloned_fields(*attrs) ⇒ Object



319
320
321
322
323
324
325
# File 'lib/active_document/base.rb', line 319

def self.uncloned_fields(*attrs)
  if attrs.empty?
    @uncloned_fields ||= [:created_at, :updated_at, :deleted_at]
  else
    uncloned_fields.concat(attrs)
  end
end

.with_each_partition(&block) ⇒ Object



79
80
81
82
83
# File 'lib/active_document/base.rb', line 79

def self.with_each_partition(&block)
  database.partitions.each do |partition|
    database.with_partition(partition, &block)
  end
end

.with_partition(partition, &block) ⇒ Object



75
76
77
# File 'lib/active_document/base.rb', line 75

def self.with_partition(partition, &block)
  database.with_partition(partition, &block)
end

.writer(*attrs) ⇒ Object



213
214
215
216
217
218
219
# File 'lib/active_document/base.rb', line 213

def self.writer(*attrs)
  attrs.each do |attr|
    define_method("#{attr}=") do |value|
      attributes[attr] = value
    end
  end
end

Instance Method Details

#==(other) ⇒ Object



286
287
288
289
# File 'lib/active_document/base.rb', line 286

def ==(other)
  return false unless other.class == self.class
  attributes == other.attributes
end

#_dump(ignored) ⇒ Object



369
370
371
372
373
# File 'lib/active_document/base.rb', line 369

def _dump(ignored)
  attributes       = @attributes.to_hash       if @attributes
  saved_attributes = @saved_attributes.to_hash if @saved_attributes
  Marshal.dump([attributes, saved_attributes])
end

#attributesObject



263
264
265
# File 'lib/active_document/base.rb', line 263

def attributes
  @attributes ||= Marshal.load(Marshal.dump(saved_attributes))
end

#changed?(field = nil) ⇒ Boolean

Returns:

  • (Boolean)


295
296
297
298
299
300
301
302
303
# File 'lib/active_document/base.rb', line 295

def changed?(field = nil)
  return false unless @attributes and @saved_attributes

  if field
    send(field) != saved.send(field)
  else
    attributes != saved_attributes
  end
end

#clone(changed_attributes = {}) ⇒ Object



310
311
312
313
314
315
316
317
# File 'lib/active_document/base.rb', line 310

def clone(changed_attributes = {})
  cloned_attributes = Marshal.load(Marshal.dump(attributes))
  uncloned_fields.each do |attr|
    cloned_attributes.delete(attr)
  end
  cloned_attributes.merge!(changed_attributes)
  self.class.new(cloned_attributes)
end

#databaseObject



25
26
27
# File 'lib/active_document/base.rb', line 25

def database
  self.class.database
end

#deleteObject



354
355
356
357
# File 'lib/active_document/base.rb', line 354

def delete
  raise 'cannot delete a record without deleted_at attribute' unless respond_to?(:deleted_at)
  saved_attributes[:deleted_at] = Time.now
end

#deleted?Boolean

Returns:

  • (Boolean)


365
366
367
# File 'lib/active_document/base.rb', line 365

def deleted?
  respond_to?(:deleted_at) and not deleted_at.nil?
end

#destroyObject



349
350
351
# File 'lib/active_document/base.rb', line 349

def destroy
  database.delete(primary_key)
end

#new_record?Boolean

Returns:

  • (Boolean)


291
292
293
# File 'lib/active_document/base.rb', line 291

def new_record?
  @saved_attributes.nil?
end

#partitionObject



93
94
95
# File 'lib/active_document/base.rb', line 93

def partition
  send(partition_by) if partition_by
end

#partition_byObject



89
90
91
# File 'lib/active_document/base.rb', line 89

def partition_by
  self.class.partition_by
end

#read_attribute(attr) ⇒ Object



267
268
269
270
271
272
273
# File 'lib/active_document/base.rb', line 267

def read_attribute(attr)
  if @attributes.nil?
    saved_attributes[attr]
  else
    attributes[attr]
  end
end

#save(opts = {}) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/active_document/base.rb', line 327

def save(opts = {})
  time = opts[:updated_at] || Time.now
  attributes[:updated_at] = time   if respond_to?(:updated_at)
  attributes[:created_at] ||= time if respond_to?(:created_at) and new_record?

  opts = {}
  if changed?(:primary_key) or (partition_by and changed?(partition_by))
    opts[:create] = true
    saved.destroy
  else
    opts[:create] = new_record?
  end

  @saved_attributes = attributes
  @attributes       = nil
  @saved            = nil
  database.set(primary_key, self, opts)
rescue Bdb::DbError => e
  raise(ActiveDocument::DuplicatePrimaryKey, e) if e.code == Bdb::DB_KEYEXIST
  raise(e)
end

#savedObject



305
306
307
308
# File 'lib/active_document/base.rb', line 305

def saved
  raise 'no saved attributes for new record' if new_record?
  @saved ||= self.class.new(saved_attributes)
end

#to_json(*args) ⇒ Object



282
283
284
# File 'lib/active_document/base.rb', line 282

def to_json(*args)
  attributes.to_json(*args)
end

#transaction(&block) ⇒ Object



33
34
35
# File 'lib/active_document/base.rb', line 33

def transaction(&block)
  self.class.transaction(&block)
end

#undeleteObject



360
361
362
363
# File 'lib/active_document/base.rb', line 360

def undelete
  raise 'cannot undelete a record without deleted_at attribute' unless respond_to?(:deleted_at)
  saved_attributes.delete(:deleted_at)
end

#update_attributes(attrs = {}) ⇒ Object



276
277
278
279
280
# File 'lib/active_document/base.rb', line 276

def update_attributes(attrs = {})
  attrs.each do |field, value|
    self.send("#{field}=", value)
  end
end