Каков "рельсовый способ" для обеспечения связи has_many, но имеет только один текущий? - PullRequest
18 голосов
/ 11 июля 2011

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

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

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

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

[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, но у меня есть еще один атрибут и ассоциация own_to, что кажется странным (почему проект должен принадлежать одной из его фаз?)

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

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

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

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

Ваша помощь очень ценится. Спасибо!

Обновление

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

Ответы [ 3 ]

18 голосов
/ 18 июля 2011

Почему бы вам просто не добавить столбец даты и времени с именем 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
4 голосов
/ 11 июля 2011

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

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

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

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

1 голос
/ 18 июля 2011

Вариант 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);
  }
})
...