1
\$\begingroup\$

I have a user model, a task model, and a junction model user_task that saves the data joining the two:

class Task < ActiveRecord::Base
  attr_accessor :completed_on
  has_many :user_tasks

class UserTask < ActiveRecord::Base
  attr_accessible :task_id, :user_id, :completed_on
  belongs_to :user
  belongs_to :task

class User < ActiveRecord::Base
  has_many :user_tasks, :dependent => :destroy
  has_many :tasks, :through => :user_tasks

In order to query the tasks and show if they're complete or not when a user is logged in, I have a transient attribute, (I think that's the correct terminology) an attr_accessor on the task model :completed_on. Right now I'm looping through the tasks as follows in order to get this attribute filled out.

This just thrown in a controller as a quick hack to provide what I need.

  @tasks.each do |task| 
    if current_user.tasks.include? task
      task.completed_on = current_user.user_tasks.find_by_task_id(task.id).completed_on
    end
  end

I need something that, given an array of tasks and a user_id, loops through the tasks and fills that variable with the completed_on date if the user has finished that task. The goal for this is that when responding to JSON, I have overriden as_json on the task model to merge the completed_on attribute into the JSON hash:

h.merge({:completed_on => completed_on})

Possible ideas to get this done more properly:

  • Create a function on the tasks model that takes a user ID, joins the user_tasks table to the tasks table, with a where clause to specify the user. This idea is actually half baked and I'm not sure where to go from here.
  • Pretty much put the code above into the user model and change current_user to self. I'm not sure what to name it though,
    completed_tasks doesn't fit since not all of them would be completed. fill_completed_on may work.
  • Utilize a decorator somehow? Is it worth it just for this single attribute? If so, how would I structure it?
  • Something like Task.where(conditions) then Task.where(conditions).completed_by(user) then setting the completed_on on the latter and merging the two results.

It feels like I'm doing this the a bad way, and could be leveraging ActiveRelation/scopes somehow.

I also think there is a better way to do include completed_on in when needed and not have to override the task model's as_json to get it to appear.

\$\endgroup\$
  • \$\begingroup\$ Why do you have the UserTask model and table? Since you're not using a has_and_belongs_to_many relationship anywhere, and UserTask isn't adding any new information, there's no need for it as far as I can tell. It could simply be "user has_many tasks" (or vice-versa), period. It'd simplify everything \$\endgroup\$ – Flambino Apr 15 '13 at 18:40
  • \$\begingroup\$ The UserTask model actually will store data specific to the task. What I didn't say (didn't think it was necessary) was that the tasks have an hstore column and I'm using single table inheritance to create different types of tasks, and same with the UserTask model in order to store different types of data related to whatever info the task needs to save on the user. \$\endgroup\$ – Omar Apr 15 '13 at 18:45
  • \$\begingroup\$ I cannot fathom why completed_on is a virtual attribute instead of a normal column in database. Can you explain? \$\endgroup\$ – tokland Apr 15 '13 at 21:11
  • \$\begingroup\$ @tokland Yah. I'm interested in serializing the tasks as JSON. Tasks can be completed by users via user_tasks. The JSON is for a mobile API to show whether the user finished the task or not when viewing the list of tasks. It is a normal column on the database -- it's on the user_tasks table. \$\endgroup\$ – Omar Apr 15 '13 at 22:25
1
\$\begingroup\$

The problem is that the data to render does not match the modelling. You call it "task", but if it includes a completed_on it's not a task, it's something more of a user_task. I definitely wouldn't use an accessor to accomplish this, that's messing with Task, this model should know nothing about completion dates of a particular user.

Even if you call it "tasks" in the frontend, conceptually in your app you should be rendering a collection of user_tasks. Something to start with (make sure, using includes or whatever, that user_task_by doesn't hit the DB repeatedly):

user_tasks = Task.where(...).includes(:users_tasks).map do |task|
  task.user_task_by(user) || task.user_tasks.new
end

Now you'd override UserTask#as_json to build a hash with the mixed structure (getting some attributes from task and completed_on from user_task).

Was this any help?

\$\endgroup\$
  • \$\begingroup\$ I apologize if I've added confusion by omitting most of the code, and I have been programming by myself for a long time, so it's hard to communicate about this sometimes... Basically the tasks are a collection that belong to another entity. Multiple users can complete the same task. I have a mobile API and the person building the mobile app is only interested in rendering the task collection on the entities, with a small addition to see whether the logged on user completed that particular task. Hope that clears things up! Let me know if this is still too ambiguous. \$\endgroup\$ – Omar Apr 16 '13 at 21:35
  • \$\begingroup\$ Also, I feel this is more of an architectural/design type of question. Does it suit this stackexchange? Or would it be more suited for stackoverflow? \$\endgroup\$ – Omar Apr 16 '13 at 21:38
  • \$\begingroup\$ This actually helped. I'm not going to go with setting a transient attribute, instead return both the task and user_task (if present) using includes. Thanks! \$\endgroup\$ – Omar Apr 17 '13 at 15:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.