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}$’);


I have chosen this database-design to make the user data extendable.

I want to access userdata a hash-like way with the user:

user.id: <%= @user.id %>

user.fullname: <%= @user.user_data_fields[:fullname] %>

user.zip: <%= @user.user_data_fields[:zip] %>

So I decided to modify the User model:

class User < ActiveRecord::Base
has_many :user_data_fields

    def after_find
            def user_data_fields.[](k)
                    if k.type != Symbol
                            super
                    else
                            self.each { |f| return f.value if

f.user_data_type.code.to_sym == k }
nil
end
end
end
end


Well, this works pretty well. It causes three selects, which can be
reduced to two with using :include option of the find method.

However, since I am beginner, I would like to get some confirmation. Is
the after_find a right place to implement such a class extension? Is
there an easier way that I missed?

Thank you for your time.

      Mage

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

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

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 :)
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’)

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

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