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

发布于 2024-11-19 13:34:16 字数 1315 浏览 1 评论 0原文

我有一个简单的 Rails 应用程序,其中包含模型项目和阶段。一个项目有许多阶段,但一次只能有一个阶段处于活动状态(即“当前”)。我仍然希望其他阶段可以访问,但当前阶段应该是应用程序的主要锚点。关于如何实现此要求的决定对我如何处理模型访问、验证和创建更新的视图/表单具有重大影响。

所以问题是:如何在不增加太多复杂性的情况下实现这种“有很多但只有一个当前关联”?主要目标是:简化当前阶段的访问+确保一次不能有超过 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

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

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

更新

添加了赏金以鼓励对该主题提出进一步的意见。最能实现上述主要目标的解决方案将获得赏金;或者,如果没有提到替代解决方案,则找到最能解释为什么我应该选择给定选项之一而不是另一个的答案。谢谢!

I have a simple rails app with models project and phase. A project has many phases, but only on phase can be active (i.e. "current") at a time. I still want the other phases to be accessible, but the current phase should be the main anchor for the application. The decision on how to implement this requirement has major implications on how I handle model access, validations and views / forms for creation update.

So the question is: How do I achieve this "has_many but has-only-one-current association" without adding too much complexity? Main goals being: simplicity in access of current phase + ensuring there cannot be more than 1 active phase at a time.

Naturally, I had some thoughts myself and came up with three options, which I want to present here. Any feedback on why I should choose one option over the other (or suggestion of a simpler solution) would be appreciated:

First Option:

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

Drawback: I have a nested form for creating projects and corresponding phases. There seems to be no easy way to set exactly one of the newly created phases as active

Second Option:

[Project] has an attribute "current_phase_id"

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

Drawback: same as option 1, but I have another attribute and a belongs_to association, which seems weird (why should a project belong to one of its phases?)

Third Option:

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

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

Drawback: I have to ensure via validations that there is only one active phase at a time, which is hard if multiple phases are created / edited at the same time OR during switch from one phase to another; plus: project.phases.active returns an array, if I'm not mistaken

Your help is greatly appreciated. Thanks!

Update

Added a bounty to encourage further opinions on the topic. Bounty will be awarded to the solution which best addresses the main goals expressed above; or if no alternative solution is mentioned, to the answer that best explains why I should favor one of the given options over the other. Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

上课铃就是安魂曲 2024-11-26 13:34:16

为什么不将名为 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

Why don't you just add a date-time column called activated_at to your Phase model. Then set this to the current time whenever you want to make a phase active.

At any given time, the phase with the latest activated_at value is the current phase so you can just get it with @project.phases.order('activated_at DESC').first. Just wrap this in a method in Project and you have a very concise representation:

# in project.rb
def current_phase
  phases.where("activated_at is NOT NULL").order('activated_at DESC').first
end
寂寞美少年 2024-11-26 13:34:16

一个提出得很好的问题。我曾为一些非常类似的事情而苦苦挣扎。我最终得到的结果与您的选项 1 类似,但使用了连接表。

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

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

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

A well-presented question. I have struggled with something very similar. What I ended up with was similar to your option 1, but using a join table.

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

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

To set exactly one of the newly created phases active I have a bit of code in the controller that makes them all inactive and then either adds a new active phase if there are no phases or picks one to make active depending on the parameters passed in and a bunch of rules. It's not pretty, but it works. I did try option 3 first, but found this got very messy for the reasons you describe

双马尾 2024-11-26 13:34:16

选项 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)取消选中所有当前复选框,但你单击了一个。

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

Option 1 looks very native. You need just to add validation to validate if there is only one phase with current flag and project_id and some javascript to control checkboxes on client side.

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

So, your views:

<%= 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 %>

And small javascript (jQuery actually) to uncheck all current checkboxes but one you clicked.

$(document).ready(function(){
  $(".current_phase").click(function(){
    $(".current_phase").not(this).attr('checked', false);
  }
})
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文