Tony Marston's Blog About software development, PHP and OOP

Is Radicore better than Ruby On Rails?

Posted on 28th May 2006 by Tony Marston

Amended on 10th July 2024

Introduction
Deficiencies in Ruby On Rails
Table names must be plural
Primary keys must be auto_increment and named 'id'
Candidate keys must be ... ?
Relationships are inferred from foreign key names
Intersection tables in Many-to-Many relationships
Primary data validation
Deficiencies overcome in Radicore
Table names are not restricted
Primary keys are not restricted
Candidate keys are recognised and not restricted
Relationships are defined, not deduced or inferred
Intersection tables do not require special names
Primary data validation is automatic
Ruby On Rails uses .... while Radicore uses ...
ActiveRecord vs Data Dictionary
Scaffolding vs Transaction Patterns
Finder methods vs WHERE string
Callbacks vs Template Methods
Software that is runnable "out of the box"
Comment on "Rails' Ridiculous Restrictions, a Rant"
Amendment History
Comments

Introduction

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.


Deficiencies in Ruby On Rails

Here are some of the deficiencies which I have found (so far):

1. Table names must be plural

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.

2. Primary keys must be auto_increment and named 'id'

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.

3. Candidate keys must be ... ?

I could not find any description on how the Rails framework deals with candidate keys (unique keys in addition to the primary key), so perhaps this has to be hard-coded by the developer. In the RADICORE framework primary keys cannot be updated but candidate keys can. When updating a record which contains a candidate key a check is made to ensure that the new value is not currently being used.

4. Relationships are inferred from foreign key names

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 author_id in stories.

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. In every DBMS which I have used the names of the foreign key and its associated primary do not have to follow any naming convention (i.e. they can be whatever you choose) but the columns MUST have the same datatypes and sizes. In other words you cannot link a CHAR field to an integer.

5. Intersection tables in Many-to-Many relationships

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 alphabeticallyFirstTablePlural_alphabeticallySecondTablePlural.

6. Primary data validation

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 manually to activate them.


Deficiencies overcome in Radicore

1. Table names are not restricted

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".

Here is the template for each table's class file:

<?php
require_once 'std.table.class.inc';
class #tablename# extends Default_Table
{
    // ****************************************************************************
    // class constructor
    // ****************************************************************************
    function __construct ()
    {
        // save directory name of current script
        $this->dirname   = dirname(__file__);
        
        $this->dbname    = '#dbname#';
        $this->tablename = '#tablename#';
        
        // call this method to get original field specifications
        // (note that they may be modified at runtime)
        $this->fieldspec = $this->loadFieldSpec();
        
    } // __construct
    
// ****************************************************************************
} // end class
// ****************************************************************************
?>

Note that this identifies both the database name and the table name as the application may be comprised of any number of subsystems, each with its own database.

The file std.table.class.inc is an abstract class which provides all the common table methods which are shared by every concrete table class. If you are wondering why the class does not contain any methods this is because all the standard methods are inherited from the abstract class. The only methods which need to be added are customisable "hook" methods in order to provide non-standard processing at various points in the processing flow.

The loadFieldSpec() method will load the contents of a separate table structure file into the common table properties which were defined in the abstract table class. This information is then available for use by various functions within the framework.

If the table's physical structure ever changes the table structure file can be rebuilt, thus keeping the object structure perfectly in line with the table's structure.

2. Primary keys are not restricted

No DBMS that I have ever used has insisted that the primary key must be an integer called "ID" and it must be auto_increment, so I see no reason to enforce this artificial restriction in my software. A primary key can have as many columns as I choose, and each column can have whatever datatype that I deem appropriate. A column's value may come from an automatic sequence, or it may not. Whatever the DBMS accepts is acceptable to me. RADICORE takes the primary key details from the database schema and loads it into the $primary_key array, so does not have to make inferences according to some artificial rules.

3. Candidate keys are recognised and not restricted

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 and load it into the $unique_keys array.

Note that in a compound candidate key any of the columns may be NULLable, but for the primary key every column must be NOT NULL.

When performing a lookup which should provide a single row RADICORE does not care if you provide a value for the primary key or one of the candidate keys - either will do.

4. Relationships are defined, not deduced or inferred

RADICORE does not deduce the existence of relationships by some arcane naming conventions - you tell RADICORE what relationships exist and it will take the relevant action at the appropriate time. 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.

Each one-to-many relationship involves two tables, a parent (one) and a child (many). Its details will appear in the $child_relations array for the parent table, and the $parent_relations array for the child table. 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.

5. Intersection tables do not require special names

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 component 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 an additional 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
?>

6. Primary data validation is automatic

Primary validation ensures that, prior to an insert or update operation, the data within any field conforms to that field's specifications in the database, otherwise the query will fail. If the validation process finds an error it will generate an error message and return control to the user without performing the update, thus allowing the user to correct the error before resubmitting the update. All indications are that the RoR developer has to add this validation code by hand whereas in RADICORE it is performed automatically by the inbuilt validation object. This uses the contents of the $fieldspec array, whose contents were exported from the database schema, to validate the contents of the $fieldarray array.

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 .... while Radicore uses ...

In the 20 years I spent on designing and building database applications before I switched to using PHP there were quite a few things that I learned which had great influence on my approach with PHP and its object oriented capabilities. For example:

After finding out what what PHP could do according to various online tutorials and books that I read, plus a little experimentation of my own, I first built a small Sample Application as a Proof Of Concept (POC). I then took the design of a development framework that I had first built in COBOL in the 1980s, and then rebuilt in UNIFACE in the 1990s, and rebuilt it in PHP in the 2000s. I emulated the Application Model in UNIFACE to create my own version which I call a Data Dictionary. In UNIFACE I encountered Component Templates which were a little clunky, so in PHP I created a series of Transaction Patterns.

I taught myself how to take advantage of PHP's OO capabilities in the following ways:

1. ActiveRecord vs Data Dictionary

AR classes must be constructed manually

Ruby On Rails uses ActiveRecord, an implementation of the Active Record design pattern. It requires a great deal of manual effort to create a class for each database table, as demonstrated by the following extracts from the documentation:

First, to create an Active Record Model type the following:

class Product < ApplicationRecord
end

The ApplicationRecord class is described as follows:

When generating an application, an abstract ApplicationRecord class will be created in app/models/application_record.rb. This is the base class for all models in an app, and it's what turns a regular ruby class into an Active Record model.

To perform Validations enter the following:

class User < ApplicationRecord
  validates :name, presence: true
end

To define Associations enter the following:

class Author < ApplicationRecord
  has_one :foobar
  has_many :books
  belongs_to :snafu
  has_many :through < to specify intersection table in a many-to-many relationship >
end

Radicore classes are generated on demand

With RADICORE all these operations are performed by pressing buttons on HTML screens, not by hammering away at the keyboard. After you create a table in your database you go into the Data Dictionary and run the following screens:

  1. IMPORT - this process reads an existing database schema and transfers those details into the dictionary database. If any table details are subsequently modified the import function can be used again to synchronise the dictionary. This will deal with new tables and new, changed or deleted columns.
  2. EDIT - this allows details which do not exist in the database schema to be added (see below).
  3. EXPORT - this makes the information on each table available to the application in two different files:

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>.dict.inc file contains the following elements which are exported from the Data Dictionary:

Note that unlike RoR it is not necessary to define within any Model how to deal with any particular relationship. The information exported from the Data Dictionary will do nothing but identify what relationships exist, and it is up to the developer to create tasks to manage that relationship from the relevant Transaction Patterns.

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.

2. Scaffolding vs Transaction Patterns

AR controllers must be constructed manually

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.

Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come with both controller logic and default templates that through introspection already know which fields to display and which input types to use. Example:
 class WeblogController < ActionController::Base
   scaffold :entry
 end
This tiny piece of code will add all of the following methods to the controller:
 class WeblogController < ActionController::Base
   verify :method => :post, :only => [ :destroy, :create, :update ],
          :redirect_to => { :action => :list }

   def index
     list
   end

   def list
     @entries = Entry.find(:all)
     render_scaffold "list"
   end

   def show
     @entry = Entry.find(params[:id])
     render_scaffold
   end

   def destroy
     Entry.find(params[:id]).destroy
     redirect_to 

Further documentation can be found at What is Scaffolding in Ruby on Rails?

This shows that each Active Record class requires its own custom-built Controller to handle all the use cases for that object. A series of Views are also generated for the standard Controller actions.

Radicore controllers are built into the framework

RADICORE uses a series of transaction patterns which contain a vast amount of pre-written and re-usable code. Each of these patterns is comprised of a Controller and an optional View which can be combined with any Model in the application to create a working user transaction (use case). The differences are:

  1. Model classes are generated from the data dictionary and automatically contain the common table methods, which are inherited from the abstract table class, which support all the CRUD operations which exist in all of the supplied controllers. Because each Model contains the same set of methods this produces a large amount of polymorphism. This allows each Controller to call the same set of methods on any Model in the application, thus making the Controllers reusable.
  2. Views are supplied in the form of a series of pre-written and reusable XSL stylesheets which require a simple screen structure script to identify which data goes where. There is never any need to modify any HTML code. There is a single View object to generate HTML, one for CSV and another for PDF.
  3. Controllers are supplied in the form of a series of pre-written controller scripts, one per Transaction Pattern, which can operate on any model in the system. This is because class names are supplied at runtime via a series of component scripts, one for each user transaction, and not hard-coded into any controller. There is never any need to write or modify any controller code.

The creation of transactions in the RADICORE framework is achieved via functions in the Data Dictionary as follows:

  1. Go into the List Tables or List Tables by Database screen, select a database table and press the "Read" button in the navigation bar.
  2. In the Enquire Table screen press the "Generate PHP" button in the navigation bar.
  3. In the Generate PHP - Select Pattern screen use the dropdown list to select a pattern, then press the SUBMIT button.
  4. In the Generate PHP - Enter Details screen to view the default values, modify if necessary, then press the SUBMIT button. This will perform the following for each pattern:

Notice that there are many patterns, each of which performs different actions on one or more tables where the identity of those tables is not known until run time. This is achieved using that mechanism known as Dependency Injection. While some of these patterns deal with only one table, other patterns deal with two tables in a one-to-many relationship, or even three tables in a many-to-many relationship.

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, and this video, 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). The tutorial also gives examples of how to use the customisable "hook" methods to add in any additional business rules.

Finder methods vs WHERE string

The biggest difference is that RoR will return one or more objects when as SQL "select" query is executed, whereas RADICORE will return an array of rows into the current Model object.

AR uses finder methods to read data

RoR uses a specially built Query Interface to retrieve data from the database. This requires special syntax in order to generate queries. If you are familiar with SQL and know exactly what query you wish to generate you first have to learn this special syntax before you can generate what you actually want.

Radicore uses simple substrings

When I started writing my Data Access Object for MySQL it was easy to write code to deal with the standard INSERT, UPDATE and DELETE operations, as indicated in the following:

INSERT INTO <tablename> SET item1='value1', item1='value2', ... , itemN='valueN'
UPDATE <tablename> SET item1='value1', item1='value2', ... , itemN='valueN' WHERE <where_string>
DELETE FROM <tablename> WHERE <where_string>

In the above queries <where_string> was limited to the primary key of a single record. The code knew which column(s) were in the primary key as they were specified in the $primary_key array.

While the default SELECT query can be very simple, as in the following:

SELECT * FROM <tablename> WHERE <where_string>

I quickly realised that I needed to cater for many more possibilities. Using my X-Ray vision (one of my super powers) I spotted that the query string was actually comprised of a series of substrings, some of where were optional, as in the following:

SELECT <select_string>
FROM   <from_string>
[WHERE <where_string>]
[GROUP BY <groupby_string>]
[HAVING <having_string>]
[ORDER BY <sort_string>]
[<limit_string>] << could also contain OFFSET >>

I created a separate property for each of these substrings, which means that once I have worked out what query I wish to execute I just have to load each of those substrings with the relevant part of that query, using the _cm_pre_getData() method. Easy Peasy Lemon Squeezy.

Callbacks vs Template Methods

During the life cycle of your Models when responding to calls from the Controller you may wish to interrupt the processing of standard code with some custom code using what are known as "hooks" or "plug-ins"

AR uses callbacks to insert custom code

Active Record uses a system of Callbacks which are described as follows:

Active Record Callbacks

This guide teaches you how to hook into the life cycle of your Active Record objects.

After reading this guide, you will know:

The callbacks fall into the following categories:

Radicore uses Template Methods

Every concrete table (Model) class in a RADICORE application inherits from an abstract table class which contains a set of common table methods as well as a set of common table properties. The use of an abstract class then allows any of its public methods to be turned into a implementation of the Template Method Pattern, which is a fundamental technique for code reuse and at the heart of framework design. Each Template Method contains a mixture of invariant/fixed methods to provide standard processing interspersed with a selection of "hook" methods which can be overridden in any concrete subclass. These "hook" methods are defined in the abstract class but, when called, do nothing unless they are overridden in the concrete subclass.

For a list of methods which are called by the Controller please consult Transaction Patterns where, for each pattern, it shows which public methods are called. Click on any of these to see which internal methods are called. Customisable methods all have a _cm_ prefix.


Software that is runnable "out of the box"

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, each of which has its own zip file:

Apart from the fact that some of these are essential parts of the framework, they actually provide a working implementation of virtually 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?


Comment on "Rails' Ridiculous Restrictions, a Rant"

In the article called Rails' Ridiculous Restrictions, a Rant the author Joel Spolsky reviews his attempt to use Ruby on Rails to write a simple bug tracking application. I examine each of his complaints and explain why they do not exist in RADICORE.

  1. IT TAKES THE RELATIONAL OUT OF RELATIONAL DATABASE MANAGEMENT SYSTEM
    Basically, Rails groks tables and columns well, but relations are second class citizens.

    This is so stupid. Rails is smart enough to look at my database's entrails and build accessors, craft templates and generate controllers to handle the whole thing. I can Create, Edit, Update and Delete without writing any code.

    As long as I don't leave the table. DO NOT LEAVE THE TABLE!

    In fact, don't even glance outside the table, like to get some labels for some ids in a popupmenu.

    This is where my Data Dictionary comes into play. You first create your table in the database, then you import that table's information into the DICT database. You are able to edit this information before you export it to create the table class file and the table structure file. This is where, among other things, you can identify which HTML control should be used when that column is displayed on a screen. The default is a textbox, but you can use a checkbox, radio button, dropdown list, or a popup. The difference between the two is that a dropdown uses an internal list which you can populate using the _cm_getExtraData() method whereas a popup uses a form which is built from one of the POPUP patterns. Once you have defined an action for a column in the Data Dictionary and exported it to the table structure file that action will performed automatically on every screen containing that column. You do not have to amend any screen structure scripts as they are only used to define which column goes where on the screen.

    OK, forget foreign keys. Let's pretend I'm too amped on Mtn Dew to care about imposing data integrity at the data level.

    The Data Dictionary does not use foreign key constraints in the database to recognise relationships as they are entirely optional. I first encountered relational databases in the mid-1990s when using the UNIFACE language which had internal drivers built for a variety of different DBMS offerings, and some of those did not support foreign key constraints. Also, in UNIFACE you defined your database first within its internal Application Model (equivalent to my Data Dictionary) and then exported it to the file system to create your CREATE TABLE scripts which you then imported into the physical database. When I built my PHP framework I reversed this process by importing from the application database into the DICT database where any relationships can be defined using a variety of screens. Note that in the Update Relationship screen the relation_type field can be set to one of the following which be be acted upon by the framework when a parent record is selected for deletion:

    It is still possible to define foreign key constraints in the database, in which case the framework will ignore the CASCADE and NULLIFY options.

    Note also that it is possible in any relationship to nominate any columns on the parent table which are to be automatically included in the SELECT query when the getData() method is called on the child table. This is described in Using Parent Relations to construct sql JOINs.

  2. RAILS DOESN'T PLAY WELL. WITH ANYONE. NOT EVEN ITSELF
    But do you remember that moment in the demo when the play turns ugly with a big ole error page? It's when he tried to update his db schema or model or something. It doesn't work because he has to reboot the webserver before those changes take effect. Which he says and moves on quickly.

    Well, it turns out that webserver rebooting requirement really sucks when you're trying to, um, Rapidly Application Develop.

    Now, I understand why this restriction exists. If Rails has to look at your schema on every request, you're going to take a performance hit.

    Excuse me? Rails has to look at your schema for every request? Are you bonkers? Way back in the 1980s when I wanted to extract a table's structure from the database so that I could reference it in my COBOL program I had the sense to write the extracted data to a disk file which I could read in when the program was compiled. Reading the extracted data from the disk file was more efficient than extracting the data again and again. I followed the same philosophy in my PHP implementation, but decided to extract the data first into my Data Dictionary so that I could edit it before exporting it to a disk file. If I ever change the schema then all I have to do is re-import it into the Data Dictionary, then re-export it to the disk file. In this way I also keep the Model in sync with the table structure.

    But having to reboot the webserver makes Rails feel like a step backward from even CGI Perl in terms of speed. At least in that environment you can just reload the script to see if your changes worked.

    Why can't Rails adopt the smart middle ground? The webserver does not need rebooting, until you set a :fast flag somewhere, in which case Rails caches the schema info.

    The idea that you have to reboot the webserver just because you make a small update in your application is an alien concept to me - it does not compute! I do not have to use any sort of data caching mechanism as the table structure file already serves that purpose.

    Since Rails already differentiates development from production, this should be pretty painless to implement.

    In my world I have a development system on a development server, a test system on a test server, and the live (production) system on a live server. The code does not work any differently depending on the server. Any code changes start on the development server, move to the test server, then when approved they move to the live server.

    Rails also doesn't play well because it will overwrite your code. Here's the scenario: you started a project in scaffolding mode because you were learning Rails and this is Rails' coolest and easiest mode.

    You made some slight modifications to the models and controllers as you associate your models with one another.

    Then you discovered you wanted to depart slightly from the scaffolding. So you backed out and figured out how to ruby script/generate scaffold Thing. Except -- Woops! -- this overwrites your old code, your models and controllers.

    This smells of bad design to me. In RADICORE there is only one way to generate Model classes:

    Note that when the export task is run it will always overwrite the table structure file, but it will not overwrite the table class file. This is because the class file may have been amended to include one or more "hook" methods, and these need to be preserved.

    In the case of controllers, you may be safe because the scaffold generator will use the plural form. But your model is probably toast.

    In the RADICORE framework Controllers are never generated or modified by the developer as they are pre-written and supplied by the framework, with a separate Controller for each Transaction Pattern. Controllers are services, not entities, and as such they are stateless and do not contain any business rules. Each Controller can perform its actions on any Model in the application. This is made possible because of the polymorphism provided by the abstract table class coupled with that mechanism called Dependency Injection.

    This "back to square one" technique returns again and again in using Rails. When I installed the login-engine plugin for Rails -- which is really cool, by the way, even Perl's CPAN does not have libraries operating at this high a level yet -- there was no documentation on how to make it work with my existing user table. Fine. I let it build its own table, made a new user and logged in.

    But then when I went to install user-engine, which adds access controls on top of login-engine, it again did not want to integrate my existing data. The user I had created 5 minutes earlier in login-engine was useless. I had to let user-engine autogenerate a new user named "admin" with an insecure default password.

    In RADICORE the login screen is not a plugin, it is a standard part of the framework. You have to login to the framework before you can use any of its features, which includes maintaining the contents of the MENU database and accessing the components in the Data Dictionary in order to build your application. After you have built your application components every user has to login to run those components, and all user access is controlled by the in-built RBAC system.

  3. YOU CAN'T READ THE FACKING MANUAL BECAUSE THERE IS NO FACKING MANUAL

    The RADICORE framework comes with a whole library of facking manuals which you can see listed on the Documentation page. Here are some entries on the Design Documents page:

    Here are some entries on the User Guides page:

    Amongst the many other documents you will find the following:

    If that doesn't amaze you then what about this - after you have installed Ruby on Rails there is nothing you can run to see how it works. After you have installed RADICORE (which cannot be done via Composer because that is for installing libraries, and RADICORE is not a library it is a framework) you have immediate access to a standard set of components. On top of that there are a series of prototype applications which you can unzip and install separately. You can then run these applications and examine the code to see how they work.

  4. RAILS ATE MY DATA, OR AT LEAST STOOD IDLY BY WHILE MY DATA WAS EATEN
    Rails knows which column is varchar(40) and which is int(11), but don't expect it to do anything with that information other than maybe size the text fields in your scaffolded HTML form.

    If you plop 80 chars of data into a 40 char field, Rails goes ahead and passes it along to the database this way. A certain extremely popular "database" will silently amputate data in this situation. Rails inserts the data and raises no error.

    This is especially silly since a) Rails already incorporates methods to validate input for particular columns and b) Rails is already recording column types.

    When RADICORE extracts a table's specifications from the INFORMATION SCHEMA it includes all the column names as well as their types and sizes. This information is passed to each table class, via the table structure file, into the $fieldspec array. During an INSERT or UPDATE operation the user data, which exists in the $fieldarray array, is automatically passed through the standard validation object in order to ensure that each column's data matches that column's specifications. Any value which does not match will cause an error message to be raised and the operation will not be allowed to continue until there are no error messages.

  5. THE FIRST RULE OF RAILS IS: DO NOT TALK ABOUT RAILS' RULES!
    Rails has lots of important rules that you need to follow if you want things to work easily and to really flex the power of the framework.

    But all the demos I saw, including the Onlamp.com tutorial, were so busy hyping how easy Rails is that they glossed over the rules, even though they are pretty simple.

    The trouble with that approach is that people end up trying the framework, and use techniques from elsewhere that can really gum up the rails works. Like naming database tables in singular form (ala Philip Greenspun) or naming a foreign key "user" instead of "user_id" (ala Class::DBI). And then they become unnecessarily frustrated with the framework.

    RADICORE has no such rules as object names are extracted from the database schema:

    There is only one naming convention which may catch some people out - I HATE CASE SENSITIVE SOFTWARE. I prefer to use snake_case instead of camelCase or StudlyCaps, so all table names and column names are automatically converted to lowercase before they are referenced within the code. SQL is not case sensitive, PHP function names are not case sensitive, and all the other software which I have used in the past 40 years has not been case sensitive, so I cannot see any advantage of using case sensitive names anywhere.

  6. THERE'S MORE THAN ONE PLACE TO SAY IT
    You want to setup some kind of configuration for some particular rails package. You:
    1. modify your existing code, which will use the package
    2. modify code in the package itself
    3. specify when you install or bootstrap the package via ruby script/* and the command line
    4. modify -APPBASE-/config/environment.rb
    5. modify -APPBASE-/app/controller/application.rb
    6. modify -APPBASE-/config/environments/-ENVIRONMENT-.rb

    In RADICORE there are the following configuration files which exist in the standard INCLUDES directory:

    Each Model class is "configured" using the contents of its table structure file which exists in the <subsystem>/classes directory. Runtime adjustments to this structure can be made in the _cm_changeConfig() method.


Amendment history:

10 Jul 2024 Added Finder methods vs WHERE string
Added Callbacks vs Template Methods
Added Comment on "Rails' Ridiculous Restrictions, a Rant"
18 Oct 2006 Updated Scaffolding vs Transaction Patterns to include a reference to the new transaction generation feature.
12 Aug 2006 Added sections for Intersection tables and Primary data validation.

counter