• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ActiveRecord for CodeIgniter: Rails-style model interactions

[eluser]beemr[/eluser]
OK, so I just tried it, and the Model remained intact. I didn't verify whether or not a library would get squashed, but it was the "name" thing that really brought fear.

I already had a model named "role" with a column "name", so I added this model:
Code:
<?php
class Name extends ActiveRecord
{
    function __construct ()
    {
        parent::ActiveRecord();
    }
    
    function check()
    {
        die("I die, yet I live");
    }

}
?>

And this library:
Code:
function collision()
    {
        $this->load->model("role");
        $this->load->model("name");
        $this->role->set_values(array('id'=>1,'name'=>'vegetable'));
        $this->role->update();
        echo ($this->name);
        $this->name->check();
    }

The result: The Model function dies as expected, and my table has the value "vegetable" inserted at id position 1. Before it dies, it also produces the error:
A PHP Error was encountered

Severity: 4096

Message: Object of class Name could not be converted to string

Filename: controllers/welcome.php

Line Number: 71

So, I think external Model namespaces are preserved safely. Whew!

[eluser]webthink[/eluser]
I think I may alter the class to use non-conventional foreign key naming. I'm all for convention over configuration but sometimes it just doesn't work. Naming the foreign key field fktable_id works most of the time but I've encountered a few scenarios where it doesn't.

The first is mostly aesthetic but annoying none-the-less
I have a users table. There are two types of users client and manager. I have a column called user_id (following FK relationship conventions) for each client this id houses the id of it's account_manager. I'd much prefer this field to be called account_manager_id. Again this is just aesthetic but ending up with an object that looks like:
first_name
last_name
user_first_name
user_last_name

IMO is far less intuitive than an object that looks like:
first_name
last_name
account_manager_first_name
account_manager_last_name

My second example is far less aesthetic and quite a bit more complicated...
Consider a messaging system where you would typically have a messages table and in that table a sender_id and recipient_id... Neither of those follow FK naming convention and even if I wanted to follow convention I couldn't have two fields called user_id.

If anyone can think of a workaround for this I'm all ears. I think it'll probably require some modification of the class to use non-conventional foreign key names.

[eluser]beemr[/eluser]
[quote author="webthink" date="1207831898"]Consider a messaging system where you would typically have a messages table and in that table a sender_id and recipient_id... Neither of those follow FK naming convention and even if I wanted to follow convention I couldn't have two fields called user_id.[/quote]

I could see that relationship as a HasAndBelongsToMany with a join table between. Since there would already be a 'messages_users' table, 'sender_id' wouldn't really be a true foreign key. It would simply be an attribute of the message. You could then deduce that the receivers would be the rest of the joined users whose id's don't equal the message's 'sender_id'. Or, your controller could just pop the 'sender_id' out of the results array before messages get posted.

Alternately, you could assign User a HasMany to Message which BelongsTo User. In this option, messages would have a 'receiver_id' attribute that you'd have to use sans the usual "automagic". Once you've joined message to user, find the receiver_id from user.

As far as aesthetics, I can't say that I am as concerned about that. I just like to know that the names of my models, tables, and joins can be anticipated from any point in the code. If FK's could be named flexibly, I think the library would lose that benefit.

[eluser]webthink[/eluser]
When I say aesthetics I'm not just talking about nice looking column names. I'm talking about names that are intuitive and describe the data actually being stored.

Consider the following example
A user's birth city. simply naming the column city_id while, it follows the convention, does nothing to describe the data actually being stored, The only thing it does describe is the table it references. Much more intuitive would be birth_city_id ... Add to this the further issue of the possibility of needing to add another foreign key for city of residence... residence_city_id would make sense and describe the data perfectly witout interfering with birth_city_id

Convention doesn't actually have to be abandoned it could simply be expanded.

instead of the current table_id the convention could be descriptor_table_id
In the example above birth_city_id fits nicely into that and has the advantage of telling me both the content of the field and the table to which it is a foreign key.
My returned object might then look something like:
first_name
last_name
birth_city_id
birth_city_name //comes from cities table

In my account manager example I would have account_manager_user_id

In my messages example I would have sender_user_id and recipient_user_id.

Of course in a lot of cases table_id is all that's necessary so any changes I make will allow that to remain.

Once I modify AR to handle these more descriptive foreign key names the only issue left will be to get it to do more than one join to the same table where appropriate such as in the messages example.

[eluser]beemr[/eluser]
Sounds like you are looking for a has_many :through relationship. There's been a lot of excitement about that relationship over in the Rails community, but it also seems to have been a difficult improvement for them to make. Some RoR devs are open about their distaste for it, and it has spawned a set of bugs that seem excessive when compared to HABTM which was whole and intact from the start.

That being said, it has definitely kept my interest as the potential "killer" function of Rails. I'd love to see a form of it implemented for our beloved CodeIgniter. For your particular use case, it sounds as if you'd use has_many :through to create self-referential relationships. I found a link for something similar in RoR.

self-referential has_many :through

[eluser]webthink[/eluser]
I managed to do both.
The modification to allow for descriptive/intuitive foreign key names turned out to be surprisingly easy.

Just add this code in join() after the conditionals inside the foreach where $inflected gets set.

Code:
foreach ($this->get_fields() as $field)
{
    if (stristr($field,$inflected))
    {
        //remove _id from end of field name
        $inflected = substr($field, 0, -3);
    }
}

That's it. This allows you to have a foreign key called something like birth_city_id
and get an object that looks something like

field1
field2
birth_city_id
birth_city_name

The changes required for having <b>multiple</b> foreign keys is a bit more involved.
The following replaces everything from
Code:
foreach ($tables as $inflected=>table)
to
Code:
$alias = $this->_class_name . '_' . $table. '_';
inclusive

here it is
Code:
for ($i=0; $i<count($tables); $i++)
        {
            $table = $tables[$i];
            
            
            if ( is_object($table) && get_parent_class($table) == 'ActiveRecord' )
            {
                $parent = $table;
                $inflected = $table->_class_name;
                $table = $table->_table;
            }
            else if ( !is_string($inflected) )
            {
                $inflected = trim(substr($table, 0, -1));
                $table = trim($table);
            }
            
            if (! array_key_exists($table, $foreign_keys) )
            {
                $foreign_keys[$table] = array();
                foreach ($this->get_fields() as $field)
                {
                    if (stristr($field,$inflected))
                    {
    
                        $foreign_keys[$table][] = $field;
                    }
                }
                if (count($foreign_keys[$table]) > 1)
                {
                    for ($j=0; $j < count($foreign_keys[$table]) - 1; $j++)
                    {
                        $tables [] = $table;
                    }
                }
                $fk_index[$table] = 0;
            }
            //remove _id from end of field name
            $inflected = substr($foreign_keys[$table][$fk_index[$table]], 0, -3);
            $fk_index[$table] ++;
            // Make an alias for a joined table to prevent collision
            $alias = $this->_class_name . '_' . $table. '_'. $fk_index[$table];

...

this will allow you to have foreign keys such as birth_city_id, residence_city_id
and end up with an object that looks like
field1
field2
birth_city_id
birth_city_name
residence_city_id
residence_city_name

These changes preserve the ability to use the regular table_id where appropriate

Hope someone is finds it helpful.

[eluser]webthink[/eluser]
I don't think has_many_through is what I wanted but it is very interesting. If I understand what it does correctly I think my modification may actually, unwittingly, help accomplish it.

If in the user model you define has_and_belongs_to_many ('users') relationship (I'm not 100% sure this is possible).

You could then have a users_users table with the fields first_user_id, second_user_id (my mod allows those fk conventions and allows you to join the same table on seperate foreign keys)

The question mark is really the first part. I do know for instance that you can already do a self referential join by doing something like $this->user->join('users')->find_all(); If you've got a field called user_id in your users table.

[eluser]beemr[/eluser]
I use Aptana IDE and its code tips show the __call function as deprecated for SoapClient->__soapCall. Do we need to have an exit strategy for Activerecord_Class, or by "deprecated," do they really mean the same thing as in HTML where <font> is also "deprecated"?

[eluser]nmweb[/eluser]
__call is not deprecated in php. They introduced it in php5, in fact in 5.3 they are introducing a __static_call or something like it so you can do someclass:Confusedome_call() where some_call is an unknown method which will be directed to __static_call()

[eluser]Unknown[/eluser]
[quote author="chromice" date="1193310405"]I wonder why there are so many eval(...) statements in this library? Is it to tackle bugs in some version of PHP5 or something?

For instance:
Code:
eval('return $this->' . $method . ';');
and
Code:
return $this->$method;
or even
Code:
eval('$return = new ' . $this->_class_name . '();');
and
Code:
$return = new $this->_class_name();
work the same, while eval version is somewhat slower and less readable?

Am I missing something?[/quote]

was there ever a reply to this question? i couldn't find anything browsing through this thread.

thanks all Smile

EDIT: actually i noticed a spot where using eval() could be a problem. line 123 reads:
Code:
eval('$return->' . $key . ' = "' . $value . '";');
if $value contains quotes, this could generate an error, i think...


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


  Theme © 2014 iAndrew  
Powered By MyBB, © 2002-2021 MyBB Group.