« May 2005 | Main | July 2005 »

June 16, 2005

Ruby on Rails and ActiveRecord

Ruby on Rails is a celebrated recent phenomenon, and like many others, I have been seduced by its ease, and simplicity. One of the things that I have found with its MVC style approach, is that you really need to have a good grip on the design of your data requirements. At the heart of this is ActiveRecord which is the Model.

Inorder to understand ActiveRecord more, I played around with using it outside of Rails ( I know that you can use the console ), to see what you can do. Seeing that I had a hard time finding much information about this (more specifically - examples), I have written up a small example that I worked through to demonstrate the way ActiveRecord encapsulates a "many to many" relationship.

I started with two entities - Users and Roles, and wished to describe:

Firstly - define the tables - ActiveRecord takes the approach of defining a table per entitiy, and then a separate table to hold relationship between the entities, and as with all things Rails, it relies heavily on naming convention to knit it together.
Users => users
Roles => roles
Roles <=> User => roles_users

CREATE TABLE roles (
  id int(10) unsigned NOT NULL auto_increment,
  name varchar(50) NOT NULL default '',
  info varchar(50) default NULL,
  PRIMARY KEY  (id)
) TYPE=MyISAM;

INSERT INTO roles VALUES (1,'role2','some role 2 info');
INSERT INTO roles VALUES (2,'role1','some role 1 info');

CREATE TABLE roles_users (
  role_id int(10) unsigned NOT NULL default '0',
  user_id int(10) unsigned NOT NULL default '0',
  KEY ur_map_idx (role_id,user_id)
) TYPE=MyISAM;

INSERT INTO roles_users VALUES (1,1);
INSERT INTO roles_users VALUES (2,1);

CREATE TABLE users (
  id int(10) unsigned NOT NULL auto_increment,
  nick varchar(50) NOT NULL default '',
  name varchar(50) default NULL,
  password varchar(50) NOT NULL default '',
  modified timestamp(14) NOT NULL,
  created timestamp(14) NOT NULL,
  access timestamp(14) NOT NULL,
  PRIMARY KEY  (id)
) TYPE=MyISAM;

INSERT INTO users VALUES (1,'pxh','Piers Harding','blah',20050512121552,20050512121552,20050512121552);

Next thing is to create the Model, and the code to access it. As this is installed using Ruby Gems, you must import gems, and then use gems to bootstrap the loading of ActiveRecord.

#!/usr/bin/ruby
# load gems, and then ActiveRecord
require 'rubygems'
require_gem 'activerecord'

# Establish a conneciton to your database
  ActiveRecord::Base.establish_connection(
      :adapter  => "mysql",
      :host     => "badger",
      :username => "root",
      :password => "",
      :database => "auths"
   )

# Define the entity Role
class Role < ActiveRecord::Base
# describe the relationship to users
  has_and_belongs_to_many :users
end

# same for users
class User < ActiveRecord::Base
  has_and_belongs_to_many :roles
end

# get the first user
user = User.find(1)

# find the roles for a user
roles = user.roles

# loop through each role
roles.each {|role|
             print "Role: #{role.info}\n"
# find users for the role
             users = role.users
             users.each {|u|
# and again find the roles for each user
                userroles = u.roles
                print "Roles for user #{u.name} \n"
                userroles.each{|r| print "\t\t -->role: #{r.name}\n"}
                }
          }

Hope this helps someone.

Posted by PiersHarding at 12:33 PM

June 3, 2005

ABAP, Strings and Concatenation

Of late I have been heavily involved in writing ABAP code that seems to be coliding with UNICODE support related issues. For example - I have a fixed-width internal table, that I want to concatenate into a string, that is the requirement of a subsequent operation such as:
  CALL METHOD CL_HTTP_UTILITY=>ENCODE_BASE64
    EXPORTING
      UNENCODED = w_stringdata
    RECEIVING
      ENCODED   = base64data.
In this instance, you would expect to beable to just loop through a table, and concatenate the lengths of fixed working storage into a single string like so:
  loop at i_pdf into w_pdf.
    concatenate w_stringdata w_pdf into w_stringdata.
  endloop.
But this does not appear to be the case - try this:
data:
  begin of rec,
    a(5),
  end of rec,
  w_row like rec,
  i_rows like standard table of rec,
  w_string type string.
  do 10 times.
    write sy-index to w_row right-justified.
    append w_row to i_rows.
  enddo.
  loop at i_rows into w_row.
    concatenate w_string w_row into w_string.
  endloop.

w_string will not be the expected 50 characters long, as it appears that the concatenate has removed a trailing space from each iteration of i_rows.

Basically - whats happening is that ABAP does not honour the true value of the fixed length character working storage. To me - fixed length is fixed length - if I wanted less than what was exactly in the piece of working storage, then I would expect to explicitly say so, not have something silent syphon it away. What is happening doesn't even conform to the generally accepted rules of C, where a string is NULL terminated, not the last non-whitespace value.

So - how do I get around this? So far, I have only found two, fairly rubbish solutions to this.
One is to pre-stretch a string, and then to specifically replace chunks of it with the value that I really want:

* operation pre-stretch - assume that fixed length ws is 5 chars
* must do this 5+1
  describe table i_row lines w_rows.
* mark beginning and end of string
  w_blank+0(1) = 'x'.
  w_blank+5(1) = 'x'.
  do w_rows times.
    concatenate w_stringdata w_blank into w_stringdata.
  enddo.

* now replace the sections of the string with the real data
  loop at i_rows into w_row.
    REPLACE SECTION offset w_cnt LENGTH 5 OF w_stringdata WITH w_row.
    w_cnt = w_cnt + 5.
  endloop.
or - I did this:
data:
* see other data declarations from above
  astring type string,
  fname(100) type c value '/tmp/shimmy.txt'.

  open dataset fname for output in binary mode.
  loop at i_rows into w_row.
    transfer w_row to fname.
  endloop.
  close dataset fname.

  open dataset fname for input in binary mode.
  read dataset fname into astring.
  close dataset fname.

This was trialed on NW4 test drive edition, and Enterprise 4.7.
I'd be really interested to hear of any other solutions that people have - or someone pointing out a glaringly obvious mistake that I've made? And for extra points - figure out why I had to pre-stretch the string with a size greter than the sum total of parts.

Posted by PiersHarding at 10:06 AM | Comments (1)

SAP::Rfc 1.36 for Active State Perl 5.8.x

Thanks once again, to Olivier for providing the win32 builds of SAP::Rfc. The last (as ever) can be found at http://www.piersharding.com/download/win32/ .

Updated:

Sorry guys - I got the permissions wrong on this - please try again.

Posted by PiersHarding at 8:00 AM | Comments (2)