Object Database
[1/7] from: louisaturk::eudoramail::com at: 9-Apr-2001 6:35
I am trying to learn how to make an object database using REBOL. I need a
simple, but complete, working example. Can anyone supply one?
Also, what are probably dumb questions:
1. If an object database contains data, will that data be lost if another
field is added?
2. Is there any way to change the name of an object in a database without
losing the data in the object?
3. Would it be possible to use email addresses for the names of the objects?
Thanks in advance for any help.
Louis
[2/7] from: carl:rebol at: 9-Apr-2001 8:28
What!? You just want us to just whip out an object database, eh? ;)
Ok, why not.... Here is a small "contacts" database keyed by email.
You can expand or reduce the record definition without corrupting
or affecting the database.
REBOL [Title: "Email Contact Database"]
db-file: %data.r
record: context [name: email: phone: web: none]
database: []
load-data: has [data] [
data: load/all db-file
clear database
foreach item blk [
item: make record item
repend database [item/email item]
]
]
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
find-data: func [email] [select database email]
remove-data: func [email] [remove/part find database email 2]
insert-data: func [email' name' phone' web'] [
repend database [
email'
make record [
email: email'
name: name'
phone: phone'
web: web'
]
]
]
You can expand/contract the record definition at any time.
This is untested... but should be close to working, less a few
minor typos. If you expect to grow this database to a large
size, you will want to MAKE HASH! the database when you load
it.
-Carl
PS: The remove/part on find really does work. Remove none is
allowed in Core 2.5.
[3/7] from: louisaturk:eudoramail at: 11-Apr-2001 1:47
Dear Carl,
I am amazed that such a small amount of code can do so much, and also that
the creator of this language himself would help me like this. Many thanks!
I have some questions. I have been studying your code carefully trying to
learn how it works. I've been on this list for quite a while, but have
only recently taken time to buy Ralph's book and Elan and John's book to
really get serious about learning the language. I'm a real beginner, so
please excuse me if my questions betray that fact.
So far I have figured out how to use your functions to enter data into a
record, and then save it to a file. By entering "print read %data.r" at
the command line, I am able to see that I have entered the data
successfully. So I think I understand how to use insert-data and
save-data. However, when I type load-data I get an error message ("blk
has no value"); what am I doing wrong? Also, I assume that I must learn
how to get load-data to work before find-data and remove-data will work;
but to keep from having to ask you later, how are these two functions used?
I want to use this database for an email list. Most of the data remains
the same, but email addresses change often. Sometime I do not even have a
name---only an email address that has to be removed or changed. How do I
change an email address?
You say:
> If you expect to grow this database to a large
>size, you will want to MAKE HASH! the database when you load
>it.
Will this modification do it?:
>load-data: has [data] [
> data: make hash! load/all db-file
<<quoted lines omitted: 4>>
> ]
>]
Will your code work with Rebol/View also? Am I correct in assuming that
View would be ideal for creating a user interface for the database?
Many thanks for your help, and for creating a truly exciting language.
Louis
At 08:28 AM 4/9/2001 -0700, you wrote:
>What!? You just want us to just whip out an object database, eh? ;)
Well, I wasn't expect anyone to whip out one especially for me,
[4/7] from: carl:rebol at: 11-Apr-2001 5:49
Wow... so many questions. It's a good think I can't sleep tonight...
My replies are mixed in below:
> -----Original Message-----
> From: [rebol-bounce--rebol--com] [mailto:[rebol-bounce--rebol--com]]On Behalf Of
<<quoted lines omitted: 7>>
> the creator of this language himself would help me like this.
> Many thanks!
Whatever I can do to help!
> I have some questions. I have been studying your code carefully
> trying to
<<quoted lines omitted: 8>>
> save-data. However, when I type load-data I get an error message ("blk
> has no value"); what am I doing wrong? Also, I assume that I must learn
I'm sorry. Change blk to data. A typo. Did I mention that it still
needed your help debugging it? ;)
> how to get load-data to work before find-data and remove-data will work;
> but to keep from having to ask you later, how are these two
> functions used?
The database works from memory. Load-data brings it into memory where the
find, remove and other functions operate on it. So, yes, you have to load
it first, or at least insert-data a few times to create some data records.
The functions work like this:
rec: find-data [bob--example--com]
print rec/name
print rec/phone
etc.
remove-data [bob--example--com]
save-data ; write back to disk
insert-data [kit--example--com] "Kitty Carson" none http:/www.a.com
save-data ; write back to disk
You can use NONE for any missing value above. You don't need to do the
save-data each time... but you must do it sooner or later.
> I want to use this database for an email list. Most of the data remains
> the same, but email addresses change often. Sometime I do not
> even have a
> name---only an email address that has to be removed or changed. How do I
> change an email address?
The data is organized (keyed) by email. To change an email address, but
keep the rest of the record intact, you'll need a new function:
change-data: func [email-old email-new /local record] [
record: find database email-old
if none? record [alert reform ["Missing record:" email-old] exit]
insert clear first record email-new ; see note below
]
Note: This is subtle, so should be noted... I'm clearing out the
actual memory string for the old email, then inserting the new string
into it.
Also, this string is shared. It is used both in the database
block (as the key) and in the record object itself. So changing it
here will change it in both places. That's a feature, but it is not
obvious, so make sure that you comment that line.
> You say:
> > If you expect to grow this database to a large
<<quoted lines omitted: 9>>
> > ]
> >]
You will want to make the hash after the database is created
(after the foreach loop).
database: make hash! database
But, don't worry about that until your database gets big. For
a few hundred names, you don't need it. Get to know the code
and debug it before hashing it.
> Will your code work with Rebol/View also? Am I correct in assuming that
> View would be ideal for creating a user interface for the database?
Yes and yes. You will want to add two new functions: one to copy
data from a record into a view form, and another to copy it back
or create a new record in the database.
> Many thanks for your help, and for creating a truly exciting language.
When you are done, you will have written an actual database system
from scratch in REBOL. Not bad for a couple pages of code, eh?
[5/7] from: louisaturk:eudoramail at: 13-Apr-2001 14:18
Scott,
Thanks much for the help. I would really appreciate it if you would send
me a copy of the View interface when you finish it.
Louis
At 12:14 AM 4/13/2001 -0700, you wrote:
[6/7] from: koolauscott:y:ahoo at: 13-Apr-2001 0:14
*****BEGINNER'S QUESTIONS*****
Dr. Turk,
I hope you don't mind my replying as I'm not much
more than a beginner myself. I remember the
difficulties I had when I was first starting out with
Rebol. I still don't understand 90% of the stuff
people talk about on this mailing list but I've
learned enough Rebol to enjoy it.
I think it is best to read through the core guide
and then the Rebol for Dummies first before trying
Rebol - The Official Guide. I started out with The
Official Guide and knocked my head against it for 3
months, and while I'm sure it improved my character, I
wouldn't recommend it for a beginner.
There is a typo in the script you're using; try
the one attached. It is an amazing script; I've been
playing with it the past couple of days. I've just
started to learn View and have been working on an
interface for this script. The only databases I've
done in Rebol have been small flat-files and they look
really bloated compared to this.
I've tried to answer the questions you raised as best
I could, I hope it helps.
To see the data you have entered use ==>
probe database
You can't print an object; use probe.
The database block contains the database when it is
loaded into Rebol. The %data file is just used to
store the database to disk.
All the database functions work with database block.
load-data loads the disk copy of the database from
disk and into the block called database.
save-data writes the database block to the %data file,
when you are done making modifications to the
database.
Thus find-data and remove-data can be called after you
create a record with insert-data. Those functions only
work with the database block and have nothing to do
with the %data file.
Hash
I've added a line that turns the database block from a
block! type to a hash!
Change an Email Address
First look-up the record to change with find-data and
assign it to a variable ==>
x: find-data [bill--example--com]
then probe x to see the object ==>
probe x
To change bill's email address use ==>
x/email: [bill--newemail--net]
When you call save-data and then load-data you can use
find-data with the new email address.
#################################################
Email Contact Database
REBOL [Title: "Email Contact Database"]
db-file: %data
record: context [name: email: phone: web: none]
database: []
load-data: has [data] [
data: load/all db-file
clear database
foreach item data [
item: make record item
repend database [item/email item]
database: to-hash database
]
]
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
find-data: func [email] [select database email]
remove-data: func [email] [remove/part find database
email 2]
insert-data: func [email' name' phone' web'] [
repend database [
email'
make record [
email: email'
name: name'
phone: phone'
web: web'
]
]
]
##################################################
--- "Dr. Louis A. Turk" <[louisaturk--eudoramail--com]>
wrote:
[7/7] from: jregent:centrum:cz at: 16-Apr-2001 21:12
Hi Carl, Luis, Dr. Turk &comp
I play with Carl's Object Database functions and View too.
Carl's code is now modified in fly by actual field list of current file.
Layout, context, data manipulation function etc. are generated before
doing.
This db-edit we will to use for administration of small files and learning.
I offer cooperation with share, testing, debuging and finishing this code.
Some coment's and idas are welcome.
Field-list can be stored in db-file..
If field is named "web" as (example 5 ), data of this field are not saved.
I thing name "web" is in conflict with internal word?
If are used keys Up/Down many times Rebol crasch (GPF)
Rebol is best :-)
Many thanks for your work and inspiration.
By
Jan Regent
----------------------------------------------------------------------------
------
rebol
Title: "db-edit"
author: "Jan Regent"
need: "view 1.0"
]
;---------------------------------------------------------------------------
--
; db-lib
;---------------------------------------------------------------------------
--
find-data: func [key] [select database key]
remove-data: func [key] [ remove/part find database key 2]
;===========================================================================
======= db actions
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
save-data-formated: has [data][
save-data
file: read db-file
formated-db-file: to-file join db-file "-temp.dat"
if exists? formated-db-file [delete formated-db-file]
;--------------------------------------------------------------- record
parsing
file: replace file "[[" "[" ;! I don't undestand why save-data saved
record with "[["
file: replace file "]]" "]" ;! debuging needed
while [true] [
parse file [thru "[" copy qrecord to "]" ]
file: remove/part file length? qrecord
if not found? find qrecord field-list/1
delete db-file
qnew-file: split-path db-file
rename formated-db-file qnew-file/2
return
]
write/append/lines formated-db-file "["
;------------------------------------------------------- field parsing
for i 1 ((length? field-list) - 1) 1 [
i1: i + 1
field-label: field-list/:i
field-label2: field-list/:i1
parse qrecord [thru field-label copy qfield to field-label2 ]
write/append/lines formated-db-file join " " [field-label " " qfield]
]
qfield: remove/part qrecord ((index? find qrecord field-label2) + length?
field-label2)
write/append/lines formated-db-file join " " [ field-label2 " " qfield]
write/append/lines formated-db-file "]"
]
]
load-data-template: {has [data] [
data: load/all db-file
clear database
foreach item data [
item: make record item
repend database [item/x-x-key-x-x item]
]
]
}
load-data-gen: has [][
qitem: copy field-list/1
qitem: replace qitem ":" ""
load-data-template: replace load-data-template "x-x-key-x-x" qitem
load-data: do load-data-template
]
db-create: func [ fields [string!]]
[
qfile: split-path db-file
if not exists? qfile/1 [ make-dir/deep qfile/1]
ddf-file: to-file join db-file ".ddf"
write/lines ddf-file "<field-list>"
write/lines/append ddf-file join " " fields
write/lines/append ddf-file "</field-list>"
cmd: join "field-list: [" [ fields "]" ]
debug-log cmd
do cmd
db-context-create
insert-data-gen
insert-data-new
save-data-formated
rec-count: 1
rec-count-save
]
rec-count-save: has []
[
rec-count-file: to-file join db-file ".reccount"
write/lines rec-count-file join "<rec-count> " [rec-count "
</rec-count>" ]
]
rec-count-read: has []
[
rec-count-file: to-file join db-file ".reccount"
parse read rec-count-file [thru "<rec-count>" copy qrec-count to
</rec-count>
]
qrec-count: trim qrec-count
rec-count: to-integer qrec-count
]
db-open: has [][
ddf-file: to-file join db-file ".ddf"
parse read ddf-file [thru "<field-list>" copy fields to "</field-list>" ]
cmd: join "field-list: [" [ fields "]" ]
debug-log cmd
do cmd
db-context-create
insert-data-gen
rec-count-read
]
db-context-create: has []
[
cmd: "context ["
foreach item field-list [cmd: join cmd [item " "]]
cmd: join cmd " none ]"
debug-log cmd
record: do cmd
]
;===========================================================================
==== record actions
save-record: has [][
foreach item field-list [
qitem: replace to-string item ":" ""
cmd: join "rec/" [item " to-string " qitem "/text"]
debug-log cmd
do cmd
]
record-no: (2 * this-record) - 1
do join "database/" [record-no ": rec"]
]
find-record-no: has [][
record-no: (2 * this-record) - 1
rec: find-data database/:record-no
if not none? rec [
unview
focus name
view layout current-layout
]
]
insert-data-gen: has [] [
qfunc: {insert-data: func [}
foreach item field-list [
qitem: copy item
qitem: replace qitem ":" "'"
qfunc: join qfunc [qitem " "]
]
append qfunc "]"
append qfunc newline
;append qfunc { "Insert record into db-file, generated function, key is
x-x-x"}
;qfunc: replace qfunc "x-x-x" field-list/1
append qfunc newline
qfunc: join qfunc [ {[
repend database [
} replace copy field-list/1 ":" "'" {
make record [
}
]
foreach item field-list [
qitem: copy item
qitem: replace qitem ":" "'"
qfunc: join qfunc [" " item " " qitem newline]
]
qfunc: join qfunc {
]
]
]
}
debug qfunc
do qfunc
]
insert-data-new: has []
[
cmd: "insert-data "
foreach item field-list [ cmd: join cmd [{ "" }]]
debug-log cmd
do cmd
]
;========================================================================
view layouts/ actions
gen-form: has []
[
simple-layout: copy []
append simple-layout join {[ h3 } [main-title ]
append simple-layout {
across
;----------------------------------------------------------------------- fields section
}
foreach item field-list [
qline: join " label " [{"} item {" 100x24 right } item { }]
append simple-layout qline
qitem: join "rec/" item
qitem: replace qitem ":" ""
curr-value: to-string do qitem
qline: join {field 362 } [ qitem " return" ]
append simple-layout qline
append simple-layout newline
]
append simple-layout {
;----------------------------------------------------------------------- control section
arrow left keycode [up ] [ show-page "prev-record" ]
arrow right keycode [down ] [ show-page "next-record" ]
label "find: " 42x24 right web: field 362 " " return
guide 125x100
button 100 "New" #"^n" [ show-page "new-record" ]
button "Del" #"^d" [ ] ;show-page "del-record"
button "Close" #"^q" [
save-record
save-data-formated
unview
]
return
]
}
if exists? %db-edit.frm [ delete %db-edit.frm]
write %db-edit.frm simple-layout
]
show-page: func [arrow-key][
save-record
if "prev-record" = arrow-key [
if this-record > 1 [
this-record: this-record - 1
find-record-no
]
]
if "next-record" = arrow-key [
if this-record < rec-count [
this-record: this-record + 1
find-record-no
]
]
if "new-record" = arrow-key [
insert-data-new
rec-count: rec-count + 1
rec-count-save
this-record: rec-count
find-record-no
]
if "del-record" = arrow-key [
remove-data to-word replace field-list/1 ":" ""
rec-count: rec-count - 1
if rec-count = 0 [
rec-count: 1
insert-data-new
]
rec-count-save
if this-record > rec-count [this-record: rec-count]
find-record-no
]
;print [arrow-key this-record ]
]
;===============================================================================================
; main
;=======================================================================
========================
db-edit: func [db-name [file!] fields [string!] ]
[
db-file: copy db-name
if not exists? db-file [db-create fields ]
db-open
load-data-gen
load-data
rec: find-data database/1
main-title: join {"Data: } [ db-name {"} ]
gen-form
current-layout: load %db-edit.frm
;debug-log current-layout
view layout current-layout
]
debug-log: func [text
][
if debug [
write/append/lines %debug.txt ""
write/append/lines %debug.txt ""
write/append/lines %debug.txt ""
write/append/lines %debug.txt to-string text
]
]
;----------------------------------------------------------- <global words>
debug: true
if debug [if exists? %debug.txt [delete %debug.txt]]
record: context []
field-list: []
db-file: %contact-db.dat
main-title: join {"} [ db-file {"} ]
database: []
this-record: 1
rec-count: 0
rec: ""
;---------------------------------------------------------- </global words>
; examples - only one line can be unremarked
;1
db-edit %db\aa.dat {"email:" "name:" "phone:" "web1:" "web3:" }
;2
;db-edit %db\cars.dat {"number:" "color:" "type:" "technical-checking:"
year:
}
;3
;db-edit %db\users.dat {"email:" "name:" "phone:" "id:" "mobile:" }
;4
;db-edit %db\main.cfg {"work-dir:" "backup-dir:" "created:" "rights:" }
;5
;db-edit %db\web.cfg {"name:" "web:" "page:" "rights:" }
-- Attached file included as plaintext by Listar --
-- File: db-edit.r
rebol [
Title: "db-edit"
author: "Jan Regent"
need: "view 1.0"
]
;-----------------------------------------------------------------------------
; db-lib
;-----------------------------------------------------------------------------
find-data: func [key] [select database key]
remove-data: func [key] [ remove/part find database key 2]
;================================================================================== db
actions
save-data: has [data] [
data: copy []
foreach [key obj] database [
append/only data third obj
]
save db-file data
]
save-data-formated: has [data][
save-data
file: read db-file
formated-db-file: to-file join db-file "-temp.dat"
if exists? formated-db-file [delete formated-db-file]
;--------------------------------------------------------------- record parsing
file: replace file "[[" "[" ;! I don't undestand why save-data saved record with "[["
file: replace file "]]" "]" ;! debuging needed
while [true] [
parse file [thru "[" copy qrecord to "]" ]
file: remove/part file length? qrecord
if not found? find qrecord field-list/1 [
delete db-file
qnew-file: split-path db-file
rename formated-db-file qnew-file/2
return
]
write/append/lines formated-db-file "["
;------------------------------------------------------- field parsing
for i 1 ((length? field-list) - 1) 1 [
i1: i + 1
field-label: field-list/:i
field-label2: field-list/:i1
parse qrecord [thru field-label copy qfield to field-label2 ]
write/append/lines formated-db-file join " " [field-label " " qfield]
]
qfield: remove/part qrecord ((index? find qrecord field-label2) + length? field-label2)
write/append/lines formated-db-file join " " [ field-label2 " " qfield]
write/append/lines formated-db-file "]"
]
]
load-data-template: {has [data] [
data: load/all db-file
clear database
foreach item data [
item: make record item
repend database [item/x-x-key-x-x item]
]
]
}
load-data-gen: has [][
qitem: copy field-list/1
qitem: replace qitem ":" ""
load-data-template: replace load-data-template "x-x-key-x-x" qitem
load-data: do load-data-template
]
db-create: func [ fields [string!]]
[
qfile: split-path db-file
if not exists? qfile/1 [ make-dir/deep qfile/1]
ddf-file: to-file join db-file ".ddf"
write/lines ddf-file "<field-list>"
write/lines/append ddf-file join " " fields
write/lines/append ddf-file "</field-list>"
cmd: join "field-list: [" [ fields "]" ]
debug-log cmd
do cmd
db-context-create
insert-data-gen
insert-data-new
save-data-formated
rec-count: 1
rec-count-save
]
rec-count-save: has []
[
rec-count-file: to-file join db-file ".reccount"
write/lines rec-count-file join "<rec-count> " [rec-count " </rec-count>" ]
]
rec-count-read: has []
[
rec-count-file: to-file join db-file ".reccount"
parse read rec-count-file [thru "<rec-count>" copy qrec-count to "</rec-count>" ]
qrec-count: trim qrec-count
rec-count: to-integer qrec-count
]
db-open: has [][
ddf-file: to-file join db-file ".ddf"
parse read ddf-file [thru "<field-list>" copy fields to "</field-list>" ]
cmd: join "field-list: [" [ fields "]" ]
debug-log cmd
do cmd
db-context-create
insert-data-gen
rec-count-read
]
db-context-create: has []
[
cmd: "context ["
foreach item field-list [cmd: join cmd [item " "]]
cmd: join cmd " none ]"
debug-log cmd
record: do cmd
]
;=============================================================================== record
actions
save-record: has [][
foreach item field-list [
qitem: replace to-string item ":" ""
cmd: join "rec/" [item " to-string " qitem "/text"]
debug-log cmd
do cmd
]
record-no: (2 * this-record) - 1
do join "database/" [record-no ": rec"]
]
find-record-no: has [][
record-no: (2 * this-record) - 1
rec: find-data database/:record-no
if not none? rec [
unview
focus name
view layout current-layout
]
]
insert-data-gen: has [] [
qfunc: {insert-data: func [}
foreach item field-list [
qitem: copy item
qitem: replace qitem ":" "'"
qfunc: join qfunc [qitem " "]
]
append qfunc "]"
append qfunc newline
;append qfunc { "Insert record into db-file, generated function, key is x-x-x"}
;qfunc: replace qfunc "x-x-x" field-list/1
append qfunc newline
qfunc: join qfunc [ {[
repend database [
} replace copy field-list/1 ":" "'" {
make record [
}
]
foreach item field-list [
qitem: copy item
qitem: replace qitem ":" "'"
qfunc: join qfunc [" " item " " qitem newline]
]
qfunc: join qfunc {
]
]
]
}
debug qfunc
do qfunc
]
insert-data-new: has []
[
cmd: "insert-data "
foreach item field-list [ cmd: join cmd [{ "" }]]
debug-log cmd
do cmd
]
;======================================================================== view layouts/
actions
gen-form: has []
[
simple-layout: copy []
append simple-layout join {[ h3 } [main-title ]
append simple-layout {
across
;----------------------------------------------------------------------- fields section
}
foreach item field-list [
qline: join " label " [{"} item {" 100x24 right } item { }]
append simple-layout qline
qitem: join "rec/" item
qitem: replace qitem ":" ""
curr-value: to-string do qitem
qline: join {field 362 } [ qitem " return" ]
append simple-layout qline
append simple-layout newline
]
append simple-layout {
;----------------------------------------------------------------------- control section
arrow left keycode [up ] [ show-page "prev-record" ]
arrow right keycode [down ] [ show-page "next-record" ]
label "find: " 42x24 right web: field 362 " " return
guide 125x100
button 100 "New" #"^n" [ show-page "new-record" ]
button "Del" #"^d" [ ] ;show-page "del-record"
button "Close" #"^q" [
save-record
save-data-formated
unview
]
return
]
}
if exists? %db-edit.frm [ delete %db-edit.frm]
write %db-edit.frm simple-layout
]
show-page: func [arrow-key][
save-record
if "prev-record" = arrow-key [
if this-record > 1 [
this-record: this-record - 1
find-record-no
]
]
if "next-record" = arrow-key [
if this-record < rec-count [
this-record: this-record + 1
find-record-no
]
]
if "new-record" = arrow-key [
insert-data-new
rec-count: rec-count + 1
rec-count-save
this-record: rec-count
find-record-no
]
if "del-record" = arrow-key [
remove-data to-word replace field-list/1 ":" ""
rec-count: rec-count - 1
if rec-count = 0 [
rec-count: 1
insert-data-new
]
rec-count-save
if this-record > rec-count [this-record: rec-count]
find-record-no
]
;print [arrow-key this-record ]
]
;===============================================================================================
; main
;===================================================
Notes
- Quoted lines have been omitted from some messages.
View the message alone to see the lines that have been omitted