Editing a Record with a HasManyThrough

View: New views
3 Messages — Rating Filter:   Alert me  

Editing a Record with a HasManyThrough

by Jon Elofson :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Since Alpha3 came out, I thought I would give it a bit of a workout
searching for bugs and the like. Basically, I am running scenarios
that mimic the things we do on a daily basis. For this one, I am
mimicking a blog database and updating a post. The post can have many
categories. I am setting the post's categories at the same time as I
add or edit the post without ajax-type events. Pretty straightforward.
I just want to make sure that my methodology is sound.
Much of the code I used from the Solar Bookmarks example.

I have my tables set correctly and the relationships are fine.

posts has many categories through posts_categories
posts has many posts_categories
categories has many posts through posts_categories
categories has many posts_categories
posts_categories belongs to posts
posts_categories belongs to categories

I am using a multiselect to pick my categories for the post.
I set my form up to use a column called 'cats'. The options of this
select are a categories->fetchPairs().
In my Posts model, I have a $_calculate_cols[] = 'cats';
I have two methods in the Posts record __setCats($vals) and __getCats()

__setCats($vals) looks like this:
if (! $this->categories) {
    $this->categories = $this->newRelated('categories');
}
$this->categories->setCategoryIds($vals);
$this->_data['cats'] = $this->categories->getCategoryIds();


__getCats() looks like this:

if (empty($this->_data['cats'])) {
    if ($this->categories) {
        $this->_data['cats'] = $this->categories->getCategoryIds();
    }
}
return $this->_data['cats'];

I have two methods in my Categories Collection; getCategoryIds() and
setCategoryIds($vals)

getCategoryIds() looks like this:

$clone = clone($this);
$category_ids = array();
foreach ($clone as $category) {
    $category_ids[] = $category->category_id;
}
return $category_ids;

setCategoryIds($vals) looks like this:

$model = $this->getModel();
$vals = (array) $vals;
$clone = clone($this);
foreach ($clone as $key => $category) {
    if (! in_array($category->category_id, $vals)) {
        $this->removeOne($key);
    }
}
$remaining = $this->getCategoryIds();
foreach ($vals as $category_id) {
    if (! in_array($category_id, $remaining)) {
        $category = $model->fetch($category_id);
        if ($category) {
            $this[] = $category;
        }
    }
}


All of this seems to work fine and is pretty logical. I guess I am
just wondering if this is the "proper" way to go about editing related
many-to-many relationships on the fly. Is there a better way?

Thanks,

Jon
_______________________________________________
Solar-talk mailing list
Solar-talk@...
http://mailman-mail5.webfaction.com/listinfo/solar-talk

Re: Editing a Record with a HasManyThrough

by Paul M Jones-4 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi Jon,

Starting first with your last question:

> All of this seems to work fine and is pretty logical. I guess I am
> just wondering if this is the "proper" way to go about editing related
> many-to-many relationships on the fly. Is there a better way?

As far as I can tell from a once-over, you're doing it correctly for  
the code in trunk.  Nice examples; thanks for writing them up.

FYI, I am making some changes in branches/unfounded that will simplify  
some of the idioms used in these cases; read on for notes about the  
changes in branches/unfounded.


> __setCats($vals) looks like this:
> if (! $this->categories) {
>    $this->categories = $this->newRelated('categories');
> }
> $this->categories->setCategoryIds($vals);
> $this->_data['cats'] = $this->categories->getCategoryIds();

The branches/unfounded codebase makes it so that you don't need the  
`if (! $this->foo) { $this->foo = $this->newRelated('foo'); }` idiom  
any more.  In your case, when using branches/unfounded, you can use  
$this->categories safely at all times, even if there were no related  
categories in the database already.  The replacement code will look  
something like this:

     public function __setCats($vals)
     {
         $this->categories->setCategoryIds($vals);
         $this->_data['cats'] = $this->categories->getCategoryIds();
     }

The idea in branches/unfounded is, if a record or collection is not  
found, you no longer get back a null or array().  Instead, you get  
back an actual Record or Collection object, with a method called  
isEmpty(). Calling $record->isEmpty() or $collection->isEmpty() is the  
equivalent of calling empty($record) or empty($collection).

An "empty" (not-found) record will not be saved, even if you call  
save() on it.  However, The moment you attempt to set a value on the  
empty record, it converts itself to a "new" record, and so will be  
saved at the appropriate time.  (The same goes for collections.)


> __getCats() looks like this:
>
> if (empty($this->_data['cats'])) {
>    if ($this->categories) {
>        $this->_data['cats'] = $this->categories->getCategoryIds();
>    }
> }
> return $this->_data['cats'];

For the same reasons as above, under branches/unfounded, you can skip  
the `if ($this->categories)` check here as well.  The replacement code  
would look like this:

     public function __getCats()
     {
         if (empty($this->_data['cats'])) {
            $this->_data['cats'] = $this->categories->getCategoryIds();
         }

         return $this->_data['cats'];
     }


> I have two methods in my Categories Collection; getCategoryIds() and
> setCategoryIds($vals)
>
> getCategoryIds() looks like this:
>
> $clone = clone($this);
> $category_ids = array();
> foreach ($clone as $category) {
>    $category_ids[] = $category->category_id;
> }
> return $category_ids;
>
> setCategoryIds($vals) looks like this:
>
> $model = $this->getModel();
> $vals = (array) $vals;
> $clone = clone($this);
> foreach ($clone as $key => $category) {
>    if (! in_array($category->category_id, $vals)) {
>        $this->removeOne($key);
>    }
> }
> $remaining = $this->getCategoryIds();
> foreach ($vals as $category_id) {
>    if (! in_array($category_id, $remaining)) {
>        $category = $model->fetch($category_id);
>        if ($category) {
>            $this[] = $category;
>        }
>    }
> }

N.b.: The getCategoryIds() method could be replaced, even in trunk,  
with a call to getColVals('category_id').  The method is implemented  
in Solar_Sql_Model_Collection.

Regarding the need for clone():  I have some new code for branches/
unfounded, as yet uncommitted, that will make the clone() step here  
unnecessary.  Instead of implementing Iterator, Solar_Struct will  
implement IteratorAggregate, which should eliminate the iteration  
conflicts that the clone() solution works around.

As such, the replacement code might look something like this:

     public function getCategoryIds()
     {
         return $this->getColVals('category_id');
     }

     public function setCategoryIds($vals)
     {
         $model = $this->getModel();
         $vals = (array) $vals;

         foreach ($this as $key => $category) {
            if (! in_array($category->category_id, $vals)) {
                $this->removeOne($key);
            }
         }

         $remaining = $this->getCategoryIds();
         foreach ($vals as $category_id) {
            if (! in_array($category_id, $remaining)) {
                $category = $model->fetch($category_id);
                /* note the use of isEmpty() below */
                if (! $category->isEmpty()) {
                    $this[] = $category;
                }
            }
         }
     }

I can see a case for automating stuff like setCategoryIds() because  
its such a common need, but I am not sure yet how to go about it.

Hope all this helps, and thanks again for taking the time to write up  
these examples. :-)


--

Paul M. Jones
http://paul-m-jones.com/




_______________________________________________
Solar-talk mailing list
Solar-talk@...
http://mailman-mail5.webfaction.com/listinfo/solar-talk

Re: Editing a Record with a HasManyThrough

by Jon Elofson :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Paul,
Thanks very much for taking the time to go through my code so
thoroughly. It's a big help and I appreciate it. Sounds like lots of
nice enhancements are forthcoming! It (Solar) gets better every day :)

Jon



On Mon, Sep 21, 2009 at 10:05 AM, Paul M Jones <pmjones@...> wrote:

> Hi Jon,
>
> Starting first with your last question:
>
>> All of this seems to work fine and is pretty logical. I guess I am
>> just wondering if this is the "proper" way to go about editing related
>> many-to-many relationships on the fly. Is there a better way?
>
> As far as I can tell from a once-over, you're doing it correctly for
> the code in trunk.  Nice examples; thanks for writing them up.
>
> FYI, I am making some changes in branches/unfounded that will simplify
> some of the idioms used in these cases; read on for notes about the
> changes in branches/unfounded.
>
>
>> __setCats($vals) looks like this:
>> if (! $this->categories) {
>>    $this->categories = $this->newRelated('categories');
>> }
>> $this->categories->setCategoryIds($vals);
>> $this->_data['cats'] = $this->categories->getCategoryIds();
>
> The branches/unfounded codebase makes it so that you don't need the
> `if (! $this->foo) { $this->foo = $this->newRelated('foo'); }` idiom
> any more.  In your case, when using branches/unfounded, you can use
> $this->categories safely at all times, even if there were no related
> categories in the database already.  The replacement code will look
> something like this:
>
>     public function __setCats($vals)
>     {
>         $this->categories->setCategoryIds($vals);
>         $this->_data['cats'] = $this->categories->getCategoryIds();
>     }
>
> The idea in branches/unfounded is, if a record or collection is not
> found, you no longer get back a null or array().  Instead, you get
> back an actual Record or Collection object, with a method called
> isEmpty(). Calling $record->isEmpty() or $collection->isEmpty() is the
> equivalent of calling empty($record) or empty($collection).
>
> An "empty" (not-found) record will not be saved, even if you call
> save() on it.  However, The moment you attempt to set a value on the
> empty record, it converts itself to a "new" record, and so will be
> saved at the appropriate time.  (The same goes for collections.)
>
>
>> __getCats() looks like this:
>>
>> if (empty($this->_data['cats'])) {
>>    if ($this->categories) {
>>        $this->_data['cats'] = $this->categories->getCategoryIds();
>>    }
>> }
>> return $this->_data['cats'];
>
> For the same reasons as above, under branches/unfounded, you can skip
> the `if ($this->categories)` check here as well.  The replacement code
> would look like this:
>
>     public function __getCats()
>     {
>         if (empty($this->_data['cats'])) {
>            $this->_data['cats'] = $this->categories->getCategoryIds();
>         }
>
>         return $this->_data['cats'];
>     }
>
>
>> I have two methods in my Categories Collection; getCategoryIds() and
>> setCategoryIds($vals)
>>
>> getCategoryIds() looks like this:
>>
>> $clone = clone($this);
>> $category_ids = array();
>> foreach ($clone as $category) {
>>    $category_ids[] = $category->category_id;
>> }
>> return $category_ids;
>>
>> setCategoryIds($vals) looks like this:
>>
>> $model = $this->getModel();
>> $vals = (array) $vals;
>> $clone = clone($this);
>> foreach ($clone as $key => $category) {
>>    if (! in_array($category->category_id, $vals)) {
>>        $this->removeOne($key);
>>    }
>> }
>> $remaining = $this->getCategoryIds();
>> foreach ($vals as $category_id) {
>>    if (! in_array($category_id, $remaining)) {
>>        $category = $model->fetch($category_id);
>>        if ($category) {
>>            $this[] = $category;
>>        }
>>    }
>> }
>
> N.b.: The getCategoryIds() method could be replaced, even in trunk,
> with a call to getColVals('category_id').  The method is implemented
> in Solar_Sql_Model_Collection.
>
> Regarding the need for clone():  I have some new code for branches/
> unfounded, as yet uncommitted, that will make the clone() step here
> unnecessary.  Instead of implementing Iterator, Solar_Struct will
> implement IteratorAggregate, which should eliminate the iteration
> conflicts that the clone() solution works around.
>
> As such, the replacement code might look something like this:
>
>     public function getCategoryIds()
>     {
>         return $this->getColVals('category_id');
>     }
>
>     public function setCategoryIds($vals)
>     {
>         $model = $this->getModel();
>         $vals = (array) $vals;
>
>         foreach ($this as $key => $category) {
>            if (! in_array($category->category_id, $vals)) {
>                $this->removeOne($key);
>            }
>         }
>
>         $remaining = $this->getCategoryIds();
>         foreach ($vals as $category_id) {
>            if (! in_array($category_id, $remaining)) {
>                $category = $model->fetch($category_id);
>                /* note the use of isEmpty() below */
>                if (! $category->isEmpty()) {
>                    $this[] = $category;
>                }
>            }
>         }
>     }
>
> I can see a case for automating stuff like setCategoryIds() because
> its such a common need, but I am not sure yet how to go about it.
>
> Hope all this helps, and thanks again for taking the time to write up
> these examples. :-)
>
>
> --
>
> Paul M. Jones
> http://paul-m-jones.com/
>
>
>
>
> _______________________________________________
> Solar-talk mailing list
> Solar-talk@...
> http://mailman-mail5.webfaction.com/listinfo/solar-talk
>
_______________________________________________
Solar-talk mailing list
Solar-talk@...
http://mailman-mail5.webfaction.com/listinfo/solar-talk