RESTful Rails with Associations explored

Posted by blackrat on December 16, 2006

I’ve recently switched to EdgeRails for Rails 1.2 RC1, and decided to try out the resource_scaffold to create RESTful classes. There’s not that much documentation on this so far, so I thought I’d combine using these with creating habtm associations. What follows is a walkthrough of the initial creation of a has_and_belongs_to_many association and the changes required to get it to do what I want within the scaffold_resource RESTful framework.

First of all, some MVC creation. I like the fact that tests are now generated by the scaffold_resource, but be warned that if you modify your migrations, you also need to change the test/fixtures/*.yml files to reflect the changes. Otherwise, your unit tests will start to error. We’re going to be using the extension to scaffold_resource and generate a string “name” for both tables, which will also generate the fixture unit test for that column.

rails –database=sqlite3 blog
cd blog
script/generate scaffold_resource user name:string
script/generate scaffold_resource tag name:string
script/generate migration create_tags_users
Change db/migration/003_create_tags_users.rb to the following:
class CreateTagsUsers < ActiveRecord::Migration
    def self.up
        create_table :tags_users, :id=>false do |t|
            t.column :tag_id, :integer
            t.column :user_id, :integer
        end
    end
    def self.down
        drop_table :tags_users
    end
end

Don’t forget the :id=>false (unless you know what you are doing, that is).
Next, modify the models to point to each other. app/models/tag.rb and app/models/user.rb become:

class Tag < ActiveRecord::Base
    has_and_belongs_to_many :users
end

and

class User < ActiveRecord::Base
    has_and_belongs_to_many :tags
end

At this point, I like to run the migrate and the tests which were created by the scaffold_resource.

rake db:migrate test
Now it’s time for a little association plumbing. I like to have a view to allow me to select the tags from the full list (we’ll ignore adding more from this view for this walkthrough) when in the user display, so the first port of call is the users_controller. I’d also like to be able to see from the index display which ones are selected, so I’ll be modifying the new, edit, create and update methods and all of the views for user.

Ok. For app/views/users the following become the new views

index.rhtml

<h1>Listing users</h1>
<table>
    <tr>
          <th>Name</th>
          <th>Tags</th>
    </tr>
    <% for user in @users %>
          <tr>
               <td><%=h user.name %></td>
               <td>
                    <% for tag in user.tags %>
                         <%= tag.name %><br />
                    <% end %>
               </td>
               <td><%= link_to 'Show', user_path(user) %></td>
               <td><%= link_to 'Edit', edit_user_path(user) %></td>
               <td><%= link_to 'Destroy', user_path(user), :confirm => 'Are you sure?', :method => :delete %></td>
        </tr>
    <% end %>
</table>
<br />
<%= link_to 'New user', new_user_path %>

new.rhtml

<h1>New user</h1>
<%= error_messages_for :user %>
<% form_for(:user, :url => users_path) do |f| %>
    <p>
          <b>Name</b><br />
          <%= f.text_field :name %>
     </p>
    <p>
        <b>Tags</b><br />
          <% for tag in @tags %>
               <br />
            <input type="checkbox"
                            id="<%=tag.id%>"
              name="tag_ids[]"
                            value="<%=tag.id%>"
                        >
            <%=tag.name%>
        <% end %>
    </p>
     <p>
          <%= submit_tag "Create" %>
     </p>
<% end %>
<%= link_to 'Back', users_path %>

show.rhtml

<p>
    <b>Name:</b>
     <%=h @user.name %>
</p>
<p>
    <b>Tags:</b>
     <% for tag in @user.tags %>
          <%= tag.name %><br />
     <% end %>
</p>
<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

and finally, edit.rhtml

<h1>Editing user</h1>
<%= error_messages_for :user %>
<% form_for(:user, :url => user_path(@user), :html => { :method => :put }) do |f| %>
    <p>
          <b>Name</b><br />
          <%= f.text_field :name %>
     </p>
     <p>
          <b>Tags</b><br />
          <% for tag in @tags %>
            <br />
               <input type="checkbox"
                   id="<%=tag.id%>"
                   name="tag_ids[]"
                   value="<%=tag.id%>"
                   <%if @user.tags.include? tag%>checked="checked"<%end%>
               >
               <%=tag.name%>
          <% end %>
     </p>
     <p>
          <%= submit_tag "Update" %>
     </p>
<% end %>
<%= link_to 'Show', user_path(@user) %> |
<%= link_to 'Back', users_path %>

So not that much different to the CRUD scaffolding. I’m going to experiment more with this, and also with the through :class approach of handling associations, which I believe looks to have some strong advantages (not the least of which being that it gives you a join class rather than a join table).

A Weekend on Rails - Day Two

Posted by blackrat on November 19, 2006

Last time, I built a standard scaffolded Rails application with a modified database, controller (admin) controlling a model named programme. The next step is to add a view and import mechanism. The standard scaffold mechanism uses a list.rhtml as the entry point, so we will create one of these to override the scaffold, and display a list of the current programmes, along with the import button.

<h1>Listing programmes</h1>
  <table>
    <tr>
      <% for column in Programme.content_columns %>
        <th><%= column.human_name %></th>
      <% end %>
    </tr>
    <% for programme in @programmes %>
      <tr>
        <% for column in Programme.content_columns %>
          <td><%=h programme.send(column.name) %></td>
        <% end %>
      </tr>
    <% end %>
  </table>
  <%= link_to ‘Previous page’, { :page => @programme_pages.current.previous } if @programme_pages.current.previous %>
  <%= link_to ‘Next page’, { :page => @programme_pages.current.next } if @programme_pages.current.next %><br />
  <%= link_to ‘Import XML’, :action => ‘load_xml’ %>

and add a new “load_xml.rhtml” template

<h1>Load XML<h1>
<%= form_tag({ :action=> ‘import_xml’}, {multipart => true }) %>
<%= file_field :document, :file %>
<%= submit_tag ‘Import’ %>
<%= end_form_tag ‘Import’ %>
<%= link_to ‘Back’, :action => ‘list’ %>

Ok.So much for the easy part. We need to add an entry to the controllerfor the ‘import_xml’ action that has just been declared. This will haveaccess to the embedded file uploaded using the file_field :document,:file instruction.

The decision to make the database table columns have exactly the same namesas the XML attributes allows for the following code to populate thedatabase. So the final app/controller/admin_controller.rb looks like:

class AdminController << ApplicationControllerscaffold :programme
  def import_xml
    require ‘rexml/document’
    file=params[:document][:file]
    doc=REXML::Document.new(file.read)
    doc.root.each_element(’//programme’) do |p|
      if not Programme.find(:first, :conditions => [ “name=?”, p.attibutes[:name] ]) then
        @programme=Programme.new
        @programme.update_attributes(p.attributes)
      end
    end
    redirect_to :action => ‘list’
  end
end

Ok. That’s it for now. I’ve scratched the rails surface and learnt something new. More discoveries soon.

A Weekend on Rails - Day One

Posted by blackrat on November 17, 2006

Since I’ve been laid up and not supposedto be working, Ruby on Rails seemed like an interesting, low-impact thing to do. I’m a complete n00b when it comes to Ruby, so it was pretty much a jump in at the deep-end for me. I’d like to think that I was swimming with Ruby rather than sinking within a matter of hours,and I’d recoded a file autostore/autorename utility in a couple of days.

I picked up the basics of array handling, regular expression manipulation, and some of the syntax of Ruby learnt, I still hadn’t quite made the leap of faith into the “Convention rather than Configuration” paradigm, so I figured it was time to get more deeply immersed in the full Ruby/Rails methodologies by building a Rails based utility.

I like to do something useful when trying out something new. Not production ready coding, but enought to move a personal project ahead and I was writing a TV programme recording prioritisation module for my next stage refinement of the PVR project.
Requirements for this were to have an input which contains the Programme name, a single aka (if appropriate), imdb and tv.com id number, and a recording priority. At this top level, I didn’t care about episodes, season information or anything else. Next extension was going to be for Genres, but I didn’t want to worry about that yet.

I already have XML files which contain the information, and I’d decided to use SQLite V3 as the database, so the Rails task I set myself was to convert the XML to the database.

As I have complete control over the XML file, I didn’t require any validation. If there was a problem, I could trash the database, correct the source file and run again.

The form of the xml file was:

<programmes>
  <programme priority=”3″ name=”Time Gentlemen Please”>
  <programme priority=”4″ name=”Time Trumpet”>
  <programme priority=”1″ name=”Torchwood”>
  <programme priority=”1″ name=”Torchwood Declassified”>
</programmes>

with possible additional attributes of aka, imdb_com, tv_com which contain other programme names, and the imdb.com and tv.com ids for the programme.

I’d read a number of Rails articles about moving image files into the database, and decided that I would do this as an import file from a webform.

Since this is my first Rails program, I’ve decided to post it here so I can point and snigger at it later. Feel free to do the same. Assumption is that all of the build prerequisites have already been installed, and I can skip straight to code generation and development work.

First step is to create the rails application framework for this, which goes by the name of BorgTest. Since I’m using SQLite3, I have a slightly modified commandline for the creation.

rails –database=sqlite3 BorgTest

Checking inside the config/database.yml shows that the correct database is being used

# SQLite version 3.x
#   gem install sqlite3-ruby

development:
  adapter: sqlite3
  database: db/development.sqlite3

# Warning: The database defined as ‘test’ will be erased and
# re-generated from your development database when you run ‘rake’.
# Do not set this db to the same as development or production.

test:
  adapter: sqlite3
  database: db/test.sqlite3

production:
  adapter: sqlite3
  database: db/production.sqlite3

Next thing to do is generate a model and controller for the database and associated views.

script/generate controller admin
script/generate model programme admin

Since I already know the exact format of the XML file, I’m going to create a database table with exactly the same columns as the names of the XML attributes.

db/migrate/001_create_programmes.rb

class CreateProgrammes < ActiveRecord::Migration
  def self.up
    create_table :programmes do |t|
      t.column :name, :string
      t.column :aka, :string, :default => “”
      t.column :priority, :integer, :default => 0
      t.column :tv_com, :string, :default => “”
      t.column :imdb_com, :string, :default => “”
    end
  end
  def self.down
    drop_table :programmes
  end
end

Save the above file and run

rake migrate

to create the initial table. Add the scaffold :programme to the app/controllers/admin_controller.rb file

class AdminController << ApplicationController
  scaffold :programme
end

Fire up script/server and point the webrowser to http://localhost:3000/admin and check that it is working.

Ok. Initial build completed and I had the basic scaffolded CRUD application. Next post will cover the addition of the controller and model to actually upload the file and perform the import.

PVR Progress

Posted by blackrat on September 07, 2006

I’ve decided that MythTV is still too bitty for the average user to set up and keep running. This is a shame, since it is obvious that a lot of time and effort has gone into building it, designing the plugin architecture etc.. I’m going to persevere with MythTV for the live home system for now, but I’ve decided to build an alternative - “The Borg Box”.

Mplayer and Xine are easy to use and have good LIRC support (remote controls are desirable in the McKibbin household), so this will start as a manager and wrapper for those rather than trying to implement all of their functionality. Weather I use occasionally, but doesn’t warrant the effort I would spend on it for a first pass, so I’ve decided to implement something similar to MythVideo first. As most code I write is cross platform, and development takes place equally on my Mac notebook and my office Debian machine, it was pot luck to see where I was going to start on this.

Mac notebook won, since I was waiting for a MythTV listings update, and wanted to be near the backend. Since I use MPlayer on the Mac, I decided to see how difficult it would be to pick up a file from the MythVideo database and play it.

MythTV stores all of its configuration data in a MySQL database called mythconverg. I picked up the username/password combination from ~/.mythtv/mysql.txt; logged in and started to examine the data. The MythTV box has been running for some time now and I’ve managed to accumulate a number of AVI files. Native Myth storage is in an MPG ring buffer, and conversion to XVID AVI’s is pretty well documented elsewhere, but if there are enough comments, I’ll produce more detail on how to do this.

All of the MythVideo converted files are referenced in the videometadata table, and you can pick up the path of the video episode from “filename”. All NFS mounted paths are the same across all of my computers, so I could run the file directly using the “open” command on the Mac. All that was really required was to set up access permissions to allow any computer on my network to access the database. A quick re-compile on a Debian machine, with the “xine -pqhf -n” instruction replacing “open” and it was running in Debian under X.

So I now have a program that will pick a random file from the MythTV database and play it on either Max or Debbie. Good enough for one night. I’m looking at making the final “Borg Box” commercial, once I have full integration.
Not that I would ship it on a Pentium II, but The Eden + Hauppauge combination works well, and Debian machines built on this platform may become my chosen distribution.

Hardware Purchases

Posted by blackrat on July 25, 2006

I decided, after some examination of systems and costs, to explore two options. MythTV on Via EPIA boards and, mainly because I like the footprint, the mini Mac as clients. So the system running currently is:

  1. MythTV backend running on an EPIA M1000 with a 400 GB drive
  2. MythTV frontend running on an EPIA M1000 with a 2.5″ laptop boot drive
  3. Two MAC mini’s (Intel) currently running Apple’s frontend, but in the process of being converted to MythTV frontends.

PVR

Posted by blackrat on March 05, 2006

About a year ago, I started looking into systems for home video and other media systems. The aim was to have a consumer friendly box to replace a plethora of others including DVD/CD player/recorder, DAB radio, VHS deck and possibly enable telephony (VoIP). The options: buy a consumer box, find an existing open-source or commercial solution, build a HTPC running Windows/Linux/Other, etc.

This category will chart recent and past progress and may serve somewhat as a warning how not to do this.