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