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
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.