28th May 2006
Amended 18th October 2006
Ruby on Rails (RoR), where Ruby is the language and Rails is the framework, is being heavily hyped as "the best thing since sliced bread" among web application frameworks. Some people may be impressed, but I am not. After a brief reading of the documentation I saw too many things which struck me as being too restrictive and inflexible, and which could be improved upon. When I say "could" I also mean "have" as these improvements already exist within the Radicore framework.
RoR uses what is termed as "convention over configuration", which means that instead of having to build a mass of configuration files which identify the structure of your database, by using certain naming conventions the framework is able to deduce the structure for itself. Unfortunately I find that these naming conventions are far too restrictive, and I am not alone. These restrictions mean that existing databases which have not been built using these conventions, which covers 99.99% of all databases, will not work with RoR, at least not without jumping through a lot of hoops. You have to know what the RoR conventions are, realise that your database does not conform to these conventions, then hunt for ways to tell the framework "don't do that, do this".
This means that the old saying:
One man's meat is another man's poison
now has a sibling:
one man's convention is another man's constriction
Radicore does not use any artificial or restrictive naming conventions - whatever is valid in the database is OK with Radicore. Neither does Radicore use configuration files which are a pain to set up and maintain - it uses a Data Dictionary which imports existing database structures directly from the database schema, then exports that information into a series of data files which can be readily accessed by the application. There is also an intermediate edit phase which allows information which is not contained within the DBMS (but is still useful to the application) to be added to the dictionary details before they are exported.
Here are some of the deficiencies which I have found (so far):
This means that you must use "stories" instead of "story", "products" instead of "product", et cetera. By default the table name is inferred from the class name, but this can be overridden in the class definition.
This for me is an instant turn-off. There is no such rule in any DBMS, so for a piece of software to insist on such rules simply proves to me that the author of that software still has a lot to learn:
More of my thoughts on this very subject can be found in Technical Keys - Their Uses and Abuses.
I could not find any description on how the Rails framework deals with candidate keys, so perhaps this has to be hard-coded by the developer. In Radicore primary keys cannot be updated but candidate keys can. When updating a candidate key a check is made to ensure that the new value is not currently being used.
All foreign keys must be named in the format
<singular of foreign table name>_id, so if you want a one-to-many relationship between
authors (note the plural) and
stories, you have to use a column called
This again is an entirely artificial restriction which does not exist in any DBMS that I know of, therefore I find it to be entirely unacceptable.
As everyone knows, Many-to-Many relationships between two tables cannot be supported in any DBMS without the use of what is known as an intersection table (also known as a 'link' or 'cross-reference' or 'xref' table). This can then be the object of two One-to-Many relationships between the original pair of tables. RoR cannot recognise intersection tables unless they are named according to the rule
RoR does not automatically perform any basic validation on user input before it is sent to the database, even though such information is provided in the database schema (this is a string, this is a number, this is a date, etc). Methods are provided, but you have to insert the code to activate them.
In the real world a table name can be anything you want, so that when a class file is generated within the Radicore framework the name of the file containing the class, the class name and the table name are taken directly from the database schema without any inferences being made. This means that Radicore can work with ANY existing database schema, and not one that has been specifically engineered to conform to the RoR "rules".
You tell the DBMS which column (or columns) to use as the primary key, and the DBMS does as its told. The column names are irrelevant, their types are irrelevant. Radicore takes the primary key details directly from the database schema, so does not have to make inferences according to some artificial rules.
A database table can have any number of unique (candidate) keys in addition to the primary key. Again the DBMS does not have any naming restrictions or column type restrictions, so neither does Radicore. You tell the DBMS what to use for any candidate keys, and Radicore will extract those details from the database schema.
Radicore does not deduce the existence of relationships by some arcane naming conventions - you tell Radicore what relationships exist and it will take the appropriate action. This includes relationships between tables in different databases. Radicore does not extract relationship details from the database schema as they can only be defined in the database in the form of foreign key restraints, and relationships can exist without having any such restraints. Instead all relationships are defined using Radicore's data dictionary. This definition also identifies which columns on the parent (one) table are related to which columns on the child (many) table. It is also possible to identify which field is to be retrieved from the parent table when reading the child table so that the sql SELECT statement which is constructed can automatically include the relevant JOIN clause. Also, if two tables are related to each other more than once, it is possible to define an alias name for each of those relationships for use in the constructed JOIN clause.
Intersection tables are, after all, simply tables with relationships, so the choice of table name, column names, primary key and foreign keys is entirely yours. Radicore knows when you want to access the three tables in a Many-to-Many relationship because you create a transaction that uses the Many-to-Many controller, which is part of the LINK 1 pattern. The transaction script looks something like this:
<?php $outer_table = 'mnu_task'; // name of outer table $link_table = 'mnu_role_task'; // name of link table $inner_table = 'mnu_role'; // name of inner table $screen = 'mnu_role_task.link(a).screen.inc'; // file identifying screen structure require 'std.link1.inc'; // activate page controller ?>
You tell the controller which tables to use, and the framework then uses the data exported from the data dictionary to tell it how each of those tables is related to the other.
As it is possible to view a Many-to-Many relationship from two directions it is a simple process in the Radicore framework to create a transaction for the alternative view:
<?php $outer_table = 'mnu_role'; // name of outer table $link_table = 'mnu_role_task'; // name of link table $inner_table = 'mnu_task'; // name of inner table $screen = 'mnu_role_task.link(b).screen.inc'; // file identifying screen structure require 'std.link1.inc'; // activate page controller ?>
Primary validation ensures that the data within any field conforms to the size and type of that field, and as this information is available in the database schema there is no reason why the framework cannot use this information to automatically filter any user input:
The Data Dictionary allows me to include additional information which can be used during the automatic validation process, such as specifying minimum and maximum values for numeric fields, for example.
This means that the developer only has to write code to perform secondary validation, such as to compare the contents of one field to another.
Ruby On Rails uses ActiveRecord, an implementation of the Active Record design pattern, which obtains the structure of each database able at runtime by querying the database schema as each class is instantiated into an object. This means that it is possible to change the structure of a database table and to have that change automatically detected by the software without having to change a single line of code. This pattern also mixes domain logic with data access logic.
Radicore uses a combination of the Table Module containing business logic and data validation logic with a separate Data Access Object (DAO) to handle communication with the physical database. The data validation rules are originally imported into the Data Dictionary from the database schema, but which may be edited or enhanced before being exported to the application. This involves a three-stage operation:
The reason for this is because the information which can be obtained from the database schema is restricted to that which is relevant to the DBMS, but which is less than may be required by the application. This "extra" information may include minimum and maximum values, directives such as "noedit", "nodisplay", "noaudit", "nosearch", "auto_insert", "auto_update", "uppercase", "lowercase", "zerofill", "blank_when_zero", the HTML control to be used in the output, and a whole lot more.
The classes/<tablename>.class.inc file identifies the DBMS engine name as well as the database and table name. Any number of table classes can be accessed in a single script, so this allows that script to obtain data from different database engines and different databases as well as different tables. Note that each table class is an extension of an abstract table class which contains all the methods to communicate with the database. This means that the developer does not have to write any SQL - it is all generated automatically. Options to customise the generated SQL are available if needed.
The classes/<tablename>.dict.inc file contains the following elements:
Another distinct advantage of the $child_relations and $parent_relations arrays is that it allows the framework to deal with multiple relationships between the same two tables, and even for a table to be related to itself.
Another difference between the two frameworks is that RoR uses a series of finders to select data whereas Radicore uses a simple polymorphic
getData($where) method which can read any data from any table using whatever
$where string is available. This is especially useful as it allows the framework to deal with any selection criteria that may be input via a SEARCH screen, and any context that may be passed from a parent form to a child form when a navigation button is pressed.
Ruby On Rails uses scaffolding to provide a series of standardized actions for listing, showing, creating, updating, and destroying objects of a particular class. With a single command you can generate the model for a database table, a controller for that model which contains methods for all the CRUD operations, and a view template for each operation which is a skeleton HTML file containing RoR commands. Each of these files can be customised to suit individual requirements.
Radicore uses a series of transaction patterns which contain a vast amount of pre-written and re-usable code. The differences are:
The creation of transactions in the Radicore framework is achieved by pressing a button. The transaction generation feature allows the user to select a database table, select one of the many Transaction Patterns, then press a button to generate all the relevant files and entries on the MENU database to implement that pattern. Unlike basic CRUD operations on a single table the catalog of patterns in the Radicore framework can also deal with one-to-many relationships, one-to-many-to-many relationships, and even many-to-many relationships. In certain cases the creation of a transaction which is the parent in a family of forms will automatically cause the associated child transactions to be created as well.
This means that fully functional transactions can be created without having to write a single line of code. As with RoR each of the generated files can be customised to suit individual requirements, but it is easier to edit an existing file which already contains the basics than to create the whole file from scratch.
The Radicore tutorial shows how easy it is to create runnable screens starting with nothing more than a database schema, and without having to write a single line of code (which includes SQL and HTML). It also gives examples of customisation in order to get your application to do what the framework cannot accomplish automatically.
One major complaint I have against all those frameworks which I have been advised to take a look at, which includes RoR, is that none of them come with any pre-written software which can be run "out of the box". All you get is a toolkit, and you cannot see a working example of any transaction built using the toolkit unless you build it yourself. This is why such frameworks are unable to supply a link to a demonstration application on their website.
This is not the case with Radicore as it comes with over 500 pre-written transactions which can be run immediately. Simply download, unzip, modify the CONFIG.INC file, create the databases and load the data from the supplied sql scripts, and off you go.
The pre-written code is broken down into a series of subsystems which are:
There is also a set of prototype user applications:
Apart from the fact that some of these are essential parts of the framework, they actually provide a working implementation of every transaction pattern in the Radicore library. This means that you can actually run a working implementation of each pattern, perhaps to see the difference between one combination of patterns and another, then examine the code to see exactly how each pattern was implemented. How cool is that?
If you would like to discuss the contents of this article please visit the Radicore forum.
© Tony Marston
28th May 2006
|18th Oct 2006||Update Scaffolding vs Transaction Patterns to include a reference to the new transaction generation feature.|
|12th Aug 2006||Added sections for Intersection tables and Primary data validation.|