Class: CouchFoo::Base
- Inherits:
-
Object
- Object
- CouchFoo::Base
- 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
-
.abstract_class ⇒ Object
Set this to true if this is an abstract class (see
abstract_class?
).
Class Method Summary collapse
-
.===(object) ⇒ Object
Overwrite the default class equality method to provide support for association proxies.
-
.abstract_class? ⇒ Boolean
Returns whether this class is a base CouchFoo class.
-
.accessible_attributes ⇒ Object
Returns an array of all the attributes that have been made accessible to mass-assignment.
-
.all(*args) ⇒ Object
This is an alias for find(:all).
-
.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)
, orattributes=(attributes)
. -
.attr_protected(*attributes) ⇒ Object
Attributes named in this macro are protected from mass-assignment, such as
new(attributes)
,update_attributes(attributes)
, orattributes=(attributes)
. -
.attr_readonly(*attributes) ⇒ Object
Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
-
.base_class ⇒ Object
Returns the base subclass that this class descends from.
-
.benchmark(title, log_level = Logger::DEBUG, use_silence = true) ⇒ Object
Log and benchmark multiple statements in a single block.
-
.create(attributes = nil, &block) ⇒ Object
Creates an object (or multiple objects) and saves it to the database, if validations pass.
-
.decrement_counter(counter_name, id) ⇒ Object
Decrement a number field by one, usually representing a count.
-
.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.
- .default_sort_order ⇒ Object
-
.delete(id, rev) ⇒ Object
Delete an object (or multiple objects) where the _id and _rev given match the record.
-
.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.
-
.descends_from_couch_foo? ⇒ Boolean
True if this isn’t a concrete subclass needing a inheritence type condition.
-
.destroy(id) ⇒ Object
Destroy an object (or multiple objects) that has the given id.
-
.destroy_all(conditions = nil) ⇒ Object
Destroys the records matching
conditions
by instantiating each record and calling the destroy method. -
.document_class_name ⇒ Object
Guesses the document name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending directly from CouchFoo::Base.
-
.exists?(id_or_conditions) ⇒ Boolean
Checks whether a document exists in the database that matches conditions given.
-
.find(*args) ⇒ Object
CouchDB has a concept called views which show a subset of documents in the database subject to criteria.
-
.first(*args) ⇒ Object
A convenience wrapper for
find(:first, *args)
. -
.get_uuid ⇒ Object
Returns a unique UUID even across multiple machines.
-
.increment_counter(counter_name, id) ⇒ Object
Increment a number field by one, usually representing a count.
-
.inheritance_column ⇒ Object
Defines the propety name for use with single table inheritance – can be set in subclasses like so: self.inheritance_column = “type_id”.
- .inspect ⇒ Object
-
.last(*args) ⇒ Object
A convenience wrapper for
find(:last, *args)
. -
.properties ⇒ Object
Returns all properties defined on this class.
-
.property(name, type = Object, options = {}) ⇒ Object
Set a property for the document.
-
.property_names ⇒ Object
Returns an array of property names.
-
.property_types ⇒ Object
Returns a hash of property name to types.
-
.protected_attributes ⇒ Object
Returns an array of all the attributes that have been protected from mass-assignment.
-
.readonly_attributes ⇒ Object
Returns an array of all the attributes that have been specified as readonly.
- .reset_document_class_name ⇒ Object
-
.reset_property_information ⇒ Object
Resets all the cached information about properties, which will cause them to be reloaded on the next request.
- .respond_to?(method_id, include_private = false) ⇒ Boolean
-
.set_document_class_name(value = nil, &block) ⇒ Object
(also: 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.
-
.set_inheritance_column(value = nil, &block) ⇒ Object
(also: 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.
-
.silence ⇒ Object
Silences the logger for the duration of the block.
-
.update(id, attributes) ⇒ Object
Updates an object (or multiple objects) and saves it to the database, if validations pass.
-
.update_all(updates, options = {}) ⇒ Object
Updates all records with details given if they match a set of conditions supplied.
-
.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.
-
.view(name, map_function, reduce_function = nil, standard_options = {}) ⇒ Object
Create a view and return the documents associated with that view.
- .view_names ⇒ Object
- .views ⇒ Object
Instance Method Summary collapse
-
#==(comparison_object) ⇒ Object
Returns true if the
comparison_object
is the same object, or is of the same type and has the same id. -
#[](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)). -
#[]=(attr_name, value) ⇒ Object
Updates the attribute identified by
attr_name
with the specifiedvalue
. -
#_id ⇒ Object
(also: #id)
Returns the unqiue id of the document.
-
#_rev ⇒ Object
(also: #rev)
Returns the revision id of the document.
-
#attribute_for_inspect(attr_name) ⇒ Object
Format attributes nicely for inspect.
-
#attribute_names ⇒ Object
Returns an array of names for the attributes available on this object sorted alphabetically.
-
#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). -
#attributes ⇒ Object
Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
-
#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).
-
#attributes_before_type_cast ⇒ Object
Returns a hash of attributes before typecasting and deserialization.
-
#becomes(klass) ⇒ Object
Returns an instance of the specified
klass
with the attributes of the current record. - #clone ⇒ Object
-
#decrement(attribute, by = 1) ⇒ Object
Initializes
attribute
to zero ifnil
and subtracts the value passed asby
(default is 1). -
#decrement!(attribute, by = 1) ⇒ Object
Wrapper around
decrement
that saves the record. - #destroy ⇒ Object
-
#eql?(comparison_object) ⇒ Boolean
Delegates to ==.
-
#freeze ⇒ Object
Freeze the attributes hash such that associations are still accessible, even on destroyed records.
-
#frozen? ⇒ Boolean
Returns
true
if the attributes hash has been frozen. -
#has_attribute?(attr_name) ⇒ Boolean
Returns true if the given attribute is in the attributes hash.
-
#hash ⇒ Object
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) ].
-
#increment(attribute, by = 1) ⇒ Object
Initializes
attribute
to zero ifnil
and adds the value passed asby
(default is 1). -
#increment!(attribute, by = 1) ⇒ Object
Wrapper around
increment
that saves the record. -
#initialize(attributes = nil) ⇒ Base
constructor
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).
-
#inspect ⇒ Object
Returns the contents of the record as a nicely formatted string.
-
#new_record? ⇒ Boolean
Returns true if this object hasn’t been saved yet – that is, a record for the object doesn’t exist yet.
-
#readonly! ⇒ Object
Marks this record as read only.
-
#readonly? ⇒ Boolean
Returns
true
if the record is read only. -
#reload(options = nil) ⇒ Object
Reloads the attributes of this object from the database.
-
#ruby_class ⇒ Object
Returns the ruby_class of the document, as stored in the document to know which ruby object to map back to.
-
#save ⇒ Object
-
No record exists: Creates a new record with values matching those of the object attributes.
-
-
#save! ⇒ Object
Attempts to save the record, but instead of just returning false if it couldn’t happen, it raises a DocumentNotSaved exception.
-
#to_param ⇒ Object
Enables Couch Foo objects to be used as URL parameters in Action Pack automatically.
-
#toggle(attribute) ⇒ Object
Assigns to
attribute
the boolean opposite ofattribute?
. -
#toggle!(attribute) ⇒ Object
Wrapper around
toggle
that saves the record. -
#update_attribute(name, value) ⇒ Object
Updates a single attribute and saves the record.
-
#update_attributes(attributes) ⇒ Object
Updates all the attributes from the passed-in Hash and saves the record.
-
#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.
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_class ⇒ Object
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.
1219 1220 1221 |
# File 'lib/couch_foo/base.rb', line 1219 def abstract_class? defined?(@abstract_class) && @abstract_class == true end |
.accessible_attributes ⇒ Object
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. # => nil
customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
customer. # => nil
customer. = "Average"
customer. # => "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. # => nil
customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
customer. # => nil
customer. = "Average"
customer. # => "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_class ⇒ Object
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_order ⇒ Object
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.
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 withfind
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_name ⇒ Object
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")
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. UseModel.find(:first, *args)
or its shortcutModel.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. UseModel.find(:last, *args)
or its shortcutModel.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 shortcutModel.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) = args. () set_readonly_option!() case args.first when :first then find_initial() when :last then find_last() when :all then find_every() else find_from_ids(args, ) 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_uuid ⇒ Object
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_column ⇒ Object
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 |
.inspect ⇒ Object
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 |
.properties ⇒ Object
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, = {}) 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, [:default]) end |
.property_names ⇒ Object
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_types ⇒ Object
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_attributes ⇒ Object
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_attributes ⇒ Object
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_name ⇒ Object
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_information ⇒ Object
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
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=
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=
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 |
.silence ⇒ Object
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 togtherunless 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, = {}) find(:all, ).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, = {}) views << View.new(name, map_function, reduce_function, ) end |
.view_names ⇒ Object
1153 1154 1155 |
# File 'lib/couch_foo/base.rb', line 1153 def view_names @view_names ||= views.map{ |view| view.name } end |
.views ⇒ Object
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 |
#_id ⇒ Object 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 |
#_rev ⇒ Object 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_names ⇒ Object
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).
1873 1874 1875 1876 |
# File 'lib/couch_foo/base.rb', line 1873 def attribute_present?(attribute) value = read_attribute(attribute) !value.blank? end |
#attributes ⇒ Object
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_cast ⇒ Object
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 |
#clone ⇒ Object
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 |
#destroy ⇒ Object
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 ==
1897 1898 1899 |
# File 'lib/couch_foo/base.rb', line 1897 def eql?(comparison_object) self == (comparison_object) end |
#freeze ⇒ Object
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.
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
1879 1880 1881 |
# File 'lib/couch_foo/base.rb', line 1879 def has_attribute?(attr_name) @attributes.has_key?(attr_name.to_s) end |
#hash ⇒ Object
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 |
#inspect ⇒ Object
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.
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.
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( = nil) #clear_aggregation_cache clear_association_cache @attributes.update(self.class.find(self.id, ).instance_variable_get('@attributes')) @attributes_cache = {} self end |
#ruby_class ⇒ Object
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 |
#save ⇒ Object
-
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_param ⇒ Object
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 |