OLM On Rails

Archive for the ‘OLM on Rails’ Category

How to Install Seed_fu

with one comment

Today, we got a pretty awesome cursory code review from Mike Gunderloy.

One thing he suggested, was that we switch from running a sloppy Ruby script to seed our database with initial data, to something like seed_fu.

So, how do we install Seed_fu?  It took me a while to figure it out, actually, since I’d never installed a Rails plugin from GitHub before.

Anyhow, here’s how you do it:

From your application root:


script/plugin install git://github.com/mbleigh/seed-fu.git

Easy.

Written by Mike Conley

June 11, 2009 at 5:07 pm

Posted in OLM on Rails

Tagged with , ,

Single Table Inheritance, and Testing Fixtures

with 2 comments

I’m still trying to get our unit tests up and running, and I ran into a snag a few hours ago.

I tried to run a single unit test, on the Admin model (which is a subclass of type User). I kept getting this error message:


ActiveRecord::StatementInvalid: PGError: ERROR: relation "students" does not exist
: DELETE FROM "students"

Hrmph. We don’t have a “students” table, so of course that won’t work. We have Students (which, like Admins, subclass from User), but we certainly don’t have a “students” table.

So how come it’s trying to access that table?

It turns out that Rails test fixtures don’t deal with Single Table Inheritance. Instead, a Rails fixture should be a YAML file that populates a particular table with its contents.

And it turns out I had a fixture called “students.yml” in my test/fixtures folder. So, Rails tried to connect to the “students” table, insert some records, and clear them out afterwards.

The solution was to remove the students.yml, tas.yml, and admins.yml files, and simply have a users.yml file. The same goes for student_memberships.yml and ta_memberships.yml. Replace those with memberships.yml. Boom. Tests run.

Now it’s just a matter of getting some good content in the fixtures, and getting some solid tests written…

Written by Mike Conley

May 26, 2009 at 1:54 pm

A Sneaky Change in Rails 2.3.2 Temporarily Broke Our Tests

leave a comment »

Today, I’ve decided to revamp the test suites for OLM. I won’t lie – we’ve been a little lazy in terms of testing. We have lots of meetings where we say “our testing is going to be solid”, but then nothing really happens.

I’m sure we’re not alone in this regard.

Anyhow, I decided to dust off our tests and give them a run. Here’s what I got:


./test/unit/../test_helper.rb:22: undefined method `use_transactional_fixtures=' for Test::Unit::TestCase:Class (NoMethodError)

Hrmph. What’s going on? After a little Googling, and it turns out that in Rails 2.3.2, “Test::Unit::TestCase changed to ActiveSupport::TestCase”.

So, I had to go into ./test/test_helper.rb, and change the class to ActiveSupport::TestCase. No big deal really, but it was confusing at first.

It’s a real blow to your self-esteem when your testing framework doesn’t even run.

Anyhow, it’s fixed. Now to write some good tests…

FOLLOW UP: You also need to change Test::Unit::TestCase to ActiveSupport::TestCase in your tests.

Written by Mike Conley

May 26, 2009 at 11:02 am

Using Subversion for File Storage

with one comment

Over the past few days, the team has been talking about how we eventually want to use Subversion as the file storage backend for the work that students submit through OLM.

Here’s what we ended up deciding:  we’re going to build a generic Repository class, and have three different implementations:  File System, Memory, and Subversion.

Each Repository will return Revision objects, and each revision will have File’s and (eventually) Directories.

So, how would this thing work?  Say I want to get all files from the root directory of a particular repository for the latest revision.  Here’s how we’d more or less want to pull it off with our classes in Ruby:

repo = Repository::RepositoryFactory("svn").open('/some/subversion/repository')
revision = repo.get_latest_revision
# I want all files for the root directory /.
files = revision.all_files('/')

This would provide a collection of File objects representing the files in the latest revision of the repository. If we wanted to download/display that file, we could use the following:

repo = Repository::RepositoryFactory("svn").open('/some/subversion/repository')
revision = repo.get_latest_revision
# Say I wanted the file Test.java from the root directory...
wanted_file = revision.all_files('/')["Test.java"]
file_output = repo.download(wanted_file)
#...insert code here to send file_output to browser

As it stands, that’s more or less how we’re designing the Repository classes. Big thanks to the Basie team for their suggestions on the design!

Anyhow, I’ll try to keep you all posted on our progress for implementing the Repositories.

Written by Mike Conley

May 25, 2009 at 12:47 am

Groups

leave a comment »

The time has come to completely incorporate groups into checkmark. With various discussions with Karen and the crew here are the changes I am going to make to the schema and where I will be heading in the future. But first …

Where We Are

Currently, checkmark supports the simple case where an assignment has groups of a specified size. These groups can be made by the instructor manually or through a CSV upload (which is incompletely implemented – a little bit more on this later) and also by the students themselves. Basic group creation currently works with the current schema: (Which does not include the rubric or annotation stuff) which is explained by Geofrey here.

But, from a need for error checking groups on creation (mainly through the CSV upload) as well how classes like CSC301 run, I decided that we must incorporate a group name (auto-generated or provided) to distinguish between groups. From this we decided to do a small (and hopefully final) revamp on the schema to support the following 3 cases:

  1. Situations when students choose to work in groups but can choose to work alone on an assignment;
  2. Situations when groups do not persist across assignments;
  3. and situations when groups persist across assignments (But sometimes the members of a
    group might change)

To support these cases, there are a number of front end and back end changes that need to be made. The front end is fairly straight forward and so I am going to focus on the back end for this post.

Back end Changes

We decided to support these cases with very few changes to the schema. We are going to remove the assignments_groups table and maintain the groups-assignments associations solely in the memberships table with the addition of an assignment_id field.  So, the schema now looks like this (Sorry for the change of format from the above picture):

The above cases are now supported as follows:

  1. Students who choose to work alone will have one entry for the specific assignment in the memberships table, this is the same as the current situation with the addition of the assignment_id field.
  2. If groups do not persist across assignments, again, this is currently supported.
  3. If groups do persist, then each student will have a distinct entry in the memberships table for each assignment. This way, memberships can change across assignments by simply changing that students group_id for that assignment and all previous assignment memberships will be maintained. Every time an assignment with persisting groups is created, all the groups from the previous assignment will be copied over by duplicating the entries for the previous assignment and updating the assignment_id.
  4. But this situation is a bit more complicated. Most likely, instructors are going to set up all assignments at the beginning of the term and so the memberships entries for those assignments may already exist before the assignment is released. So, to ensure that each assignment has the most up-to-date group information, we will cascade any group modification actions that happen on any previous assignments to the future (and maybe currently open — by an extension) assignments. This is an expensive operation, but it is not likely to happen often.

It seems that few changes to the schema actually supported what we wanted, without sacrificing much performance (hopefully I am right in claiming this).

Where We Are Going

Now I am going to outline where I plan to go in the future.

Schema Changes

I am going to apply all the schema migrations within the next couple of days.

The relationships within the models do not actually change, since assignments_groups was simply a join table and does not hold any extra information on the relationship.

Changes to the Models and Controllers

  • I am sure there will be some refactoring that will need to be done, so I will revisit the old code and make sure everything that worked before still works.
  • I need to fully incorporate the group names, allowing the instructor to choose when the names will be auto-generated or provided by the user and when the group names should be viewed as well as who can view them.
  • Complete the CSV upload, with proper error checking and group creation.
  • Implement the functionality (front end and back end) described above for persisting groups.
  • … etc. I am sure there is more.

But before I make these changes, I am going to try something I have never tried before and which should be interesting…

Unit Testing, Unit Testing, Unit Testing and Test Driven Development

I am going to try my hand at test driven development and write all the unit tests required to (hopefully) adequately test all of the group functionality. Not only that, I am going to keep a journal of sorts and blog about it! So stay tuned for exciting unit testing goodness.

Written by Catherine

February 12, 2009 at 3:05 am

Posted in OLM on Rails

Testing Rails with Authentication

leave a comment »

I’ve been putting off doing functional testing with rails for a while now because of the snag that we couldn’t figure out how to test with authentication.  Finally I’ve decided to solve this once and for all and get to the root of the problem. First approach we tried was to explicitly log in using a post request to the login page in the test setup. Of course, it didn’t work as each request I believe is independent of the subsequent requests, at least during functional testing.

The next approach we tried was to go down one level and expose the method that sets the current user for the session, and then call that as a controller function.  This made me a bit uneasy because we were making the current user setter public, which could probably mean that it would be exposed as an action that can be exploited.  Either way, this approach still didn’t work and was still giving us a 302 redirected status.

We decided to go one more level down and tried then to explicitly set the session hash, which is how we keep track of the current user.  One way that I came across was to set the session variable in a request object, that is

def setup
@controller = AssignmentsController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@admin = users(:admin)
@request.session['uid'] = @admin.id # login before testing
end

or some reason, this didn’t work out either.  Finally, it turns out that session variables can be set on every (get/post/put/delete) request made as an optional parameter. Now, each of the request type methods are wrapped with an extra user parameter to make that request on behalf of that user. For example, we now have a get_as method defined as:

# Performs GET request as the supplied user for authentication
def get_as(user, action, params=nil, flash=nil)
session_vars = { 'uid' => user.id, 'timeout' => 3.days.from_now }
get(action, params, session_vars, flash)
end

Written by Geofrey

November 7, 2008 at 2:38 pm

Posted in OLM on Rails

Tagged with , ,

Rebooting the Schema (part 2)

with 2 comments

Last time, I talked about the old model schema, and the problems it had that lead us to refactor the code. After refactoring, this is what the database looks like:

Association

The relationships are now more concrete with the addition of memberships and assignments_groups tables. The assignments_groups is a Rails convention of declaring a many-to-many relationship between two objects by use of the join table. Thus, an assignment can have many groups, and groups can also have many assignments if a group persists throughout the course. A caveat though is to make sure that the join table is in alphabetical order, meaning it must be assignments_groups and not groups_assignments. That’s just the “convention-over-configuration” mantra of Rails at work.

Once we have the database schema set, we can then just go in and declare those relationships in the ActiveRecord classes respectively:


class Group < ActiveRecord::Base
has_and belongs_to_many :assignments
end
class Assignment < ActiveRecord::Base
has_and belongs_to_many :groups
end

However it is a different case if the join table contains extra information, which is our case with the memberships table. Here, not only does it reference the user and the group together, but it also contains extra information such as status of the member. Thus, we need to have a Membership class representing a member, and use has-many-through relationship. which sort of explicitly states that the association between a User and a Group uses memberships as its link. Here we declare the relationship as follows:

class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, :through => :memberships
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
end

Abstraction

We’ve also separated the old submissions table to a submissions and submission_files tables. The new submissions table doesn’t seem to have much information and seems to be a waste of space. However, having this table allows us to delegate submission functions to a Submissions class rather than mixing them directly with either the User or Group classes. All we have to do now is just ask a User or Group for its Submission instance, and handle all queries related to submitted files from it.

Since we also want to avoid checking to see if it is a User or a Group submission everytime, we’ve abstracted the Submissions class and added separate classes for each type, UserSubmission and GroupSubmission – classes that are linked to Users and Groups respectively. Since instead of declaring the relationship with Submissions, we have:

class User < ActiveRecord::Base
has_many :submissions, :classname => UserSubmission
end
class Group < ActiveRecord::Base
has_many :submissions, :classname => GroupSubmission
end

class UserSubmission < Submission
has_many :users
end
class Group
Submission < Submission
has_many :groups
end

This allows us to call either user.submissions or group.submissions and return with an instance of the appropriate Submission subclass type.

Final Results

The refactored models with the appropriate associations gave way to a much cleaner code in the end. With the schema set in place, I’ve revisited the old code and heeded the advice in the first post, stuffing all the business logic in the appropriate models and leaving workflow control to the controllers. The result turned several functions with 200+ lines into a single function with less than 50 lines. I was also able to create more thorough unit testing while code was being written. Here, we can see that we’ve improved our stats quite a bit:

$ for f in app/controllers app/models app/helpers; do echo $f
`find $f -name "*.rb" |xargs wc -l |tail -n1`; done
app/controllers 563
app/models 591
app/helpers 60

In retrospect, I think the refactoring decisions suits us very well with what we have in mind and gives us room for modifications at the same time…until we actually start porting OLM. Stay tuned.

Written by Geofrey

October 14, 2008 at 6:32 pm