Question

I've got a Rails 4 app. Fairly simple question; I've written a method, but I don't know where it should go and how to call it.

I have a resource, Goals, and a nested resource, Tasks (ie. each goal has tasks). All that part works fine.

What I want to do is set it so that once the status of each task for a goal is true, the status of that goal changes to true.

The content of the method I'd like to use is:

completed = true
@goal.tasks.each do |t|
  if !t.status?
    completed = false
  end
end
if completed?
  @goal.status = true
end

which checks if all a goal's tasks have status:true, and if so, changes the status of the goal to true as well.

My idea would be to put that method in the Goal model, but that's as far as I can guess.

  • Where does the method go? (is it in the model like I thought?)
  • How do I ( / do I need to) call the method?

(I'm aware the method might have a few minor syntax errors in it, but once I can actually use it, I'll be able to test and tweak it!)

Thanks to anybody taking the time to have a look.

Was it helpful?

Solution

You can simplify your function as follows:

@goal.status = @goals.tasks.map(&:complete).all?

Assuming you want to know as soon as possible whether or not your goal is complete, you'll want to put this function in your Task model. You can do it as follows:

def Task < ActiveRecord::Base
  belongs_to :goal

  after_save :goal_completed

  def goal_completed
    if self.goal.tasks.map(&:completed).all?
      self.goal.update_attribute(:completed, true)
    end
  end
end

As you can see, if you have a lot of tasks for one goal, this could get costly very quickly, since you're pulling all of a goal's tasks every time a task is saved. So, for a larger database, I'd recommend keeping a count of completed tasks for each goal. (This is actually pretty similar to something I implemented in a project of mine.)

To start, you'll need to add a counter cache column in your goals for tasks_count (if you haven't already), and add another column to goals called completed_tasks_count, or something like that. Make sure they both default to zero.

def Goal < ActiveRecord::Base
  has_many :tasks, inverse_of: :goal, counter_cache: true
end

def Task < ActiveRecord::Base
  belongs_to :goal, inverse_of: :tasks

  after_initial :set_completed_tasks_count, on: new
  around_save :goal_complete

  def set_completed_tasks_count
    self.completed = false if self.new_record? && self.completed.nil?
  end

  def goal_complete
    yield
    if self.completed? && !self.completed_was
      self.review.increment!(:completed_tasks_count)
    elsif (!self.completed? && self.completed_was) || self.marked_for_destruction?
      self.review.decrement!(:completed_tasks_count)
    end
  end
end

Hopefully this helps. Happy coding!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top