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)
TYPES =
{ :text => "", :number => 0, :decimal => 0.0, :boolean => true }

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) {|_self| ... } ⇒ Base

Initializes an ActiveCouch::Base object. The constructor accepts both a hash, as well as a block to initialize attributes

Examples:

class Person < ActiveCouch::Base
  has :name
end

person1 = Person.new(:name => "McLovin")
person1.name # => "McLovin"

person2 = Person.new do |p|
  p.name = "Seth"
end
person2.name # => "Seth"

Yields:

  • (_self)

Yield Parameters:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/active_couch/base.rb', line 23

def initialize(params = {})
  # Object instance variable
  @attributes = {}; @associations = {}; @callbacks = Hash.new; @connection = self.class.connection
  # Initialize local variables from class instance variables
  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
  # First, initialize all the attributes
  klass_atts.each_key do |property|
    @attributes[property] = klass_atts[property]
    self.instance_eval "def #{property}; attributes[:#{property}]; end"
    self.instance_eval "def #{property}=(val); attributes[:#{property}] = val; end"
    # These are special attributes which need aliases (for now, it's _id and _rev)
    if property.to_s[0,1] == '_'
      aliased_prop = property.to_s.slice(1, property.to_s.size)
      self.instance_eval "def #{aliased_prop}; self.#{property}; end"
      self.instance_eval "def #{aliased_prop}=(val); self.#{property}=(val); end"
    end
  end
  # Then, initialize all the associations
  klass_assocs.each_key do |k|
    @associations[k] = klass_assocs[k]
    self.instance_eval "def #{k}; @#{k} ||= []; 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_#{k.singularize}(val); @#{k} = #{k} << val; end"
  end
  # Finally, all the calbacks      
  klass_callbacks.each_key do |k|
    @callbacks[k] = klass_callbacks[k].dup
  end
  # Set any instance variables if any, which are present in the params hash
  from_hash(params)
  # Handle the block, which can also be used to initialize the object
  yield self if block_given?
end

Class Method Details

.base_classObject



505
506
507
# File 'lib/active_couch/base.rb', line 505

def base_class
  class_of_active_couch_descendant(self)
end

.count(search_params = {}) ⇒ Object

Retrieves the count of the number of objects in the CouchDB database, based on the search parameters given.

Example:

class Person < ActiveCouch::Base
  has :name
end

# This returns the count of the number of objects
people_count = Person.count(:params => {:name => "McLovin"})


397
398
399
400
401
402
# File 'lib/active_couch/base.rb', line 397

def count(search_params = {})
  path = "/#{database_name}/_view/#{query_string(search_params[:params], {:limit => 0})}"
  result = connection.get(path)
  
  JSON.parse(result)['total_rows'].to_i
end

.count_allObject

Retrieves the count of the number of objects in the CouchDB database, irrespective of any search criteria

Example:

class Person < ActiveCouch::Base
  has :name
end

# This returns the count of the number of objects
people_count = Person.count_all


414
415
416
417
# File 'lib/active_couch/base.rb', line 414

def count_all
  result = connection.get("/#{database_name}")
  JSON.parse(result)['doc_count'].to_i
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


430
431
432
433
434
435
436
437
438
# File 'lib/active_couch/base.rb', line 430

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.



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/active_couch/base.rb', line 199

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 = parent.database_name.singularize
      contained << '_'
    end
    "#{contained}#{base.name.pluralize.demodulize.underscore}"
  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


479
480
481
482
483
484
485
486
# File 'lib/active_couch/base.rb', line 479

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')


449
450
451
452
453
454
455
456
# File 'lib/active_couch/base.rb', line 449

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 =~ /20[0,2]/).nil?
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"})


349
350
351
352
353
354
355
356
357
358
# File 'lib/active_couch/base.rb', line 349

def find(*arguments)
  scope = arguments.slice!(0)
  search_params = arguments.slice!(0) || {}
  
  case scope
    when :all    then find_every(search_params)
    when :first  then find_every(search_params, {:limit => 1}).first
    else              find_one(scope)
  end
end

.find_from_url(url) ⇒ Object

Retrieves one or more object(s) from a CouchDB database, based on the search parameters given. This method is similar to the find_by_sql method in ActiveRecord, in a way that instead of using any conditions, we use a raw URL to query a CouchDB view.

Example:

class Person < ActiveCouch::Base
  has :name
end

# This returns a single instance of an ActiveCouch::Base subclass
people = Person.find_from_url("/people/_view/by_name/by_name?key=%22Mclovin%22")


372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/active_couch/base.rb', line 372

def find_from_url(url)
  # If the url contains the word '_view' it means it will return objects as an array,
  # how ever if it doesn't it means the user is getting an ID-based url like /properties/abcd
  # which will only return a single object
  if url =~ /_view/
    instantiate_collection(connection.get(url))
  else
    begin
      instantiate_object(connection.get(url))
    rescue ResourceNotFound
      nil
    end
  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"


330
331
332
333
334
# File 'lib/active_couch/base.rb', line 330

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


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

def has(name, options = {})
  unless name.is_a?(String) || name.is_a?(Symbol)
    raise ArgumentError, "#{name} is neither a String nor a Symbol"
  end
  # Set the attributes value to options[:with_default_value]
  # In the constructor, this will be used to initialize the value of 
  # the 'name' instance variable to the value in the hash
  @attributes[name] = options[:with_default_value] || TYPES[:which_is]
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


291
292
293
294
295
296
297
# File 'lib/active_couch/base.rb', line 291

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] = get_klass(name, options)
end

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

Defines a single object which is a ‘child’ of this class. The has_one 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 Child < ActiveCouch::Base
  has :name
end

class GrandParent < ActiveCouch::Base
  has_one :child
end


312
313
314
315
316
317
318
# File 'lib/active_couch/base.rb', line 312

def has_one(name, options = {})
  unless name.is_a?(String) || name.is_a?(Symbol)
    raise ArgumentError, "#{name} is neither a String nor a Symbol"
  end

  @associations[name] = get_klass(name, options)
end

.inherited(subklass) ⇒ Object



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/active_couch/base.rb', line 488

def inherited(subklass)
  subklass.class_eval do
    include ActiveCouch::Callbacks
  end
  
  subklass.instance_eval do
    @attributes = { :_id => nil, :_rev => nil }
    @associations = {}
    @callbacks = Hash.new([])
    @connection = ActiveCouch::Base.instance_variable_get('@connection')
  end
  
  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


225
226
227
# File 'lib/active_couch/base.rb', line 225

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


239
240
241
# File 'lib/active_couch/base.rb', line 239

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

Instance Method Details

#delete(options = {}) ⇒ Object

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


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/active_couch/base.rb', line 148

def delete(options = {})
  database = options[:from_database] || self.class.database_name
  
  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("/#{database}/#{id}?rev=#{rev}")
  # Set the id and rev to nil, since the object has been successfully deleted from CouchDB
  if response.code =~ /20[0,2]/
    self.id = nil; self.rev = nil
    true
  else
    false
  end
end

#marshal_dumpObject

:nodoc:



166
167
168
169
# File 'lib/active_couch/base.rb', line 166

def marshal_dump # :nodoc:
  # Deflate using Zlib
  self.to_json
end

#marshal_load(str) ⇒ Object

:nodoc:



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

def marshal_load(str) # :nodoc:
  self.instance_eval do
    # Inflate first, and then parse the JSON
    hash = JSON.parse(str)
    initialize(hash)
  end
  self
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)


136
137
138
# File 'lib/active_couch/base.rb', line 136

def new?
  rev.nil?
end

#save(options = {}) ⇒ Object

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


108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/active_couch/base.rb', line 108

def save(options = {})
  database = options[:to_database] || self.class.database_name
  if id
    response = connection.put("/#{database}/#{id}", to_json)
  else
    response = connection.post("/#{database}", 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"}


83
84
85
86
87
88
89
90
91
# File 'lib/active_couch/base.rb', line 83

def to_json
  hash = {}
  # First merge the attributes...
  hash.merge!(attributes.reject{ |k,v| v.nil? })
  # ...and then the associations
  associations.each_key { |name| hash.merge!({ name => self.__send__(name.to_s) }) }
  # and by the Power of Grayskull, convert the hash to json
  hash.to_json
end