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.
1 Answer 1
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?
-
\$\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– Omar2013年04月16日 21:35:16 +00:00Commented Apr 16, 2013 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– Omar2013年04月16日 21:38:42 +00:00Commented Apr 16, 2013 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– Omar2013年04月17日 15:46:25 +00:00Commented Apr 17, 2013 at 15:46
UserTask
model and table? Since you're not using ahas_and_belongs_to_many
relationship anywhere, andUserTask
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\$completed_on
is a virtual attribute instead of a normal column in database. Can you explain? \$\endgroup\$