# Rails: Setting a transient attribute on a set of objects from one model based on information from a junction model

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

belongs_to :user

class User < ActiveRecord::Base


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|
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.

• 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 – Flambino Apr 15 '13 at 18:40
• 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. – Omar Apr 15 '13 at 18:45
• I cannot fathom why completed_on is a virtual attribute instead of a normal column in database. Can you explain? – tokland Apr 15 '13 at 21:11
• @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. – Omar Apr 15 '13 at 22:25

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|

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).