Что такое "путь рельсов" для принудительного использования ассоциации has_many, но имеет только один-ток?

У меня есть простое приложение для рельсов с моделями проекта и фазы. Проект имеет много фаз, но только по фазе может быть активным (т.е. "Ток" ) за раз. Я все еще хочу, чтобы другие фазы были доступны, но текущая фаза должна быть основным якорем для приложения. Решение о том, как реализовать это требование, имеет серьезные последствия для того, как я обрабатываю доступ к модели, проверки и представления/формы для создания обновления.

Итак, возникает вопрос: как мне достичь этой "has_many, но имеет единственно-единственную связь", не добавляя слишком много сложностей? Основными задачами являются: простота доступа к текущей фазе + обеспечение не более чем одной активной фазы за раз.

Естественно, у меня были некоторые мысли, и я придумал три варианта, которые я хочу здесь представить. Любые отзывы о том, почему я должен выбрать один вариант по сравнению с другим (или предложение более простого решения), будут оценены:

Первый вариант:

[Project] has_many :phases
[Project] has_one  :current_phase, :class_name => "Phase", :conditions => { :current => true }

Недостаток: у меня есть вложенная форма для создания проектов и соответствующих этапов. Кажется, нет простого способа установить точно одну из вновь созданных фаз в качестве активного

Второй вариант:

[Project] has an attribute "current_phase_id"

[Project] has_many :phases
[Project] belongs_to phase, :foreign_key => "current_phase_id"

Недостаток: тот же, что и для опции 1, но у меня есть другой атрибут и ассоциация belongs_to, которая кажется странной (почему проект должен принадлежать одной из его фаз?)

Третий вариант:

[Phase] has an attribute "active" (boolean)
[Phase] scope :active, :conditions => { :active => true}

# Access to current phase via: project.phases.active

Недостаток: я должен обеспечить с помощью валидации, что за один раз активна только одна активная фаза, что сложно, если одновременно создавать или редактировать несколько фаз или во время переключения с одной фазы на другую; plus: project.phases.active возвращает массив, если я не ошибаюсь

Ваша помощь очень признательна. Спасибо!

Update

Добавлена ​​щедрость, чтобы поощрять дальнейшие мнения по этой теме. Bounty будет награжден решением, которое наилучшим образом отразит основные цели, изложенные выше; или если альтернативное решение не упоминается, ответ, который лучше всего объясняет, почему я должен одобрять один из указанных вариантов над другим. Спасибо!

Ответы

Ответ 1

Почему бы вам просто не добавить столбец с датой, называемый activated_at, в вашу модель Phase. Затем установите это на текущее время всякий раз, когда вы хотите активировать фазу.

В любой момент времени фаза с последним значением activated_at является текущей фазой, поэтому вы можете просто получить ее с помощью @project.phases.order('activated_at DESC').first. Просто оберните это методом Project и вы получите очень сжатое представление:

# in project.rb
def current_phase
  phases.where("activated_at is NOT NULL").order('activated_at DESC').first
end

Ответ 2

Хорошо представленный вопрос. Я боролся с чем-то очень похожим. То, что я закончил, было похоже на ваш вариант 1, но с использованием таблицы соединений.

class Project < ActiveRecord::Base
has_many :phases, :through=> :project_phase

has_one :active_project_phase, :class_name => 'ProjectPhase'`

Чтобы установить ровно одну из вновь созданных фаз, у меня есть бит кода в контроллере, который делает их неактивными, а затем либо добавляет новую активную фазу, если нет фаз, либо выбирает один для активации в зависимости от параметров прошел и кучу правил. Это не очень, но это работает. Сначала я попробовал вариант 3, но нашел, что это стало очень грязным по причинам, которые вы описываете.

Ответ 3

Вариант 1 выглядит очень родным. Вам нужно просто добавить проверку, чтобы проверить, есть ли только одна фаза с флагом current и project_id, а некоторые элементы управления javascript - на стороне клиента.

class Project < AR::Base
  has_many :phases
  has_one  :current_phase, :class_name => "Phase", :conditions => { :current => true }
  accepts_nested_attributes_for :phases, :allow_destroy => true
end

class Phase < AR::Base
  belongs_to :project
  validates :project_id, :uniqueness => {:scope => :current}, :if => proc{ self.current }
end

Итак, ваши взгляды:

<%= form_for @project do |f| %>
  ...
  <%= f.fields_for :phases do |phase| %>
    <%= phase.text_field :title %> # or whatever
    <%= phase.check_box :current, :class => "current_phase" %>
  <% end %>
  ...
<% end %>

И небольшой javascript (на самом деле jQuery), чтобы снять флажки всех current, но один щелчок.

$(document).ready(function(){
  $(".current_phase").click(function(){
    $(".current_phase").not(this).attr('checked', false);
  }
})