From: Logan Capaldo Date: 2005-09-14T11:57:53+09:00 Subject: Re: KirbyBase [ANN (sort-of)] proof-of-concept KirbyBase ORM 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.