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

#91
[eluser]webthink[/eluser]
Ok I've made another alteration to the class.
I wanted the ability to update a number of records without holding them as instances in my model and without knowing their primary keys. In other words do an UPDATE...WHERE... using an arbitrary where clause possibly updating a number of records at the same time.

I think if we were going to make use of all the additional clauses (WHERE, LIKE etc) going forward the architecture should probably change to use something similar to the _compile_select method. But this alteration will work to add custom where clauses to an update statement.

The first thing I did was to add an array to store our where clauses. Currently the where() method is used to build the actual where clause string rather than hold the conditions in a associative array. We need the associative array.

Code:
function ActiveRecord ()
{
    parent::Model();
    
    $this->_class_name = strtolower(get_class($this));
    $this->_table = $this->_class_name . 's';
    $this->_primary_keys = array('id');
    $this->_where_array = array();
//...

Then we need to alter the where method to store the field and value
Code:
function where ($key, $value = null)
{
    $this->_where_array[$key] = $value;
        $this->_add_clause($this->_where, $key, $value, 'AND', 'WHERE');
        
        return $this;
}

This sets us up to change the update method so that it now a)looks for these where clauses and b)accepts an array of values for the update.

Code:
/**
* update
*
* Similar to the save() method, except that it will update the row
* corresponding to the current object.
*
* or will now accept an array of new values and update rows corrosponding preset where conditions
*
* @access    public
* @param   array
* @return    void
*/
function update ($values=array())
{
    $data = new stdclass();
    
    // Cache the list of fields
    if ( $this->_cache_structure ) $this->db->cache_on();
    
    if (count($values) == 0)
    {
        foreach ( $this->db->list_fields($this->_table) as $field )
        {
        if ( !in_array($field,$this->_primary_keys)  &&property;_exists($this,$field) )
        {
                $data->$field = $this->$field;
            }
        }
        $values = get_object_vars($data);
    }
    // Stop caching
    if ( $this->_cache_structure ) $this->db->cache_off();
    
    // Serialize the data
    if ( method_exists($this,'serialize') )
        $this->serialize($data);
        if ( count($this->_where_array) > 0 )
    {
        $where = $this->_where_array;
    }
    else
    {
        foreach( $this->_primary_keys as $key )
        {
            if ( property_exists($this,$key) )
                $where[$key] = $this->$key;
            else
                log_message('error', 'Cannot update ' . $this->_table . ' : $this->' . $key . ' is not set!');            
        }
    }        

    $sql = $this->db->update_string($this->_table, $values, $where);
    if ( !$this->db->query($sql) )
    {
        log_message('error', $this->db->last_query());
    }
    $this->_clean();
}

The only other change to make is that we need to make sure we release the stored where array after each query.
Code:
function _clean ()
{
    $this->_select = array();    // all columns
    $this->_fields = array();    // columns to be returned
    $this->_join = array();
    $this->_where = '';
    $this->_where_array = array();
//...

Now we can update multiple records by making a call like
Code:
$this->user->where('user_type',5)->update(array ('confirmed'=>1) );

But as I stated in the post above I prefer calling it with dynamic methods so I added the ability to call
Code:
$this->user->update_all_by_user_type(5, array ('confirmed'=>1) );

So I added the following
Code:
function __call ($method, $args)
{
    $watch = array(
        'find_by_',
        'find_all_by_',
        'update_all_by_',
//...

And then added the function just below _find_all_by()

Code:
/**
* _update_all_by
*
* Updates all records using fieldname as where clause when called as update_all_by_[fieldname]
*
* @access    private
* @param    string
* @param    array
* @return    array
*/
function _update_all_by ($field, $args)
{
    $field = $this->_table . '.' . $field;
        switch ( $args[0] )
    {
        case IS_NULL:
            $this->where($field . IS_NULL);
            break;
        case NOT_NULL:
            $this->where($field . NOT_NULL);
            break;
        default:
            $this->where($field, $args[0]);
    }
        return $this->update(( isset($args[1]) ) ? $args[1] : null );
}

I hope this helps someone.

Cheers

#92
[eluser]cayasso[/eluser]
@webthink

Thank you a lot for gremlin clarification, I didnt see what I was doing wrong Smile
and I am implementing your changes at this point, they looks great

Thanks for the collaboration!!!

#93
[eluser]beemr[/eluser]
The core extension library, MY_Validation_-_Callbacks_into_Models, makes a great complement to this one. I put up a simple example for validating uniqueness in its wiki, MY_Validation_-_Callbacks_into_Models.

#94
[eluser]beemr[/eluser]
Here, to save you a click, this is what I wrote.

To integrate a “unique” validation into models based on the Activerecord_Class_Mod library, simply tie in the object features and put your standard callback functions into application/libraries/Activerecord.php:
Code:
function is_unique($value, $field)
    {
    $this->validation->set_message($this->_class_name.'->is_unique','The %s "'.$value.'" is not available. Try a different %s.');
    $this->db->where($field,$value);
    $this->db->from($this->_table);
    return ($this->db->count_all_results() == 0);
    }

#95
[eluser]webthink[/eluser]
I've run into a fairly problematic namespace issue with this class.
It happens if one of your column names is the same as one of the models (or presumably libraries etc) you load then they get overwritten when trying to update using active record.

Consider the following case:
I have a table/model called category with the fields id, category
when you use $this->category->set_values(array(id=>1,id=>'vegetable'));
$this->category at that point ceases to hold your object and instead contains the string 'vegetable'
so any call you try to make after that like
$this->category->update(); will fail because it's not an object.

This will happen if any of your column names interfere with any of your models, or libraries or $this->anything you happen to load.

I'm considering changing active record to use an array called $table_data or something which set_values will write to and update and save methods will use. Can anyone see any problems with this approach?

#96
[eluser]m4rw3r[/eluser]
Maybe construct the model as a factory, that creates Active Record objects (with a reference to the model (which they use to get data from the database))?

#97
[eluser]beemr[/eluser]
I think you might be missing the point of libraries such as these. The standard mantra is "convention over configuration", so the value is placed on opinionated direction rather than open flexibility. In your example, the naming is not true to the spirit of the convention.
If you consider your "categories" table as an object based on its model "Category", you wouldn't want to use the same name for your column. The object name of that column would be a redundant "Category.category".
However, if you accept the convention that object names should have sensible meaning, you would simply name the column "name", so that you could call it as "Category.name" which gives your code clarity for the small price of a simple naming convention.

#98
[eluser]webthink[/eluser]
and if for whatever reason i had a library called 'name'? Or any model/library matching any of my field names? I think you're missing the point of the issue.

#99
[eluser]beemr[/eluser]
Of course, something like a library could easily be prefixed to avoid name collisions.

However, suppose you have two models, Category and Name, and you have 'id' and 'name' inside of category. Are you saying that $this->category->set_values(array(id=>1,name=>’vegetable’)) and calling $this->category->update(), will instead turn the unreferenced model Name into a string containing 'vegetable'?

[eluser]webthink[/eluser]
That's exactly the problem. Of course with a bit of creative naming the issue can be avoided... But it's an inconvenience that when you're designing or altering your db you have to keep in mind all the names of all your models and all your libraries and all of codeigniters libraries as well. Don't name any of your columns 'validation' for instance Smile What we did was create an array called $table_data to store the values.
This has made it a non issue for me. Another way it could probably be avoided is to prefix all column names with the table name (so 'category_name') much the way joined table's fields are prefixed currently.


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


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