简体   繁体   中英

Complex associations for Rails

Stuck on this one, if you could take a look.. :)

What I want is to get all unfinished projects, including all unfinished tasks for a certain user.

This is my setup so far:

User (devise)
    has_one :employee

Employee
    belongs_to :user
    has_and_belongs_to_many :tasks
    has_and_belongs_to_many :unfinished_tasks, :conditions => { :tasks => { :completed_at => nil } }, :class_name => "Task"
    has_many :unfinished_projects, :through => :unfinished_tasks, :source => :project, :uniq => true   ( :include => :unfinished_tasks OR :include => :tasks ? )

Project
    has_many :tasks

Task
    belongs_to :project
    has_and_belongs_to_many :employees

In my view (haml) I'd like to have something like this:

- for project in current_user.employee.unfinished_projects

    = project.name

    # THESE ARE NOT THE ONLY THE TASKS FOR THE CURRENT_USER
    - for task in project.tasks    ( OR project.unfinished_tasks ? )

        = task.name

This setup works for the projects, there are only projects which have unfinished tasks.

But I'm not sure how to include the unfinished tasks with these projects.

Anyone knows the best way for doing this, I'd like to have a single query for all this if that's possible.

EDIT: The tricky part is that the tasks have to be for the current_user. The projects are loaded perfectly.

But when it loads the tasks:

- for task in project.tasks.unfinished

It does this:

Task Load (1.3ms)  SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`project_id` IN (12, 7, 13, 15, 14, 10, 16, 17, 9, 2, 3)
Task Load (0.4ms)  SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`project_id` = 12 AND `tasks`.`completed_at` IS NULL
Task Load (0.3ms)  SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`project_id` = 7 AND `tasks`.`completed_at` IS NULL
Task Load (0.6ms)  SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`project_id` = 13 AND `tasks`.`completed_at` IS NULL
etc.

What it should do is get the tasks of the employee:

Employee
  Projects
    Tasks

Which should be the tasks that were inner joined in the projects query.

Assuming my question (above) is a true example of what you'd like to see.

you can use named_scopes to cover this.

For instance:

Task
   named_scope :unfinished, :conditions => {:unfinished => true}

or whatever condition used to show that it's unfinished. Then you can use:

 - for task in project.tasks.unfinished

The named_scope for "unfinished_projects" is similar but slightly more complex. It requires some bare SQL - which will depend on the db that you use, but generally requires that you join the tasks on and find the project sht at have one unfinished task. I'm guessing this might come close to what you need:

Project
   named_scope :unfinished, 
    :joins => 'inner join tasks on tasks.project_id = projects.id',
    :conditions => ['tasks.unfinished IS TRUE']

You would then use it as above:

  - for project in current_user.employee.projects.unfinished

For one that only finds projects that have unfinished tasks for a user , you can add the user as a parameter to the scope eg:

Project
   named_scope :unfinished_for_user, lambda {|the_user_id|
    :joins => 'inner join tasks on tasks.project_id = projects.id',
    :conditions => ['tasks.unfinished IS TRUE AND tasks.user_id = ?', the_user_id]
   }

So the answer to my question is as follows:

The models:

Employee:
  has_and_belongs_to_many :tasks
  has_many :projects, :through => :tasks, :uniq => true

Project:
  has_many :tasks
  scope :unfinished, :include => :tasks, :conditions => { :tasks => { :completed_at => nil } }

Task:
  scope :unfinished, :conditions => { :completed_at => nil }
  scope :of_user, lambda { |user| { :include => :employees, :conditions => { :employees => { :id => user.employee.id } } } }

The view:

- for project in current_user.employee.projects.unfinished

    - for task in project.tasks.unfinished.of_user( current_user )

I find it VERY annoying that Project.unfinished already includes the incomplete tasks.

WHY do I need to get these again, gives me a new query for each project! Should be unnecessary right?

So, after doing this all over again I didn't really find what my problem was. There are a few things changed now: I'm using scopes, includes for eager load are in the scope, and no scope on the project.tasks itself.

The project model:

has_many :tasks
scope :assigned_to, lambda { |employee| { :include => { :tasks => :employees }, :conditions => { :tasks => { :employees => { :id => employee.id } } } } }
scope :unfinished, :include => :tasks, :conditions => { :tasks => { :completed_at => nil } }

The view:

- for project in Project.assigned_to( current_user.employee ).unfinished
  = project.name
  - for task in project.tasks
    = task.name

Gives me this nice MySQL-query:

SELECT `projects`.`id` AS t0_r0, `projects`.`name` AS t0_r1, `projects`.`description` AS t0_r2, `projects`.`completed_at` AS t0_r3, `projects`.`created_at` AS t0_r4, `projects`.`updated_at` AS t0_r5, `tasks`.`id` AS t1_r0, `tasks`.`name` AS t1_r1, `tasks`.`description` AS t1_r2, `tasks`.`estimated_duration` AS t1_r3, `tasks`.`calculated_duration` AS t1_r4, `tasks`.`status` AS t1_r5, `tasks`.`completed_at` AS t1_r6, `tasks`.`project_id` AS t1_r7, `tasks`.`created_at` AS t1_r8, `tasks`.`updated_at` AS t1_r9, `employees`.`id` AS t2_r0, `employees`.`alias` AS t2_r1, `employees`.`comments` AS t2_r2, `employees`.`user_id` AS t2_r3, `employees`.`created_at` AS t2_r4, `employees`.`updated_at` AS t2_r5 FROM `projects` LEFT OUTER JOIN `tasks` ON `tasks`.`project_id` = `projects`.`id` LEFT OUTER JOIN `employees_tasks` ON `employees_tasks`.`task_id` = `tasks`.`id` LEFT OUTER JOIN `employees` ON `employees`.`id` = `employees_tasks`.`employee_id` WHERE `employees`.`id` = 3 AND `tasks`.`completed_at` IS NULL

Works like a charm!

Special thanks to Taryn.. ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM