College Park Crime Reports
Basic Setup: A Quick Rails Recap
The following section is intended to be pretty familiar to experienced Rails users, but I include it as a record of what I did, and hopefully to be helpful to those new to Rails. I have taken a similar, though not identical approach, to the one outlined in Bruce Tate's and Curt Hibbs' book Ruby on Rails: Up and Running. I would recommend this book to anyone who likes to learn by doing and wants to learn about Rails.
Files, Models, and Data
First it's time to create the project's directory structure, the database, users and passwords, and a model of our first class. The first thing I do is create three new MySQL databases: one for development, one for testing, and one for production. This is a Rails standard. I use an online configuration tool at my web host to do this. I also create a new user and password for access to the databases, and I note all this information--DB names, user names, and passwords--for use in Rails configuration.
Now I open a UNIX shell session and change directories into my projects folder.
$ rails cpcr $ cd cpcr $ vi config/database.yml $ chmod 600 config/database.yml $ ruby script/generate model CrimeReport
The rails command creates directories and files to hold all the information necessary for the web application. I'm going to be most interested in the app, config, db, and test folders. I now change directories into the new cpcr folder.
Next, I create the database.yml file. As per Rails standards, I'm describing three databases, labeled development, test, and production. Here is a representative snippet:
development: adapter: mysql database: cpcr_development username: <some user> password: <somepassword> host: <usually localhost> test: ... etc ...This file is in the YAML format. Depending on your mood, YAML stands for "Yet Another Markup Language", or the Stallman-esque "YAML Ain't Markup Language". In these examples, I'm using the vi editor, in part because I'm so hardcore, but also because it's available on almost all UNIX systems and is a great way to make quick edits to files from the shell. It takes a little getting used to though. One could use another text editor and/or FTP to edit files.
The config file I just made is in a web-accessible directory. I immediately need to prevent others from reading it, since it contains MySQL usernames and passwords. So I chmod it so that I can read it (and the Rails process is happily running as my user) but others, including Apache, cannot.
Now it's time to generate the model for our first class, CrimeReport. I run the generate script, passing it the 'model' and 'CrimeReport' arguments to tell it I want a model stub for a CrimeReport class. Like the rails command earlier, this generates directories and files in my project folder.
Now I'm going to instantiate the persistence layer for my application, which is just to say I'm going to specify a table for the CrimeReports class. In the old days, this step marked the beginning of a commitment to keep the database, the application code, and the glue code that handles CRUD actions in synch. Rails doesn't relieve you entirely of this responsibility, but it does a lot of the work of mapping your objects with your DB records with just a little information.
$ vi db/migrate/001__create_crime_reports.rb
When I used the generate script, it created a file in the db/migrate folder that specifies instructions for setting up a table for my new CrimeReports model. The file is just a stub. I go ahead and fill out the up method like so:
class CreateCrimeReports < ActiveRecord::Migration def self.up create_table :crime_reports do |t| t.column 'address', :string t.column 'date', :string # for now t.column 'description', :string t.column 'case_number', :string t.column 'lat', :float t.column 'long', :float end end ... end
This looks a lot like DDL, like a Create Table command. It contains the same information, and when you use the rake command (coming right up), the same thing happens, but the information is expressed in a Ruby idiom, and its execution is now brought under the control of the Rails system. The file is in a folder called migrate because migration is Rails approach to managing the database. You can change the definition of your database by altering these migration files. The down method describes how to clean up the existing structure, and the up method describes how to set up the new structure.
In that light, let's look at the data I'm describing. Address, date, description, all seem pretty obvious. I'm adding case_number so that I, and other users, may find other information the police are maintaining under those numbers. The lat and long columns, for latitude and longitude, are here so I can feed them to Google maps. You may have noticed there's no id column to serve as a primary key. Rails handles that for me, by convention.
Hey, dig that do |t|. What is t? It's a reference to the table you just created, and all the t.column business that follows is instructing the caller of the method to add these columns to the new table. This isn't a configuration language, and it certainly isn't XML. It's just Ruby.
I mentioned you needed to use a command to put the migration file into action. Here it is:
$ rake migrate
The rake program is, briefly, a Rails analog to make, or ant from the Java world. It allows the execution of scripts to manage your application. In this case, the 'migrate' argument passed to rake executes the migration scripts. It creates the database table I've described in the up method.
Testing
Rails takes automated testing seriously, which is one reason why so many proponents of agile methodologies are interested in Rails. At this point in my application development effort, I can already run unit tests on my models, in this case, the CrimeReport model, by using rake:
$ rake test:units 1 tests, 1 assertions, 0 failures, 0 error
What did I just test? Well, when I used the generate script, a set of test files were generated for me, in the test folder. Let's take a look at the previously-generated unit test I just ran:
$ vi test/unit/crime_report_test.rb
class CrimeReportTest < Test::Unit::TestCase ... # Replace this with your real tests. def test_truth assert true end ...
Again, it's just a stub. By calling assert true, you're testing nothing. I need to put some real code in here. I'll start simple.
def test_crud cr = CrimeReport.new assert cr.save end
Now I'm testing that I can create a new CrimeReport object and then call a save method on that object. Does the test pass? Yep. Did I ever code up a CrimeReport class? Nope. When I generated the CrimeReport model, Rails did produced a stub class: app/models/crime_report.rb. Here it is.
class CrimeReport < ActiveRecord::Base end
Obviously, the save method is inherited from the parent class ActiveRecord::Base, but how does the parent class's code know about the structure I have in mind for CrimeReport? It gets it on the fly, from the structure of the database. Rails makes heavy use of Ruby's splendid tools for metaprogramming. It would take too long to go into it here, but because I defined those columns in the migration script and executed that script, Rails can now dynamically construct objects with the right attributes and setter and getter methods.
One of the things I like about testing first is that you start trying to use your classes before you code them. The basic test I've coded creates a new CrimeReport object and saves it to the database. What have I specified about the object? Nothing. Do I like that? Should someone be able to create a persistent record of a crime report without data? No. Some fields should be mandatory. A description, for one. So, actually, my test should look like this.
def test_crud cr = CrimeReport.new assert !cr.save end
This fails. I have explicitly introduced the requirement that a crime report without information can not be saved by the system, and now I need to implement it.
I will edit the model class stub. I can express the requirement of a description with the validates_presence_of instruction:
$ vi app/models/crime_report.rb
class CrimeReport < ActiveRecord::Base validates_presence_of :description end
But this leads to more detailed thinking. What else is essential? Well, if I want every report to appear on a Google Map, I need coordinates for every one: a latitude and longitude. I adjust my test to isolate and express these specific requirements.
def test_crud cr = CrimeReport.new # Test long requirement cr.description = 'test description' cr.lat = 38.1250 assert !cr.save, "Save should not have been successful without required field 'long'" cr = CrimeReport.new # Test lat requirement cr.description = 'test description' cr.long = -74.381250 assert !cr.save, "Save should not have been successful without required field 'lat'" cr = CrimeReport.new # Test description requirement cr.lat = 38.1250 cr.long = -74.381250 assert !cr.save, "Save should not have been successful without required field 'description'" cr = CrimeReport.new # Test minimal requirements cr.description = 'test description' cr.lat = 38.1250 cr.long = -74.381250 assert cr.save, "Save should have been successful" end
Of course, this doesn't work yet, so I will edit the model class accordingly.
class CrimeReport < ActiveRecord::Base validates_presence_of :description validates_presence_of :long validates_presence_of :lat end
$ rake test:units 1 tests, 4 assertions, 0 failures, 0 errors
Let's test things just a little more thoroughly, to reassure ourselves everything works, by adding this code to the end of the test_crud method:
assert_not_nil cr2 = CrimeReport.find(cr.id) assert_equal cr, cr2 cr2.description = 'foo description' assert cr2.save cr3 = CrimeReport.find(cr2.id) assert_equal 'foo description', cr3.description assert cr3.destroy assert_equal 2, CrimeReport.count
$ rake test:units 1 tests, 10 assertions, 0 failures, 0 errors