Class: CouchFoo::Base

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

Overview

Introduction

CouchDB (couchdb.apache.org/) works slightly differently to relational databases. First, and foremost, it is a document-orientated database. That is, data is stored in documents each of which have a unique id that is used to access and modify it. The contents of the documents are free from structure (or schema free) and bare no relation to one another (unless you encode that within the documents themselves). So in many ways documents are like records within a relational database except there are no tables to keep documents of the same type in.

CouchDB interfaces with the external world via a RESTful interface. This allows document creation, updating, deletion etc. The contents of a document are specified in JSON so its possible to serialise objects within the database record efficiently as well as store all the normal types natively.

As a consequence of its free form structure there is no SQL to query the database. Instead you define (table-oriented) views that emit certain bits of data from the record and apply conditions, sorting etc to those views. For example if you were to emit the colour attribute you could find all documents with a certain colour. This is similar to indexed lookups on a relational table (both in terms of concept and performance).

CouchDB has been designed from the ground up to operate in a distributed way. It provides robust, incremental replication with bi-directional conflict detection and resolution. It’s an excellent choice for unstructed data, large datasets that need sharding efficiently and situations where you wish to run local copies of the database (for example in satellite offices).

If using CouchDB in a more traditional database sense, it is common to specify a class attribute in the document so that a view can be defined to find all documents of that class. This is similar to a relational database table. CouchFoo defines a ruby_class property that holds the class name of the model that it’s representing. It is then possible to do User.all for example.

As such it has been possible, with a few minor exceptions, to take the interface to ActiveRecord and re-implement it for CouchDB. This should allow for easy migration from MySQL and friends to CouchDB. Further, CouchFoo, has been designed to take advantages of some features that CouchDB offers that are not available in relational databases. For example - multiple updates and efficient sharding of data

In terms of performance CouchDB operates differently in some areas when compared to relationsal databases. That’s not to say it’s better or worse, just different - you pay the price for certain operations at different points. As such there’s a performance section below that details which areas are better performing and which worse. This is something to be aware when writing or migrating applications and using CouchDB.

Quick start guide - what’s different?

This section outlines differences between ActiveRecord and CouchFoo and a few specific CouchDB points to be aware of

As CouchDB is schema-free there are no migrations and no schemas to worry about. You simply specify properties inside the model (a bit like in DataMapper) and add/subtract from them as you need. Adding new properties initializes that attribute to nil when using a document that doesn’t have the corresponding data and removing a field makes that attribute no longer available (and removes it from the record on next save). You can optionally specify a type with a property although it makes sense to do so if you can (it’ll convert to that type). For example:

class Address < CouchFoo::Base
  property :number, Integer
  property :street, String
  property :postcode # Any generic type is fine as long as .to_json and class.from_json(json) can be called on it
end

Documents have three more properties that get added automatically. _id and _rev are CouchDB internal properties. The _id is the document UUID and never changes. This is created by the gem to be unique accross mulitple computers so should never result in a conflict. It is also aliased to id to ensure compatability with ActiveRecord. The _rev attribute is changed by CouchDB each time an update is made. It is used internally by CouchDB to detect conflicts. The final property is ruby_class that is used by CouchFoo to determine which documents map to which models.

CouchDB has a concept called bulk saving where mutliple operations can be built up and commited at once. This has the added advantage of being in a transaction so all or none of the operations will complete. By default bulk saving is switched off but you can set the CouchFoo::Base.bulk_save_default setting to true to achieve good performance gains when updating large amounts of documents. Beware it is the developers responsability to commit the work though. For example:

User.all.size # => 27
User.create(:name => "george")
User.all.size # => 27
User.database.commit # or you can use self.class.database.commit if just using 1 database
User.all.size # => 28

If using this option in rails it would be a good idea to specify an after_filter so that any changes are commited at the end of an action. If you are sharding database across several databases you will need to deal with this in the after_filter logic as well.

Conflicts occur when a copy of the document is altered under CouchFoo. That is since loading the document into memory another person or program has altered its contents. CouchDB detects this through the _rev attribute. If a conflict occurs a DocumentConflict error is raised. This is effectively the same as ActiveRecord Optimistic locking mechanism. Exceptions should be dealt with in application logic if using save! - save will just return false

On the finders and associations there are a few options that are no longer relevant - :select, :joins, :having, :group, :from and :lock Everything else is available although :conditions and :order work slightly differently and :include hasn’t been implemented yet. :conditions is now only a hash, eg :conditions => => user_name and doesn’t accept an array or SQL. :offset or :skip is quite inefficient so should be used sparingly :order isn’t a SQL fragement but a field to sort the results on - eg :order => :created_at At the moment this can only be one field so it’s best to sort the results yourself once you’ve got them back if you require complex ordering. As the order is applied once the results are retrieved from the database it cannot be used with :limit. The reason :order is applied after the results are retrieved is CouchDB can only order on items exposed in the key. Thus if you wanted to sort User.all 5 different ways throughout your application you would need 5 indexes rather than one. This is quite inefficient so ordering is performed once the data is retrieved from the database with the cost that using :order with :limit has to be performed an alternate way.

When using finders views are automatically built by CouchFoo for the model. For example, with the class House, the first House.find(:id) will create a House design document with a view that exposes the id of the house as the key - any house can then be selected on this. If you then do a query for a certain colour house, ie House.find_all_by_colour(“red”) then a view that exposes the house colour as the key will be added to the design document. This works well when using conditions (as we can generate the key from the conditions used) but means it’s not possible to find the oldest 5 users in the system if :created_at isn’t exposed in the key. As such it is possible to set the key to use with a view directly. For example:

Person.find(:all, :use_key => [:name, :category], :conditions => {:category => "Article"}, :limit => 50) # Finds 50 people with a category of "Article" sorted by name

We must use the condition keys in the :use_key array as we want to restrict the results on this.

It should be noted that the query will get the desired results but at the expense of creating a new index so shouldn’t be used excessively. For more complex queries it is also possible to specify your own map and reduce functions using the CouchFoo#view call. See the CouchDB view documentation and the CouchFoo#view documentation for more on this.

Using relational databases with incrementing keys we have become accustom to adding a new record and then using find(:last) to retrieve it. As each document in CouchDB has a unique identifier this may no longer the case. This is particularly important when creating user interfaces as it is normal to add items to the bottom of lists and expect on reload for the order to be maintained. In couch_foo items are sorted by the :created_at property if it is available and there are no conditions on the query - eg User.all, User.first As this is a minor use case it is recommended to use the CouchFoo#default_sort macro that applies a default sort order to the model each time it’s retrieved from the database. This way you can set default_sort :created_at and not worry about hitting the problem again.

With CouchDB the price to pay for inserting data into an indexed view isn’t paid at insertion time like MySQL and friends, but at the point of next retrieving that view (although it’s possible to override this in order to sacrifice performance for accuracy). As such you can either pay the performance cost when accessing the view (which might be ok if the view gets used a lot but not if a million applicable documents have been added since the last view) or add an external process which gets called everytime a document is created in CouchDB. More of this is outlined in the CouchFoo#find documentation

Connecting to the database is done in a way similar to ActiveRecord where the database is inherited by each subclass unless overridden. In CouchFoo you use the call set_database method with a host and database name. As there’s no database connection to be maintained sharding data is very efficient and some applications even use one database per user.

If using rails, for now you need to specify your own initializer to make the default database connection. This will be brought inline with rails in the future, using a couchdb.yml configuration file or similar. But for now an initializer file in config/initializers like the following will do the trick:

CouchFoo::Base.set_database(:host => "http://localhost:5984", :database => "mydatabase")
CouchFoo::Base.logger = Rails.logger

A few tidbits:

  • When specifying associations you still need to specify the object_id and object_type (if using polymorphic association) properties. We have this automated as part of the association macro soon

  • validates_uniqueness_of has had the :case_sensitive option dropped

  • Because there’s no SQL there’s no SQL finder methods but equally there’s no SQL injection to worry about. You should still sanitize output when displaying back to the user though because you will still be vunerable to JS attacks etc.

  • Some operations are more efficient than relational databases, others less so. See the performance section for more details on this

  • Every so often the database will need compacting to recover space lost to old document revisions. More importantly initial indications show un-compacted databases can effect performance. See wiki.apache.org/couchdb/Compaction for more details on this.

The following things are not in this implementation (but are in ActiveRecord):

  • :include - Although the finders and associations allow this option the actual implementation is yet to be written

  • Timezones

  • Aggregations

  • Fixtures and general testing support

  • Query cache

Properties

Couch Foo objects specify their properties through the use of property definitions inside the model itself. This is unlike ActiveRecord (but similar to DataMapper). As the underlying database document is stored in JSON (String, Integer, Float, Time, DateTime, Date and Boolean/TrueClass are the available types) there are a few differences between CouchFoo and ActiveRecord. The following table highlights any changes that will need to be made to model types:

ActiveRecord type | CouchFoo type
------------------+---------------------------------
#Text             | String
#Decimal          | Float
#Timestamp        | Time
#Binary           | Attachment (not yet implemented)

An example of a model is as follows:

class Address < CouchFoo::Base
  property :number, Integer
  property :street, String
  property :postcode # Any generic type is fine as long as .to_json can be called on it
end

Creation

Couch Foo accept constructor parameters either in a hash or as a block. The hash method is especially useful when you’re receiving the data from somewhere else, like an HTTP request. It works like this:

user = User.new(:name => "David", :occupation => "Code Artist")
user.name # => "David"

You can also use block initialization:

user = User.new do |u|
  u.name = "David"
  u.occupation = "Code Artist"
end

And of course you can just create a bare object and specify the attributes after the fact:

user = User.new
user.name = "David"
user.occupation = "Code Artist"

Conditions

Conditions are specified as a Hash. This is different from ActiveRecord where they can be specified as a String, Array or Hash. Example:

class User < ActiveRecord::Base
  def self.authenticate_safely_simply(user_name, password)
    find(:first, :conditions => { :user_name => user_name, :password => password })
  end
end

A range may be used in the hash to find documents between two values:

Student.find(:all, :conditions => { :grade => 9..12 })

A startkey or endkey can be used to find documents where the documents exceed or preceed the value. A key is needed here so CouchFoo knows which property to apply the startkey to:

Student.find(:all, :use_key => :grade, :startkey => 80)

Finally an array may be used in the hash to use find records just matching those values. This operation requires CouchDB > 0.8 though:

Student.find(:all, :conditions => { :grade => [9,11,12] })

Overwriting default accessors

All column values are automatically available through basic accessors on the Active Record object, but sometimes you want to specialize this behavior. This can be done by overwriting the default accessors (using the same name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things. Example:

class Song < ActiveRecord::Base
  # Uses an integer of seconds to hold the length of the song

  def length=(minutes)
    write_attribute(:length, minutes.to_i * 60)
  end

  def length
    read_attribute(:length) / 60
  end
end

You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and read_attribute(:attribute) as a shorter form.

Attribute query methods

In addition to the basic accessors, query methods are also automatically available on the Active Record object. Query methods allow you to test whether an attribute value is present.

For example, an Active Record User with the name attribute has a name? method that you can call to determine whether the user has a name:

user = User.new(:name => "David")
user.name? # => true

anonymous = User.new(:name => "")
anonymous.name? # => false

Accessing attributes before they have been typecasted

Sometimes you want to be able to read the raw attribute data without having the property typecast run its course first. That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.

This is especially useful in validation situations where the user might supply a string for an integer field and you want to display the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn’t what you want.

Dynamic attribute-based finders

Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries. They work by appending the name of an attribute to find_by_ or find_all_by_, so you get finders like Person.find_by_user_name, Person.find_all_by_last_name, and Payment.find_by_transaction_id. So instead of writing Person.find(:first, :conditions => {:user_name => user_name}), you just do Person.find_by_user_name(user_name). And instead of writing Person.find(:all, :conditions => {:last_name => last_name}), you just do Person.find_all_by_last_name(last_name).

It’s also possible to use multiple attributes in the same find by separating them with “and”, so you get finders like Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing Person.find(:first, :conditions => {:user_name => user_name, :password => password}), you just do Person.find_by_user_name_and_password(user_name, password).

It’s even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is actually Person.find_by_user_name(user_name, options). So you could call Payment.find_all_by_amount(50, :order => :created_at).

The same dynamic finder style can be used to create the object if it doesn’t already exist. This dynamic finder is called with find_or_create_by_ and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won’t be set unless they are given in a block. For example:

# No 'Summer' tag exists
Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")

# Now the 'Summer' tag does exist
Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")

# Now 'Bob' exist and is an 'admin'
User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }

Use the find_or_initialize_by_ finder if you want to return a new record without saving it first. Protected attributes won’t be setted unless they are given in a block. For example:

# No 'Winter' tag exists
winter = Tag.find_or_initialize_by_name("Winter")
winter.new_record? # true

To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of a list of parameters. For example:

Tag.find_or_create_by_name(:name => "rails", :creator => current_user)

That will either find an existing tag named “rails”, or create a new one while setting the user that created it.

Saving arrays, hashes, and other non-mappable objects in text columns

CouchFoo will try and serialize any types that are not specified in properties by calling .to_json on the object This means its not only possible to store arrays and hashes (with no extra work required) but also any generic type you care to define, so long as it has a .to_json method defined. Example:

class User < CouchFoo::Base
  property :name, String
  property :house # Can be any object so long as it responds to .to_json
end

Inheritance

Couch Foo allows inheritance by storing the name of the class in a column that by default is named “type” (can be changed by overwriting Base.inheritance_column). This means that an inheritance looking like this:

class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end

When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = “Firm”. You can then fetch this row again using Company.find(:first, {:name => "37signals"}) and it will return a Firm object.

If you don’t have a type column defined in your table, inheritance won’t be triggered. In that case, it’ll work just like normal subclasses with no special magic for differentiating between them or reloading the right type with find.

Connection to multiple databases in different models

Connections are usually created through CouchFoo::Base.set_database and retrieved by CouchFoo::Base.database All classes inheriting from CouchFoo::Base will use this connection. But you can also set a class-specific connection. For example, if Course is an CouchFoo::Base, but resides in a different database, you can just say Course.set_database url and Course and all of its subclasses will use this connection instead.

Performance

CouchDB operates via a RESTful interface and so isn’t as efficient as a local database when running over a local socket. This is due to the TCP/IP overhead. But given any serious application runs a separate database and application server then it is unfair to judge CouchDB on this alone.

Generally speaking CouchDB performs as well as or better than relational databases when it already has the document in memory. If it doesn’t it performs worse as it must first find the document and submit the new version (as there’s no structure to documents it can’t update fields on an add-hoc basis, it must have the whole document). This makes class operations such as update_all less efficient. If your application is high load and uses these excessively you may wish to consider other databases. On the flip side if you have lots of documents in memory and wish to update them all, using bulk_save is an excellent way to make performance gains.

Below is a list of operations where the performance differs from ActiveRecord. More notes are available in the functions themselves:

  • class.find - when using list of ids is O(n) rather than O(1) if not on CouchDB 0.9

  • class.create - O(1) rather than O(n) if using bulk_save

  • class.delete - O(n) rather than O(1) so less efficient for > 1 document

  • class.update_all - O(n+1) rather than O(1)

  • class.delete_all - O(2n) rather than O(1)

  • class.update_counters - O(2) rather than O(1)

  • class.increment_counter - O(2) rather than O(1)

  • class.decrement_counter - O(2) rather than O(1)

  • save, save!, update_attribute, update_attributes, update_attributes!, increment!, decrement!,

toggle! - if using bulk_save then O(1) rather than O(n)

Constant Summary collapse

@@document_name_prefix =
""
@@document_name_suffix =
""
@@unchangeable_property_names =
[:_id, :_rev, :ruby_class]
@@default_timezone =
:local

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = nil) ⇒ Base

New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated property names). In both instances, valid attribute keys are determined by the property names of the model – hence you can’t have attributes that aren’t part of the model.



1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
# File 'lib/couch_foo/base.rb', line 1632

def initialize(attributes = nil)
  @attributes = attributes_from_property_definitions
  @attributes_cache = {}
  @new_record = true
  ensure_proper_type
  self.attributes = attributes
  self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
  result = yield self if block_given?
  callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
  result
end

Class Attribute Details

.abstract_classObject

Set this to true if this is an abstract class (see abstract_class?).



1215
1216
1217
# File 'lib/couch_foo/base.rb', line 1215

def abstract_class
  @abstract_class
end

Class Method Details

.===(object) ⇒ Object

Overwrite the default class equality method to provide support for association proxies.



1203
1204
1205
# File 'lib/couch_foo/base.rb', line 1203

def ===(object)
  object.is_a?(self)
end

.abstract_class?Boolean

Returns whether this class is a base CouchFoo class. If A is a base class and B descends from A, then B.base_class will return B.

Returns:



1219
1220
1221
# File 'lib/couch_foo/base.rb', line 1219

def abstract_class?
  defined?(@abstract_class) && @abstract_class == true
end

.accessible_attributesObject

Returns an array of all the attributes that have been made accessible to mass-assignment.



951
952
953
# File 'lib/couch_foo/base.rb', line 951

def accessible_attributes # :nodoc:
  read_inheritable_attribute("attr_accessible")
end

.all(*args) ⇒ Object

This is an alias for find(:all). You can pass in all the same arguments to this method as you can to find(:all)



647
648
649
# File 'lib/couch_foo/base.rb', line 647

def all(*args)
  find(:all, *args)
end

.attr_accessible(*attributes) ⇒ Object

Specifies a white list of model attributes that can be set via mass-assignment, such as new(attributes), update_attributes(attributes), or attributes=(attributes)

This is the opposite of the attr_protected macro: Mass-assignment will only set attributes in this list, to assign to the rest of attributes you can use direct writer methods. This is meant to protect sensitive attributes from being overwritten by malicious users tampering with URLs or forms. If you’d rather start from an all-open default and restrict attributes as needed, have a look at attr_protected.

class Customer < CouchFoo::Base
  attr_accessible :name, :nickname
end

customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
customer.credit_rating # => nil
customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
customer.credit_rating # => nil

customer.credit_rating = "Average"
customer.credit_rating # => "Average"


946
947
948
# File 'lib/couch_foo/base.rb', line 946

def attr_accessible(*attributes)
  write_inheritable_attribute("attr_accessible", Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
end

.attr_protected(*attributes) ⇒ Object

Attributes named in this macro are protected from mass-assignment, such as new(attributes), update_attributes(attributes), or attributes=(attributes).

Mass-assignment to these attributes will simply be ignored, to assign to them you can use direct writer methods. This is meant to protect sensitive attributes from being overwritten by malicious users tampering with URLs or forms.

class Customer < CouchFoo::Base
  attr_protected :credit_rating
end

customer = Customer.new("name" => David, "credit_rating" => "Excellent")
customer.credit_rating # => nil
customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
customer.credit_rating # => nil

customer.credit_rating = "Average"
customer.credit_rating # => "Average"

To start from an all-closed default and enable attributes as needed, have a look at attr_accessible.



913
914
915
# File 'lib/couch_foo/base.rb', line 913

def attr_protected(*attributes)
  write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
end

.attr_readonly(*attributes) ⇒ Object

Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards



957
958
959
# File 'lib/couch_foo/base.rb', line 957

def attr_readonly(*attributes)
  write_inheritable_attribute("attr_readonly", Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
end

.base_classObject

Returns the base subclass that this class descends from. If A extends CouchFoo::Base, A.base_class will return A. If B descends from A through some arbitrarily deep hierarchy, B.base_class will return A.



1210
1211
1212
# File 'lib/couch_foo/base.rb', line 1210

def base_class
  class_of_active_record_descendant(self)
end

.benchmark(title, log_level = Logger::DEBUG, use_silence = true) ⇒ Object

Log and benchmark multiple statements in a single block. Example:

Project.benchmark("Creating project") do
  project = Project.create("name" => "stuff")
  project.create_manager("name" => "David")
  project.milestones << Milestone.find(:all)
end

The benchmark is only recorded if the current level of the logger is less than or equal to the log_level, which makes it easy to include benchmarking statements in production software that will remain inexpensive because the benchmark will only be conducted if the log level is low enough.

The logging of the multiple statements is turned off unless use_silence is set to false.



1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
# File 'lib/couch_foo/base.rb', line 1183

def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
  if logger && logger.level <= log_level
    result = nil
    seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
    logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
    result
  else
    yield
  end
end

.create(attributes = nil, &block) ⇒ Object

Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.

The attributes parameter can be either be a Hash or an Array of Hashes. These Hashes describe the attributes on the objects that are to be created.

If using bulk save this operation is O(1) rather than O(n) so much more efficient

Examples

# Create a single new object
User.create(:first_name => 'Jamie')

# Create an Array of new objects
User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])

# Create a single object and pass it into a block to set other attributes.
User.create(:first_name => 'Jamie') do |u|
  u.is_admin = false
end

# Creating an Array of new objects using a block, where the block is executed for each object:
User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
  u.is_admin = false
end


689
690
691
692
693
694
695
696
697
698
# File 'lib/couch_foo/base.rb', line 689

def create(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    object = new(attributes)
    yield(object) if block_given?
    object.save
    object
  end
end

.decrement_counter(counter_name, id) ⇒ Object

Decrement a number field by one, usually representing a count. This works the same as increment_counter but reduces the property value by 1 instead of increasing it. Unlike ActiveRecord this does not update the database directly but has to first find the record.

Therefore updates are O(2) rather than O(1)

Attributes

  • counter_name - The name of the field that should be decremented.

  • id - The id of the object that should be decremented.

Examples

# Decrement the post_count property for the record with an id of 'aef343ab2'
DiscussionBoard.decrement_counter(:post_count, 'aef343ab2')


885
886
887
# File 'lib/couch_foo/base.rb', line 885

def decrement_counter(counter_name, id)
  update_counters(id, {counter_name => -1})
end

.default_sort(property) ⇒ Object

Sets an order which all queries to this model will be sorted by unless overriden in the finder This is useful for setting a created_at sort field by default so results are automatically sorted in the order they were added to the database. NOTE - this sorts after the results are returned so will not give expected behaviour when using limits or find(:first), find(:last) For example, class User < CouchFoo::Base

property :name, String
property :created_at, DateTime
default_sort :created_at

end



1117
1118
1119
# File 'lib/couch_foo/base.rb', line 1117

def default_sort(property)
  @default_sort_order = property
end

.default_sort_orderObject



1121
1122
1123
# File 'lib/couch_foo/base.rb', line 1121

def default_sort_order
  @default_sort_order
end

.delete(id, rev) ⇒ Object

Delete an object (or multiple objects) where the _id and _rev given match the record. No callbacks are fired off executing so this is an efficient method of deleting documents that don’t need cleaning up after or other actions to be taken.

This operations is O(n) compared to O(1) so is less efficient than ActiveRecord when deleting more than one document

Objects are not instantiated with this method.

Attributes

  • id - Can be either a String or an Array of Strings.

Examples

# Delete a single object
Todo.delete('6180e9a0-cdca-012b-14a5-001a921a2bec', '12345678')

# Delete multiple objects
ids = ['6180e9a0-cdca-012b-14a5-001a921a2bec', 'e6f6a870-cdc9-012b-14a3-001a921a2bec']
revs = ['12345678', '12345679']
Todo.delete(ids, revs)


749
750
751
752
753
754
755
756
757
# File 'lib/couch_foo/base.rb', line 749

def delete(id, rev)
  if id.is_a?(Array)
    idx = -1
    id.collect {|i| idx += 1; delete(i, rev[idx])}
  else
    database.delete({"_id" => id, "_rev" => rev})
    true
  end
end

.delete_all(conditions = nil) ⇒ Object

Currently there is no way to do selective delete in CouchDB so this simply defers to CouchFoo#destroy_all for API compatability with ActiveRecord

This operations is O(2n) compared to O(1) so much less efficient than ActiveRecord



825
826
827
# File 'lib/couch_foo/base.rb', line 825

def delete_all(conditions = nil)
  destroy_all(conditions)
end

.descends_from_couch_foo?Boolean

True if this isn’t a concrete subclass needing a inheritence type condition.

Returns:



1099
1100
1101
1102
1103
1104
1105
# File 'lib/couch_foo/base.rb', line 1099

def descends_from_couch_foo?
  if superclass.abstract_class?
    superclass.descends_from_couch_foo?
  else
    superclass == Base
  end
end

.destroy(id) ⇒ Object

Destroy an object (or multiple objects) that has the given id. Unlike delete this doesn’t require a _rev as the object if found, created from the attributes and then destroyed. As such all callbacks and filters are fired off before the object is deleted. This method is the same in efficiency terms as CouchFoo#delete unlike in ActiveRecord where delete is more efficient

Examples

# Destroy a single object
Todo.destroy('6180e9a0-cdca-012b-14a5-001a921a2bec')

# Destroy multiple objects
Todo.destroy(['6180e9a0-cdca-012b-14a5-001a921a2bec', 'e6f6a870-cdc9-012b-14a3-001a921a2bec'])


772
773
774
775
776
777
778
# File 'lib/couch_foo/base.rb', line 772

def destroy(id)
  if id.is_a?(Array)
    id.map { |one_id| destroy(one_id) }
  else
    find(id).destroy
  end
end

.destroy_all(conditions = nil) ⇒ Object

Destroys the records matching conditions by instantiating each record and calling the destroy method. This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting many records. If you want to simply delete records without worrying about dependent associations or callbacks, use the much faster delete_all method instead.

Attributes

  • conditions - Conditions are specified the same way as with find method.

Example

Person.destroy_all "last_login < '2004-04-04'"

This loads and destroys each person one by one, including its dependent associations and before_ and after_destroy callbacks.



817
818
819
# File 'lib/couch_foo/base.rb', line 817

def destroy_all(conditions = nil)
  find(:all, :conditions => conditions).each { |object| object.destroy }
end

.document_class_nameObject

Guesses the document name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending directly from CouchFoo::Base. So if the hierarchy looks like: Reply < Message < CouchFoo::Base, then Reply is used as the document name. The rules used to do the guess are handled by the Inflector class in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.

Nested classes and enclosing modules are not considered.

Example

class Invoice < CouchFoo::Base; end;
file                  class               document_name
invoice.rb            Invoice             invoice

Additionally, the class-level document_name_prefix is prepended and the document_name_suffix is appended. So if you have “myapp_” as a prefix, the document name guess for an Invoice class becomes “myapp_invoices”.

You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a “mice” document. Example:

class Mouse < CouchFoo::Base
  set_document_name "mice"
end


991
992
993
# File 'lib/couch_foo/base.rb', line 991

def document_class_name
  reset_document_class_name
end

.exists?(id_or_conditions) ⇒ Boolean

Checks whether a document exists in the database that matches conditions given. These conditions can either be a key to be found, or a condition to be matched like using CouchFoo#find.

Examples

Person.exists?('5a1278b3c4e')
Person.exists?(:name => "David")

Returns:



657
658
659
660
661
662
663
# File 'lib/couch_foo/base.rb', line 657

def exists?(id_or_conditions)
  if (id_or_conditions.is_a?(Hash))
    !find(:first, :conditions => {:ruby_class => document_class_name}.merge(id_or_conditions)).nil?
  else
    !find(id_or_conditions, :conditions => {:ruby_class => document_class_name}).nil? rescue false
  end
end

.find(*args) ⇒ Object

CouchDB has a concept called views which show a subset of documents in the database subject to criteria. The documents selected are chosen according to a map function and an optional reduce function.

The later is useful for example, to count all the documents that have been matched by the initial map function. CouchFoo automatically creates views for each of the data models you use as you require them.

For example take the class House. The first House.find(:id) will create a House design document with a view that exposes the id of the house as a key - any house can then be selected on this. If you then do a query for a certain colour house, ie House.find_all_by_colour(“red”) then a view that exposes the house colour as a key will be added to the design document. So as you perform new queries the design document for a model is updated so you are always accessing via an ‘indexed’ approach. This should be transparent to the developer but the resulting views can be seen by looking up the design document in the database.

CouchFoo cannot handle automatic view generation for the case where both an :order and :limit should be applied to a result set. This is because the ordering is performed after retrieving data from CouchDB whereas the limit is applied at the database level. As such if you were limiting to 5 results and ordering on a property you would get the same 5 results in a different order rather than the first 5 and last 5 results for that data type. To overcome this restriction either define the property you wish to order on in the :use_key option (make sure you add conditions you’re using in here as well) or create your own views using CouchFoo#view. You can then use the :descending => true to reverse the results order.

There is a slight caveat to the way views work in CouchDB. The index is only updated each time a view is accessed (although this can be overridden using :update). This is both good and bad. Good in the sense you don’t pay the price at insertion time, bad in the sense you pay when accessing the view the next time (although this can be more efficient). The simple solution is to write a script that periodically calls the view to update the index as described on the CouchDB site in this FAQ: wiki.apache.org/couchdb/Frequently_asked_questions#update_views_more_often Needless to say creating a new view on a large dataset is expensive but this is no different from creating an index on a large MySQL table (although in general it’s a bit slower as all the documents are in one place rather than split into tables)

It is possible to perform unindexed queries by using slow views, although this is not recommended for production use. Like MySQL performing unindexed lookups is very inefficient on large datasets.

One final point to note is we’re used to using relational databases that have auto-incrementing keys. Therefore the newest rows added to the database have the highest key value (some databases go back and fill in the missing/deleted keys after a restart but generally speaking…) and are therefore shown last in lists on the front end. When using CouchDB each item is allocated a UUID which varies massively depending on time, computer IP etc. Therefore it is likely that adding a new item to a page via AJAX will add the item to the bottom of the list but when the page is reloaded it occurs in the middle somewhere. This is very confusing for users so it is therefore recommended that you sort items on a :created_at field by default (see CouchFoo#default_sort).

More information can be found on CouchDB views at: wiki.apache.org/couchdb/Introduction_to_CouchDB_views

CouchFoo operates with four different retrieval approaches:

  • Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). If no record can be found for all of the listed ids, then DocumentNotFound will be raised. This only accepts the :conditions and :readonly options. Note when using a list/array of ids the lookup is O(n) efficiency rather than O(1) (as with ActiveRecord) if using CouchDB<0.9

  • Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args). It is recommended you use CouchFoo#default_sort on the model if you wish to use this with ordering.

  • Find last - This will return the last record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:last, *args) or its shortcut Model.last(*args). It is recommended you use CouchFoo#default_sort on the model if you wish to use this with ordering.

  • Find all - This will return all the records matched by the options used. If no records are found, an empty array is returned. Use Model.find(:all, *args) or its shortcut Model.all(*args).

All approaches accept an options hash as their last parameter.

Attributes

NOTE: Only :conditions and :readonly are available on find by id lookups

  • :conditions - This can only take a Hash of options to match, not SQL fragments like ActiveRecord. For example :conditions => = 6, :price => 30..80 or :conditions => => [6, 8, 10] Note when using the later approach and specifying a discrete list CouchDB doesn’t support ranges in the same query

  • :order - With a field name sorts on that field. This is applied after the results are returned from the database so should not be used with :limit and is fairly pointless with find(:first) and find(:last) types. See CouchFoo#view for how to create views that can be sorted ActiveRecord style

  • :include - not implemented yet

  • :limit - an integer determining the limit on the number of rows that should be returned. Take caution if using with :order (read notes in :order and section header)

  • :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4. This is the same as :skip listed below in the further options. Note: This is not particulary efficient in CouchDB

  • :update - If set to false will not update the view so although the access will be faster some of the data may be out of date. Recommended if you are managing view updation independently

  • :readonly - Mark the returned documents read-only so they cannot be saved or updated.

  • :view_type - by default views are created for queries where there is no view (this is equivalent to no index on the column in MySQL) to keep lookups efficient. However by passing :view_type => :slow a CouchDB slow query will be performed. As the name suggests these are slow and should only be used in development not production. Be sure to read the note above on how CouchDB indexing works and responsabilites of the developer.

  • :use_key - The key to emit in the view. The key is used for selection and ordering so is a good way to order results and limit to a certain quantity, or to find results that are greater or less than a certain value (in combination with :startkey, :endkey). Normally this value is automatically determined when using :conditions. As such when using in combination with :conditions option this must contain both the items you would like in the key and the items you’re using in the conditions. For example: User.find(:all, :use_key => [:name, :administrator], :conditions => => true)

  • :startkey - Used to find all documents from this value up, for example User.find(:all, :startkey => 20) This needs to be used with a custom map function where the user has chosen the exposing key for it to be meaningful.

  • :endkey - As :startkey but documents upto that key rather than from it

  • :return_json - If you are emitting something other than the document as the value on a custom map function you may wish to return the raw JSON as instantiating objects may not be possible. Using this option will ignore any :order or :readonly settings

  • Further options - The CouchDB view options :descending, :group, :group_level, :skip, :keys, :startkey_docid and :endkey_docid are supported on views but they are unlikely to be required unless the developer is specifying their own map or reduce function. Note some of these require CouchDB 0.9 (see CouchDB wiki for list)

Examples

# find by id
Person.find(1)       # returns the object for ID = 1
Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
Person.find([1])     # returns an array for the object with ID = 1
Person.find(1, :conditions => {:administrator => 1})

Unlike ActiveRecord order will be maintained on multiple id selection but the operation
is not as efficient as there is no multiple get from the database 

# find first
Person.find(:first) # returns the first object fetched by key (so this is unlikely to be
the oldest person document in the database)
Person.find(:first, :use_key => :created_at) # Finds earliest person but at the expense of 
creating a new view
Person.find(:first, :use_key => :created_at, :startkey => "2009/09/01")) # Finds 1st person
since 1st September 2009 but uses the same index as above

# find last
Person.find(:last) # returns the last object, again may not be what's expected
Person.find(:last, :conditions => { :user_name => user_name}) 

# find all
Person.find(:all) # returns an array of objects
Person.find(:all, :conditions => {:category => "Article"}, :limit => 50)
Person.find(:all, :use_key => [:name, :category], :conditions => {:category => "Article"}, :limit => 50)
# Creates a name, category index and finds the first 50 people ordered by name with a category of "Article"


620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/couch_foo/base.rb', line 620

def find(*args)
  options = args.extract_options!
  validate_find_options(options)
  set_readonly_option!(options)

  case args.first
    when :first then find_initial(options)
    when :last  then find_last(options)
    when :all   then find_every(options)
    else             find_from_ids(args, options)
  end
end

.first(*args) ⇒ Object

A convenience wrapper for find(:first, *args). You can pass in all the same arguments to this method as you can to find(:first).



635
636
637
# File 'lib/couch_foo/base.rb', line 635

def first(*args)
  find(:first, *args)
end

.get_uuidObject

Returns a unique UUID even across multiple machines



1231
1232
1233
1234
# File 'lib/couch_foo/base.rb', line 1231

def get_uuid
  @uuid ||= UUID.new
  @uuid.generate
end

.increment_counter(counter_name, id) ⇒ Object

Increment a number field by one, usually representing a count. Unlike ActiveRecord this does not update the database directly but has to first find the record. Therefore updates are O(2) rather than O(1)

Attributes

  • counter_name - The name of the field that should be incremented.

  • id - The id of the object that should be incremented.

Examples

# Increment the post_count property for the record with an id of 'aef343ab2'
DiscussionBoard.increment_counter(:post_count, 'aef343ab2')


867
868
869
# File 'lib/couch_foo/base.rb', line 867

def increment_counter(counter_name, id)
  update_counters(id, {counter_name => 1})
end

.inheritance_columnObject

Defines the propety name for use with single table inheritance – can be set in subclasses like so: self.inheritance_column = “type_id”



1019
1020
1021
# File 'lib/couch_foo/base.rb', line 1019

def inheritance_column
  @inheritance_column ||= "type".freeze
end

.inspectObject



1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
# File 'lib/couch_foo/base.rb', line 1157

def inspect
  if self == Base
    super
  elsif abstract_class?
    "#{super}(abstract)"
  else
    attr_list = properties.map { |p| "#{p.name}: #{p.type || 'JSON'}" } * ', '
    "#{super}(#{attr_list})"
  end
end

.last(*args) ⇒ Object

A convenience wrapper for find(:last, *args). You can pass in all the same arguments to this method as you can to find(:last).



641
642
643
# File 'lib/couch_foo/base.rb', line 641

def last(*args)
  find(:last, *args)
end

.propertiesObject

Returns all properties defined on this class



1068
1069
1070
1071
1072
1073
1074
1075
1076
# File 'lib/couch_foo/base.rb', line 1068

def properties
  if @properties.nil?
    @properties = Set.new
    @properties.merge(superclass.properties) unless self == base_class
    @properties
  else
    @properties
  end
end

.property(name, type = Object, options = {}) ⇒ Object

Set a property for the document. These can be passed a type and options hash. If no type is passed a #to_json method is called on the ruby object and the result stored in the database. When it is retrieved from the database a class.from_json(json) method is called on it or if that doesn’t exist it just uses the value (more on this at www.rowtheboat.com/archives/35). If a type is passed then the object is cast before storing in the database. This does not guarantee that the object is the correct type (use the validaters for that), it merely tries to convert the current type to the desired one

  • for example:

‘123’ => 123 # useful ‘a’ => 0 # probably not desired behaviour The later would fail with a validator

The options hash supports: default - the default value for the attribute to be initalized to

Example:

class Invoice < CouchFoo::Base

property :number, Integer
property :paid, TrueClass, :default => false
property :notes, String
property :acl # or acl, Object is equivalent
property :price, Price

end



1061
1062
1063
1064
1065
# File 'lib/couch_foo/base.rb', line 1061

def property(name, type = Object, options = {})
  logger.warn("Using type as a property name may issue unexpected behaviour") if name == :type
  properties.delete_if{|e| e.name == name} # Subset properties override
  properties << Property.new(name, type, options[:default])
end

.property_namesObject

Returns an array of property names



1087
1088
1089
# File 'lib/couch_foo/base.rb', line 1087

def property_names
  @property_names ||= properties.map { |property| property.name }
end

.property_typesObject

Returns a hash of property name to types



1079
1080
1081
1082
1083
1084
# File 'lib/couch_foo/base.rb', line 1079

def property_types
  @properties_type ||= properties.inject({}) do |types, property|
    types[property.name] = property.type
    types
  end
end

.protected_attributesObject

Returns an array of all the attributes that have been protected from mass-assignment.



918
919
920
# File 'lib/couch_foo/base.rb', line 918

def protected_attributes # :nodoc:
  read_inheritable_attribute("attr_protected")
end

.readonly_attributesObject

Returns an array of all the attributes that have been specified as readonly



962
963
964
# File 'lib/couch_foo/base.rb', line 962

def readonly_attributes
  read_inheritable_attribute("attr_readonly")
end

.reset_document_class_nameObject



995
996
997
998
999
1000
1001
1002
1003
1004
# File 'lib/couch_foo/base.rb', line 995

def reset_document_class_name
  name = self.name
  unless self == base_class
    name = superclass.document_class_name
  end
  
  doc_class_name = "#{document_name_prefix}#{name}#{document_name_suffix}"
  set_document_class_name(doc_class_name)
  doc_class_name
end

.reset_property_informationObject

Resets all the cached information about properties, which will cause them to be reloaded on the next request.



1093
1094
1095
1096
# File 'lib/couch_foo/base.rb', line 1093

def reset_property_information
  generated_methods.each { |name| undef_method(name) }
  @property_names = @properties = @property_types = @generated_methods = @inheritance_column = nil
end

.respond_to?(method_id, include_private = false) ⇒ Boolean

Returns:



1223
1224
1225
1226
1227
1228
# File 'lib/couch_foo/base.rb', line 1223

def respond_to?(method_id, include_private = false)
  if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
    return true if all_attributes_exists?(extract_attribute_names_from_match(match))
  end
  super
end

.set_document_class_name(value = nil, &block) ⇒ Object Also known as: document_class_name=

Sets the document class name to use to the given value, or (if the value is nil or false) to the value returned by the given block.

class Project < CouchFoo::Base
  set_document_class_name "project"
end


1012
1013
1014
# File 'lib/couch_foo/base.rb', line 1012

def set_document_class_name(value = nil, &block)
  define_attr_method :document_class_name, value, &block
end

.set_inheritance_column(value = nil, &block) ⇒ Object Also known as: inheritance_column=

Sets the name of the inheritance column to use to the given value, or (if the value # is nil or false) to the value returned by the given block.

class Project < CouchFoo::Base
  set_inheritance_column do
    original_inheritance_column + "_id"
  end
end


1032
1033
1034
# File 'lib/couch_foo/base.rb', line 1032

def set_inheritance_column(value = nil, &block)
  define_attr_method :inheritance_column, value, &block
end

.silenceObject

Silences the logger for the duration of the block.



1195
1196
1197
1198
1199
1200
# File 'lib/couch_foo/base.rb', line 1195

def silence
  old_logger_level, logger.level = logger.level, Logger::ERROR if logger
  yield
ensure
  logger.level = old_logger_level if logger
end

.update(id, attributes) ⇒ Object

Updates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.

Attributes

  • id - This should be the id or an array of ids to be updated.

  • attributes - This should be a Hash of attributes to be set on the object, or an array of Hashes.

Examples

# Updating one record:
Person.update('6180e9a0-cdca-012b-14a5-001a921a2bec', { :user_name => 'Samuel', :group => 'expert' })

# Updating multiple records:
people = { '6180e9a0-cdca-012b-14a5-001a921a2bec' => { "first_name" => "David" }, 'e6f6a870-cdc9-012b-14a3-001a921a2bec' => { "first_name" => "Jeremy" } }
Person.update(people.keys, people.values)


716
717
718
719
720
721
722
723
724
725
# File 'lib/couch_foo/base.rb', line 716

def update(id, attributes)
  if id.is_a?(Array)
    idx = -1
    id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
  else
    object = find(id)
    object.update_attributes(attributes)
    object
  end
end

.update_all(updates, options = {}) ⇒ Object

Updates all records with details given if they match a set of conditions supplied. Even though this uses a bulk save and immediately commits it must first find the relevant documents so is O(n+1) rather than O(1)

Attributes

  • updates - A hash of attributes to update

  • options - As CouchFoo#find. Unlike ActiveRecord :order and :limit cannot be used togther

    unless via a custom view (see notes in CouchFoo#find)
    

Examples

# Update all billing objects with the 3 different attributes given
Billing.update_all( :category => 'authorized', :approved => 1, :author => 'David' )

# Update records that match our conditions
Billing.update_all( {:author = 'David'}, :conditions => {:title => 'Rails'} )


797
798
799
800
# File 'lib/couch_foo/base.rb', line 797

def update_all(updates, options = {})
  find(:all, options).each {|d| d.update_attributes(updates, true)}
  database.commit
end

.update_counters(id, counters) ⇒ Object

A generic “counter updater” implementation, intended primarily to be used by increment_counter and decrement_counter, but which may also be useful on its own. Unlike ActiveRecord this does not update the database directly but has to first find the record. Therefore updates require 2 database requests.

Attributes

  • id - The id of the object you wish to update a counter on.

  • counters - An Array of Hashes containing the names of the fields to update as keys and the amount to update the field by as values.

Examples

# For the Post with id of '5aef343ab2', decrement the comment_count by 1, and
# increment the action_count by 1
Post.update_counters 'aef343ab2', :comment_count => -1, :action_count => 1


846
847
848
849
850
851
852
# File 'lib/couch_foo/base.rb', line 846

def update_counters(id, counters)
  record = find(id)
  counters.each do |key,value|
    record.increment(key, value)
  end
  record.save
end

.view(name, map_function, reduce_function = nil, standard_options = {}) ⇒ Object

Create a view and return the documents associated with that view. It requires a name, find_function and optional reduce function (see wiki.apache.org/couchdb/HTTP_view_API). At the moment this function assumes you’re going to emit a doc as the value (required to rebuild the model after running the query)

For example:

class Note
  view :latest_submissions, "function(doc) {if(doc.ruby_class == 'Note') {emit([doc.created_at , doc.note], doc); } }", nil, :descending => true
...
end

This example would be an effective way to get the latest notes sorted by create date and note contents. The above view could then be called: Note.latest_submissions(:limit => 5)

NOTE: We use descending => true and not order as order is applied after the results are retrieved from CouchDB whereas descending is a CouchDB view option. More on this can be found in the #find documentation NOTE: Custom views do not worked with named scopes, any desired scopes should be coded into the map function



1145
1146
1147
# File 'lib/couch_foo/base.rb', line 1145

def view(name, map_function, reduce_function = nil, standard_options = {})
  views << View.new(name, map_function, reduce_function, standard_options)
end

.view_namesObject



1153
1154
1155
# File 'lib/couch_foo/base.rb', line 1153

def view_names
  @view_names ||= views.map{ |view| view.name }
end

.viewsObject



1149
1150
1151
# File 'lib/couch_foo/base.rb', line 1149

def views
  @views ||= Set.new()
end

Instance Method Details

#==(comparison_object) ⇒ Object

Returns true if the comparison_object is the same object, or is of the same type and has the same id.



1889
1890
1891
1892
1893
1894
# File 'lib/couch_foo/base.rb', line 1889

def ==(comparison_object)
  comparison_object.equal?(self) ||
    (comparison_object.instance_of?(self.class) &&
      comparison_object.id == id &&
      !comparison_object.new_record?)
end

#[](attr_name) ⇒ Object

Returns the value of the attribute identified by attr_name after it has been typecast (for example, “2004-12-12” in a data property is cast to a date object, like Date.new(2004, 12, 12)). (Alias for the protected read_attribute method).



1812
1813
1814
# File 'lib/couch_foo/base.rb', line 1812

def [](attr_name)
  read_attribute(attr_name)
end

#[]=(attr_name, value) ⇒ Object

Updates the attribute identified by attr_name with the specified value. (Alias for the protected write_attribute method).



1818
1819
1820
# File 'lib/couch_foo/base.rb', line 1818

def []=(attr_name, value)
  write_attribute(attr_name, value)
end

#_idObject Also known as: id

Returns the unqiue id of the document



1645
1646
1647
# File 'lib/couch_foo/base.rb', line 1645

def _id
  attributes["_id"]
end

#_revObject Also known as: rev

Returns the revision id of the document



1651
1652
1653
# File 'lib/couch_foo/base.rb', line 1651

def _rev
  attributes["_rev"]
end

#attribute_for_inspect(attr_name) ⇒ Object

Format attributes nicely for inspect.



1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
# File 'lib/couch_foo/base.rb', line 1859

def attribute_for_inspect(attr_name)
  value = read_attribute(attr_name)

  if value.is_a?(String) && value.length > 50
    "#{value[0..50]}...".inspect
  elsif value.is_a?(Date) || value.is_a?(Time)
    %("#{value.to_s(:db)}")
  else
    value.inspect
  end
end

#attribute_namesObject

Returns an array of names for the attributes available on this object sorted alphabetically.



1884
1885
1886
# File 'lib/couch_foo/base.rb', line 1884

def attribute_names
  @attributes.keys.map{|a| a.to_s}.sort
end

#attribute_present?(attribute) ⇒ Boolean

Returns true if the specified attribute has been set by the user or by a database load and is neither nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).

Returns:



1873
1874
1875
1876
# File 'lib/couch_foo/base.rb', line 1873

def attribute_present?(attribute)
  value = read_attribute(attribute)
  !value.blank?
end

#attributesObject

Returns a hash of all the attributes with their names as keys and the values of the attributes as values.



1843
1844
1845
1846
1847
1848
# File 'lib/couch_foo/base.rb', line 1843

def attributes
  self.attribute_names.inject({}) do |attrs, name|
    attrs[name] = read_attribute(name)
    attrs
  end
end

#attributes=(new_attributes, guard_protected_attributes = true) ⇒ Object

Allows you to set all the attributes at once by passing in a hash with keys matching the attribute names (which again matches the property names). Sensitive attributes can be protected from this form of mass-assignment by using the attr_protected macro. Or you can alternatively specify which attributes can be accessed with the attr_accessible macro. Then all the attributes not included in that won’t be allowed to be mass-assigned.



1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
# File 'lib/couch_foo/base.rb', line 1827

def attributes=(new_attributes, guard_protected_attributes = true)
  return if new_attributes.nil?
  attributes = normalize_attrs(new_attributes)

  multi_parameter_attributes = []
  attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes

  attributes.each do |k, v|
    k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
  end

  assign_multiparameter_attributes(multi_parameter_attributes)
end

#attributes_before_type_castObject

Returns a hash of attributes before typecasting and deserialization.



1851
1852
1853
1854
1855
1856
# File 'lib/couch_foo/base.rb', line 1851

def attributes_before_type_cast
  self.attribute_names.inject({}) do |attrs, name|
    attrs[name] = read_attribute_before_type_cast(name)
    attrs
  end
end

#becomes(klass) ⇒ Object

Returns an instance of the specified klass with the attributes of the current record. This is mostly useful in relation to inheritance structures where you want a subclass to appear as the superclass. This can be used along with record identification in Action Pack to allow, say, Client < Company to do something like render :partial => @client.becomes(Company) to render that instance using the companies/company partial instead of clients/client.

Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either instance will affect the other.



1714
1715
1716
1717
1718
1719
1720
# File 'lib/couch_foo/base.rb', line 1714

def becomes(klass)
  returning klass.new do |became|
    became.instance_variable_set("@attributes", @attributes)
    became.instance_variable_set("@attributes_cache", @attributes_cache)
    became.instance_variable_set("@new_record", new_record?)
  end
end

#cloneObject



1697
1698
1699
1700
1701
1702
1703
# File 'lib/couch_foo/base.rb', line 1697

def clone
  attrs = clone_attributes(:read_attribute_before_type_cast)
  attributes_protected_by_default.each {|a| attrs.delete(a) unless a == "ruby_class"}
  record = self.class.new
  record.send :instance_variable_set, '@attributes', attrs
  record
end

#decrement(attribute, by = 1) ⇒ Object

Initializes attribute to zero if nil and subtracts the value passed as by (default is 1). The decrement is performed directly on the underlying attribute, no setter is invoked. Only makes sense for number-based attributes. Returns self.



1766
1767
1768
1769
1770
# File 'lib/couch_foo/base.rb', line 1766

def decrement(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] -= by
  self
end

#decrement!(attribute, by = 1) ⇒ Object

Wrapper around decrement that saves the record. This method differs from its non-bang version in that it passes through the attribute setter. Saving is not subjected to validation checks. Returns true if the record could be saved.



1776
1777
1778
# File 'lib/couch_foo/base.rb', line 1776

def decrement!(attribute, by = 1)
  decrement(attribute, by).update_attribute(attribute, self[attribute])
end

#destroyObject



1690
1691
1692
1693
1694
1695
# File 'lib/couch_foo/base.rb', line 1690

def destroy
  unless new_record?
    self.class.database.delete(@attributes)
  end
  freeze
end

#eql?(comparison_object) ⇒ Boolean

Delegates to ==

Returns:



1897
1898
1899
# File 'lib/couch_foo/base.rb', line 1897

def eql?(comparison_object)
  self == (comparison_object)
end

#freezeObject

Freeze the attributes hash such that associations are still accessible, even on destroyed records.



1908
1909
1910
# File 'lib/couch_foo/base.rb', line 1908

def freeze
  @attributes.freeze; self
end

#frozen?Boolean

Returns true if the attributes hash has been frozen.

Returns:



1913
1914
1915
# File 'lib/couch_foo/base.rb', line 1913

def frozen?
  @attributes.frozen?
end

#has_attribute?(attr_name) ⇒ Boolean

Returns true if the given attribute is in the attributes hash

Returns:



1879
1880
1881
# File 'lib/couch_foo/base.rb', line 1879

def has_attribute?(attr_name)
  @attributes.has_key?(attr_name.to_s)
end

#hashObject

Delegates to id in order to allow two records of the same type and id to work with something like:

[ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]


1903
1904
1905
# File 'lib/couch_foo/base.rb', line 1903

def hash
  id.hash
end

#increment(attribute, by = 1) ⇒ Object

Initializes attribute to zero if nil and adds the value passed as by (default is 1). The increment is performed directly on the underlying attribute, no setter is invoked. Only makes sense for number-based attributes. Returns self.



1749
1750
1751
1752
1753
# File 'lib/couch_foo/base.rb', line 1749

def increment(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] += by
  self
end

#increment!(attribute, by = 1) ⇒ Object

Wrapper around increment that saves the record. This method differs from its non-bang version in that it passes through the attribute setter. Saving is not subjected to validation checks. Returns true if the record could be saved.



1759
1760
1761
# File 'lib/couch_foo/base.rb', line 1759

def increment!(attribute, by = 1)
  increment(attribute, by).update_attribute(attribute, self[attribute])
end

#inspectObject

Returns the contents of the record as a nicely formatted string.



1929
1930
1931
1932
1933
1934
# File 'lib/couch_foo/base.rb', line 1929

def inspect
  attributes_as_nice_string = (self.class.property_names + unchangeable_property_names).collect { |name|
    "#{name}: #{attribute_for_inspect(name)}"
  }.compact.join(", ")
  "#<#{self.class} #{attributes_as_nice_string}>"
end

#new_record?Boolean

Returns true if this object hasn’t been saved yet – that is, a record for the object doesn’t exist yet.

Returns:



1668
1669
1670
# File 'lib/couch_foo/base.rb', line 1668

def new_record?
  defined?(@new_record) && @new_record
end

#readonly!Object

Marks this record as read only.



1924
1925
1926
# File 'lib/couch_foo/base.rb', line 1924

def readonly!
  @readonly = true
end

#readonly?Boolean

Returns true if the record is read only. Records loaded through joins with piggy-back attributes will be marked as read only since they cannot be saved.

Returns:



1919
1920
1921
# File 'lib/couch_foo/base.rb', line 1919

def readonly?
  defined?(@readonly) && @readonly == true
end

#reload(options = nil) ⇒ Object

Reloads the attributes of this object from the database. The optional options argument is passed to find when reloading so you may do e.g. record.reload(:lock => true) to reload the same record with an exclusive row lock.



1800
1801
1802
1803
1804
1805
1806
# File 'lib/couch_foo/base.rb', line 1800

def reload(options = nil)
  #clear_aggregation_cache
  clear_association_cache
  @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
  @attributes_cache = {}
  self
end

#ruby_classObject

Returns the ruby_class of the document, as stored in the document to know which ruby object to map back to



1658
1659
1660
# File 'lib/couch_foo/base.rb', line 1658

def ruby_class
  attributes["ruby_class"]
end

#saveObject

  • No record exists: Creates a new record with values matching those of the object attributes.

  • A record does exist: Updates the record with values matching those of the object attributes.

Note: If your model specifies any validations then the method declaration dynamically changes to:

save(perform_validation=true, bulk_save = self.class.database.bulk_save?)

Calling save(false) saves the model without running validations. See CouchFoo::Validations for more information.



1680
1681
1682
# File 'lib/couch_foo/base.rb', line 1680

def save
  create_or_update
end

#save!Object

Attempts to save the record, but instead of just returning false if it couldn’t happen, it raises a DocumentNotSaved exception.



1686
1687
1688
# File 'lib/couch_foo/base.rb', line 1686

def save!
  create_or_update || raise(DocumentNotSaved)
end

#to_paramObject

Enables Couch Foo objects to be used as URL parameters in Action Pack automatically.



1663
1664
1665
# File 'lib/couch_foo/base.rb', line 1663

def to_param
  (id = self.id) ? id.to_s : nil
end

#toggle(attribute) ⇒ Object

Assigns to attribute the boolean opposite of attribute?. So if the predicate returns true the attribute will become false. This method toggles directly the underlying value without calling any setter. Returns self.



1784
1785
1786
1787
# File 'lib/couch_foo/base.rb', line 1784

def toggle(attribute)
  self[attribute] = !send("#{attribute}?")
  self
end

#toggle!(attribute) ⇒ Object

Wrapper around toggle that saves the record. This method differs from its non-bang version in that it passes through the attribute setter. Saving is not subjected to validation checks. Returns true if the record could be saved.



1793
1794
1795
# File 'lib/couch_foo/base.rb', line 1793

def toggle!(attribute)
  toggle(attribute).update_attribute(attribute, self[attribute])
end

#update_attribute(name, value) ⇒ Object

Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. Note: This method is overwritten by the Validation module that’ll make sure that updates made with this method aren’t subjected to validation checks. Hence, attributes can be updated even if the full object isn’t valid.



1727
1728
1729
1730
# File 'lib/couch_foo/base.rb', line 1727

def update_attribute(name, value)
  send(name.to_s + '=', value)
  save
end

#update_attributes(attributes) ⇒ Object

Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will fail and false will be returned.



1734
1735
1736
1737
# File 'lib/couch_foo/base.rb', line 1734

def update_attributes(attributes)
  self.attributes = attributes
  save
end

#update_attributes!(attributes) ⇒ Object

Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.



1741
1742
1743
1744
# File 'lib/couch_foo/base.rb', line 1741

def update_attributes!(attributes)
  self.attributes = attributes
  save!
end