Class: ActiveCouch::Base

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

Constant Summary collapse

SPECIAL_MEMBERS =
%w(attributes associations connection callbacks)
DEFAULT_ATTRIBUTES =
%w(id rev)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) ⇒ Base

Returns a new instance of Base.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/active_couch/base.rb', line 6

def initialize(params = {})
  # Object instance variable
  @attributes = {}; @associations = {}; @callbacks = Hash.new; @connection = self.class.connection
  klass_atts = self.class.attributes; klass_assocs = self.class.associations; klass_callbacks = self.class.callbacks
  # ActiveCouch::Connection object will be readable in every 
  # object instantiated from a subclass of ActiveCouch::Base
  SPECIAL_MEMBERS.each do |k|
    self.instance_eval "def #{k}; @#{k}; end"
  end
  
  klass_atts.each_key do |k|
    @attributes[k] = klass_atts[k].dup
    self.instance_eval "def #{k}; attributes[:#{k}].value; end"
    self.instance_eval "def #{k}=(val); attributes[:#{k}].value = val; end"
  end
  
  klass_assocs.each_key do |k|
    @associations[k] = HasManyAssociation.new(klass_assocs[k].name, :class => klass_assocs[k].klass)
    self.instance_eval "def #{k}; associations[:#{k}].container; end"
    # If you have has_many :people, this will add a method called add_person to the object instantiated
    # from the class
    self.instance_eval "def add_#{Inflector.singularize(k)}(val); associations[:#{k}].push(val); end"
  end
  
  klass_callbacks.each_key do |k|
    @callbacks[k] = klass_callbacks[k].dup
  end
  
  DEFAULT_ATTRIBUTES.each do |x|
    self.instance_eval "def #{x}; _#{x}; end"
    self.instance_eval "def #{x}=(val); self._#{x}=(val); end"
  end
  
  # Set any instance variables if any, which are present in the params hash
  from_hash(params)
end

Class Method Details

.base_classObject



382
383
384
# File 'lib/active_couch/base.rb', line 382

def base_class
  class_of_active_couch_descendant(self)
end

.create(arguments) ⇒ Object

Initializes a new subclass of ActiveCouch::Base and saves in the CouchDB database as a new document

Example:

class Person < ActiveCouch::Base
  has :name
end

person = Person.create(:name => "McLovin")
person.id.nil? # false
person.new?    # false


307
308
309
310
311
312
313
314
315
# File 'lib/active_couch/base.rb', line 307

def create(arguments)
  unless arguments.is_a?(Hash)
    raise ArgumentError, "The arguments must be a Hash"
  else
    new_record = self.new(arguments)
    new_record.save
    new_record
  end
end

.database_nameObject

Returns the CouchDB database name that’s backing this model. The database name is guessed from the name of the class somewhat similar to ActiveRecord conventions.

Examples:

class Invoice < ActiveCouch::Base; end;
file                  class               database_name
invoice.rb            Invoice             invoices

class Invoice < ActiveCouch::Base; class Lineitem < ActiveCouch::Base; end; end;
file                  class               database_name
invoice.rb            Invoice::Lineitem   invoice_lineitems

module Invoice; class Lineitem < ActiveCouch::Base; end; end;
file                  class               database_name
invoice/lineitem.rb   Invoice::Lineitem   lineitems

You can override this method or use set_database_name to override this class method to allow for names that can’t be inferred.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/active_couch/base.rb', line 160

def database_name
  base = base_class
  name = (unless self == base
    base.database_name
  else
    # Nested classes are prefixed with singular parent database name.
    if parent < ActiveCouch::Base
      contained = Inflector.singularize(parent.database_name)
      contained << '_'
    end
    "#{contained}#{Inflector.underscore(Inflector.demodulize(Inflector.pluralize(base.name)))}"
  end)
  set_database_name(name)
  name
end

.define_attr_method(name, value = nil, &block) ⇒ Object

Defines an “attribute” method. A new (class) method will be created with the given name. If a value is specified, the new method will return that value (as a string). Otherwise, the given block will be used to compute the value of the method.

The original method, if it exists, will be aliased, with the new name being prefixed with “original_”. This allows the new method to access the original value.

This method is stolen from ActiveRecord.

Example:

class Foo < ActiveCouch::Base
  define_attr_method :database_name, 'foo'
  # OR
  define_attr_method(:database_name) do
    original_database_name + '_legacy'
  end
end


356
357
358
359
360
361
362
363
# File 'lib/active_couch/base.rb', line 356

def define_attr_method(name, value = nil, &block)
  metaclass.send(:alias_method, "original_#{name}", name)
  if block_given?
    meta_def name, &block
  else
    metaclass.class_eval "def #{name}; #{value.to_s.inspect}; end"
  end
end

.delete(options = {}) ⇒ Object

Deletes a document from the CouchDB database, based on the id and rev parameters passed to it. Returns true if the document has been deleted

Example:

class Person < ActiveCouch::Base
  has :name
end

Person.delete(:id => 'abc-def', :rev => '1235')


326
327
328
329
330
331
332
333
# File 'lib/active_couch/base.rb', line 326

def delete(options = {})
  if options.nil? || !options.has_key?(:id) || !options.has_key?(:rev)
    raise ArgumentError, "You must specify both an id and a rev for the document to be deleted"
  end
  response = connection.delete("/#{self.database_name}/#{options[:id]}?rev=#{options[:rev]}")
  # Returns true if the 
  response.code == '202'
end

.find(*arguments) ⇒ Object

Retrieves one or more object(s) from a CouchDB database, based on the search parameters given.

Example:

class Person < ActiveCouch::Base
  has :name
end

# This returns a single instance of an ActiveCouch::Base subclass
people = Person.find(:first, :params => {:name => "McLovin"})

# This returns an array of ActiveCouch::Base subclass instances
person = Person.find(:all, :params => {:name => "McLovin"})


285
286
287
288
289
290
291
292
293
294
# File 'lib/active_couch/base.rb', line 285

def find(*arguments)
  scope = arguments.slice!(0)
  options = arguments.slice!(0) || {}
  
  case scope
    when :all    then find_every(options)
    when :first  then find_every(options).first
    else              raise ArgumentError("find must have the first parameter as either :all or :first")
  end
end

.from_json(json) ⇒ Object

Initializes an object of a subclass of ActiveCouch::Base based on a JSON representation of the object.

Example:

class Person < ActiveCouch::Base
  has :name
end

person = Person.from_json('{"name":"McLovin"}')
person.name # "McLovin"


266
267
268
269
270
# File 'lib/active_couch/base.rb', line 266

def from_json(json)
  hash = JSON.parse(json)
  # Create new based on parsed 
  self.new(hash)
end

.has(name, options = {}) ⇒ Object

Defines an attribute for a subclass of ActiveCouch::Base. The parameters for this method include name, which is the name of the attribute as well as an options hash.

The options hash can contain the key ‘which_is’ which can have possible values :text, :decimal, :number. It can also contain the key ‘with_default_value’ which can set a default value for each attribute defined in the subclass of ActiveCouch::Base

Examples:

class Person < ActiveCouch::Base
  has :name
end

person = Person.new
p.name.methods.include?(:name) # true
p.name.methods.include?(:name=) # false

class AgedPerson < ActiveCouch::Base
  has :age, :which_is => :number, :with_default_value = 18
end

person = AgedPerson.new
person.age # 18


228
229
230
231
232
233
# File 'lib/active_couch/base.rb', line 228

def has(name, options = {})
  unless name.is_a?(String) || name.is_a?(Symbol)
    raise ArgumentError, "#{name} is neither a String nor a Symbol"
  end
  @attributes[name] = Attribute.new(name, options)  
end

.has_many(name, options = {}) ⇒ Object

Defines an array of objects which are ‘children’ of this class. The has_many function guesses the class of the child, based on the name of the association, but can be over-ridden by the :class key in the options hash.

Examples:

class Person < ActiveCouch::Base
  has :name
end

class GrandPerson < ActiveCouch::Base
  has_many :people # which will create an empty array which can contain 
                   # Person objects
end


249
250
251
252
253
254
# File 'lib/active_couch/base.rb', line 249

def has_many(name, options = {})
  unless name.is_a?(String) || name.is_a?(Symbol)
    raise ArgumentError, "#{name} is neither a String nor a Symbol"
  end
  @associations[name] = HasManyAssociation.new(name, options)
end

.inherited(subklass) ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/active_couch/base.rb', line 365

def inherited(subklass)
  subklass.class_eval do
    include ActiveCouch::Callbacks
  end
  
  # TODO: Need a cleaner way to do this
  subklass.instance_variable_set "@attributes", { :_id => Attribute.new(:_id, :with_default_value => nil), 
                                                  :_rev => Attribute.new(:_rev, :with_default_value => nil) }
  subklass.instance_variable_set "@associations", {}
  subklass.instance_variable_set "@callbacks", Hash.new([])
  subklass.instance_variable_set "@connections", nil
                                                  
  SPECIAL_MEMBERS.each do |k|
    subklass.instance_eval "def #{k}; @#{k}; end"
  end
end

.set_database_name(database = nil, &block) ⇒ Object Also known as: database_name=

Sets the database name to the given value, or (if the value is nil or false) to the value returned by the given block. Useful for setting database names that can’t be automatically inferred from the class name.

This method is aliased as database_name=.

Example:

class Post < ActiveCouch::Base
  set_database_name 'legacy_posts'
end


186
187
188
# File 'lib/active_couch/base.rb', line 186

def set_database_name(database = nil, &block)
  define_attr_method(:database_name, database, &block)
end

.site(site) ⇒ Object

Sets the site which the ActiveCouch object has to connect to, which initializes an ActiveCouch::Connection object.

Example:

class Person < ActiveCouch::Base
  site 'localhost:5984'
end

Person.connection.nil? # false


200
201
202
# File 'lib/active_couch/base.rb', line 200

def site(site)
  @connection = Connection.new(site)
end

Instance Method Details

#deleteObject

Deletes a document from a CouchDB database. This is an instance-level delete method. Example:

class Person < ActiveCouch::Base
  has :name
end

person = Person.create(:name => 'McLovin')
person.delete # true


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/active_couch/base.rb', line 124

def delete
  if new?
    raise ArgumentError, "You must specify a revision for the document to be deleted"
  elsif id.nil?
    raise ArgumentError, "You must specify an ID for the document to be deleted"
  end
  response = connection.delete("/#{self.class.database_name}/#{id}?rev=#{rev}")
  # Set the id and rev to nil, since the object has been successfully deleted from CouchDB
  if response.code == '202'
    self.id = nil; self.rev = nil
    true
  else
    false
  end
end

#new?Boolean

Checks to see if a document has been persisted in a CouchDB database. If a document has been retrieved from CouchDB, or has been persisted in a CouchDB database, the attribute _rev would not be nil.

Examples:

class Person < ActiveCouch::Base
  has :name, :which_is => :text
end

person = Person.new(:name => 'McLovin')
person.id = 'abc'
person.save # true
person.new? # false

Returns:

  • (Boolean)


112
113
114
# File 'lib/active_couch/base.rb', line 112

def new?
  rev.nil?
end

#saveObject

Saves a document into a CouchDB database. A document can be saved in two ways. One if it has been set an ID by the user, in which case the connection object needs to use an HTTP PUT request to the URL /database/user_generated_id. For the document needs a CouchDB-generated ID, the connection object needs to use an HTTP POST request to the URL /database.

Examples:

class Person < ActiveCouch::Base
  has :name, :which_is => :text
end

person = Person.new(:name => 'McLovin')
person.id = 'abc'
person.save # true
person.new? # false


85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/active_couch/base.rb', line 85

def save
  if id
    response = connection.put("/#{self.class.database_name}/#{id}", to_json)
  else
    response = connection.post("/#{self.class.database_name}", to_json)
  end
  # Parse the JSON obtained from the body...
  results = JSON.parse(response.body)
  # ...and set the default id and rev attributes
  DEFAULT_ATTRIBUTES.each { |a| self.__send__("#{a}=", results[a]) }
  # Response sent will be 201, if the save was successful [201 corresponds to 'created']
  return response.code == '201'
end

#to_jsonObject

Generates a JSON representation of an instance of a subclass of ActiveCouch::Base. Ignores attributes which have a nil value.

Examples:

class Person < ActiveCouch::Base
  has :name, :which_is => :text, :with_default_value => "McLovin"
end

person = Person.new
person.to_json # {"name":"McLovin"}

class AgedPerson < ActiveCouch::Base
  has :age, :which_is => :decimal, :with_default_value => 3.5
end

aged_person = AgedPerson.new
aged_person.id = 'abc-def'
aged_person.to_json # {"age":3.5, "_id":"abc-def"}


61
62
63
64
65
66
67
68
# File 'lib/active_couch/base.rb', line 61

def to_json
  hash = {}
  
  attributes.each_value { |v| hash.merge!(v.to_hash) unless v.nil? }
  associations.each_value { |v| hash.merge!(v.to_hash) }
  # and by the Power of Grayskull, convert the hash to json
  hash.to_json
end