RobotEyes
RobotEyes

Reputation: 5250

Using both has_one and has_many association

I'm trying to the has_one and has_many methods in one model. A taskflow can have many tasks but it also has a single default task.

I'm trying to create a column in the taskflow table that contains the id of a task. However when I try and set that default task it does not work.

class Taskflow < ActiveRecord::Base
    has_many :tasks
    has_one :default_task, :class_name => 'Task'

class Task < ActiveRecord::Base
    belongs_to :taskflow

I seed the database then in the rails console I try to assign a task to be a taskflow default_task:

taskflow1 = Taskflow.first
task1 = Task.first
taskflow1.default_task = task1

This doesn't work with the taskflow default_task value remaining 'nil'. What the correct way to achieve the desired behaviour?

Any help would be much appreciated.

Edit

Migration files are:

class CreateTaskflows < ActiveRecord::Migration
  def change
    create_table :taskflows do |t|
      t.string :title
      t.string :description
      t.references :default_task
      t.timestamps null: false
    end
  end
end

class CreateTasks < ActiveRecord::Migration
  def change
    create_table :tasks do |t|
      t.string :task_type
      t.text :help
      t.text :data
      t.belongs_to :taskflow
      t.timestamps null: false
    end
  end
end

Upvotes: 4

Views: 1929

Answers (4)

user4776684
user4776684

Reputation:

I would implement it differently. I would create a boolean field default_task on the Task model. And Taskflow would have the following has_one and has_many associations:

class Taskflow < ActiveRecord::Base
    has_many :tasks
    has_one :default_task, class_name: 'Task', condition: proc{"tasks.default_task = true"}

Semantically, Taskflow should have many tasks and have one default task among those tasks. And on my opinion that a taskflow belongs to a default task sounds a bit artificial.

You can also add a functionality to constantly maintain a single task as a default task (with bit set to true, more details here), or create a default task at the time of creation of a Taskflow. It all depends on your requirements.

AND, if you still want to have the default_task_id column to be on your Taskflow model and use has_one association, you can do the following:

class Taskflow < ActiveRecord::Base
    has_many :tasks
    has_one :default_task, class_name: "Task", primary_key: "default_task_id", foreign_key: "id"

Upvotes: 5

bpieck
bpieck

Reputation: 288

People here are right about belongs_to or how to hack has_one. I understand that belongs_to does not feel right in this situation, but if you are using "referencing" in table you normally always want to use belongs_to. So I would assume to mark the default task on the tasks itself as default - but if they can be part of different task_flows where different tasks could be the default - then of course that's not possible.

Another option to those here already mentioned is: You can add a scope to the has_many :tasks relation. Like this:

has_many :tasks do
  def default
    joins(:task_flow).where('tasks.id = task_flows.default_task_id').first
  end
end

Then you can request for

@task_flow.tasks.default

Upvotes: 2

dquimper
dquimper

Reputation: 926

has_one is used to specifie a one-to-one association with another class. This method should only be used if the other class contains the foreign key.

In your case, you should use belong_to as the default_task reference is in the TaskFlow model (in your migration).

The TaskFlow model has two relations:

  • A TaskFlow has many tasks
  • A TaskFlow has one specific task that is the default task.

class Taskflow < ActiveRecord::Base has_many :tasks belongs_to :default_task, :class_name => 'Task'

class Task < ActiveRecord::Base belongs_to :taskflow

The way you had it, Rails wouldn't know which task is the default one.

Upvotes: 2

RobotEyes
RobotEyes

Reputation: 5250

I got this to work. Instead of has_one, belongs_to is needed in the model.

class Taskflow < ActiveRecord::Base
    has_many :tasks
    belongs_to :default_task, :class_name => 'Task'

class Task < ActiveRecord::Base
    belongs_to :taskflow

I think something to do with has_one having the reverse relationship to my assumption. http://guides.rubyonrails.org/association_basics.html#the-has-one-association

Upvotes: 1

Related Questions