lib/ampere/model.rb in ampere-0.1.3 vs lib/ampere/model.rb in ampere-1.0.0
- old
+ new
@@ -1,14 +1,26 @@
module Ampere
- class Model
- attr_reader :id
+ module Model
- @fields = []
- @field_defaults = {}
- @indices = []
- @field_types = {}
+ def self.included(base)
+ base.extend(ClassMethods)
+
+ base.class_eval do
+ attr_reader :id
+
+ attr_accessor :fields
+ attr_accessor :field_defaults
+ attr_accessor :indices
+ attr_accessor :field_types
+ @fields = []
+ @field_defaults = {}
+ @indices = []
+ @field_types = {}
+ end
+ end
+
### Instance methods
# 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)
@@ -136,227 +148,228 @@
end
end
### Class methods
- # Returns an array of all the records that have been stored.
- def self.all
- Ampere.connection.keys("#{to_s.downcase}.*").map{|m| find m}
- end
+ module ClassMethods
+ # Returns an array of all the records that have been stored.
+ def all
+ Ampere.connection.keys("#{to_s.downcase}.*").map{|m| find m}
+ end
- # Declares a belongs_to relationship to another model.
- def self.belongs_to(field_name, options = {})
- has_one field_name, options
- end
+ # Declares a belongs_to relationship to another model.
+ def belongs_to(field_name, options = {})
+ has_one field_name, options
+ end
- # Like @indices, but only returns the compound indices this class defines.
- def self.compound_indices
- @indices.select{|i| i.class == Array}
- end
+ # Like @indices, but only returns the compound indices this class defines.
+ def compound_indices
+ @indices.select{|i| i.class == Array}
+ end
- # Returns the number of instances of this record that have been stored.
- def self.count
- Ampere.connection.keys("#{to_s.downcase}.*").length
- end
+ # Returns the number of instances of this record that have been stored.
+ def count
+ Ampere.connection.keys("#{to_s.downcase}.*").length
+ end
- # Instantiates and saves a new record.
- def self.create(hash = {})
- new(hash).save
- end
+ # Instantiates and saves a new record.
+ def create(hash = {})
+ new(hash).save
+ end
- # Deletes the record with the given ID.
- def self.delete(id)
- Ampere.connection.del(id)
- end
+ # Deletes the record with the given ID.
+ def delete(id)
+ Ampere.connection.del(id)
+ end
- # Declares a field. See the README for more details.
- def self.field(name, options = {})
- @fields ||= []
- @field_defaults ||= {}
- @indices ||= []
- @unique_indices ||= []
- @field_types ||= {}
+ # Declares a field. See the README for more details.
+ def field(name, options = {})
+ @fields ||= []
+ @field_defaults ||= {}
+ @indices ||= []
+ @unique_indices ||= []
+ @field_types ||= {}
- @fields << name
+ @fields << name
- # attr_accessor :"#{name}"
+ # attr_accessor :"#{name}"
- # Handle default value
- @field_defaults[name] = options[:default]
+ # Handle default value
+ @field_defaults[name] = options[:default]
- # Handle type, if any
- if options[:type] then
- @field_types[:"#{name}"] = options[:type].to_s
- end
+ # Handle type, if any
+ if options[:type] then
+ @field_types[:"#{name}"] = options[:type].to_s
+ end
- define_method :"#{name}" do
- instance_variable_get("@#{name}") or self.class.field_defaults[name]
- end
+ define_method :"#{name}" do
+ instance_variable_get("@#{name}") or self.class.field_defaults[name]
+ end
- define_method :"#{name}=" do |val|
- if not self.class.field_types[:"#{name}"] or val.is_a?(eval(self.class.field_types[:"#{name}"])) then
- instance_variable_set("@#{name}", val)
- else
- raise "Cannot set field of type #{self.class.field_types[name.to_sym]} with #{val.class} value"
+ define_method :"#{name}=" do |val|
+ if not self.class.field_types[:"#{name}"] or val.is_a?(eval(self.class.field_types[:"#{name}"])) then
+ instance_variable_set("@#{name}", val)
+ else
+ raise "Cannot set field of type #{self.class.field_types[name.to_sym]} with #{val.class} value"
+ end
end
end
- end
- def self.fields
- @fields
- end
+ def fields
+ @fields
+ end
- def self.field_defaults
- @field_defaults
- end
+ def field_defaults
+ @field_defaults
+ end
- def self.field_types
- @field_types
- end
+ def field_types
+ @field_types
+ end
- # Finds the record with the given ID, or the first that matches the given conditions
- def self.find(options = {})
- if options.class == String then
- if Ampere.connection.exists(options) then
- new(Ampere.connection.hgetall(options), true)
+ # Finds the record with the given ID, or the first that matches the given conditions
+ def find(options = {})
+ if options.class == String then
+ if Ampere.connection.exists(options) then
+ new(Ampere.connection.hgetall(options), true)
+ else
+ nil
+ end
else
- nil
+ # TODO Write a handler for this case, even if it's an exception
+ raise "Cannot find by #{options.class} yet"
end
- else
- # TODO Write a handler for this case, even if it's an exception
- raise "Cannot find by #{options.class} yet"
end
- end
- # Defines a has_one relationship with another model. See the README for more details.
- def self.has_one(field_name, options = {})
- referred_klass_name = (options[:class] or options['class'] or field_name)
- my_klass_name = to_s.downcase
+ # 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
- field :"#{field_name}_id"
+ field :"#{field_name}_id"
- define_method(field_name.to_sym) do
- return if self.send("#{field_name}_id").nil?
- eval(referred_klass_name.to_s.capitalize).find(self.send("#{field_name}_id"))
- end
+ define_method(field_name.to_sym) do
+ return if self.send("#{field_name}_id").nil?
+ eval(referred_klass_name.to_s.capitalize).find(self.send("#{field_name}_id"))
+ end
- define_method(:"#{field_name}=") do |val|
- return nil if val.nil?
- # Set attr with key where referred model is stored
- self.send("#{field_name}_id=", val.id)
- # Also update that model's hash with a pointer back to here
- val.send("#{my_klass_name}_id=", self.send("id"))
+ define_method(:"#{field_name}=") do |val|
+ return nil if val.nil?
+ # Set attr with key where referred model is stored
+ self.send("#{field_name}_id=", val.id)
+ # Also update that model's hash with a pointer back to here
+ val.send("#{my_klass_name}_id=", self.send("id"))
+ end
end
- end
- # Defines a has_many relationship with another model. See the README for more details.
- def self.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
+ # Defines a has_many relationship with another model. See the README for more details.
+ 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|
- eval(klass_name.to_s.capitalize).find(id)
+ define_method(:"#{field_name}") do
+ (Ampere.connection.smembers("#{to_s.downcase}.#{self.id}.has_many.#{field_name}")).map do |id|
+ eval(klass_name.to_s.capitalize).find(id)
+ end
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)
- # Set pointer for belongs_to
- Ampere.connection.hset(v.id, "#{my_klass_name}_id", self.send("id"))
+ define_method(:"#{field_name}=") do |val|
+ val.each do |v|
+ Ampere.connection.sadd("#{to_s.downcase}.#{self.id}.has_many.#{field_name}", v.id)
+ # Set pointer for belongs_to
+ Ampere.connection.hset(v.id, "#{my_klass_name}_id", self.send("id"))
+ end
end
end
- end
- # Defines an index. See the README for more details.
- def self.index(field_name, options = {})
- # TODO There has just got to be a better way to handle this.
- @fields ||= []
- @field_defaults ||= {}
- @indices ||= []
- @unique_indices ||= []
- @field_types ||= {}
+ # Defines an index. See the README for more details.
+ def index(field_name, options = {})
+ # TODO There has just got to be a better way to handle this.
+ @fields ||= []
+ @field_defaults ||= {}
+ @indices ||= []
+ @unique_indices ||= []
+ @field_types ||= {}
- if field_name.class == String or field_name.class == Symbol then
- # Singular index
- raise "Can't index a nonexistent field!" unless @fields.include?(field_name)
- elsif field_name.class == Array then
- # Compound index
- field_name.each{|f| raise "Can't index a nonexistent field!" unless @fields.include?(f)}
- field_name.sort!
- else
- raise "Can't index a #{field_name.class}"
- end
+ if field_name.class == String or field_name.class == Symbol then
+ # Singular index
+ raise "Can't index a nonexistent field!" unless @fields.include?(field_name)
+ elsif field_name.class == Array then
+ # Compound index
+ field_name.each{|f| raise "Can't index a nonexistent field!" unless @fields.include?(f)}
+ field_name.sort!
+ else
+ raise "Can't index a #{field_name.class}"
+ end
- @indices << field_name
- @unique_indices << field_name if options[:unique]
- end
+ @indices << field_name
+ @unique_indices << field_name if options[:unique]
+ end
- def self.indices
- @indices
- end
+ def indices
+ @indices
+ end
- def self.unique_indices
- @unique_indices
- end
+ def unique_indices
+ @unique_indices
+ end
- # Finds an array of records which match the given conditions. This method is
- # much faster when all the fields given are indexed.
- def self.where(options = {})
- if options.empty? then
- Ampere::Collection.new(eval(to_s), [])
- else
- indexed_fields = (options.keys & @indices) + compound_indices_for(options)
- nonindexed_fields = (options.keys - @indices) - compound_indices_for(options).flatten
+ # Finds an array of records which match the given conditions. This method is
+ # much faster when all the fields given are indexed.
+ def where(options = {})
+ if options.empty? then
+ Ampere::Collection.new(eval(to_s), [])
+ else
+ indexed_fields = (options.keys & @indices) + compound_indices_for(options)
+ nonindexed_fields = (options.keys - @indices) - compound_indices_for(options).flatten
- results = nil
+ 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)}
- else
- # Compound index
- Ampere.connection.hget(
- "ampere.index.#{to_s.downcase}.#{key.join(':')}",
- key.map{|k| options[k]}.join(':')
- ).split(/:/) #.map {|id| find(id)}
- end
- }.each {|s|
- return s if s.empty?
+ 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)}
+ else
+ # Compound index
+ Ampere.connection.hget(
+ "ampere.index.#{to_s.downcase}.#{key.join(':')}",
+ key.map{|k| options[k]}.join(':')
+ ).split(/:/) #.map {|id| find(id)}
+ end
+ }.each {|s|
+ return s if s.empty?
- if results.nil? then
- results = s
- else
- results &= s
- end
- }
- end
-
- unless nonindexed_fields.empty?
- results = all if results.nil?
- results = results.to_a.map{|r| r.class == String ? find(r) : r}
- nonindexed_fields.each do |key|
- results.select!{|r|
- r.send(key) == options[key]
+ if results.nil? then
+ results = s
+ else
+ results &= s
+ end
}
end
- end
+
+ unless nonindexed_fields.empty?
+ results = all if results.nil?
+ results = results.to_a.map{|r| r.class == String ? find(r) : r}
+ nonindexed_fields.each do |key|
+ results.select!{|r|
+ r.send(key) == options[key]
+ }
+ end
+ end
- # TODO The eval(to_s) trick seems a little... ghetto.
- Ampere::Collection.new(eval(to_s), results.reverse)
+ # TODO The eval(to_s) trick seems a little... ghetto.
+ Ampere::Collection.new(eval(to_s), results.reverse)
+ end
end
- end
- private
+ private
- def self.compound_indices_for(query)
- compound_indices.select{|ci|
- (query.keys - ci).empty?
- }
+ def compound_indices_for(query)
+ compound_indices.select{|ci|
+ (query.keys - ci).empty?
+ }
+ end
end
-
end
end