majkel
majkel

Reputation: 66

Has_one association confusion, I want it reversed

I'm pretty new to rails and lately I found that I understood activerecords has_one association contrary to how it actually works. Refering to the example from rubyonrails guide I imagined that it is the supplier that should hold its account_id, since I see no point in forcing every account to hold its supplier.

Because I don't really understand why, or simply don't agree with the object maintaining it's foreign keys in other objects, I don't know what would be the correct rails solution for my simple problem: I have two objects - document, and draft. Every document has many drafts, and one of them is marked as the current draft. I imagined the table layout to be something like this:

table document
    id
    current_draft_id
table draft
    id
    document_id
    text

What I'm looking for here is something like a has_one association, but reversed, so that the document would held and maintain the current_draft_id. Using belongs_to is not an option because of its different behaviour. For example I'd like document.current_draft = new_draft to update the foreign_key correctly.

How to approach this in rails?

-- update 1

To clarify my problem, please assume that the draft being current won't have nothing to do with created_at and updated_at fields, so scopes won't do.

Adding a current field to the drafts table would be a weird move from the table design point of view. I'm also planning to add information about the published draft to the document object, and multiplying such informations in drafts tableseems to be an odd step.

I like Amesee's idea, but still I have some inner resistances, similar to adding the current column to the drafts table.

Upvotes: 0

Views: 190

Answers (2)

Matt
Matt

Reputation: 14038

How do you enforce which draft is the "current" draft? Would it be the most recently created one? The last one edited? I would have the current draft be a logically calculated facet of the draft table and find it with scopes, rather than force a fixed ID that may not always match the logic.

class Document < ActiveRecord::Base
  has_many :drafts

  def current_draft
    self.drafts.ordered.first
  end
end

class Draft < ActiveRecord::Base
  belongs_to :document

  scope :all
  scope :ordered, order_by(:updated_at)
end

Alternatively, add a :current, :boolean, :default => false field to the drafts table and have there be only one child with current being true. (A good explanation of the logic for this method can be found here: Rails 3 app model how ensure only one boolean field set to true at a time)

If you really want to have a fixed child ID in the parent, then you need a :belongs_to with a defined foreign key:

documents table:

id
current_draft_id

Model:

class Document < ActiveRecord::Base
  has_many :drafts
  belongs_to :current_draft, :class_name => 'Draft', :foreign_key => 'current_draft_id'
end

Controller code somewhere:

@document.current_draft = @draft

Upvotes: 0

James
James

Reputation: 4737

I would argue that Draft is a Document so it may make more sense to manage these classes with single table inheritance. You can tell that a draft is a "current draft" by its type.

class CreateDocuments < ActiveRecord::Migration
  def change
    create_table :documents do |t|
      t.string :type
      # ...

      t.timestamps
    end
  end
end

And the models.

class Document < ActiveRecord::Base
  # ...
end

class Draft < Document
  # ...
end

class CurrentDraft < Draft
  # ...
end

Later on, when a draft isn't "current" anymore, update its type by changing its type attribute to "Draft" or "Document." I think this is a better solution than constantly checking a boolean or date attribute on the object and asking about its state everywhere in the application.

Upvotes: 1

Related Questions