extending ActiveModel to handle multiple tables as hash

Hello,

I am just introducing myself to Rails. Please let me know if the above method is acceptable or if it is not efficient.

I have three tables:

create table users (
id bigserial primary key,
username varchar,
password varchar,
-- more system specific data
);

create table user_data_types (
id bigserial primary key,
name varchar,
code varchar,
restriction varchar
);

create table user_data_fields (
id bigserial primary key,
user_id bigint not null references users(id) on delete cascade,
user_data_type_id bigint not null references user_data_types(id) on delete cascade,
value varchar
);

insert into user_data_types (name, code, restriction) values ('Fullname', 'fullname',null);
insert into user_data_types (name, code, restriction) values ('Zipcode', 'zip','^[\d]{4}$');

Hello Mage,
    If I may jst take a couple of seconds of your life, and recount a
(sadly) true story;

    I started work about 7 years back, at a very large scale bank. I had
been brought in to try and diagnose why there was a slow down in the
current system that they were using. It was a web-based order tracking
system. The chief designer at the time had very little knowledge of
database's, nor what information was going to be stored in the system,
nor infact, how the system would be used on a day to day basis. So. He
did what your doing, or rather, he took it one step further.

    He created a 'flex' table. This had three columns inside. The first
was 'tablename', the second was 'column name' and the third was 'value'.
This allowed him to add "columns" to the "table", or even create whole
new "tables" on an 'ad-hoc' basis. That was perhaps okay for the test
sytem, and then after this, he could say 'users should have firstname,
and lastname' etc. However, he left it and let it go into production.

    So, when the amount of records hit 200k, and each 'record' had 17
'fields', well, that meant that -one- table had 17 rows, when it should
have only had 1. Now, imagine storing around 40 tables inside this one
'flex' table.

    Mess.

    Now, if we can relate this to your situation, your definitely going
down that road from what I can see, but, your also eschewing the whole
'validates_' helpers as well as getting rid of the 'R' in RDBMS. How do
you expect to have any 'relational' query perform well agianst a "flex"
table ?. That 'restriction' column also looks -suspiciously- like an
attempt at a constraint, which, is re-inventing the wheel (and not
'well', no offense meant). I would say either use the database
constraints and/or the rails helpers.

    This is, at least to my eyes, what you appear to be doing. If
nothing else, if not even for your own sanity, please think of any poor
programmers who come after you. It's true that programmers often think
differently about the same problem, but, this is not the best way to go.

    This is not an attack, nor a 'chewing out', but, perhaps a
suggestion to re-think how your using Rails. If this email offends you
in anyway, feel free to ignore these as the mindless ramblings of a
grizzled but optimistic programmer :slight_smile:

    Regards
    Stef

Mage wrote:

Hello Stef,

this e-mail doesn't offend me, moreover thank you for your time. I am glad to read it.

Like You I also have seen some messed databases, and even I know that coherent data should be put into one (but not two, three) table.

However, in the past I had to implement different login models every time when I started writing a new web-based application. My contractors wanted different things to be stored, to be logged in every systems we made.

I am trying to create a login system which is user-data independent. Which will be very same in every of my rails application. This is the reason of I put application-specific user-data and activity logs into separated tables.

I understand your point. Putting "Fullname" into another table is a very bad example. This field should be accessed at every page load, maybe because at the top of the page there will be a "Hello Mr./Mrs. #{fullname}" like text. However the chances are that zipcode, street, country data, postal address, phone number will not be read at most of page loads. User-data modification form will be used rarely. And if they are not accessed then they will not be read from the database.

Trivial data (fullname, email address) really must go into the "users" table.

You mentioned validators, which, in my opinion are a complicated part of Rails. Nothing but database rules, triggers and constraints can make the database consistent for sure. I cannot implement a system based only on Rails validators. I simply can't. It would hurt me. I put every important constraints into the database too. Even because there might be cases when other clients are reading or writing the database, and they will not have the validators.

I was been thinking of this for a while, because it seems to break the DRY property of Rails, and then it turned out (for me) that is not a problem, even not the case. I think that validators are for telling the user in a human-readable, easy understable way that his/her entered data is wrong. Database constaints are for protecting the database's consistence for 100%. I have to use both.

I think these field-specific constrains at the bottom of my email (zip => "^[\d]{4}$") in the column "restriction" will only be used by the validators, because they are not related to consistence, only to business rules.

What's your opinion after reading this? Did it change a bit?

       Mage

Stef T wrote:

Hello Mage,
    Good good, jst making sure that I don't cause offense. Sometimes,
programmers tend to have .. urm .. 'code love' that verge's on 'code
obsession' (Eg; my way is -THE- right way ;). I don't think there is
anything 'wrong' or 'bad' about thinking about a problem differently,
and good to know that you don't either :slight_smile:

    I heartily agree with you that you should really use validators
-AND- constraints, or rather, use logic at the user level as -well- as
putting the checks in the database as well. I can understand that you
may want other clients accessing your database, so you want to stop them
from putting in a non-zip into a zip field. I can even understand the
intent of DRY'ing the code into the database and then grabbing it out to
use as a constraint inside RoR, although, this does then seem to be
R'ing in the DRY. It's also redundant in my eyes, because there is
nothing to say that other clients will even honor the restraints at the
user level, so storing them appears - well - redundant. However, thats
your personal call, your closer to the application than me :wink:

    I guess my other question/query is, -why- are you wanting to
re-implement an authentication system ? *chuckles* *raises hands* now
now, not that I am saying its a bad idea if you want to learn and play
with RoR. Heck, I make test systems that do all sorts of 'funky' things
and then promptly fling them away. If its not, however, may I suggest
one of the pre-rolled gems ?

    script/plugin discover
    script/plugin install acts_as_authenticated
    script/generate authenticated User Account
    before_filter :login_required

    In your controller and you have it protected by using the gem
acts_as_authenticated. I think it has quite a bit of other goodness
inside it as well. Path of Least Work - in a real project - would be the
way I would go :wink: There is also user_engine of course. Ymmv :wink:

    Then again, if your doing this as a -learning- exercise, more power
to you. Personally, I would have a table of 'logins' which keeps track
of whom authenticated (or tried to). A login page would actually query
directly against the user table (using username, password and 'isActive'
or such). on success (or failure :wink: fling a 'login attempt/success' into
the login table and it should all be good. Then again, if your doing
this to learn, then whichever way you want to do it is 'the right way'.
The 'wrong way', when your learning, will become apparent, as you will
have to start adding layer and layer of complexity into the system.
Remember, in Ruby (to horribly mangle Keats) 'simple is beauty, and
beauty is simple'*

    Regards, good luck, and let us all know what you decide to do :slight_smile:
    Stef
(* The original quotation is, of course, 'Beauty is truth, truth beauty
- that is all, Ye know on earth, and all ye need to know')

Mage wrote:

Hello Stef T,

of course I like my code, but if you say things which extend my mind
then I will write better code. And I will love the better code more.

The reason of putting regular expression contrains into database is that
these fields are dynamically extendable.

With using a static user table, these restrictions would go to the
model, but if I define the new user-attributes in a table, shouldn't I
put the adequat rules nearby? And if one day I happen to implement these
format-restrictions in database level, I will only have to write a
stored procedure in plperl or plpython or, what I really wish, that day
postgresql will support plruby (officially).

(Well, I am not sure. I am just thinking...)

You guessed well, I implement the login system for learning Rails, which
seems to be a good lesson to challenge my fresh knowledge about models,
sessions, cookies, controllers, partial templates and maybe even AJAX. I
keep the book open although.

On the second hand, there are many types of authentication. I will write
a scalable one, allowing one user to be logged from multiple computers
at same time, optionally keeping each of his login alive on the
computers he/she use ('remember me' function). He must be able to log
out from every logged computer at once if he wants. I also will
implement toggleable logging of ip addresses browser types.

Maybe there is a ready-to-use generator for all of these, but what I
mostly like in Ruby and Rails that coding is very fast. I will be
finished in one day or two, then, as you have written, I can fine tune
my code.

And I am curious to know the answer for my first question, in general.
Is the after_find callback a good place to extend a model's children
array? I was a bit surprised when realized that has_many relationship
makes the children a simple array of similar activemodels. I was
expected one, array-like object.

       Mage

Stef T wrote:

Hello Mage,

Mage wrote:

format-restrictions in database level, I will only have to write a stored procedure in plperl or plpython or, what I really wish, that day postgresql will support plruby (officially).

(Well, I am not sure. I am just thinking...)

Oh no, perfectly okay, although thinking tends to get me into trouble :wink: You know, if you look for dr nic and his magic modules, there is even a way to have constraints such as maxlength etc enforced without you having to use validates.. may not help in the case of the zip but.. you get the idea :slight_smile: Food for thought, either way :slight_smile:

You guessed well, I implement the login system for learning Rails, which seems to be a good lesson to challenge my fresh knowledge about models, sessions, cookies, controllers, partial templates and maybe even AJAX. I keep the book open although.

If its all for 'learning', then thats perfectly understandable. Be advised though, that acts_as_authenticate also has (on its homepage I believe) snippets of code. One of which does the 'remember me' function you describe :wink:

Maybe there is a ready-to-use generator for all of these, but what I mostly like in Ruby and Rails that coding is very fast. I will be finished in one day or two, then, as you have written, I can fine tune my code.

Ah. once on the lips, lifetime on the hips. (or to re-phrase, easy to code, harder to debug :). But, again, your code and your learning route :smiley:

And I am curious to know the answer for my first question, in general. Is the after_find callback a good place to extend a model's children array? I was a bit surprised when realized that has_many relationship makes the children a simple array of similar activemodels. I was expected one, array-like object.

To be honest, in 'general' in Rails, I don't think that most people -do- extend the children's array after a find [watch someone now quote that in application X they do Y because of Z ;]. So. if it works for you, it works *polite shrug* Your trying to work around Rails other functions, so why worry about this one :stuck_out_tongue_winking_eye: (joking, sort of, but not meaning it in a derogatory way, I assure you :slight_smile:

Regards and happy hacking
Stef