Skip to content

Instantly share code, notes, and snippets.

@Bodacious
Created August 20, 2012 17:56
Show Gist options
  • Save Bodacious/3406159 to your computer and use it in GitHub Desktop.
Save Bodacious/3406159 to your computer and use it in GitHub Desktop.
An "ActiveRecord" library for RubyMotion
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