How To Factorise Named Scopes

Hi there,

I have three models: Task, Worker and Material, where a Material BELONGS_TO a Task and a Task HAS_MANY workers. The named scope [color="#0000FF"]unassigned[/color] inside my Task model refers to those tasks to which no worker is assigned:



public function scopes() {

    return array(

        'unassigned' => array(

            'with' => 'workers',

            'condition' => 'workers.id is null',

            'together' => true

        )

    );

}



Similarly, the [color="#0000FF"]unassignedTask[/color] scope inside my Material model refers to materials belonging to those tasks to which no worker is assigned:




public function scopes() {

    return array(

        'unassignedTask' => array(

            'with' => 'task.workers',

            'condition' => 'workers.id is null',

            'together' => true

        )

    );

}



I have simplified thoroughly, but this example illustrates my main concern, that is, the code looks redundant and forces me to synchronize code in multiple files of my application.

Is there any way I could factorise to above code so that the unassignedTask scope somehow "goes" through the Task model? What would you do?

No one? Any suggestion will be appreciated.

Dear Friend

I think you want to acheive something like this.




$materials=Material::model()->with(array('task'=>array(


'scopes'=>array('unassigned'),


)))->findAll();



I miserably failed to acheive that.




public function scopes() {

    return array(

        'unassigned' => array(

            'with' => 'workers',

            'together' => true,

            'condition' => '`workers`.`id` is null',

        )

    );

   }



Because the where clause in the above code is attached to join clause somehow.

But the following code works fine.




tasks=Task::model()->unassigned()->findAll();



I am totally lost.

Are you expecting something different?

Hello seenivasan!

thank you for your answer. I think you have understood very well my problem; as you say the [b]where[/b] clause is somehow attached to the [b]join[/b] clause and I cannot see how could I define the scope just once in the Task model and make use of it in the Material model.





Still looking for a way...

What exactly failed with this :


$materials=Material::model()->with(array('task'=>array(


'scopes'=>array('unassigned'),


)))->findAll();

Dear Friend

This is the error.

Tables:

1.task(id,name)

2.worker(id,name,task_id)

3.material(id,name,task_id)

material BELONGS_TO task

task HAS_MANY workers.

[color="#000080"]Column not found: 1054 [color="#FF0000"]Unknown column ‘workers.id’ in ‘on clause’[/color]. The SQL statement executed was: SELECT t.id AS t0_c0, t.name AS t0_c1, t.task_id AS t0_c2, task.id AS t1_c0, task.name AS t1_c1, workers.id AS t2_c0, workers.name AS t2_c1, workers.task_id AS t2_c2 FROM material t LEFT OUTER JOIN task task ON (t.task_id=task.id) [color="#FF0000"]AND (workers.id is null)[/color] LEFT OUTER JOIN worker workers ON (workers.task_id=task.id)[/color]

May be some simple things could be at fault.

I hope you will solve the issue.

Regards.

Try this :


public function scopes() {

    return array(

        'unassigned' => array(

            'with' => 

			array(

				'workers' => array(

					'condition' => '`workers`.`id` is null',

				)

			),

            'together' => true

        )

    );

}

Previous code was setting a condition on the task table but we want to set it on the workers table.

Ya! Ya! Ya!

That is working!

Many THANKS! I lost half of the sunday on that.

I was reluctant to put condition on workers table as there was no workers for the task.

My notion is entirely wrong.

Regards.

This looks just like the code that I originally posted, doesn’t it? I am looking for I way to factorise this code as explained.

I think this sets me on the right direction, but need an additional value in the [font=“Courier New”]with[/font] array. It’d be something like:




function scopes() {

    return array(

        'unassignedTask' => array(

            'with' => array(

                'task.taskWorkers',

                'task' => array(

                    'scopes' => 'unassigned'

                )

            )

        )

    )

}



What would be perfect is:




function scopes() {

    return array(

        'unassignedTask' => array(

            'with' => array(

                'task' => array(

                    'scopes' => 'unassigned'

                )

            )

        )

    )

}



But unfortunately this gives an error:




Undefined table: 7 ERROR: missing FROM-clause entry for table "taskWorkers"

LINE 1: ..."task" "task" ON ("t"."task_id"="task"."id") AND ("taskWorke...

^. The SQL statement executed was: SELECT COUNT(DISTINCT "t"."id") FROM "material_requisition" "t" LEFT OUTER JOIN "task" "task" ON ("t"."task_id"="task"."id") AND ("taskWorkers".id is null) LEFT OUTER JOIN "task_worker" "taskWorkers" ON ("taskWorkers"."task_id"="task"."id") WHERE (task.tenant_id = :tenant_id)



I find the solution quite acceptable as it saves me having to change the ‘unassignedTask’ scope in Material model whenever ‘unassigned’ scope in Task changes, which is what I was after.

Thanks very much for your help seenivasan and Leto. Best regards.

Sorry, although it was apparently working ok because it gave no errors, the last proposed approach does not produce the desired results, i.e. the scope is bound to Task model but has no effect on Material model.

Thanks Leto! Your idea worked for me.