December 5, 2016 at 10:32 am #9868
I think we are clear to get rid of any code within the UI classes such as the remote forms and the window classes. However some people like using object classes in order to implement the business rules and loading the data from the database.
Some people like to use table classes instead as I do.
I say table classes are just like object classes since you should implement your own methods into it but in addition they also act as variables to hold the data to be displayed. That said you would not need to use return methods to get the data out of the object variable.December 5, 2016 at 11:47 am #9880Rod CooperParticipant
I must admit to having a preference for object classes. Table classes can feel a bit like a black box at times and guess I just like to have total control, I also like having one class to look after many tables with nicely formatted sql structured with Begin Statement, Sta, End Statement blocks clearly showing any joins.December 5, 2016 at 12:06 pm #9881
Sure Rod, nothing hinders you to implement your own functions into a table class using Statement blocks. In fact there is no difference implementing your own business rules and functions.December 5, 2016 at 2:17 pm #9882Richard MillerParticipant
One of the problems with table classes is that they add a layer of complication that is not needed. An object class can store all your sql methods for all your tables; it can still be sub-classed if you only want some methods available for certain tables and seems a more elegant solution. Table classes can end up polluting your library with lots of extra classes as you already have a schema class to define the table definition.December 21, 2016 at 4:59 pm #9896Mark PhillipsParticipant
Many years ago I took the approach of using Table classes for db table manipulations. This allowed me to use the built-in methods to generate most SQL statements and to use Omnis list and row variables to bind data to the UX classes. To handle complex relationships between tables, and to provide a place for hand crafted SQL statements, I use Object classes. This is, in my mind, a reasonable use of simple classes within more sophisticated classes. It affords a bit of mix and match, and hopefully less code overall.
This structure made simple the trapping of error conditions and deriving a class hierarchy to reduce code redundancy at elementary and higher levels. This design allows me to craft UX elements independent of data manipulation and to share code, although that advantage is not unique to my approach; others achieve the same result.
This does come at a price. As Rod and Richard pointed out, the Table + Schema classes can be many and keeping them in sync with the data model can be a bit of work. In my case, the data model is modest; 67 tables, 55 views, 16 functions and a passel of triggers and constraints. The results is roughly 100 Table and Schema classes in the application.
I have found introducing programmers to this design takes a little bit of time. To review the code without introduction often leaves one scratching his head. A number of actions that happen deep in the hierarchy need to be presented to get the idea. So while I am satisfied with the design for some reasons, I cannot say I reached my academic goal of simplicity.January 6, 2017 at 10:29 am #9912
One of the advantages of the table class is that the object is actually not only the interface but also the data container itself. Hence it does not require an additional object (list and object variable) but only the list variable in the UI.
If we would allow a list that is bound to a table class to get the column definition from the result of the SQL statement then we would combine the advantages of an object variable (making complex select statements in a statement block)…
with the advantages of the table class (having the list variable as the interface object).
I have added an enhancement request (ST/TA/018) to allow this. This enhancement request would allow to have table classes that are not bound to any schema or query class. One could put statement blocks into a method and finally do a $fetch(kfetpchall) to receive the result into the list itself. Up to that point it should already work but the list ought to be defined from the result of the select statement.January 10, 2017 at 10:26 pm #9925
For years, and I will keep advocating this for anyone who writes his/her software in Omnis and wants some measure of portability between backends, I have used table classes for each and every table in my database. I have fully featured base classes from which my table classes are subclassed and it really is a very strong way of interacting with database tables. They do anything from validating foreign keys, to joining in extra tables, to writing more complete CRUD SQL statements then Omnis does natively.
But object classes are just as important but for me they are containers of structure. To paraphrase a recent post I did on the Omnis list, I have an object class for an invoice and inside that object class I have a row variable defined from my invoice header table class and a list defined from my invoice detail table. The object class has a $load function that will load the header and the details using the $select and $fetch methods on the table class (my base class actually has a $build method that combines these actions) and a $save function that will start a transaction, then create/update my header and update my details.
My windows then simply uses that invoice object class to display the invoice and interact with the user.
So both have a place in my heart. The table class contains all the business intelligence logic related to one specific table, but the object as a container contains the business logic related to the structure as a unit. It makes sure the totals on my invoice match the sum of the details, it makes sure the correct tax is calculated over the details, it makes sure of all things that can not be done in relation to just one table, as that one table is only one part of the invoice.
That all said, I’ve started down a different path but this is a path where I’m using more technologies then just Omnis and we’re increasingly dealing with either larger organisations as clients or increasing our risks by putting our solution online. We live in a much less secure world then we once did and one large downside of an SQL centric solution as we implement is that we open ourselves up to a lot of risk when someone connects to our database outside of our Omnis application. In order to update my invoice with table classes I need to give my user the ability to log on to my database and give them CRUD access to both the invoice table and invoice detail table. If they do so through my application, my applications business logic ensures invoices are correctly updated. If a disgruntled employee at my client however figures out we’ve got a SQL based solution and they download any of the many free SQL tools available, they can do a lot more damage then they would be able to do logging in through our application (which you can’t prevent if you want happy employees to work too).
However, we still want to deploy a full desktop application written in Omnis but in this brave new world this Omnis desktop application no longer connects directly to the database but instead talks to the middle tier. It has no inkling of the existence of an invoice header table and an invoice detail table and whatever other table is referenced by these tables. Instead it calls a function on the API to retrieve an invoice, and calls a function on the API to save an invoice. Table classes have no use at this side of the solution anymore. I still use schema classes but purely as an easy way to define a list of columns, not as a representation of the table in the database.
Now even in the intermediate solution I talked about on the Omnis list table classes are taking a back seat. This does require choosing one database platform and sticking with it and closes the door to be cross SQL database. I’m actually closing off direct access to tables so that someone logging into our database doesn’t even see that we have an invoice header and invoice detail table or at best just has read only access. Instead we provide authenticated access to a stored function that gets an invoice, and a stored function that saves an invoice, and both mirror the API of the middle tier I eventually intent to replace this with. All the logic that currently resides in my table classes is therefor moving to the backend and is no longer part of the Omnis solution.
So yes, while I absolutely love table classes, and I absolutely endorse what you say here Andreas, and I would absolutely recommend anyone using Omnis to go down this path first and always go down this path if your solution does CRUD directly through SQL queries to the database, there are very valid arguments for taking a very different approach.
P.S. I don’t get your ST/TA/018, unless I am missing something, I have had that functionality for years. If you leave the schema class property of a table class empty and you simply execute a query through its statement object, you can do a fetch and the list will be defined according to your resultset. There is a bug in the Postgres dam that a text column will be defined with a length of 0 and while it is retrieved correctly, if you try and update the column you loose the data but that can be worked around with a bit of notational trickery. Anyway try adding the following method to a brand new table class and try it out:
$getStuff Calculate lvSQL as 'select * from atable' if $cinst.$statementobject.$execdirect(lvSQL) do $cinst.$fetch(kfetchAll) else do $cinst.$sqlerror(kSelect, $cinst.$statementobject.$errcode, $cinst.$statementobject.$errtext) end if
Obviously make sure you’ve assigned your session to your table class first as per usual.January 11, 2017 at 11:25 am #9928
Thank you Bas, I did not know that one can use a table class without an assigned schema or query class. This is great.
My strongest point not to use object classes is that when using a JS form or a window you do not need an extra variable to represent the object. You can just call the $load (or whatever method you implement) of your list object (rather using another object variable and then return the result in the list). There is just that one data object that fills itself.
Also inside your list (table class) you could use another list that is also bound to a table class. That said you can build i.e. an invoice object (row variable) that contains the details as a list. So you are able to nest them just as you could do with object classes.January 11, 2017 at 11:59 am #9929
Well thats not entirely true. While I will in many cases grab a copy of my row for display purposes this is mostly due to a few bugs in Omnis in relation to using objects through object references (which I absolutely love). There are a few enhancement requests for that submitted to Omnis Software as well.
Anyway if I have an object class called oInvoice and inside I have a row variable called ivHeaderRow defined from my table class. I can create a getter and setter method on my object for each column:
$invoiceNumber quit Method ivHeaderRow.invoiceNumber $invoiceNumber.$assign(pvNumber) calculate ivHeaderRow.invoiceNumber as pvNumber
Now on my window where I have an instance variable called ivInvoiceObj of type object reference into which I assign a reference to an instance of my invoice object, I can now set $dataname of my invoice number entry field to ivInvoiceObj.$invoiceNumber
On a redraw Omnis will call my $invoiceNumber method and display the returned value.
When the user changes the value it will call $invoiceNumber.$assign when the user tries to leave the field.
Now this on face value seems to be taking the long way around however now that I have logic on my $invoiceNumber.$assign I can do a lot more then just copy the new value. I can check if the new value is allowed. I can make additional updates to other fields, etc. For me a good example here is changing the amount on an invoice detail. The assign logic doesn’t just update the amount, it calculates the tax, it sums the amounts of all the details together and updates the total amount of the invoice, etc.
As I mentioned to you privately my list of details isn’t actually a list defined from a schema or table class but instead is a list of object references. When I’ve loaded my invoice I actually loop through the details to instantiate an object for each detail and add the object reference to my detail list. Each detail is displayed on my window using its own subwindow which also has the object reference to the detail being shown. It is really amazing what you can achieve GUI wise once you go down this path.
However, and I really stress this, for run of the mill windows, especially in a situation where you’re not working through an API and have direct access to your database, your suggested approach is much easier in so many ways 🙂January 12, 2017 at 5:13 pm #9934Sten-Erik BjörlingParticipant
My approach is to use object classes, utilising table class instances when performing standard SQL routines like update, delete etc. and this controlled within the object class. I am a bit unique since I also mostly prefer to write the SQL manually instead of relying on the approach using schema classes.
By this approach I can change the entity from being managed by an SQL database to being managed by service calls without changing the overall system architecture since the object is acting as a black box with known contractual APIs and result sets.
Another factor of importance can be that the object can be called during system startup and check if relevant tables etc. are present in the system – simplifies installation. I also use the example of Dan Ridinger using the actual tables on the server to build the table classes used in defining the row variables for the table – less risk of versioning conflicts.
I also separate between objects representing entities and objects representing processes or complex operations.
Have to eat pizza, take care…
SteneJanuary 12, 2017 at 10:34 pm #9935
Your remark about the approach inspired by Dan Ridinger is something that is an incredibly interesting subject, maybe even worth a topic on its own.
It is interesting to see how many new frameworks work like this, the database is considered leading and the logic in the framework simply uses the meta data available in the database. There is no duplication of meta data inside of the application. Making changes to the database structure is seen as a task done outside of the application often with the assumption the application is but one of many accessing the same database. Many frameworks do have functionality to push out database changes as part of automatically upgrading to a new build but its often separate or only loosely linked to the rest of the application.
However when we look at many Omnis applications often the eco system is such that the Omnis application stands by itself and for all intents and purposes owns the database. It being the leading source of meta data about the database structure and pushing that structure out to the database itself like we’ve done since native datafile days is a totally valid way of going about things. If you have the meta data in your application itself and can assume it is leading, it makes sense to use it especially if your application is designed to be database agnostic and you may know more about your database structure then the database engine does.
Both approaches are equally valid in my opinion, it really depends on the type of application you are building and to what type of clients you deploy.
It is therefor very strange in my humble opinion that when Omnis Studio 1.0 came out Raining Data (they were Raining Data back then, or still Omnis Software, I can’t remember) they seemed to not have been able to make up their minds on where to go. The schema class only contains about 25% of the meta data to go down the same path as with the Omnis Native Datafile so there is a lot of work to do if you want to go down scenario 2. But for scenario 1 the schema classes are completely overkill.
Anyway, that is a whole new topic on its own:)February 15, 2017 at 9:04 am #10021Andrea ZenParticipant
I agree on Sten’s approach. I see Objects and Tables as two different layers: Objects are a high level layer that should know nothing about the repository you use (database, file…); Tables should access the repository as DAOs.
As for Object References, they are mandatory as soon as you try to apply some modern Design Pattern to your applications (Factory classes, MVC, State pattern…).February 22, 2017 at 3:36 pm #10031
Using a table class that does not have any $sqlclass (neither a schema nor a query) assigned is absolutely great.
The result of the SQL will then automatically define the list instance and thus one would not need to have an object and a list variable in order to use both. One would than just use something like:
.. and the list would be defined and filled with the data from the $load method. While the $load method can be overridden from the super table class and perform an idividual SQL join using “Begin Statement” and “End Statement” commands.
I could probably do the same from within an object class but it would require one more variable in my remote form or window.
As for the argument with the web service I could surely use a list object to get the data rather an object variable. I do not see much difference only that I need one more variable.February 22, 2017 at 9:32 pm #10036
Andreas yeah I love using that approach. Do note there is one pitfall with it, at least with postgres, and that is that text fields tent to become character fields with a sublen of 0. When you try and update the contents of the list Omnis will panic as it will treat the column as one that can’t hold characters.
It’s easily remedied, I’ve overridden my $fetch to fetch into a temporary list, copy the column definitions making a few choice changes and then merge the temporary list into my real list.February 23, 2017 at 8:17 pm #10039Klaus SchrödlKeymaster
Didn’t know that either. But it sounds really great.
Will try it tomorrow in a project, I’m currently working on.
You must be logged in to reply to this topic.