lib/ampere/model.rb in ampere-1.1.1 vs lib/ampere/model.rb in ampere-1.2.0
- old
+ new
@@ -1,16 +1,28 @@
module Ampere
+ # Including the `Ampere::Model` module into one of your classes mixes in all
+ # the class and instance methods of an Ampere model. See individual methods
+ # for more information.
module Model
-
def self.included(base)
base.extend(ClassMethods)
+ base.extend(Keys)
+
base.class_eval do
+ extend(::ActiveModel::Callbacks)
+ define_model_callbacks :create, :update, :save
+
include(::ActiveModel::Validations)
+ include(Rails.application.routes.url_helpers) if defined?(Rails)
+ include(ActionController::UrlFor) if defined?(Rails)
+
+ include(Ampere::Keys)
attr_reader :id
-
+ attr_reader :destroyed
+
attr_accessor :fields
attr_accessor :field_defaults
attr_accessor :indices
attr_accessor :field_types
@@ -25,13 +37,13 @@
# Compares this model with another one. If they are literally the same object
# or have been stored and have the same ID, then they are equal.
def ==(other)
super or
- other.instance_of?(self.class) and
+ (other.instance_of?(self.class) and
not id.nil? and
- other.id == id
+ other.id == id)
end
# Returns a Hash with all the fields and their values.
def attributes
{:id => @id}.tap do |hash|
@@ -41,13 +53,19 @@
end
end
# Deletes this instance out of the database.
def destroy
+ @destroyed = true
self.class.delete(@id)
end
+ # Returns true if this record has been deleted
+ def destroyed?
+ @destroyed
+ end
+
# Delegates to ==().
def eql?(other)
self == other
end
@@ -59,102 +77,129 @@
# Initialize an instance like this:
#
# Post.new :title => "Kitties: Are They Awesome?"
def initialize(hash = {}, unmarshal = false)
+ @destroyed = false
+
hash.each do |k, v|
if k == 'id' then
@id = unmarshal ? Marshal.load(v) : v
+ elsif k =~ /_id$/
+ self.send("#{k}=", v.to_i)
else
- self.send("#{k}=", (unmarshal and not k =~ /_id$/) ? Marshal.load(v) : v)
+ self.send("#{k}=", unmarshal ? Marshal.load(v) : v)
end
end
end
# Returns true if this record has not yet been saved.
def new?
- @id.nil? or not Ampere.connection.exists(@id)
+ @id.nil? or not Ampere.connection.exists(key_for_find(self.class, @id))
end
+ alias :new_record? :new?
+ def persisted?
+ not @id.nil?
+ end
+
# Reloads this record from the database.
def reload
if self.new? then
raise "Can't reload a new record"
end
self.class.fields.each do |k|
- v = Ampere.connection.hget(@id, k)
+ v = Ampere.connection.hget(key_for_find(self.class, @id), k)
if k =~ /_id$/ then
- self.send("#{k}=", v)
+ self.send("#{k}=", v.to_i)
else
self.send("#{k}=", Marshal.load(v))
end
end
self
end
# Saves this record to the database.
def save
- self.class.unique_indices.each do |idx|
- # For each uniquely-indexed field, look up the index for that field,
- # and throw an exception if this record's value for that field is in
- # the index already.
- if Ampere.connection.hexists("ampere.index.#{self.class.to_s.downcase}.#{idx}", instance_variable_get("@#{idx}")) then
- raise "Cannot save non-unique value for #{idx}"
- end
- end
-
- # Grab a fresh GUID from Redis by incrementing the "__guid" key
- if @id.nil? then
- @id = "#{self.class.to_s.downcase}.#{Ampere.connection.incr('__guid')}"
- end
-
- self.attributes.each do |k, v|
- Ampere.connection.hset(@id, k, k =~ /_id$/ ? v : Marshal.dump(v))
- end
-
- self.class.indices.each do |index|
- if index.class == String or index.class == Symbol then
- Ampere.connection.hset(
- "ampere.index.#{self.class.to_s.downcase}.#{index}",
- instance_variable_get("@#{index}"),
- ([@id] | (Ampere.connection.hget("ampere.index.#{self.class.to_s.downcase}.#{index}", instance_variable_get("@#{index}")) or "")
- .split(/:/)).join(":")
- )
- elsif index.class == Array then
- key = index.map{|i| instance_variable_get("@#{i}")}.join(':')
- val = ([@id] | (Ampere.connection.hget("ampere.index.#{self.class.to_s.downcase}.#{index}", key) or "")
+ run_callbacks :save do
+ run_callbacks :create do
+ self.class.unique_indices.each do |idx|
+ # For each uniquely-indexed field, look up the index for that field,
+ # and throw an exception if this record's value for that field is in
+ # the index already.
+ if Ampere.connection.hexists(key_for_index(idx), instance_variable_get("@#{idx}")) then
+ raise "Cannot save non-unique value for #{idx}"
+ end
+ end
+
+ # Grab a fresh GUID from Redis by incrementing the "__guid" key
+ if @id.nil? then
+ @id = Ampere.connection.incr('__guid')
+ end
+
+ self.attributes.each do |k, v|
+ Ampere.connection.hset(key_for_find(self.class, @id), k, k =~ /_id$/ ? v : Marshal.dump(v))
+ end
+
+ self.class.indices.each do |index|
+ if index.class == String or index.class == Symbol then
+ Ampere.connection.hset(
+ key_for_index(index),
+ instance_variable_get("@#{index}"),
+ ([@id] | (Ampere.connection.hget(key_for_index(index), instance_variable_get("@#{index}")) or "")
.split(/:/)).join(":")
- Ampere.connection.hset(
- "ampere.index.#{self.class.to_s.downcase}.#{index.join(':')}",
- key,
- val
- )
+ )
+ elsif index.class == Array then
+ key = index.map{|i| instance_variable_get("@#{i}")}.join(':')
+ val = ([@id] | (Ampere.connection.hget(key_for_index(index), key) or "")
+ .split(/:/)).join(":")
+ Ampere.connection.hset(
+ key_for_index(index.join(':')),
+ key,
+ val
+ )
+ end
+ end
+ self
end
end
- self
end
+ def to_key #:nodoc:
+ # @id.nil? ? [] : [@id.to_i]
+ if destroyed?
+ [ @id.to_i ]
+ else
+ persisted? ? [ @id.to_i ] : nil
+ end
+ end
+
+ def to_param #:nodoc:
+ @id.to_s
+ end
+
def update_attribute(key, value)
- raise "Cannot update a nonexistent field!" unless self.class.fields.include?(key)
+ raise "Cannot update nonexistent field '#{key}'!" unless self.class.fields.include?(key.to_sym)
self.send("#{key}=", value)
- Ampere.connection.hset(@id, key, Marshal.dump(value))
+ Ampere.connection.hset(key_for_find(self.class, @id), key, Marshal.dump(value))
end
def update_attributes(hash = {})
# The efficient way, that I haven't figured out how to do yet:
# Ampere.connection.hmset(@id, hash)
# The inefficient way I know how to do right now:
hash.each do |k, v|
- update_attribute(k, v)
+ self.send("#{k}=", v)
end
+ self.save
end
### Class methods
- module ClassMethods
- # Returns an array of all the records that have been stored.
+ module ClassMethods #:nodoc:
+ # Returns a lazy collection of all the records that have been stored.
def all
Ampere::Collection.new(self, Ampere.connection.keys("#{to_s.downcase}.*"))
end
# Declares a belongs_to relationship to another model.
@@ -174,14 +219,17 @@
# Instantiates and saves a new record.
def create(hash = {})
new(hash).save
end
+ alias :create! :create
# Deletes the record with the given ID.
def delete(id)
- Ampere.connection.del(id)
+ record = find(id)
+ Ampere.connection.del(key_for_find(self, id))
+ record
end
# Declares a field. See the README for more details.
def field(name, options = {})
@fields ||= []
@@ -227,22 +275,28 @@
@field_types
end
# Finds the record with the given ID, or the first that matches the given conditions
def find(options = {})
- if options.class == String then
+ if options.class == String or options.is_a?(Fixnum) then
+ options = key_for_find(self, options)
+
if Ampere.connection.exists(options) then
new(Ampere.connection.hgetall(options), true)
else
nil
end
else
# TODO Write a handler for this case, even if it's an exception
raise "Cannot find by #{options.class} yet"
end
end
-
+
+ def first
+ all.first
+ end
+
# Defines a has_one relationship with another model. See the README for more details.
def has_one(field_name, options = {})
referred_klass_name = (options[:class] or options['class'] or field_name)
my_klass_name = to_s.downcase
@@ -266,20 +320,20 @@
def has_many(field_name, options = {})
klass_name = (options[:class] or options['class'] or field_name.to_s.gsub(/s$/, ''))
my_klass_name = to_s.downcase
define_method(:"#{field_name}") do
- (Ampere.connection.smembers("#{to_s.downcase}.#{self.id}.has_many.#{field_name}")).map do |id|
+ (Ampere.connection.smembers(key_for_has_many(to_s.downcase, self.id, field_name))).map do |id|
eval(klass_name.to_s.capitalize).find(id)
end
end
define_method(:"#{field_name}=") do |val|
val.each do |v|
- Ampere.connection.sadd("#{to_s.downcase}.#{self.id}.has_many.#{field_name}", v.id)
+ Ampere.connection.sadd(key_for_has_many(to_s.downcase, self.id, field_name), v.id)
# Set pointer for belongs_to
- Ampere.connection.hset(v.id, "#{my_klass_name}_id", self.send("id"))
+ Ampere.connection.hset(key_for_find(v.class, v.id), "#{my_klass_name}_id", self.send("id"))
end
end
end
# Defines an index. See the README for more details.
@@ -307,11 +361,15 @@
end
def indices
@indices
end
-
+
+ def last
+ all.last
+ end
+
def unique_indices
@unique_indices
end
# Finds an array of records which match the given conditions. This method is
@@ -326,15 +384,15 @@
results = nil
unless indexed_fields.empty?
indexed_fields.map {|key|
if key.class == String or key.class == Symbol then
- Ampere.connection.hget("ampere.index.#{to_s.downcase}.#{key}", options[key]).split(/:/) #.map {|id| find(id)}
+ Ampere.connection.hget(key_for_index(key), options[key]).split(/:/) #.map {|id| find(id)}
else
# Compound index
Ampere.connection.hget(
- "ampere.index.#{to_s.downcase}.#{key.join(':')}",
+ key_for_index(key.join(':')),
key.map{|k| options[k]}.join(':')
).split(/:/) #.map {|id| find(id)}
end
}.each {|s|
return s if s.empty?
@@ -362,14 +420,15 @@
end
end
private
- def compound_indices_for(query)
+ def compound_indices_for(query) #:nodoc:
compound_indices.select{|ci|
(query.keys - ci).empty?
}
end
+
end
end
end