强制执行has_many但只有一个当前关联的“轨道方式”是什么?

我有一个简单的rails应用程序,包含模型项目和阶段。 一个项目有很多阶段,但只有阶段可以一次激活(即“当前”)。 我仍然希望其他阶段可以访问,但当前阶段应该是应用程序的主要锚点。 关于如何实现此要求的决定对我如何处理模型访问,validation和视图/表单以进行创建更新具有重要意义。

所以问题是:如何在不增加太多复杂性的情况下实现这个“has_many但只有一个当前的关联”? 主要目标是:简化当前阶段的访问+确保一次不能超过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相同,但我有另一个属性和belongs_to关联,这看起来很奇怪(为什么一个项目属于它的一个阶段?)

第三种选择:

 [Phase] has an attribute "active" (boolean) [Phase] scope :active, :conditions => { :active => true} # Access to current phase via: project.phases.active 

缺点:我必须通过validation确保一次只有一个活动阶段,如果同时创建/编辑多个阶段或从一个阶段切换到另一个阶段,则很难; 加:project.phases.active返回一个数组,如果我没有弄错的话

非常感谢您的帮助。 谢谢!

更新

增加了奖励以鼓励就该主题提出进一步意见。 Bounty将获得最佳解决上述主要目标的解决方案; 或者,如果没有提到替代解决方案,那么答案最能解释为什么我应该支持给定选项中的一个而不是另一个。 谢谢!

为什么不直接在Phase模型中添加名为activated_at的日期时间列。 然后,只要您想激活相位,就将其设置为当前时间。

在任何给定的时间,具有最新的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 

一个很好的问题。 我一直在努力寻找非常相似的东西。 我最终得到的是类似于您的选项1,但使用连接表。

 class Project < ActiveRecord::Base has_many :phases, :through=> :project_phase has_one :active_project_phase, :class_name => 'ProjectPhase'` 

要准确设置一个新创建的阶段,我在控制器中有一些代码使它们全部处于非活动状态,然后如果没有阶段则添加一个新的活动阶段,或者根据传入的参数选择一个激活阶段。一堆规则。 它不漂亮,但它的工作原理。 我首先尝试了选项3,但发现由于你描述的原因,它非常混乱

选项1看起来很原生。 您只需要添加validation来validation是否只有一个具有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); } })