Well after hacking away today for a couple of hours I've come up with the beginnings of a rudimentary ORM for KirbyBase. I'll post what I have so far, and people can let me know if they are interested in a more complete version. It currently looks a lot like ActiveRecord, but obviously not as powerful. First it doesn't do ActiveRecords pluralization magic, although that should be easy to add. Secondly it doesn't have as many variations of find (but that's mitigated by KirbyBases's use of ruby syntax to perform selections). I've only implemented one kind of relationship (one-to-many, or is it many-to-one, some db guy can correct me on my terminology) using a ActiveRecord-esque has_many method. Some other problems include that it doesn't yet do ruby-esque getters and setters (ie. obj.something, and obj.something = value) instead it has get_something and set_something methods. I did this since I was just hacking away its easily remedied if anyone shows any interest in my continuing this. One other thing, it does not make use of KirbyBase's Link type yet, although if it did, it would probably be better. Again something to add if anyone really wants to use this. I learned a couple of things developing this, mostly that @@variables are evil, and that class instance vars are better. Without further ado here is the code. Its followed by some examples:
$ cat kirbyrecord.rb
require 'kirbybase'
module KirbyRecord
class Base
def self.open_database( *kirby_base_new_args )
@@db = KirbyBase.new( *kirby_base_new_args )
end
def self.get_database
@@db
end
def initialize
@state = {}
end
def self.inherited(subclass)
subclass.inform_about_table
subclass.class_eval do
subclass.fields.each do |name, type|
define_method("get_#{name}".intern) do
@state[name]
end
define_method("set_#{name}".intern) do |value|
@state[name] = case type
when :String
value.to_s
when :Integer
value.to_i
when :Float
value.to_f
when :Boolean
if value then true else false end
when :Date
value
when :DateTime
value
end
end
end
end
end
def self.inform_about_table
table = get_database.get_table(table_name)
@fields = {}
table.field_names.zip( table.field_types ) do |field_name, field_type|
@fields[field_name] = field_type
end
self
end
def self.find(card)
db = get_database
tbl = db.get_table(table_name)
results = if block_given? then tbl.select { |r| yield(r) } else tbl.select end
obj_results = []
if card == :all
results.each do |res|
obj = self.new
self.fields.each do |field, type|
obj.instance_eval { @state[field] = res.send(field) }
end
obj_results << obj
end
return obj_results
elsif card == :first
obj = self.new
res = results.first
self.fields.each do |field, type|
obj.instance_eval { @state[field] = res.send(field) }
end
return obj
end
end
def self.table_name
self.to_s.split(/::/).last.downcase
end
def self.fields
@fields
end
def self.has_many(table_sym)
class_eval do
define_method("get_#{table_sym}".intern) do
table_class_name = table_sym.to_s.capitalize
rel_table_class = Object.const_get(table_class_name)
results = rel_table_class.find(:all) { |r| r.send("#{self.class.table_name}_id") == @state[:recno] }
end
end
end
def new_record?
@state[:recno].nil?
end
def save
db = self.class.get_database( )
tbl = db.get_table(self.class.table_name.intern)
data = @state.dup
data.delete(:recno)
if new_record?
@state[:recno] = tbl.insert(data)
else
tbl.update(data) { |r| r.recno == @state[:recno] }
end
self
end
end
% cat setup_example_db.rb
require 'kirbybase'
db = KirbyBase.new
author_tbl = db.create_table(:author, :name, :String)
book_tbl = db.create_table(:book, :title, :String, :author_id, :Integer)
rn = author_tbl.insert(:name => "John Doe")
book_tbl.insert(:title => "Ruby for Dummies", :author_id => rn)
book_tbl.insert(:title => "ORM for REAL Dummies", :author_id => rn)
% ruby setup_example_db.rb
% cat has_many_example.rb
require 'kirbyrecord'
KirbyRecord::Base.open_database( )
class Author < KirbyRecord::Base
has_many :book
end
class Book < KirbyRecord::Base
end
john_doe = Author.find(:first) { |r| r.name =~ /John Doe/ }
john_doe.get_book.each do |book|
puts "#{john_doe.get_name} wrote #{book.get_title}"
end
% ruby has_many_example.rb
John Doe wrote Ruby for Dummies
John Doe wrote ORM for REAL Dummies
And so on.
Other neat things
% cat setup_db.rb
require 'kirbybase'
db = KirbyBase.new
person_tbl = db.create_table(:person, :name, :String, :age, :Integer)
% ruby setup_db.rb
% cat person_ex.rb
require 'kirbyrecord'
KirbyRecord::Base.open_database( )
class Person < KirbyRecord::Base
end
person1 = Person.new
person1.set_name "John Doe"
person1.set_age 25
person1.save
person2 = Person.new
person2.set_name "Jane Doe"
person2.set_age 23
person2.save
people = Person.find(:all)
people.each do |person|
puts "name: #{person.get_name}, age: #{person.get_age}"
person.set_name( person.get_name + " Smith" )
person.save
end
people = Person.find(:all)
puts "After updates:"
people.each do |person|
puts "name: #{person.get_name}, age: #{person.get_age}"
end
% ruby person_ex.rb
name: John Doe, age: 25
name: Jane Doe, age: 23
After updates:
name: John Doe Smith, age: 25
name: Jane Doe Smith, age: 23
Other than that the only thing to mention is that KirbyRecord::Base.open_database( ) takes the same arguments as KirbyBase.new and that you should call it before inheriting from KirbyRecord::Base (incidently anyone have any ideas on how to avoid having to do this?). Also KirbyRecord::Base#find can take a block just like KBTable#select. There are probably lots and lots of bugs.