Class: Ohm::Model
- Inherits:
-
Object
- Object
- Ohm::Model
- Includes:
- Validations
- Defined in:
- lib/ohm.rb
Overview
This is the class that you need to extend in order to define your own models.
Probably the most magic happening within Model is the catching of const_missing exceptions to allow the use of constants even before they are defined.
Defined Under Namespace
Modules: Validations Classes: Collection, Index, IndexNotFound, List, MissingID, Set, Wrapper
Constant Summary
- @@attributes =
Hash.new { |hash, key| hash[key] = [] }
- @@collections =
Hash.new { |hash, key| hash[key] = [] }
- @@counters =
Hash.new { |hash, key| hash[key] = [] }
- @@indices =
Hash.new { |hash, key| hash[key] = [] }
Instance Attribute Summary (collapse)
Class Method Summary (collapse)
-
+ (Ohm::Model?) [](id)
Allows you to find an Model instance by its id.
-
+ (Object) all
Returns a set containing all the members of a given class.
-
+ (Object) attribute(name)
Defines a string attribute for the model.
-
+ (Object) attributes
All the defined attributes within a class.
-
+ (Object) collection(name, model, reference = to_reference)
Define a collection of objects which have a reference to this model.
- + (Boolean) collection?(value) private
-
+ (Object) collections
All the defined collections within a class.
-
+ (Object) connect(*options)
Makes the model connect to a different Redis instance.
-
+ (Object) const_missing(name)
protected
Wraps any missing constants lazily in Wrapper delaying the evaluation of constants until they are actually needed.
-
+ (Object) counter(name)
Defines a counter attribute for the model.
-
+ (Object) counters
All the defined counters within a class.
-
+ (Ohm::Model) create(*args)
Convenience method to create and return the newly created object.
-
+ (Object) db
private
Provides access to the Redis database.
- + (Object) db=(connection) private
-
+ (String) encode(value)
Encode a value, making it safe to use as a key.
- + (Boolean) exists?(id) private
-
+ (Object) find(hash)
Search across multiple indices and return the intersection of the sets.
-
+ (Object) index(att)
Creates an index (a set) that will be used for finding instances.
-
+ (Ohm::Key) index_key_for(name, value)
private
Get the index name for a specific index and value pair.
-
+ (Object) indices
All the defined indices within a class.
-
+ (Object) key
private
Allows you to do key manipulations scoped solely to your class.
-
+ (Object) list(name, model)
Defines a list attribute for the model.
-
+ (Object) reference(name, model)
Define a reference to another object.
-
+ (Object) set(name, model)
Defines a set attribute for the model.
-
+ (Symbol) to_reference
Used by collection to infer the reference.
Instance Method Summary (collapse)
-
- (true, false) ==(other)
(also: #eql?)
Implementation of equality checking.
- - (Object) add_to_index(att, value = send(att)) private
- - (Object) add_to_indices private
-
- (Object) attributes
Convenience wrapper for attributes.
- - (Boolean) collection?(value) private
-
- (Object) collections
Convenience wrapper for collections.
-
- (Object) counters
Convenience wrapper for counters.
-
- (Ohm::Model?) create
Create this model if it passes all validations.
- - (Object) create_model_membership private
- - (Object) db private
-
- (Object) decr(att, count = 1)
Decrement the counter denoted by :att.
-
- (Ohm::Model) delete
Delete this object from the Redis datastore, ensuring that all indices, attributes, collections, etc are also deleted with it.
- - (Object) delete_attributes(atts) private
- - (Object) delete_from_indices private
- - (Object) delete_model_membership private
-
- (Fixnum) hash
Allows you to safely use an instance of Model as a key in a Ruby hash without running into weird scenarios.
-
- (Object) incr(att, count = 1)
Increment the counter denoted by :att.
-
- (Object) index_key_for(att, value)
private
Thin wrapper around index_key_for.
-
- (Object) indices
Convenience wrapper for indices.
-
- (Model) initialize(attrs = {})
constructor
Constructor for all subclasses of Model, which optionally takes a Hash of attribute value pairs.
-
- (Object) initialize_id
private
The meat of the ID generation code for Ohm.
-
- (Object) inspect
Returns everything, including attributes, collections, counters, and the id of this object.
-
- (Ohm::Key) key
A key scoped to the model which uses this object’s id.
-
- (Object) lock!
private
Lock the object so no other instances can modify it.
- - (Boolean) lock_expired?(timestamp) private
-
- (Object) mutex
Lock the object before executing the block, and release it once the block is done.
-
- (true, false) new?
Whether or not this object has an id.
-
- (String) read_local(att)
private
Get the value of a specific attribute.
-
- (Object) read_locals(attrs)
private
Read attributes en masse locally.
-
- (String) read_remote(att)
private
Used internally be the @_attributes hash to lazily load attributes when you need them.
-
- (Object) read_remotes(attrs)
private
Read attributes en masse remotely.
-
- (Ohm::Model?) save
Create or update this object based on the state of #new?.
-
- (Object) to_hash
Export the id and errors of the object.
-
- (String) to_json(*args)
Returns the JSON representation of the #to_hash for this object.
-
- (Object) unlock!
private
Release the lock.
-
- (Ohm::Model?) update(attrs)
Update this object, optionally accepting new attributes.
-
- (Object) update_attributes(attrs)
Locally update all attributes without persisting the changes.
- - (Object) update_indices private
-
- (Object) write
protected
Write all the attributes and counters of this object.
-
- (Object) write_local(att, value)
private
Write the value of an attribute locally, without persisting it.
-
- (Object) write_remote(att, value)
protected
Write a single attribute both locally and remotely.
Methods included from Validations
Methods included from Validations
#assert, #assert_format, #assert_numeric, #assert_present, #errors, #valid?, #validate
Constructor Details
- (Model) initialize(attrs = {})
Constructor for all subclasses of Ohm::Model, which optionally takes a Hash of attribute value pairs.
Starting with Ohm 0.1.0, you can use custom ids instead of being forced to use auto incrementing numeric ids, but keep in mind that you have to pass in the preferred id during object initialization.
1436 1437 1438 1439 1440 1441 |
# File 'lib/ohm.rb', line 1436 def initialize(attrs = {}) @id = nil @_memo = {} @_attributes = Hash.new { |hash, key| hash[key] = read_remote(key) } update_attributes(attrs) end |
Instance Attribute Details
- (Object) id
1048 1049 1050 |
# File 'lib/ohm.rb', line 1048 def id @id or raise MissingID end |
Class Method Details
+ (Ohm::Model?) [](id)
Allows you to find an Ohm::Model instance by its id.
1284 1285 1286 |
# File 'lib/ohm.rb', line 1284 def self.[](id) new(:id => id) if id && exists?(id) end |
+ (Object) all
Returns a set containing all the members of a given class.
1310 1311 1312 |
# File 'lib/ohm.rb', line 1310 def self.all Ohm::Model::Index.new(key[:all], Wrapper.wrap(self)) end |
+ (Object) attribute(name)
Defines a string attribute for the model. This attribute will be persisted by Redis as a string. Any value stored here will be retrieved in its string representation.
If you’re looking to have typecasting built in, you may want to look at Ohm::Typecast in Ohm::Contrib.
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 |
# File 'lib/ohm.rb', line 1061 def self.attribute(name) define_method(name) do read_local(name) end define_method(:#{name}=") do |value| write_local(name, value) end attributes << name unless attributes.include?(name) end |
+ (Object) attributes
All the defined attributes within a class.
1316 1317 1318 |
# File 'lib/ohm.rb', line 1316 def self.attributes @@attributes[self] end |
+ (Object) collection(name, model, reference = to_reference)
Define a collection of objects which have a reference to this model.
class Comment < Ohm::Model attribute :content reference :post, Post end class Post < Ohm::Model attribute :content collection :comments, Comment reference :author, Person end class Person < Ohm::Model attribute :name # When the name of the reference cannot be inferred, # you need to specify it in the third param. collection :posts, Post, :author end @person = Person.create :name => "Albert" @post = Post.create :content => "Interesting stuff", :author => @person @comment = Comment.create :content => "Indeed!", :post => @post @post.comments.first.content # => "Indeed!" @post..name # => "Albert"
Important: Please note that even though a collection is a set, you should not add or remove objects from this collection directly.
1254 1255 1256 1257 1258 1259 |
# File 'lib/ohm.rb', line 1254 def self.collection(name, model, reference = to_reference) model = Wrapper.wrap(model) define_method(name) { model.unwrap.find(:#{reference}_id" => send(:id)) } end |
+ (Boolean) collection?(value) (private)
1879 1880 1881 1882 |
# File 'lib/ohm.rb', line 1879 def self.collection?(value) value.kind_of?(Enumerable) && value.kind_of?(String) == false end |
+ (Object) collections
1341 1342 1343 |
# File 'lib/ohm.rb', line 1341 def self.collections @@collections[self] end |
+ (Object) connect(*options)
Makes the model connect to a different Redis instance. This is useful for scaling a large application, where one model can be stored in a different Redis instance, and some other groups of models can be in another Redis instance.
This approach of splitting models is a lot simpler than doing a distributed Redis solution and may well be the right solution for certain cases.
1717 1718 1719 |
# File 'lib/ohm.rb', line 1717 def self.connect(*) self.db = Ohm.connection(*) end |
+ (Object) const_missing(name) (protected)
Wraps any missing constants lazily in Wrapper delaying the evaluation of constants until they are actually needed.
1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 |
# File 'lib/ohm.rb', line 1790 def self.const_missing(name) wrapper = Wrapper.new(name) { const_get(name) } # Allow others to hook to const_missing. begin super(name) rescue NameError end wrapper end |
+ (Object) counter(name)
Defines a counter attribute for the model. This attribute can’t be assigned, only incremented or decremented. It will be zero by default.
1077 1078 1079 1080 1081 1082 1083 |
# File 'lib/ohm.rb', line 1077 def self.counter(name) define_method(name) do read_local(name).to_i end counters << name unless counters.include?(name) end |
+ (Object) counters
All the defined counters within a class.
1322 1323 1324 |
# File 'lib/ohm.rb', line 1322 def self.counters @@counters[self] end |
+ (Ohm::Model) create(*args)
Convenience method to create and return the newly created object.
1363 1364 1365 1366 1367 |
# File 'lib/ohm.rb', line 1363 def self.create(*args) model = new(*args) model.create model end |
+ (Object) db (private)
Provides access to the Redis database. This is shared accross all models and instances.
1805 1806 1807 |
# File 'lib/ohm.rb', line 1805 def self.db Ohm.threaded[self] || Ohm.redis end |
+ (Object) db=(connection) (private)
1809 1810 1811 |
# File 'lib/ohm.rb', line 1809 def self.db=(connection) Ohm.threaded[self] = connection end |
+ (String) encode(value)
Encode a value, making it safe to use as a key. Internally used by index_key_for to canonicalize the indexed values.
1394 1395 1396 |
# File 'lib/ohm.rb', line 1394 def self.encode(value) Base64.encode64(value.to_s).gsub("\n", "") end |
+ (Boolean) exists?(id) (private)
1818 1819 1820 |
# File 'lib/ohm.rb', line 1818 def self.exists?(id) key[:all].sismember(id) end |
+ (Object) find(hash)
Search across multiple indices and return the intersection of the sets.
1378 1379 1380 1381 1382 1383 1384 1385 1386 |
# File 'lib/ohm.rb', line 1378 def self.find(hash) unless hash.kind_of?(Hash) raise ArgumentError, "You need to supply a hash with filters. " + "If you want to find by ID, use #{self}[id] instead." end all.find(hash) end |
+ (Object) index(att)
Creates an index (a set) that will be used for finding instances.
If you want to find a model instance by some attribute value, then an index for that attribute must exist.
1144 1145 1146 |
# File 'lib/ohm.rb', line 1144 def self.index(att) indices << att unless indices.include?(att) end |
+ (Ohm::Key) index_key_for(name, value) (private)
Get the index name for a specific index and value pair. The return value is an instance of Key, which you can readily do Redis operations on.
1966 1967 1968 1969 |
# File 'lib/ohm.rb', line 1966 def self.index_key_for(name, value) raise IndexNotFound, name unless indices.include?(name) key[name][encode(value)] end |
+ (Object) indices
All the defined indices within a class.
1347 1348 1349 |
# File 'lib/ohm.rb', line 1347 def self.indices @@indices[self] end |
+ (Object) key (private)
Allows you to do key manipulations scoped solely to your class.
1814 1815 1816 |
# File 'lib/ohm.rb', line 1814 def self.key Key.new(self, db) end |
+ (Object) list(name, model)
Defines a list attribute for the model. It can be accessed only after the model instance is created, or if you assign an :id during object construction.
1112 1113 1114 1115 |
# File 'lib/ohm.rb', line 1112 def self.list(name, model) define_memoized_method(name) { List.new(key[name], Wrapper.wrap(model)) } collections << name unless collections.include?(name) end |
+ (Object) reference(name, model)
Define a reference to another object.
1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 |
# File 'lib/ohm.rb', line 1181 def self.reference(name, model) model = Wrapper.wrap(model) reader = :#{name}_id" writer = :#{name}_id=" attributes << reader unless attributes.include?(reader) index reader define_memoized_method(name) do model.unwrap[send(reader)] end define_method(:#{name}=") do |value| @_memo.delete(name) send(writer, value ? value.id : nil) end define_method(reader) do read_local(reader) end define_method(writer) do |value| @_memo.delete(name) write_local(reader, value) end end |
+ (Object) set(name, model)
Defines a set attribute for the model. It can be accessed only after the model instance is created. Sets are recommended when insertion and retreival order is irrelevant, and operations like union, join, and membership checks are important.
1123 1124 1125 1126 |
# File 'lib/ohm.rb', line 1123 def self.set(name, model) define_memoized_method(name) { Set.new(key[name], Wrapper.wrap(model)) } collections << name unless collections.include?(name) end |
+ (Symbol) to_reference
Used by collection to infer the reference.
1265 1266 1267 1268 1269 1270 |
# File 'lib/ohm.rb', line 1265 def self.to_reference name.to_s. match(/^(?:.*::)*(.*)$/)[1]. gsub(/([a-z\d])([A-Z])/, '\1_\2'). downcase.to_sym end |
Instance Method Details
- (true, false) ==(other) Also known as: eql?
Implementation of equality checking. Equality is defined by two simple rules:
They have the same class.
They have the same key (Redis key e.g. Post:1 == Post:1).
1624 1625 1626 1627 1628 |
# File 'lib/ohm.rb', line 1624 def ==(other) other.kind_of?(self.class) && other.key == key rescue MissingID false end |
- (Object) add_to_index(att, value = send(att)) (private)
1884 1885 1886 1887 1888 |
# File 'lib/ohm.rb', line 1884 def add_to_index(att, value = send(att)) index = index_key_for(att, value) index.sadd(id) key[:_indices].sadd(index) end |
- (Object) add_to_indices (private)
1868 1869 1870 1871 1872 1873 |
# File 'lib/ohm.rb', line 1868 def add_to_indices indices.each do |att| next add_to_index(att) unless collection?(send(att)) send(att).each { |value| add_to_index(att, value) } end end |
- (Object) attributes
Convenience wrapper for attributes.
1598 1599 1600 |
# File 'lib/ohm.rb', line 1598 def attributes self.class.attributes end |
- (Boolean) collection?(value) (private)
1875 1876 1877 |
# File 'lib/ohm.rb', line 1875 def collection?(value) self.class.collection?(value) end |
- (Object) collections
Convenience wrapper for collections.
1608 1609 1610 |
# File 'lib/ohm.rb', line 1608 def collections self.class.collections end |
- (Object) counters
Convenience wrapper for counters.
1603 1604 1605 |
# File 'lib/ohm.rb', line 1603 def counters self.class.counters end |
- (Ohm::Model?) create
Create this model if it passes all validations.
1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 |
# File 'lib/ohm.rb', line 1452 def create return unless valid? initialize_id mutex do create_model_membership write add_to_indices end end |
- (Object) create_model_membership (private)
1854 1855 1856 |
# File 'lib/ohm.rb', line 1854 def create_model_membership self.class.all << self end |
- (Object) db (private)
1846 1847 1848 |
# File 'lib/ohm.rb', line 1846 def db self.class.db end |
- (Object) decr(att, count = 1)
Decrement the counter denoted by :att.
1526 1527 1528 |
# File 'lib/ohm.rb', line 1526 def decr(att, count = 1) incr(att, -count) end |
- (Ohm::Model) delete
Delete this object from the Redis datastore, ensuring that all indices, attributes, collections, etc are also deleted with it.
1503 1504 1505 1506 1507 1508 |
# File 'lib/ohm.rb', line 1503 def delete delete_from_indices delete_attributes(collections) unless collections.empty? delete_model_membership self end |
- (Object) delete_attributes(atts) (private)
1850 1851 1852 |
# File 'lib/ohm.rb', line 1850 def delete_attributes(atts) db.del(*atts.map { |att| key[att] }) end |
- (Object) delete_from_indices (private)
1890 1891 1892 1893 1894 1895 1896 |
# File 'lib/ohm.rb', line 1890 def delete_from_indices key[:_indices].smembers.each do |index| db.srem(index, id) end key[:_indices].del end |
- (Object) delete_model_membership (private)
1858 1859 1860 1861 |
# File 'lib/ohm.rb', line 1858 def delete_model_membership key.del self.class.all.delete(self) end |
- (Fixnum) hash
Allows you to safely use an instance of Ohm::Model as a key in a Ruby hash without running into weird scenarios.
1651 1652 1653 |
# File 'lib/ohm.rb', line 1651 def hash new? ? super : key.hash end |
- (Object) incr(att, count = 1)
Increment the counter denoted by :att.
1514 1515 1516 1517 1518 1519 1520 |
# File 'lib/ohm.rb', line 1514 def incr(att, count = 1) unless counters.include?(att) raise ArgumentError, "#{att.inspect} is not a counter." end write_local(att, key.hincrby(att, count)) end |
- (Object) index_key_for(att, value) (private)
Thin wrapper around index_key_for.
1972 1973 1974 |
# File 'lib/ohm.rb', line 1972 def index_key_for(att, value) self.class.index_key_for(att, value) end |
- (Object) indices
Convenience wrapper for indices.
1613 1614 1615 |
# File 'lib/ohm.rb', line 1613 def indices self.class.indices end |
- (Object) initialize_id (private)
The meat of the ID generation code for Ohm. For cases where you want to customize ID generation (i.e. use GUIDs or Base62 ids) then you simply override this method in your model.
1842 1843 1844 |
# File 'lib/ohm.rb', line 1842 def initialize_id @id ||= self.class.key[:id].incr.to_s end |
- (Object) inspect
Returns everything, including attributes, collections, counters, and the id of this object.
Useful for debugging and for doing irb work.
1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 |
# File 'lib/ohm.rb', line 1676 def inspect everything = (attributes + collections + counters).map do |att| value = begin send(att) rescue MissingID nil end [att, value.inspect] end sprintf("#<%s:%s %s>", self.class, new? ? "?" : id, everything.map {|e| e.join("=") }.join(" ") ) end |
- (Ohm::Key) key
A key scoped to the model which uses this object’s id.
1725 1726 1727 |
# File 'lib/ohm.rb', line 1725 def key self.class.key[id] end |
- (Object) lock! (private)
Lock the object so no other instances can modify it. This method implements the design pattern for locks described at: code.google.com/p/redis/wiki/SetnxCommand
1981 1982 1983 1984 1985 1986 1987 1988 1989 |
# File 'lib/ohm.rb', line 1981 def lock! until key[:_lock].setnx(Time.now.to_f + 0.5) next unless = key[:_lock].get sleep(0.1) and next unless lock_expired?() break unless = key[:_lock].getset(Time.now.to_f + 0.5) break if lock_expired?() end end |
- (Boolean) lock_expired?(timestamp) (private)
1997 1998 1999 |
# File 'lib/ohm.rb', line 1997 def lock_expired?() .to_f < Time.now.to_f end |
- (Object) mutex
1663 1664 1665 1666 1667 1668 1669 |
# File 'lib/ohm.rb', line 1663 def mutex lock! yield self ensure unlock! end |
- (true, false) new?
Whether or not this object has an id.
1444 1445 1446 |
# File 'lib/ohm.rb', line 1444 def new? !@id end |
- (String) read_local(att) (private)
Get the value of a specific attribute. An important fact about attributes in Ohm is that they are all loaded lazily.
1903 1904 1905 |
# File 'lib/ohm.rb', line 1903 def read_local(att) @_attributes[att] end |
- (Object) read_locals(attrs) (private)
Read attributes en masse locally.
1931 1932 1933 1934 1935 |
# File 'lib/ohm.rb', line 1931 def read_locals(attrs) attrs.map do |att| send(att) end end |
- (String) read_remote(att) (private)
Used internally be the @_attributes hash to lazily load attributes when you need them. You may also use this in your code if you know what you are doing.
1921 1922 1923 1924 1925 1926 1927 1928 |
# File 'lib/ohm.rb', line 1921 def read_remote(att) unless new? value = key.hget(att) value.respond_to?(:force_encoding) ? value.force_encoding("UTF-8") : value end end |
- (Object) read_remotes(attrs) (private)
Read attributes en masse remotely.
1938 1939 1940 1941 1942 |
# File 'lib/ohm.rb', line 1938 def read_remotes(attrs) attrs.map do |att| read_remote(att) end end |
- (Ohm::Model?) save
Create or update this object based on the state of #new?.
1467 1468 1469 1470 1471 1472 1473 1474 1475 |
# File 'lib/ohm.rb', line 1467 def save return create if new? return unless valid? mutex do write update_indices end end |
- (Object) to_hash
Export the id and errors of the object. The `to_hash` takes the opposite approach of providing all the attributes and instead favors a white listed approach.
1561 1562 1563 1564 1565 1566 |
# File 'lib/ohm.rb', line 1561 def to_hash attrs = {} attrs[:id] = id unless new? attrs[:errors] = errors unless errors.empty? attrs end |
- (String) to_json(*args)
1593 1594 1595 |
# File 'lib/ohm.rb', line 1593 def to_json(*args) to_hash.to_json(*args) end |
- (Object) unlock! (private)
Release the lock.
1993 1994 1995 |
# File 'lib/ohm.rb', line 1993 def unlock! key[:_lock].del end |
- (Ohm::Model?) update(attrs)
Update this object, optionally accepting new attributes.
1483 1484 1485 1486 |
# File 'lib/ohm.rb', line 1483 def update(attrs) update_attributes(attrs) save end |
- (Object) update_attributes(attrs)
Locally update all attributes without persisting the changes. Internally used by #initialize and #update to set attribute value pairs.
1493 1494 1495 1496 1497 |
# File 'lib/ohm.rb', line 1493 def update_attributes(attrs) attrs.each do |key, value| send(:#{key}=", value) end end |
- (Object) update_indices (private)
1863 1864 1865 1866 |
# File 'lib/ohm.rb', line 1863 def update_indices delete_from_indices add_to_indices end |
- (Object) write (protected)
Write all the attributes and counters of this object. The operation is actually a 2-step process:
Delete the current key, e.g. Post:2.
Set all of the new attributes (using HMSET).
The DEL and HMSET operations are wrapped in a MULTI EXEC block to ensure the atomicity of the write operation.
1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 |
# File 'lib/ohm.rb', line 1748 def write unless (attributes + counters).empty? atts = (attributes + counters).inject([]) { |ret, att| value = send(att).to_s ret.push(att, value) if not value.empty? ret } db.multi do key.del key.hmset(*atts.flatten) if atts.any? end end end |
- (Object) write_local(att, value) (private)
Write the value of an attribute locally, without persisting it.
1911 1912 1913 |
# File 'lib/ohm.rb', line 1911 def write_local(att, value) @_attributes[att] = value end |
- (Object) write_remote(att, value) (protected)
Write a single attribute both locally and remotely. It’s very important to know that this method skips validation checks, therefore you must ensure data integrity and validity in your application code.
1775 1776 1777 1778 1779 1780 1781 1782 1783 |
# File 'lib/ohm.rb', line 1775 def write_remote(att, value) write_local(att, value) if value.to_s.empty? key.hdel(att) else key.hset(att, value) end end |