As I said over there:
... for a little side project at home, I'm writing a ham radio web site in uby/Rails. I started it in Perl and gave up on Perl as I went from the 'display the database information on the web page' to the 're-display the information from the database and allow the user to update the database using the web page' stage and realized I had more work to do than I had just finished doing.
Now I've gotten it from 'show form with lots of things to fill out' through 'save the data from the forms in the database' and 'retrieve said info from db and display on web page' all the way to 'try to save the changed data into the db' - and I'm running in to problems now.
Now, I'll continue over here, and apologize up front for its length, but I figured that a single post is probably better than a bunch of little ones which don't really give the full flavor of how far off into the weeds I've gotten :-).
Let's say that I have the following data layout:
Expand|Select|Wrap|Line Numbers
- hams:
- id int
- name varchar(100)
- gps_coords varchar(50)
- geographic_location_id (references geographic_locations below)
- general_comments varchar(100)
- geographic_locations:
- id int
- full_title varchar(255)
- icon varchar(128)
- num_hams int
- ham_secrets:
- id int
- date_last_updated date
- password varchar(100)
- ham_id (references hams above)
- callsign_lists
- id int
- ham_id (references hams above)
- callsign varchar(20)
- sequence int
- phone_numbers
- int id
- phone_number varchar(30)
- ham_id (references hams above)
- publish int (0 means do not publish, 1 means do)
- email_addresses
- int id
- ham_id (references hams above)
- sequence int
- publish int # zero if want private, 1 to publish on the web page
- ...stuff you'd expect here...
Other tables which reference hams are email_addresses, pending_verifications,
ham_expertise_entries (which also references expertise_items), and a few other
things for future expansion.
So, for example, I might register and it would look something like this:
the database entries for me would be:
Expand|Select|Wrap|Line Numbers
- hams:
- id = 1
- name rusty c
- gps_coords ""
- geographic_location_id 3
- general_comments ""
- ham_secrets:
- id 1
- date_last_updated (today)
- password "a password of some kind"
- ham_id 1
- callsign_lists
- id 1
- ham_id 1
- callsign "N7IKQ"
- sequence 1
phone_numbers - I didn't choose to enter any phone numbers, so this table has no entries referencing me.
Expand|Select|Wrap|Line Numbers
- email_addresses (two entries):
- id 1
- ham_id 1
- sequence 1
- publish 0
- ...stuff you'd expect here...
- id 2
- ham_id 1
- sequence 2
- ...stuff you'd expect here with my email address and comments...
The process is - user goes to web site, and a navigate to a page that displays
all the above in one nice page. They fill in the fields they want to (and leave the others blank) and hit 'submit'. Their info gets saved, a new 'pending verification' entry gets created, and an email is sent to each of the email addresses with a unique number and a web page address.
The user then goes to that address, enters the unique number and their email address, and the fields get re-displayed for them to verify and approve.
They verify and hit 'approve' (or whatever I called the button), and their updates are made and they are now 'published' on the site.
So, what does the ruby/rails code look like?
Probably hosed - here's some background info and snippets:
I have 2 controllers - one I call 'elmers' and one I call 'registration'. You talk to the 'elmers' controller up until the registration verification step.
elmers_controller.rb:
Expand|Select|Wrap|Line Numbers
- class ElmersController < ApplicationController
- def registration_page # this displays the page that they fill out initially
- # get the tables of items that can appear (kinds of email (home, etc),
- # different special interests (over 100 at this time), etc)
- @emailtypes = EmailType.find(:all)
- @subjects = ExpertiseItem.find(:all, :conditions => "id > 1")
- @phonetypes = PhoneNumberType.find(:all)
- @geolocs = GeographicLocation.find(:all)
- end
- def process_registration # this is the ruby that gets run when they say 'submit'
- # to the above-generated page
- @ham = Ham.new(params[:ham])
- @ham.active = 1
- @ham.ok_to_publish = 1
- @ham.general_comments = @params[:webpage][:general_comments]
- @ham.private_comments = @params[:webpage][:private_comments]
- if (@ham.save)
- hamid = @ham.id
- hamsecrets = HamSecret.new(:ham_id => hamid, ... etc etc etc...)
- hamsecrets.save!
- if (@params[:callsign][:callsign1] != "")
- callsignlist = CallsignList.new(:ham_id =>hamid, :callsign => @params[:callsign][:callsign1], :sequence = 1)
- callsignlist.save!
- end
- ... repeat above code for 3 other possible callsigns....
- emailtypes = EmailType.find(:all)
- priority = 2
- # now, do each of the possible email types, using the emailtypes table to control.
- emailtypes.each do |emailtype|
- emat = "email_addr_#(emailtype.email_name)"
- # the above builds something like "email_addr_primary"
- if (params[:"#(emat)"][:addr] != "")
- emailSecretNo = rand(655360)
- # build string with, e.g. "email_addr_primary_comments"
- comments = "#(emat)"+"_comments"
- # if they have selected the 'publish' checkbox, set pub to 1
- pub = 0
- if ("yes" == params[:#(emat)"][:publish] )
- pub = 1
- end
- # create and save the new email address entry
- email = EmailAddress.new(:ham_id => hamid, :emailaddr => params[:"#(emat)"][:addr], :type_of_addr => emailtype.email_name, :publish => pub, :priority => priority, :comments => params[:"(comments)"][:comments])
- email.save!
- # if this kind of email address is an INTERNET-style email address (we can have non-internet addresses (ham email and ICQ, for example)
- if (1 == emailtype.send_email)
- ... create 'pending verification' db entry and send them email.....
- end
- priority += 1
- end
- # phone numbers and postal addresses are handled in almost exactly the same way, which I won't show here
- # now, get the expertise items they have selected, their comments, and save them in db
- 1.upto(200) do |exno|
- if (params[:"subject_#{exno}"])
- if (params[:"subject_#{exno}"][:expertise])
- if ("yes" == params[:"subject_#{exno}"][:expertise])
- exp = HamExpertiseEntry.new(:comments => params[:"subject_#{exno}"][:comments], :expertise_id => exno, :ham_id => hamid)
- exp.save!
- end
- end
- end
- end
- end
- # whew! That SHOULD be the end of process_registration
- end # end of elmers controller
So, my registration_page.rhtml says (at least in part):
Expand|Select|Wrap|Line Numbers
- <%= start_form_tag(:action => "process_registration") %>
- <H2>Required:</H2>
- <P>
- <STRONG>Name:</STRONG> <%=text_field("ham", "name", "size" => 60) %> (always published)
- <BR>
- <BR>
- <center>Email address(es) - must enter 'primary email' as a minimum. The rest are optional.</center>
- <table BORDER=1>
- <tr>
- <% for email in @emailtypes %>
- <% emailname = email.email_name %>
- <TD> <STRONG> <%= email.type_of_email %>:</STRONG><br>
- <%= text_field("email_addr_#{emailname}", "addr", "size" => 50) %><br>
- <TD><DL><DD>
- <%= check_box("email_addr_#{emailname}", "publish", {}, "yes", "no") %> <STRONG>Publish</STRONG>
- <td> <dd>Comments: <%= text_field("email_addr_#{emailname}_comments", "comments", "size" => 80) %>
- </td></tr>
- <% end %>
- </TABLE>
- Remember - we <b>strongly</b> advise against publishing any of the above (well, ok, other than home page ;-).<p>
- <BR><STRONG>Personal Callsign(s) (Government-issued Amateur or MARS only, please):</STRONG> (always published)<P>
- <%= text_field("callsign", "callsign1", "size" => 20 ) %>
- <%= text_field("callsign", "callsign2", "size" => 20 ) %>
- <%= text_field("callsign", "callsign3", "size" => 20 ) %>
- <%= text_field("callsign", "callsign4", "size" => 20 ) %>
- <P>
- <STRONG>Please enter a password:</STRONG>
- <%= text_field("ham_secrets", "password", "size" => 100) %>
- <br>
- (Note that this password should NOT be the same as anything you use anywhere else,....!)
- <P>
- <H3>Categories/Area(s) of Expertise</H3>
- (Please select all that apply; if any are not listed, select "OTHER"
- and describe them in the text message at the bottom. ....
- <p><b>This information is ALWAYS published!</b> (I mean, good grief, that's the whole point of this exercise, eh? ;-)
- <P>
- <% @numExpertise = 0 %>
- <% for subject in @subjects %>
- <% subjid = "subject_#{subject.id}" %>
- <%= check_box(subjid, "expertise", {}, "yes", "no") %> <b><%= subject.description %></b>
- <br> Additional comments: <%= text_field(subjid, "comments", "size" => 80) %>
- <br> <% @numExpertise = @numExpertise + 1 %>
- <% end %>
- <hr>
- </DL>
- <HR>
- <H2>Optional (Fill out as much or as little as you want):</H2>
- <P>
- <H3>Telephone Numbers (Be sure to include area and country codes):</H3>
- <table BORDER=1>
- <TR>
- <% for phone in @phonetypes %>
- <% phtype = phone.type_of_phone %>
- <TD> <STRONG> <%= phtype %>:</STRONG>
- <br><%= text_field("phone_no_#{phtype}", "phone_number", "size" => 50) %><br>
- <TD><DL><DD>
- <%= check_box("phone_no_#{phtype}", "publish", {}, "1", "0") %> <STRONG>Publish</STRONG>
- <td> <dd>Comments: <%= text_field("phone_no_#{phtype}", "comments", "size" => 80) %>
- </td></tr>
- <% end %>
- </TABLE>
- <P>
- <H3>Location for the purposes of providing a 'geographic location' search capability - .....)</H3>
- <%= collection_select("ham", "geographic_location_id", @geolocs, "id", "full_title") %>
- <P>
- <H3>Location (Either full mailing address or city, state/province, country; Remember that your name has already been given above and should not be retyped here):</H3>
- <P>
- <DL><DD> <%= radio_button("postal", "publish", "none") %>Keep everything Private</dd>
- <DD> <%= radio_button("postal", "publish", "cs" ) %>Publish only City/State/Zip/Country</dd>
- <DD> <%= radio_button("postal", "publish", "all") %>Publish Entire Address information (including Street) (not wise)</dd>
- </DL>
- <STRONG>Street Line 1:</STRONG> <%= text_field("postal", "street1", "size" => 80) %><br>
- <STRONG>Street Line 2:</STRONG> <%= text_field("postal", "street2", "size" => 80) %><br>
- <STRONG>City:</STRONG> <%= text_field("postal", "city", "size" => 50) %><br>
- <STRONG>State:</STRONG> <%= text_field("postal", "state", "size" => 50) %><br>
- <STRONG>Postal Code:</STRONG> <%= text_field("postal", "postalcode", "size" => 10) %><br>
- <STRONG>Country:</STRONG> <%= text_field("postal", "country", "size" => 50) %><br>
- <STRONG>Comments:</STRONG> <%= text_field("postal", "comments", "size" => 50) %><br>
- <br>
- (Note - ...blah blah blah...)
- <p><STRONG>If you wish it to be public, please enter your GPS coordinates</strong>
- <%= text_field("ham","gps_coords", "size" => 50) %>
- <p><STRONG>If you wish it to be public, please enter your Maidenhead Grid Locator</strong>
- <%= text_field("ham","grid_square", "size" => 20) %>
- <P><STRONG>Please provide any additional information, ...
- <P><%= text_area("webpage", "html", "cols" =>70, "rows" => 20 ) %>
- <P><H3>Here is your opportunity to put private information into the database. The following information will NOT be published on the web pages created by this site:</H3>
- <p><%= text_area("webpage", "privatecomments", "cols" =>70, "rows" => 10 ) %>
- <p>
- <P>(Please be patient, and press Send button only once. You will receive acknowledgement when the form is successfully submitted, which may take up to a minute or two.)
- <P><%= submit_tag("Submit") %>
- <%= end_form_tag %>
- <P>
- <HR>
Yow! I'm not sure I want to put up all the rest of the thing - I'll try to make it smaller than the above!
So, when they come back, the end up running 'verify_data_and_confirm', which basically just grabs all the stuff from the database. The rhtml associated with it looks a lot like the above (I think I cut-and-pasted it and added whatever was needed), which I will skip for brevity purposes!
When they click 'confirm' after updating whatever they want to change, we run the following (again, I've removed a bit):
Expand|Select|Wrap|Line Numbers
- def confirm_registration_and_update_info
- #note - this is basically process_registration from elmers_controller
- @haminfo = get_haminfo
- @hamsecrets = @haminfo.get_secrets
- hamid = @haminfo.get_hamid
- emailaddr = @haminfo.get_email
- # update ham record
- @ham = Ham.find(hamid)
- @ham.active = 1
- @ham.ok_to_publish = 1
- @ham.general_comments = params[:webpage][:html]
- @ham.private_comments = params[:webpage][:privatecomments]
- if (@ham.save)
- # update callsigns
- callsignlist = CallsignList.find_by_hamid(hamid)
- 0.upto(3) do |i|
- csname = "callsign#{i+1}"
- if (@params[:callsign][csname] != "")
- callsign = @params[:callsign][:callsign1]
- if (callsignlist[i])
- callsignlist[i].callsign = @params[:callsign][:callsign1]
- else
- callsignlist[i] = CallsignList.new(:ham_id => hamid, :callsign => callsign, :sequence=>i+1)
- end
- # note that this date is actually wrong - we should look this up in the
- # callsign database, or ask them...
- callsignlist[i].save!
- else
- # here if empty in form, make sure nothing in database!
- if (callsignlist[i])
- # oops, there is! Nuke it!
- callsignlist[i].delete!
- end
- end
- end
- # process email - but only the one we sent email to!
- email = EmailAddress.find_by_hamid_and_addr(hamid, emailaddr)
- email.date_of_next_verify = 3.months.from_now
- email.date_last_verified = Time.now
- email.save!
- # ok, now its possible for them to CHANGE the email info, so we have to handle
- # that here also. (Which means they could end up deleting the one they just
- # verified, which sounds dumb but is possible)
- emailtypes = EmailType.find(:all)
- emailtypes.each do |emailtype|
- emat = "email_addr_#{emailtype.email_name}"
- if (params[:"#{emat}"][:addr] != "")
- # something in the field - find it in the db and update it.
- email = EmailAddress.find_by_hamid_and_type(hamid, emailtype.email_name)
- if (email)
- if (email.date_of_next_verify < Date.today)
- email.date_of_next_verify = Time.now
- end
- email.publish = params[:"#{emat}"][:publish]
- email.comments = params[:"#{emat}"][:comments]
- else
- puts "Email address writing?"
- puts params[:"#{emat}"][:comments]
- email = EmailAddress.new(:ham_id => hamid, :emailaddr => params[:"#{emat}"][:addr], :type_of_addr => emailtype.email_name, :publish => params[:"#{emat}"][:publish], :priority => priority, :date_of_next_verify => Time.now, :active => 1, :comments => params[:"#{emat}"][:comments])
- end
- email.save!
- end
- # phone_numbers, postal_addresses, pending_verifications
- ... lots of code just like the above.....
- else
- puts "blew it"
- redirect_to(:action => "error" )
- end
- end
The problems I'm having are with getting the info back into the database - the above doesn't seem to do the job. But I'll bet my approach is wrong, so:
Any suggestions for improvement, any info on 'build' and how it might help, whatever (shoot, I might even accept flames, at this point!).
In case it matters, I'm using RoR on Linux (Debian).