Created
August 20, 2012 17:56
-
-
Save Bodacious/3406159 to your computer and use it in GitHub Desktop.
An "ActiveRecord" library for RubyMotion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ActiveRecord | |
class Base | |
COLUMN_TYPES_MAPPING = { | |
:int => 'INTEGER', | |
:long => 'INTEGER', | |
:longLongInt => 'BIGINT', | |
:bool => 'BOOLEAN', | |
:double => 'DECIMAL', | |
# :text => 'TEXT', | |
:UTF8String => 'TEXT', | |
:string => 'TEXT', | |
:date => 'DATE', | |
:data => 'BLOB', | |
:dataNoCopy => 'BLOB', | |
:object => 'BLOB', | |
} | |
APP_DATE_FORMAT = "%m-%d" | |
SQL_DATE_FORMAT = '%Y-%m-%d %H:%M:%S %z' | |
attr_accessor :id | |
class << self | |
def createTable | |
columnsArray = [] | |
attributes.each do |name, options| | |
next if name.to_sym == :id | |
string = "#{name} " | |
string << "#{COLUMN_TYPES_MAPPING[options[:type]]} " | |
string << "(#{options[:limit]}) " if options[:limit] | |
string << "NOT NULL " if options[:null] == false | |
string << "UNIQUE " if options[:unique] | |
if options[:type] == :bool && !options[:default].nil? | |
options[:default] = 1 if options[:default] == true | |
options[:default] = 0 if options[:default] == false | |
options[:default] = "\"#{options[:default]}\"" unless options[:default].is_a?(Integer) | |
end | |
string << "DEFAULT(#{options[:default]}) " if options[:default] | |
puts "DEFAULT(#{options[:default]}) " if options[:default] | |
columnsArray << string | |
end | |
columnsString = columnsArray.join(',') | |
execute "CREATE TABLE #{tableName} (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,#{columnsString});" | |
# Create indices for attributes marked with index: true | |
log "Creating indices for #{tableName};" | |
attributes.each do |name, options| | |
next unless options[:index] == true | |
execute "CREATE INDEX index_#{tableName}_#{name} ON #{tableName} (#{name});" | |
end | |
end | |
def tableName | |
raise("table name not defined for class #{self}") | |
end | |
def attribute(name, options = {}) | |
defaults = { type: :string } | |
options = defaults.merge(options) | |
raise "Invalid attribute type: #{options[:type]} for attribute: #{name}" unless COLUMN_TYPES_MAPPING.keys.include?(options[:type].to_sym) | |
attr_accessor name | |
attributes[name.to_sym] = options | |
end | |
def attributes | |
@attributes ||= { | |
id: { type: :int } | |
} | |
end | |
def execute(query) | |
log(query) | |
query.match(/^([A-Z]+)/i) | |
case $1 | |
when 'SELECT' then executeQuery(query) | |
else | |
executeUpdate(query) | |
end | |
end | |
def find(id) | |
results = execute("SELECT * FROM #{tableName} WHERE #{tableName}.id = #{id} LIMIT 1;") | |
results.first if results | |
end | |
def first | |
results = execute("SELECT * FROM #{tableName} ORDER BY #{tableName}.id ASC LIMIT 1;") | |
results.first if results | |
end | |
def last | |
results = execute("SELECT * FROM #{tableName} ORDER BY #{tableName}.id DESC LIMIT 1;") | |
results.first if results | |
end | |
def count | |
execute("SELECT COUNT(id) FROM #{tableName};") | |
end | |
def all | |
execute("SELECT * FROM #{tableName}") | |
end | |
def where(statement) | |
execute("SELECT * FROM #{tableName} WHERE #{tableName}.id = #{id}") | |
end | |
def database | |
@database ||= begin | |
db = FMDatabase.databaseWithPath(databaseFilePath) | |
db.open | |
db | |
end | |
end | |
def database=(value) | |
@database = value | |
end | |
def create(_attributes) | |
r = new(_attributes) | |
if r.save | |
r | |
end | |
end | |
def beforeSaveCallbacks | |
@beforeSaveCallbacks ||= {} | |
end | |
def beforeSave(methodName, options = {}) | |
self.beforeSaveCallbacks[methodName] = options | |
end | |
private | |
def executeUpdate(query) | |
database.executeUpdate(query) | |
end | |
def executeQuery(query) | |
values = [] | |
result = database.executeQuery(query) | |
if result | |
while result.next do | |
atts = {} | |
for name, hash in attributes | |
# TODO: Review this later (boolForColumn and dateForColumn aren't working) | |
hash[:type] = :string if hash[:type] == :date | |
hash[:type] = :int if hash[:type] == :bool | |
atts[name] = result.send("#{hash[:type]}ForColumn", name) | |
end | |
values << new(atts) | |
end | |
values | |
end | |
end | |
def databaseFilePath | |
@databaseFilePath ||= File.join(App.documents_path, "#{App.identifier}.sqlite") | |
end | |
end | |
def newRecord? | |
id.nil? | |
end | |
def persisted? | |
!newRecord? | |
end | |
def execute(query) | |
self.class.execute(query) | |
end | |
def initialize(_attributes={}) | |
persisted = false | |
_attributes.each do |name, value| | |
value = self.class.attributes[name.to_sym][:default] if value.nil? and self.class.attributes[name.to_sym] | |
value = nil if value.is_a?(String) and value.strip.empty? | |
send("#{name}=", value) | |
end | |
self | |
end | |
def attributes | |
hash = {} | |
self.class.attributes.keys.each do |att| | |
hash[att] = send(att) | |
end | |
hash | |
end | |
def attributesWithoutID | |
hash = {} | |
self.class.attributes.keys.each do |att| | |
next if att.to_sym == :id | |
hash[att] = send(att) | |
end | |
hash | |
end | |
def save | |
if persisted? | |
self.class.beforeSaveCallbacks.each do |callback, options| | |
val = send(callback) unless (options[:on] && options[:on].to_sym == :create) | |
# don't save if beforeSave callbacks return false | |
return if val == false | |
end | |
attsWithoutID = attributes | |
attsWithoutID.delete(:id) | |
setArray = [] | |
attsWithoutID.each do |att, val| | |
setArray << "#{att}=\"#{val}\"" | |
end | |
setString = setArray.join(',') | |
execute("UPDATE #{self.class.tableName} SET #{setString} WHERE #{self.class.tableName}.id = #{id}") | |
else | |
self.class.beforeSaveCallbacks.each do |callback, options| | |
val = send(callback) unless (options[:on] && options[:on].to_sym == :update) | |
# don't save if beforeSave callbacks return false | |
return if val == false | |
end | |
valueArray = [] | |
attributesWithoutID.values.each do |value| | |
valueArray << case value | |
when Integer then value | |
else | |
"\"#{value}\"" | |
end | |
end | |
valueString = valueArray.join(',') | |
execute("INSERT INTO #{self.class.tableName} (#{attributesWithoutID.keys.join(',')}) VALUES (#{valueString});") | |
end | |
end | |
def delete | |
execute("DELETE FROM #{self.class.tableName} WHERE #{self.class.tableName}.id = #{id}") | |
end | |
def dateWithString(string) | |
NSDate.dateWithString(string) | |
end | |
def stringWithDate(date) | |
date.strftime(APP_DATE_FORMAT) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment