Class: Ohm::Model
- Inherits:
-
Object
- Object
- Ohm::Model
- Includes:
- Scrivener::Validations
- Defined in:
- lib/ohm.rb,
lib/ohm/json.rb
Overview
The base class for all your models. In order to better understand it, here is a semi-realtime explanation of the details involved when creating a User instance.
Example:
class User < Ohm::Model
attribute :name
index :name
attribute :email
unique :email
counter :points
set :posts, :Post
end
u = User.create(:name => "John", :email => "foo@bar.com")
u.incr :points
u.posts.add(Post.create)
When you execute `User.create(...)`, you run the following Redis commands:
# Generate an ID
INCR User:id
# Add the newly generated ID, (let's assume the ID is 1).
SADD User:all 1
# Store the unique index
HSET User:uniques:email foo@bar.com 1
# Store the name index
SADD User:indices:name:John 1
# Store the HASH
HMSET User:1 name John email foo@bar.com
Next we increment points:
HINCR User:1:counters points 1
And then we add a Post to the `posts` set. (For brevity, let's assume the Post created has an ID of 1).
SADD User:1:posts 1
Instance Attribute Summary (collapse)
-
- (Object) id
Access the ID used to store this model.
Class Method Summary (collapse)
-
+ (Object) [](id)
Retrieve a record by ID.
-
+ (Object) all
An Ohm::Set wrapper for Model.key.
-
+ (Object) attribute(name, cast = nil)
The bread and butter macro of all models.
-
+ (Object) collection(name, model, reference = to_reference)
A macro for defining a method which basically does a find.
- + (Object) collections protected
- + (Object) conn
- + (Object) connect(options)
-
+ (Object) counter(name)
Declare a counter.
-
+ (Object) create(atts = {})
Syntactic sugar for Model.new(atts).save.
- + (Object) db
-
+ (Boolean) exists?(id)
Check if the ID exists within <Model>:all.
- + (Object) filters(dict) protected
-
+ (Object) find(dict)
Find values in indexed fields.
-
+ (Object) index(attribute)
Index any method on your model.
- + (Object) indices protected
-
+ (Object) key
The namespace for all the keys generated using this model.
-
+ (Object) list(name, model)
Declare an Ohm::List with the given name.
- + (Object) lua
- + (Object) new_id protected
-
+ (Object) reference(name, model)
A macro for defining an attribute, an index, and an accessor for a given model.
-
+ (Object) set(name, model)
Declare an Ohm::Set with the given name.
-
+ (Object) to_proc
Retrieve a set of models given an array of IDs.
- + (Object) to_reference protected
- + (Object) toindices(att, val) protected
-
+ (Object) unique(attribute)
Create a unique index for any method on your model.
- + (Object) uniques protected
-
+ (Object) with(att, val)
Find values in `unique` indices.
Instance Method Summary (collapse)
-
- (Object) ==(other)
(also: #eql?)
Check for equality by doing the following assertions:.
- - (Object) __save__
- - (Object) _delete_indices(atts) protected
- - (Object) _delete_uniques(atts) protected
- - (Object) _detect_duplicate protected
- - (Object) _initialize_id protected
- - (Object) _read_index_type(type) protected
- - (Object) _save protected
- - (Object) _save_indices(indices) protected
- - (Object) _save_uniques(uniques) protected
- - (Object) _skip_empty(atts) protected
- - (Object) _unique_keys protected
- - (Object) _verify_uniques protected
- - (Object) attributes
- - (Object) db protected
-
- (Object) decr(att, count = 1)
Decrement a counter atomically.
-
- (Object) delete
Delete the model, including all the following keys:.
-
- (Object) get(att)
Read an attribute remotly from Redis.
-
- (Object) hash
Return a value that allows the use of models as hash keys.
-
- (Object) incr(att, count = 1)
Increment a counter atomically.
-
- (Model) initialize(atts = {})
constructor
Initialize a model using a dictionary of attributes.
-
- (Object) key
Manipulate the Redis hash of attributes directly.
-
- (Object) load!
Preload all the attributes of this model from Redis.
- - (Object) model protected
- - (Boolean) new?
-
- (Object) save(&block)
Persist the model attributes and update indices and unique indices.
-
- (Object) save! {|t| ... }
Saves the model without checking for validity.
-
- (Object) set(att, val)
Update an attribute value atomically.
-
- (Object) to_hash
Export the ID and the errors of the model.
-
- (Object) to_json(*args)
Export a JSON representation of the model by encoding `to_hash`.
- - (Object) transaction protected
-
- (Object) update(attributes)
Update the model attributes and call save.
-
- (Object) update_attributes(atts)
Write the dictionary of key-value pairs to the model.
Constructor Details
- (Model) initialize(atts = {})
Initialize a model using a dictionary of attributes.
Example:
u = User.new(:name => "John")
1096 1097 1098 1099 1100 |
# File 'lib/ohm.rb', line 1096 def initialize(atts = {}) @attributes = {} @_memo = {} update_attributes(atts) end |
Instance Attribute Details
- (Object) id
Access the ID used to store this model. The ID is used together with the name of the class in order to form the Redis key.
Example:
class User < Ohm::Model; end
u = User.create
u.id
# => 1
u.key
# => User:1
1116 1117 1118 1119 |
# File 'lib/ohm.rb', line 1116 def id raise MissingID if not defined?(@id) @id end |
Class Method Details
+ (Object) [](id)
Retrieve a record by ID.
Example:
u = User.create
u == User[u.id]
# => true
750 751 752 |
# File 'lib/ohm.rb', line 750 def self.[](id) new(:id => id).load! if id && exists?(id) end |
+ (Object) all
An Ohm::Set wrapper for Model.key.
1062 1063 1064 |
# File 'lib/ohm.rb', line 1062 def self.all Set.new(key[:all], key, self) end |
+ (Object) attribute(name, cast = nil)
The bread and butter macro of all models. Basically declares persisted attributes. All attributes are stored on the Redis hash.
Example:
class User < Ohm::Model
attribute :name
end
# It's the same as:
class User < Ohm::Model
def name
@attributes[:name]
end
def name=(name)
@attributes[:name] = name
end
end
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 |
# File 'lib/ohm.rb', line 1016 def self.attribute(name, cast = nil) if cast define_method(name) do cast[@attributes[name]] end else define_method(name) do @attributes[name] end end define_method(:#{name}=") do |value| @attributes[name] = value end end |
+ (Object) collection(name, model, reference = to_reference)
A macro for defining a method which basically does a find.
Example:
class Post < Ohm::Model
reference :user, :User
end
class User < Ohm::Model
collection :posts, :Post
end
# is the same as
class User < Ohm::Model
def posts
Post.find(:user_id => self.id)
end
end
930 931 932 933 934 935 |
# File 'lib/ohm.rb', line 930 def self.collection(name, model, reference = to_reference) define_method name do model = Utils.const(self.class, model) model.find(:#{reference}_id" => id) end end |
+ (Object) collections (protected)
1375 1376 1377 |
# File 'lib/ohm.rb', line 1375 def self.collections @collections ||= [] end |
+ (Object) conn
704 705 706 |
# File 'lib/ohm.rb', line 704 def self.conn @conn ||= Connection.new(name, Ohm.conn.) end |
+ (Object) connect(options)
708 709 710 711 712 |
# File 'lib/ohm.rb', line 708 def self.connect() @key = nil @lua = nil conn.start() end |
+ (Object) counter(name)
Declare a counter. All the counters are internally stored in a different Redis hash, independent from the one that stores the model attributes. Counters are updated with the `incr` and `decr` methods, which interact directly with Redis. Their value can't be assigned as with regular attributes.
Example:
class User < Ohm::Model
counter :points
end
u = User.create
u.incr :points
Ohm.redis.hget "User:1:counters", "points"
# => 1
Note: You can't use counters until you save the model. If you try to do it, you'll receive an Ohm::MissingID error.
1053 1054 1055 1056 1057 1058 1059 |
# File 'lib/ohm.rb', line 1053 def self.counter(name) define_method(name) do return 0 if new? key[:counters].hget(name).to_i end end |
+ (Object) create(atts = {})
Syntactic sugar for Model.new(atts).save
1067 1068 1069 |
# File 'lib/ohm.rb', line 1067 def self.create(atts = {}) new(atts).save end |
+ (Object) db
714 715 716 |
# File 'lib/ohm.rb', line 714 def self.db conn.redis end |
+ (Boolean) exists?(id)
Check if the ID exists within <Model>:all.
771 772 773 |
# File 'lib/ohm.rb', line 771 def self.exists?(id) key[:all].sismember(id) end |
+ (Object) filters(dict) (protected)
1379 1380 1381 1382 1383 1384 1385 1386 1387 |
# File 'lib/ohm.rb', line 1379 def self.filters(dict) unless dict.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 dict.map { |k, v| toindices(k, v) }.flatten end |
+ (Object) find(dict)
Find values in indexed fields.
Example:
class User < Ohm::Model
attribute :email
attribute :name
index :name
attribute :status
index :status
index :provider
index :tag
def provider
email[/@(.*?).com/, 1]
end
def tag
["ruby", "python"]
end
end
u = User.create(:name => "John", :status => "pending", :email => "foo@me.com")
User.find(:provider => "me", :name => "John", :status => "pending").include?(u)
# => true
User.find(:tag => "ruby").include?(u)
# => true
User.find(:tag => "python").include?(u)
# => true
User.find(:tag => ["ruby", "python"]).include?(u)
# => true
830 831 832 833 834 835 836 837 838 |
# File 'lib/ohm.rb', line 830 def self.find(dict) keys = filters(dict) if keys.size == 1 Ohm::Set.new(keys.first, key, self) else Ohm::MultiSet.new(key, self).append(:sinterstore, keys) end end |
+ (Object) index(attribute)
Index any method on your model. Once you index a method, you can use it in `find` statements.
842 843 844 |
# File 'lib/ohm.rb', line 842 def self.index(attribute) indices << attribute unless indices.include?(attribute) end |
+ (Object) indices (protected)
1367 1368 1369 |
# File 'lib/ohm.rb', line 1367 def self.indices @indices ||= [] end |
+ (Object) key
The namespace for all the keys generated using this model.
Example:
class User < Ohm::Model
User.key == "User"
User.key.kind_of?(String)
# => true
User.key.kind_of?(Nest)
# => true
To find out more about Nest, see:
http://github.com/soveran/nest
738 739 740 |
# File 'lib/ohm.rb', line 738 def self.key @key ||= Nest.new(self.name, db) end |
+ (Object) list(name, model)
Declare an Ohm::List with the given name.
Example:
class Comment < Ohm::Model
end
class Post < Ohm::Model
list :comments, :Comment
end
p = Post.create
p.comments.push(Comment.create)
p.comments.unshift(Comment.create)
p.comments.size == 2
# => true
Note: You can't use the list until you save the model. If you try to do it, you'll receive an Ohm::MissingID error.
901 902 903 904 905 906 907 908 909 |
# File 'lib/ohm.rb', line 901 def self.list(name, model) collections << name unless collections.include?(name) define_method name do model = Utils.const(self.class, model) Ohm::List.new(key[name], model.key, model) end end |
+ (Object) lua
718 719 720 |
# File 'lib/ohm.rb', line 718 def self.lua @lua ||= Lua.new(File.join(Dir.pwd, "lua"), db) end |
+ (Object) new_id (protected)
1399 1400 1401 |
# File 'lib/ohm.rb', line 1399 def self.new_id key[:id].incr end |
+ (Object) reference(name, model)
A macro for defining an attribute, an index, and an accessor for a given model.
Example:
class Post < Ohm::Model
reference :user, :User
end
# It's the same as:
class Post < Ohm::Model
attribute :user_id
index :user_id
def user
@_memo[:user] ||= User[user_id]
end
def user=(user)
self.user_id = user.id
@_memo[:user] = user
end
def user_id=(user_id)
@_memo.delete(:user_id)
self.user_id = user_id
end
end
967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 |
# File 'lib/ohm.rb', line 967 def self.reference(name, model) reader = :#{name}_id" writer = :#{name}_id=" index reader define_method(reader) do @attributes[reader] end define_method(writer) do |value| @_memo.delete(name) @attributes[reader] = value end define_method(:#{name}=") do |value| @_memo.delete(name) send(writer, value ? value.id : nil) end define_method(name) do @_memo[name] ||= begin model = Utils.const(self.class, model) model[send(reader)] end end end |
+ (Object) set(name, model)
Declare an Ohm::Set with the given name.
Example:
class User < Ohm::Model
set :posts, :Post
end
u = User.create
u.posts.empty?
# => true
Note: You can't use the set until you save the model. If you try to do it, you'll receive an Ohm::MissingID error.
871 872 873 874 875 876 877 878 879 |
# File 'lib/ohm.rb', line 871 def self.set(name, model) collections << name unless collections.include?(name) define_method name do model = Utils.const(self.class, model) Ohm::MutableSet.new(key[name], model.key, model) end end |
+ (Object) to_proc
Retrieve a set of models given an array of IDs.
Example:
ids = [1, 2, 3]
ids.map(&User)
Note: The use of this should be a last resort for your actual application runtime, or for simply debugging in your console. If you care about performance, you should pipeline your reads. For more information checkout the implementation of Ohm::Set#fetch.
766 767 768 |
# File 'lib/ohm.rb', line 766 def self.to_proc lambda { |id| self[id] } end |
+ (Object) to_reference (protected)
1360 1361 1362 1363 1364 1365 |
# File 'lib/ohm.rb', line 1360 def self.to_reference name.to_s. match(/^(?:.*::)*(.*)$/)[1]. gsub(/([a-z\d])([A-Z])/, '\1_\2'). downcase.to_sym end |
+ (Object) toindices(att, val) (protected)
1389 1390 1391 1392 1393 1394 1395 1396 1397 |
# File 'lib/ohm.rb', line 1389 def self.toindices(att, val) raise IndexNotFound unless indices.include?(att) if val.kind_of?(Enumerable) val.map { |v| key[:indices][att][v] } else [key[:indices][att][val]] end end |
+ (Object) unique(attribute)
Create a unique index for any method on your model. Once you add a unique index, you can use it in `with` statements.
Note: if there is a conflict while saving, an `Ohm::UniqueIndexViolation` violation is raised.
852 853 854 |
# File 'lib/ohm.rb', line 852 def self.unique(attribute) uniques << attribute unless uniques.include?(attribute) end |
+ (Object) uniques (protected)
1371 1372 1373 |
# File 'lib/ohm.rb', line 1371 def self.uniques @uniques ||= [] end |
+ (Object) with(att, val)
Find values in `unique` indices.
Example:
class User < Ohm::Model
unique :email
end
u = User.create(:email => "foo@bar.com")
u == User.with(:email, "foo@bar.com")
# => true
787 788 789 790 |
# File 'lib/ohm.rb', line 787 def self.with(att, val) id = key[:uniques][att].hget(val) id && self[id] end |
Instance Method Details
- (Object) ==(other) Also known as: eql?
Check for equality by doing the following assertions:
-
That the passed model is of the same type.
-
That they represent the same Redis key.
1126 1127 1128 1129 1130 |
# File 'lib/ohm.rb', line 1126 def ==(other) other.kind_of?(model) && other.key == key rescue MissingID false end |
- (Object) __save__
1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 |
# File 'lib/ohm.rb', line 1279 def __save__ Transaction.new do |t| t.watch(*_unique_keys) t.watch(key) if not new? t.before do _initialize_id if new? end existing = nil uniques = nil indices = nil t.read do _verify_uniques existing = key.hgetall uniques = _read_index_type(:uniques) indices = _read_index_type(:indices) end t.write do model.key[:all].sadd(id) _delete_uniques(existing) _delete_indices(existing) _save _save_indices(indices) _save_uniques(uniques) end end end |
- (Object) _delete_indices(atts) (protected)
1476 1477 1478 1479 1480 1481 1482 |
# File 'lib/ohm.rb', line 1476 def _delete_indices(atts) model.indices.each do |att| val = atts[att.to_s] model.key[:indices][att][val].srem(id) end end |
- (Object) _delete_uniques(atts) (protected)
1470 1471 1472 1473 1474 |
# File 'lib/ohm.rb', line 1470 def _delete_uniques(atts) model.uniques.each do |att| model.key[:uniques][att].hdel(atts[att.to_s]) end end |
- (Object) _detect_duplicate (protected)
1449 1450 1451 1452 1453 1454 |
# File 'lib/ohm.rb', line 1449 def _detect_duplicate model.uniques.detect do |att| id = model.key[:uniques][att].hget(send(att)) id && id != self.id.to_s end end |
- (Object) _initialize_id (protected)
1418 1419 1420 |
# File 'lib/ohm.rb', line 1418 def _initialize_id @id = model.new_id.to_s end |
- (Object) _read_index_type(type) (protected)
1456 1457 1458 1459 1460 1461 1462 |
# File 'lib/ohm.rb', line 1456 def _read_index_type(type) {}.tap do |ret| model.send(type).each do |att| ret[att] = send(att) end end end |
- (Object) _save (protected)
1436 1437 1438 1439 1440 1441 |
# File 'lib/ohm.rb', line 1436 def _save catch :empty do key.del key.hmset(*_skip_empty(attributes).to_a.flatten) end end |
- (Object) _save_indices(indices) (protected)
1484 1485 1486 1487 1488 1489 1490 |
# File 'lib/ohm.rb', line 1484 def _save_indices(indices) indices.each do |att, val| model.toindices(att, val).each do |index| index.sadd(id) end end end |
- (Object) _save_uniques(uniques) (protected)
1464 1465 1466 1467 1468 |
# File 'lib/ohm.rb', line 1464 def _save_uniques(uniques) uniques.each do |att, val| model.key[:uniques][att].hset(val, id) end end |
- (Object) _skip_empty(atts) (protected)
1422 1423 1424 1425 1426 1427 1428 1429 1430 |
# File 'lib/ohm.rb', line 1422 def _skip_empty(atts) {}.tap do |ret| atts.each do |att, val| ret[att] = send(att).to_s unless val.to_s.empty? end throw :empty if ret.empty? end end |
- (Object) _unique_keys (protected)
1432 1433 1434 |
# File 'lib/ohm.rb', line 1432 def _unique_keys model.uniques.map { |att| model.key[:uniques][att] } end |
- (Object) _verify_uniques (protected)
1443 1444 1445 1446 1447 |
# File 'lib/ohm.rb', line 1443 def _verify_uniques if att = _detect_duplicate raise UniqueIndexViolation, "#{att} is not unique." end end |
- (Object) attributes
1201 1202 1203 |
# File 'lib/ohm.rb', line 1201 def attributes @attributes end |
- (Object) db (protected)
1414 1415 1416 |
# File 'lib/ohm.rb', line 1414 def db model.db end |
- (Object) decr(att, count = 1)
Decrement a counter atomically. Internally uses HINCRBY.
1180 1181 1182 |
# File 'lib/ohm.rb', line 1180 def decr(att, count = 1) incr(att, -count) end |
- (Object) delete
Delete the model, including all the following keys:
-
<Model>:<id>
-
<Model>:<id>:counters
-
<Model>:<id>:<set name>
If the model has uniques or indices, they're also cleaned up.
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 |
# File 'lib/ohm.rb', line 1318 def delete transaction do |t| t.read do |store| store[:existing] = key.hgetall end t.write do |store| _delete_uniques(store[:existing]) _delete_indices(store[:existing]) model.collections.each { |e| key[e].del } model.key[:all].srem(id) key[:counters].del key.del end yield t if block_given? end end |
- (Object) get(att)
Read an attribute remotly from Redis. Useful if you want to get the most recent value of the attribute and not rely on locally cached value.
Example:
User.create(:name => "A")
Session 1 | Session 2
--------------|------------------------
u = User[1] | u = User[1]
u.name = "B" |
u.save |
| u.name == "A"
| u.get(:name) == "B"
1155 1156 1157 |
# File 'lib/ohm.rb', line 1155 def get(att) @attributes[att] = key.hget(att) end |
- (Object) hash
Return a value that allows the use of models as hash keys.
Example:
h = {}
u = User.new
h[:u] = u
h[:u] == u
# => true
1196 1197 1198 |
# File 'lib/ohm.rb', line 1196 def hash new? ? super : key.hash end |
- (Object) incr(att, count = 1)
Increment a counter atomically. Internally uses HINCRBY.
1175 1176 1177 |
# File 'lib/ohm.rb', line 1175 def incr(att, count = 1) key[:counters].hincrby(att, count) end |
- (Object) key
Manipulate the Redis hash of attributes directly.
Example:
class User < Ohm::Model
attribute :name
end
u = User.create(:name => "John")
u.key.hget(:name)
# => John
For more details see
http://github.com/soveran/nest
1086 1087 1088 |
# File 'lib/ohm.rb', line 1086 def key model.key[id] end |
- (Object) load!
Preload all the attributes of this model from Redis. Used internally by `Model::[]`.
1134 1135 1136 1137 |
# File 'lib/ohm.rb', line 1134 def load! update_attributes(key.hgetall) unless new? return self end |
- (Object) model (protected)
1410 1411 1412 |
# File 'lib/ohm.rb', line 1410 def model self.class end |
- (Boolean) new?
1170 1171 1172 |
# File 'lib/ohm.rb', line 1170 def new? !defined?(@id) end |
- (Object) save(&block)
Persist the model attributes and update indices and unique indices. The `counter`s and `set`s are not touched during save.
If the model is not valid, nil is returned. Otherwise, the persisted model is returned.
Example:
class User < Ohm::Model
attribute :name
def validate
assert_present :name
end
end
User.new(:name => nil).save
# => nil
u = User.new(:name => "John").save
u.kind_of?(User)
# => true
1264 1265 1266 1267 |
# File 'lib/ohm.rb', line 1264 def save(&block) return if not valid? save!(&block) end |
- (Object) save! {|t| ... }
Saves the model without checking for validity. Refer to `Model#save` for more details.
1271 1272 1273 1274 1275 1276 1277 |
# File 'lib/ohm.rb', line 1271 def save! t = __save__ yield t if block_given? t.commit(db) return self end |
- (Object) set(att, val)
Update an attribute value atomically. The best usecase for this is when you simply want to update one value.
Note: This method is dangerous because it doesn't update indices and uniques. Use it wisely. The safe equivalent is `update`.
1165 1166 1167 1168 |
# File 'lib/ohm.rb', line 1165 def set(att, val) val.to_s.empty? ? key.hdel(att) : key.hset(att, val) @attributes[att] = val end |
- (Object) to_hash
Export the ID and the errors of the model. The approach of Ohm is to whitelist public attributes, as opposed to exporting each (possibly sensitive) attribute.
Example:
class User < Ohm::Model
attribute :name
end
u = User.create(:name => "John")
u.to_hash
# => { :id => "1" }
In order to add additional attributes, you can override `to_hash`:
class User < Ohm::Model
attribute :name
def to_hash
super.merge(:name => name)
end
end
u = User.create(:name => "John")
u.to_hash
# => { :id => "1", :name => "John" }
1233 1234 1235 1236 1237 1238 1239 |
# File 'lib/ohm.rb', line 1233 def to_hash attrs = {} attrs[:id] = id unless new? attrs[:errors] = errors if errors.any? return attrs end |
- (Object) to_json(*args)
Export a JSON representation of the model by encoding `to_hash`.
6 7 8 |
# File 'lib/ohm/json.rb', line 6 def to_json(*args) to_hash.to_json(*args) end |
- (Object) transaction (protected)
1405 1406 1407 1408 |
# File 'lib/ohm.rb', line 1405 def transaction txn = Transaction.new { |t| yield t } txn.commit(db) end |
- (Object) update(attributes)
Update the model attributes and call save.
Example:
User[1].update(:name => "John")
# It's the same as:
u = User[1]
u.update_attributes(:name => "John")
u.save
1349 1350 1351 1352 |
# File 'lib/ohm.rb', line 1349 def update(attributes) update_attributes(attributes) save end |
- (Object) update_attributes(atts)
Write the dictionary of key-value pairs to the model.
1355 1356 1357 |
# File 'lib/ohm.rb', line 1355 def update_attributes(atts) atts.each { |att, val| send(:#{att}=", val) } end |