Что такое "путь рельсов" для принудительного использования ассоциации 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);
}
})