Class: Ohm::Model
- Inherits:
-
Object
- Object
- Ohm::Model
- 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 => "[email protected]")
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.com 1
# Store the name index
SADD User:indices:name:John 1
# Store the HASH
HMSET User:1 name John email foo.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
-
#id ⇒ Object
readonly
Access the ID used to store this model.
Class Method Summary collapse
-
.[](id) ⇒ Object
Retrieve a record by ID.
-
.all ⇒ Object
An Ohm::Set wrapper for Model.key.
-
.attribute(name, cast = nil) ⇒ Object
The bread and butter macro of all models.
-
.collection(name, model, reference = to_reference) ⇒ Object
A macro for defining a method which basically does a find.
-
.counter(name) ⇒ Object
Declare a counter.
-
.create(atts = {}) ⇒ Object
Syntactic sugar for Model.new(atts).save.
-
.exists?(id) ⇒ Boolean
Check if the ID exists within <Model>:all.
-
.fetch(ids) ⇒ Object
Retrieve a set of models given an array of IDs.
-
.find(dict) ⇒ Object
Find values in indexed fields.
-
.index(attribute) ⇒ Object
Index any method on your model.
-
.key ⇒ Object
Returns the namespace for all the keys generated using this model.
-
.list(name, model) ⇒ Object
Declare an Ohm::List with the given name.
- .mutex ⇒ Object
- .redis ⇒ Object
- .redis=(redis) ⇒ Object
-
.reference(name, model) ⇒ Object
A macro for defining an attribute, an index, and an accessor for a given model.
-
.set(name, model) ⇒ Object
Declare an Ohm::Set with the given name.
- .synchronize(&block) ⇒ Object
-
.to_proc ⇒ Object
Retrieve a set of models given an array of IDs.
-
.track(name) ⇒ Object
Keep track of
key[name]and remove when deleting the object. -
.unique(attribute) ⇒ Object
Create a unique index for any method on your model.
-
.with(att, val) ⇒ Object
Find values in
uniqueindices.
Instance Method Summary collapse
-
#==(other) ⇒ Object
(also: #eql?)
Check for equality by doing the following assertions:.
-
#attributes ⇒ Object
Returns a hash of the attributes with their names as keys and the values of the attributes as values.
-
#decr(att, count = 1) ⇒ Object
Decrement a counter atomically.
-
#delete ⇒ Object
Delete the model, including all the following keys:.
-
#get(att) ⇒ Object
Read an attribute remotely from Redis.
-
#hash ⇒ Object
Return a value that allows the use of models as hash keys.
-
#incr(att, count = 1) ⇒ Object
Increment a counter atomically.
-
#initialize(atts = {}) ⇒ Model
constructor
Initialize a model using a dictionary of attributes.
-
#key ⇒ Object
Returns the namespace for the keys generated using this model.
-
#load! ⇒ Object
Preload all the attributes of this model from Redis.
-
#new? ⇒ Boolean
Returns
trueif the model is not persisted. -
#save ⇒ Object
Persist the model attributes and update indices and unique indices.
-
#script(file, *args) ⇒ Object
Run lua scripts and cache the sha in order to improve successive calls.
-
#set(att, val) ⇒ Object
Update an attribute value atomically.
-
#to_hash ⇒ Object
Export the ID of the model.
-
#to_json(*args) ⇒ Object
Export a JSON representation of the model by encoding
to_hash. -
#update(attributes) ⇒ Object
Update the model attributes and call save.
-
#update_attributes(atts) ⇒ Object
Write the dictionary of key-value pairs to the model.
Constructor Details
#initialize(atts = {}) ⇒ Model
Initialize a model using a dictionary of attributes.
Example:
u = User.new(:name => "John")
1095 1096 1097 1098 1099 |
# File 'lib/ohm.rb', line 1095 def initialize(atts = {}) @attributes = {} @_memo = {} update_attributes(atts) end |
Instance Attribute Details
#id ⇒ Object
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
1115 1116 1117 1118 |
# File 'lib/ohm.rb', line 1115 def id raise MissingID if not defined?(@id) @id end |
Class Method Details
.[](id) ⇒ Object
Retrieve a record by ID.
Example:
u = User.create
u == User[u.id]
# => true
726 727 728 |
# File 'lib/ohm.rb', line 726 def self.[](id) new(:id => id).load! if id && exists?(id) end |
.all ⇒ Object
An Ohm::Set wrapper for Model.key.
1074 1075 1076 |
# File 'lib/ohm.rb', line 1074 def self.all Ohm::Set.new(self, key, key[:all]) end |
.attribute(name, cast = nil) ⇒ Object
The bread and butter macro of all models. Basically declares persisted attributes. All attributes are stored on the Redis hash.
class User < Ohm::Model
attribute :name
end
user = User.new(name: "John")
user.name
# => "John"
user.name = "Jane"
user.name
# => "Jane"
A lambda can be passed as a second parameter to add typecasting support to the attribute.
class User < Ohm::Model
attribute :age, ->(x) { x.to_i }
end
user = User.new(age: 100)
user.age
# => 100
user.age.kind_of?(Integer)
# => true
Check rubydoc.info/github/cyx/ohm-contrib#Ohm__DataTypes to see more examples about the typecasting feature.
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 |
# File 'lib/ohm.rb', line 1019 def self.attribute(name, cast = nil) attributes << name unless attributes.include?(name) 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 |
.collection(name, model, reference = to_reference) ⇒ Object
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
918 919 920 921 922 923 |
# File 'lib/ohm.rb', line 918 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 |
.counter(name) ⇒ Object
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
u.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.
1058 1059 1060 1061 1062 1063 1064 1065 1066 |
# File 'lib/ohm.rb', line 1058 def self.counter(name) counters << name unless counters.include?(name) define_method(name) do return 0 if new? redis.call("HGET", key[:counters], name).to_i end end |
.create(atts = {}) ⇒ Object
Syntactic sugar for Model.new(atts).save
1079 1080 1081 |
# File 'lib/ohm.rb', line 1079 def self.create(atts = {}) new(atts).save end |
.exists?(id) ⇒ Boolean
Check if the ID exists within <Model>:all.
747 748 749 |
# File 'lib/ohm.rb', line 747 def self.exists?(id) redis.call("SISMEMBER", key[:all], id) == 1 end |
.fetch(ids) ⇒ Object
Retrieve a set of models given an array of IDs.
Example:
User.fetch([1, 2, 3])
824 825 826 |
# File 'lib/ohm.rb', line 824 def self.fetch(ids) all.fetch(ids) end |
.find(dict) ⇒ Object
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: "[email protected]")
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
808 809 810 811 812 813 814 815 816 |
# File 'lib/ohm.rb', line 808 def self.find(dict) keys = filters(dict) if keys.size == 1 Ohm::Set.new(self, key, keys.first) else Ohm::Set.new(self, key, [:SINTER, *keys]) end end |
.index(attribute) ⇒ Object
Index any method on your model. Once you index a method, you can use it in find statements.
830 831 832 |
# File 'lib/ohm.rb', line 830 def self.index(attribute) indices << attribute unless indices.include?(attribute) end |
.key ⇒ Object
Returns the namespace for all the keys generated using this model.
Example:
class User < Ohm::Model
end
User.key == "User"
User.key.kind_of?(String)
# => true
User.key.kind_of?(Nido)
# => true
To find out more about Nido, see:
http://github.com/soveran/nido
714 715 716 |
# File 'lib/ohm.rb', line 714 def self.key @key ||= Nido.new(self.name) end |
.list(name, model) ⇒ Object
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.
889 890 891 892 893 894 895 896 897 |
# File 'lib/ohm.rb', line 889 def self.list(name, model) track(name) define_method name do model = Utils.const(self.class, model) Ohm::List.new(key[name], model.key, model) end end |
.mutex ⇒ Object
689 690 691 |
# File 'lib/ohm.rb', line 689 def self.mutex @@mutex ||= Mutex.new end |
.redis ⇒ Object
685 686 687 |
# File 'lib/ohm.rb', line 685 def self.redis defined?(@redis) ? @redis : Ohm.redis end |
.redis=(redis) ⇒ Object
681 682 683 |
# File 'lib/ohm.rb', line 681 def self.redis=(redis) @redis = redis end |
.reference(name, model) ⇒ Object
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
955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 |
# File 'lib/ohm.rb', line 955 def self.reference(name, model) reader = :"#{name}_id" writer = :"#{name}_id=" attributes << reader unless attributes.include?(reader) 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 |
.set(name, model) ⇒ Object
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.
859 860 861 862 863 864 865 866 867 |
# File 'lib/ohm.rb', line 859 def self.set(name, model) track(name) define_method name do model = Utils.const(self.class, model) Ohm::MutableSet.new(model, model.key, key[name]) end end |
.synchronize(&block) ⇒ Object
693 694 695 |
# File 'lib/ohm.rb', line 693 def self.synchronize(&block) mutex.synchronize(&block) end |
.to_proc ⇒ Object
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::List#fetch.
742 743 744 |
# File 'lib/ohm.rb', line 742 def self.to_proc lambda { |id| self[id] } end |
.track(name) ⇒ Object
Keep track of key[name] and remove when deleting the object.
1069 1070 1071 |
# File 'lib/ohm.rb', line 1069 def self.track(name) tracked << name unless tracked.include?(name) end |
.unique(attribute) ⇒ Object
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.
840 841 842 |
# File 'lib/ohm.rb', line 840 def self.unique(attribute) uniques << attribute unless uniques.include?(attribute) end |
.with(att, val) ⇒ Object
Find values in unique indices.
Example:
class User < Ohm::Model
unique :email
end
u = User.create(:email => "[email protected]")
u == User.with(:email, "[email protected]")
# => true
763 764 765 766 767 768 |
# File 'lib/ohm.rb', line 763 def self.with(att, val) raise IndexNotFound unless uniques.include?(att) id = redis.call("HGET", key[:uniques][att], val) new(:id => id).load! if id end |
Instance Method Details
#==(other) ⇒ Object 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.
1125 1126 1127 1128 1129 |
# File 'lib/ohm.rb', line 1125 def ==(other) other.kind_of?(model) && other.key == key rescue MissingID false end |
#attributes ⇒ Object
Returns a hash of the attributes with their names as keys and the values of the attributes as values. It doesn’t include the ID of the model.
Example:
class User < Ohm::Model
attribute :name
end
u = User.create(:name => "John")
u.attributes
# => { :name => "John" }
1234 1235 1236 |
# File 'lib/ohm.rb', line 1234 def attributes @attributes end |
#decr(att, count = 1) ⇒ Object
Decrement a counter atomically. Internally uses HINCRBY.
1199 1200 1201 |
# File 'lib/ohm.rb', line 1199 def decr(att, count = 1) incr(att, -count) end |
#delete ⇒ Object
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.
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 |
# File 'lib/ohm.rb', line 1330 def delete uniques = {} model.uniques.each { |field| uniques[field] = send(field) } script(LUA_DELETE, 0, { "name" => model.name, "id" => id, "key" => key }.to_msgpack, uniques.to_msgpack, model.tracked.to_msgpack ) return self end |
#get(att) ⇒ Object
Read an attribute remotely 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"
1154 1155 1156 |
# File 'lib/ohm.rb', line 1154 def get(att) @attributes[att] = redis.call("HGET", key, att) end |
#hash ⇒ Object
Return a value that allows the use of models as hash keys.
Example:
h = {}
u = User.new
h[:u] = u
h[:u] == u
# => true
1215 1216 1217 |
# File 'lib/ohm.rb', line 1215 def hash new? ? super : key.hash end |
#incr(att, count = 1) ⇒ Object
Increment a counter atomically. Internally uses HINCRBY.
1194 1195 1196 |
# File 'lib/ohm.rb', line 1194 def incr(att, count = 1) redis.call("HINCRBY", key[:counters], att, count) end |
#key ⇒ Object
Returns the namespace for the keys generated using this model. Check Ohm::Model.key documentation for more details.
1085 1086 1087 |
# File 'lib/ohm.rb', line 1085 def key model.key[id] end |
#load! ⇒ Object
Preload all the attributes of this model from Redis. Used internally by Model::[].
1133 1134 1135 1136 |
# File 'lib/ohm.rb', line 1133 def load! update_attributes(Utils.dict(redis.call("HGETALL", key))) unless new? return self end |
#new? ⇒ Boolean
Returns true if the model is not persisted. Otherwise, returns false.
Example:
class User < Ohm::Model
attribute :name
end
u = User.new(:name => "John")
u.new?
# => true
u.save
u.new?
# => false
1189 1190 1191 |
# File 'lib/ohm.rb', line 1189 def new? !defined?(@id) end |
#save ⇒ Object
Persist the model attributes and update indices and unique indices. The ‘counter`s and `set`s are not touched during save.
Example:
class User < Ohm::Model
attribute :name
end
u = User.new(:name => "John").save
u.kind_of?(User)
# => true
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 |
# File 'lib/ohm.rb', line 1287 def save indices = {} model.indices.each { |field| indices[field] = Array(send(field)) } uniques = {} model.uniques.each { |field| uniques[field] = send(field) } features = { "name" => model.name } if defined?(@id) features["id"] = @id end response = script(LUA_SAVE, 0, features.to_msgpack, _sanitized_attributes.to_msgpack, indices.to_msgpack, uniques.to_msgpack ) if response.is_a?(RuntimeError) if response. =~ /(UniqueIndexViolation: (\w+))/ raise UniqueIndexViolation, $1 else raise response end end @id = response return self end |
#script(file, *args) ⇒ Object
Run lua scripts and cache the sha in order to improve successive calls.
1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 |
# File 'lib/ohm.rb', line 1348 def script(file, *args) cache = LUA_CACHE[redis.url] if cache.key?(file) sha = cache[file] else src = File.read(file) sha = redis.call("SCRIPT", "LOAD", src) cache[file] = sha end redis.call("EVALSHA", sha, *args) end |
#set(att, val) ⇒ Object
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.
1164 1165 1166 1167 1168 1169 1170 1171 1172 |
# File 'lib/ohm.rb', line 1164 def set(att, val) if val.to_s.empty? redis.call("HDEL", key, att) else redis.call("HSET", key, att, val) end @attributes[att] = val end |
#to_hash ⇒ Object
Export the ID 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" }
1266 1267 1268 1269 1270 1271 |
# File 'lib/ohm.rb', line 1266 def to_hash attrs = {} attrs[:id] = id unless new? return attrs end |
#to_json(*args) ⇒ Object
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 |
#update(attributes) ⇒ Object
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
1375 1376 1377 1378 |
# File 'lib/ohm.rb', line 1375 def update(attributes) update_attributes(attributes) save end |
#update_attributes(atts) ⇒ Object
Write the dictionary of key-value pairs to the model.
1381 1382 1383 |
# File 'lib/ohm.rb', line 1381 def update_attributes(atts) atts.each { |att, val| send(:"#{att}=", val) } end |