Looks Quiet. Isn’t.

Posted by blackrat on April 16, 2007

Maintaining 4 separate blogs (2 project specific and intraneted, rather than external), doesn’t take any more time than 1, but it means that less gets added to this one than might otherwise be the case. However, one thing I can share is the work that’s been going on under the covers to get some Ajax code working on Blogger. (Yet another blog). This all came about when I had looked at some code and thought. Hmm. That would be so much easier to create, maintain and extend in Ruby.
I didn’t want the full blown features of Rails, but since I was experimenting with cutting down the code needed for yet another project, I thought that this would give me a good quick way of injecting Ajax code into a 3rd party site.
One of the rules of Ajax, is that you can’t go outside the domain for the call. So where an XMLHttpRequest.open(’GET’,'http://random.external.site.com/CGI_program’), would throw an error, you can do this by performing the call from an page on http://random.external.site.com.

For example, the header on Nofnords is an Ajaxified update from fnord.pqmf.com (a subdomain of this site), which has been embedded using an iframe which uses http://fnord.pqmf.com/fnord.html as the jumping off point. What this means is that the XMLHttpRequest comes from the same domain as the iframe’d page. Since this is a separate request, there is still a separate between the domains, so data isn’t accessible between the two pieces of content directly, but with some CSS, the appearance is (almost) seamless, and allows for some “personalized” customisation that would otherwise be difficult to achieve.

Displaying a subset of items from an association

Posted by blackrat on December 16, 2006

As of this post I have two classes joined by have_and_belongs_to_many (habtm), and I figured that I’d like to be able to click on an entry in one and have a subset index of the other appear. Not an uncommon task and used by tags and blogs everywhere. Rather than search for a snippet, I thought I’d give it a go myself first.What I came up with surprised me with it’s simplicity and may not be the most expedient manner, but seems to be a neat and easy extension of the index method.

The obvious way was to add an entry to the list of actions which can be applied to a tag item. A simple addition of a “Show Users” link pointing to the Users controller and passing a tag_id parameter was the starting point for this.

Opening up app/views/tags/index.rhtml, we can add a user_path with :tag_id=>tag as the parameter. This will translate to http://localhost:3000/users?tag_id=x.


<h1>Listing tags</h1>

<table>
  <tr>
    <th>Name</th>
  </tr>

<% for tag in @tags %>
  <tr>
    <td><%=h tag.name %></td>
    <td><%= link_to 'Users', user_path(:tag_id=>tag) %></td>
    <td><%= link_to 'Show', tag_path(tag) %></td>
    <td><%= link_to 'Edit', edit_tag_path(tag) %></td>
    <td><%= link_to 'Destroy', tag_path(tag), :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
  <% end %>
</table>
<br />
<%= link_to 'New tag', new_tag_path %>

This will then allow the following in the index view of the users controller.

  # GET /users
  # GET /users.xml
  def index
    @tag=Tag.find(params[:tag_id]) if params[:tag_id]
    @users = @tag.users || throw rescue User.find(:all)

    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render : xml => @users.to_xml }
    end
  end

Note the unusual throw rescue construct. Since @tag.users would throw an exception, as well as having a nil possibility, this is the dryest form of getting the original User.find(:all) executed on error. I mainly wanted to see if this would work, and it does! I’m really starting to like what you can do with Ruby.

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.