Last Revised: July 11, 2010 = MushroomObserver The following is an overview of the code for the Mushroom Observer website for prospective developers. See http://mushroomobserver.org/observer/intro for an introduction to the website itself. This version of the README is not completely up to date, but I've added some notes where I know there are inconsistencies. == Installation Everything you need to get a local copy of MO running on your computer is kept in a subversion repository at collectivesource.com. It is world-readable, so just checkout a copy, and give it a shot. (The latest stable release is in "trunk". See link below.) I'm pretty sure you must be running a Unix-type O/S, Linux and MacOSX have been tested, but I know Rails and Mysql and everything else MO needs is available in Windows, so feel free to be the first to try it out! Nathan has written up excellent notes describing how he's installed it on Ubuntu 6.10, Edgy Eft (see README_INSTALL). You'll at very least need to install ruby (1.8.7: the code hasn't been tested with 1.9), rails (2.1.1: we really need to upgrade to 2.3, but it's not backwards compatible) and mysql (5.1.47 is known to work, but later ones probably will as well). Fortunately these are all very popular so it's easy to find pre-built binaries for most operating systems. I was hopeless at system administration when I started, and I had no trouble getting it up and running in an hour or two. For more information: MO Source:: http://svn.collectivesource.com/mushroom_sightings/ Nathan's Notes:: link:files/README_INSTALL.html Subversion Manual:: http://svnbook.red-bean.com/ == Ruby on Rails MO is written using Ruby on Rails, or simply Rails. This is just a set of three heavily interrelated Ruby packages: ActionController, ActionView, and ActiveRecord. The basic architecture is model, view, controller -- that is, the webserver receives a request, passes it to the appropriate controller, which decides what actions to take and gathers whatever data is needed (from the "models"), then renders the result via HTML templates (the "views"). Documentation is automatically generated using rdoc. Basically, it just grabs comments before classes, modules, and methods and creates a little webpage. Just run rake rdoc and point your browser at file://#{RAILS_ROOT}/rdoc/index.html. Rails can be extended by installing plugins. We've only used a few, but it's worth mentioning. Just google "rails plugins" (or browse a directory, see below), find a plugin you like, the run: script/plugin install http://svn.domain.com/path/to/plugin Rails applications run in one of three different modes: development, production and test. Each has its own entirely separate database (named appropriately +observer_development+, +observer_production+ and +observer_test+). Otherwise it doesn't *really* matter which you use, but there are subtle differences: development Automatically reloads any code that has been changed. (Only modules that have been required using +require_dependency+.) Doesn't cache data the same way as the production server does. production Need to restart the server whenever you change any code. Some things are cached differently than in development, so if you see different behavior on the production server, start here: you might just need to reload an object. test Used by unit tests. All changes are thrown away between each test. And probably a host of other technical things. For more information (note that most of these no longer discuss Rails 2.1, but you should be able to find the docs for this version by digging a bit): Ruby Documentation:: http://www.ruby-doc.org/core/ Ruby Quick Ref:: http://www.zenspider.com/Languages/Ruby/QuickRef.html Rails Documentation:: http://api.rubyonrails.org/ Rdoc Documentation:: http://rdoc.sourceforge.net/doc/index.html Rails Plugin Wiki:: http://wiki.rubyonrails.org/rails/pages/Plugins Rails Plugin Directory:: http://agilewebdevelopment.com/plugins MVC Architecture:: http://en.wikipedia.org/wiki/Model-view-controller == Database MO uses MySQL. The current schema is db/schema.rb. All modifications of the structure, such as adding tables or changing existing columns, are handled using the handy migrations in db/migrate. rake db:migrate # Create or update database. rake db:migrate VERSION=0nn # Rollback to pervious version. Access is all done via subclasses of ActiveRecord::Base. Look for observations in the class Observation, user/account settings in User, taxonomy in Name and Synonym, and so on. These are all found in app/models. Here are the major ones [Note: As of April 2010, we've broken up Names and Locations to add NameDescriptions and LocationDescriptions, so the following is not completely accurate, but you'll get the idea]: User:: Users: name, email, password, prefs, etc. Observation:: Observations: where, when, what, notes. Name:: Scientific name bundled with notes, citation, etc. Location:: Locations: lat/long/elev, notes, etc. Image:: Images: mostly mushrooms, but also mugshots or anything else. Comment:: Comments: attached to an Observation now, later will attach to other things, too. SpeciesList:: Set of Observations (*_not_* Names). These are used in naming and voting on observations: Naming:: Proposed Name for a given Observation. NamingReason:: Reasons for proposing a name: appearance, literature, etc. Vote:: Votes on a given Naming for a given Observation. Emails use several: QueuedEmail:: I haven't looked at this code yet. QueuedEmailInteger:: ?? QueuedEmailNote:: ?? QueuedEmailString:: ?? CommentEmail:: Subclass of QueuedEmail, for comments. FeatureEmail:: Subclass of QueuedEmail, for site-wide emails. And a few other random database classes: Synonym:: Group of synonymized Name's. PastName:: Old versions of Name's. (deprecated: using the acts_as_version plugin now) PastLocation:: Old versions of Location's. (deprecated: using the acts_as_version plugin now) License:: Types of licenses available for Image's to use. RssLog:: Used to report changes via RSS feed. The following non-database-related classes are also found in app/models: AccountMailer:: Used to actually send an email NameParse:: Used by Name to parse scientific names. NameSorter:: Used by Name to... I don't remember. SearchState:: Used to keep track of search queries. (Note: Jason totally rewrote this stuff.) SequenceState:: Used to keep track of prev/next position. (Note: Jason totally rewrote this stuff.) SiteData:: Used to calculate number of objects people have created. For those who prefer a graphical representation: (Arrow denotes "belongs to".) (Dotted line represents proposal to let users comment on objects other than observations.) User ^ |--- Image --> License < - - - - - - | | | |--- Location < - - - - - - | | ^ | '-- PastLocations | | | | | Synonym -------------. | ^ | | |--- Names --------------| < - - - - - - | ^ | | | '-- PastNames |--> RssLog | | | | | |--- SpeciesList --------| < - - - - - - | | ^ | |--- Observations -------' <-------------| | ^ | |----- Namings --> Name Comment --> User | ^ | |-- NamingReasons |------ Votes | | '--- QueuedEmail ^ |-- QueuedEmailIntegers |-- QueuedEmailNotes `-- QueuedEmailStrings For more information: ActiveRecord Docs:: http://api.rubyonrails.org/files/vendor/rails/activerecord/README.html Migration Docs:: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html Migration Cheatsheet:: http://garrettsnider.backpackit.com/pub/367902 == Controllers There are only a few controllers (in app/controllers), roughly one for each of the major data types: ObserverController:: Deals with observations, namings, votes, and everything else. AccountController:: Deals with user account stuff. NameController:: Deals with names. LocationController:: Deals with locations. ImageController:: Deals with images. CommentController:: Deals with comments. SpeciesListController:: Deals with species lists. These controllers are just subclasses of ActionController. Actions are just instance methods. These correspond directly to the URL of the request: http://mushroomobserver.org/controller/action Note that the default controller is "observer", the default action is "show", and the "id" parameter is shortened to a bare integer: http://mo.org/42 Show observation #42. http://mo.org/image/123 Show image #123. http://mo.org/observer/show_user/3 Show summary for user #3. Controller methods -- that is, actions -- have access to quite a number of helpers and data. Here are just a few, see ActionController for more: session['user']:: Session: hash of data that follows the user around from request to request. cookies[:mo_user]:: It is trivial to read or write cookies on the user's browser. request.method:: You have access to the full CGI request object. flash[:notice]:: Temporary storage: this hash persists only until the next request. @user:: If user is logged in this will be their User object. @js:: True if javascript is enabled on the user's browser. @ua[:ie7]:: Quick way to tell what browser the user is running. Controller methods are very deeply interconnected with views. I've never quite understood the underlying mechanics, but views have access to all the controller's instance variables (e.g. @user and any others you set in the action). This is how actions pass information to the view for rendering as HTML. By default a template of the same name as the action is rendered (app/views/#{controller}/#{action}.rhtml), however this can be overridden in a number of ways. This can get quite complicated, and you must be careful not to accidentally attempt to render two templates. Example: class ExampleController << ActionController # This causes autologin() to be run before every action. before_filter :autologin def my_action case params[:mode] # Mode not set: Render "my_action.rhtml" implicitly. when "" @choices = %w(show index) # Show mode: Lookup object and render "show.rhtml". # Note that it does NOT call the show() method!! when "show" @object = Model.find(params[:id]) render :action => "show" # Index mode: Get list of all objects and render "index.rhtml". # Note that it does NOT call the index() method!! when "index" @objects = Model.find(:all) render :action => "index" # Error: Redirect browser to another action. # Note, no templates will be rendered at all. else redirect_to :action => "error" end end end For more information: ActionPack Docs:: http://api.rubyonrails.org/files/vendor/rails/actionpack/README.html ActionController Docs:: http://api.rubyonrails.org/classes/ActionController/Base.html == Views Views, unlike models and controllers, are not classes, or strictly speaking even ruby code at all. They are templates in which you may (and frequently will) embed Ruby code. All the views are found in: app/views/#{controller}/#{action}.rhtml Some templates that are shared between controllers can be stored in: app/views/shared And "partial" views (see render :partial) are prefixed with an underscore: app/views/#{controller}/_#{partial}.rhtml Syntax is all HTML with two new mark-ups: <% ruby code that is evaluated but doesn't affect output %> <%= ruby expression whose (string) value is inserted into the output %> An example makes it plentifully clear: