Skip to content

Instantly share code, notes, and snippets.

@JEG2
Created April 3, 2011 23:11
Show Gist options
  • Save JEG2/900912 to your computer and use it in GitHub Desktop.
Save JEG2/900912 to your computer and use it in GitHub Desktop.
require "date"
require "digest/sha2"
require "pathname"
require "time"
require "uri"
require "rubygems"
require "rufus/tokyo"
class OklahomaMixer
class Database < Rufus::Tokyo::Table
def transaction(&block)
return block.call if defined?(@in_transaction) and @in_transaction
begin
@in_transaction = true
super
ensure
@in_transaction = false
end
end
end
def self.db_file
@db_file ||= "mixer.tct"
end
def self.db_file=(path)
@db_file = path
end
def self.db(mode = "wc")
return yield(@db) if defined?(@db) and @db
Database.open(db_file, :mode => mode) do |db|
begin
@db = db
yield @db
ensure
@db = nil
end
end
end
def self.validations
@validations ||= [ ]
end
def self.validate(validation, key = nil, &test)
validations << [key.nil? ? key : key.to_s, validation, test]
end
def self.apply_validation(key, name, options, &test)
if option = options.delete(name)
validate(name, key) { |object|
args = [ object,
object.respond_to?(key) ? object.send(key) : object[key] ]
args << option if test.arity > 1
test[*args]
}
end
end
private_class_method :apply_validation
def self.apply_validations(key, kind, options)
options = options.inject({ }) { |all, option|
all.merge(option.is_a?(Hash) ? option : {option => true})
}
apply_validation(key, :required, options) { |object, _|
keys = kind == :password ? %W[#{key}_encrypted #{key}_salt] : [key]
keys.all? { |k|
value = object[k]
not (value.nil? or (value.respond_to?(:empty?) and value.empty?))
}
}
apply_validation(key, :confirm, options) { |object, value|
case kind
when :password
object.send("#{key}_matches?", object.send("#{key}_confirmation"))
else
value == object.send("#{key}_confirmation")
end
} and define_confirmation_accessor(key, kind)
apply_validation(key, :matches, options) { |_, value, regexp|
value =~ regexp
}
apply_validation(key, :numeric, options) { |object, _|
(Integer(object[key]) rescue Float(object[key])) rescue false
}
apply_validation(key, :integer, options) { |object, _|
Integer(object[key]) rescue false
}
apply_validation(key, :float, options) { |object, _|
Float(object[key]) rescue false
}
apply_validation(key, :<, options) { |_, value, limit| value < limit }
apply_validation(key, :<=, options) { |_, value, limit| value <= limit }
apply_validation(key, :>, options) { |_, value, limit| value > limit }
apply_validation(key, :>=, options) { |_, value, limit| value >= limit }
apply_validation(key, :==, options) { |_, value, limit| value == limit }
apply_validation(key, :===, options) { |_, value, test| test === value }
apply_validation(key, :in, options) { |_, value, allowed|
allowed.include? value
}
apply_validation(key, :not_in, options) { |_, value, not_allowed|
not not_allowed.include? value
}
apply_validation(key, :max_size, options) { |_, value, max|
value.size <= max
}
apply_validation(key, :min_size, options) { |_, value, min|
value.size >= min
}
apply_validation(key, :size, options) { |_, value, size|
size === value.size
}
end
private_class_method :apply_validations
def self.define_confirmation_accessor(key, kind)
var = "@#{key}_confirmation"
define_method(var[1..-1]) do
return unless instance_variable_defined? var
string_to_value(instance_variable_get(var), kind)
end
define_method("#{var[1..-1]}=") do |value|
instance_variable_set(var, value_to_string(value, kind))
end
end
private_class_method :define_confirmation_accessor
def self.define_password_accessor(key)
define_method("#{key}_matches?") do |password|
self["#{key}_encrypted"] ==
encrypt_password(password, self["#{key}_salt"])
end
define_method("#{key}=") do |value|
if value.nil?
self["#{key}_salt"] = nil
self["#{key}_encrypted"] = nil
else
salt = generate_password_salt
self["#{key}_salt"] = salt
self["#{key}_encrypted"] = encrypt_password(value, salt)
end
end
end
private_class_method :define_password_accessor
def self.define_attribute_accessor(key, kind)
define_method("#{key}#{'?' if kind == :boolean}") do
string_to_value(self[key], kind)
end
define_method("#{key}=") do |value|
self[key] = value_to_string(value, kind)
end
end
private_class_method :define_attribute_accessor
def self.attribute(key, kind = :string, *options)
apply_validations(key, kind, options)
if kind == :password
define_password_accessor(key)
else
define_attribute_accessor(key, kind)
end
end
def self.string(key, *options)
attribute(key, :string, *options)
end
def self.password(key, *options)
attribute(key, :password, *options)
end
def self.integer(key, *options)
attribute(key, :integer, *options)
end
def self.float(key, *options)
attribute(key, :float, *options)
end
def self.boolean(key, *options)
attribute(key, :boolean, *options)
end
def self.timestamp(key, *options)
attribute(key, :timestamp, *options)
end
def self.path(key, *options)
attribute(key, :path, *options)
end
def self.url(key, *options)
attribute(key, :url, *options)
end
def self.find(*args)
if args.first == :all
OklahomaMixer.db do |db|
db.query do |query|
build_query(query, args.last.is_a?(Hash) ? args.last : { })
end
end
end
end
def self.build_query(query, details)
query.add("", :starts_with, "#{self}:")
end
private_class_method :build_query
def initialize(*args)
@id = args.first.is_a?(Hash) ? nil : args.first
@new = @id.nil?
@attributes = { }
@errors = [ ]
update(args.last) if args.last.is_a? Hash
end
attr_accessor :id
attr_reader :errors
def primary_key
"#{self.class}:#{id}"
end
def new?
@new
end
def [](key)
@attributes[key.to_s]
end
def []=(key, value)
if value.nil?
@attributes.delete(key.to_s)
else
@attributes[key.to_s] = value.to_s
end
end
def update(attributes)
attributes.each do |key, value|
setter = "#{key}="
if respond_to? setter
self.send(setter, value)
else
self[key] = value
end
end
end
def valid?
@errors.clear
self.class.validations.each do |key, validation, test|
@errors << [key, validation] unless test[self]
end
@errors.empty?
end
def invalid?(key)
[email protected](key.to_s)
end
def save
return false unless valid?
OklahomaMixer.db do |db|
self.id ||= db.generate_unique_id
db[primary_key] = @attributes
end
@new = false
true
rescue Rufus::Edo::EdoError
false
end
private
def string_to_value(string, kind)
return string if string.nil?
case kind
when :integer then Integer(string) rescue string.to_i
when :float then Float(string) rescue string.to_f
when :boolean then !!(string =~ /\A(?:t(?:rue)?|y(?:es)?|1)\z/i)
when :timestamp then Time.xmlschema(string).utc
when :path then Pathname.new(string)
when :url then URI.parse(string) rescue string
else string
end
end
def value_to_string(value, kind)
case kind
when :timestamp
case value
when Time then value.getutc.xmlschema
when DateTime then value.new_offset(0).strftime("%Y-%m-%dT%H:%M:%SZ")
when Date then value.strftime("%Y-%m-%dT%H:%M:%SZ")
else value
end
when :url
case value
when String then value.sub(/\A(?!\w+:)(?=.)/, "http://")
else value
end
else
value
end
end
def generate_password_salt
[Array.new(6) { rand(256).chr }.join].pack("m").chomp
end
def encrypt_password(password, salt)
Digest::SHA256.hexdigest("#{password}#{salt}")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment