gpt4 book ai didi

ruby-on-rails - 绕过只读?保存 ActiveRecord 时

转载 作者:数据小太阳 更新时间:2023-10-29 07:41:38 26 4
gpt4 key购买 nike

我使用 readonly? 函数将我的 Invoice 发送后标记为不可变;对于 by InvoiceLine,我只是将 readonly? 函数代理到 Invoice

一个简化的例子:

class Invoice < ActiveRecord::Base
has_many :invoice_lines
def readonly?; self.invoice_sent? end
end

def InvoiceLine < ActiveRecord::Base
def readonly?; self.invoice.readonly? end
end

这很好用,除了在一个特定的场景中我想更新 InvoiceLine 而不管 readonly? 属性。

有什么办法吗?

我尝试使用 save(validate: false),但这没有效果。我查看了 AR 源代码中的 persistence.rb,这似乎就是这样:

def create_or_update
raise ReadOnlyRecord if readonly?
...
end

有没有明显的方法可以避免这种情况?

我可能会在 Python 中做的(有点肮脏的)解决方法:

original = line.readonly?
line.readonly? = lambda: false
line.save()
line.readonly? = original

但这在 Ruby 中行不通,因为函数不是一流的对象......

最佳答案

您可以非常轻松地在实例化对象中重新定义方法,但语法是定义而不是赋值。例如。在更改需要调整其他只读对象的架构时,我使用这种形式:

line = InvoiceLine.last
def line.readonly?; false; end

等等,状态被覆盖了!实际发生的是对象的特征类而不是其类中的 readonly? 方法的定义。不过,这实际上是在对象的内部进行挖掘;在架构更改之外,这是一种严重的代码味道。

一个粗略的替代方案是强制 Rails 将更新的列直接写入数据库:

line.update_columns(description: "Compliments cost nothing", amount: 0)

它是大规模杀伤性的:

InvoiceLine.where(description: "Free Stuff Tuesday").update_all(amount: 0)

但同样,它们都不应该出现在迁移之外的生产代码中,并且偶尔出现在一些精心编写的框架代码中。这两个绕过了所有验证和其他逻辑,并冒着使对象处于不一致/无效状态的风险。最好以某种方式在模型代码和交互中明确传达需求和行为。你可以这样写:

class InvoiceLine < ActiveRecord::Base
attr_accessor :force_writeable

def readonly?
invoice.readonly? unless force_writeable
end
end

因为那时客户端代码可以说

line.force_writable = true
line.update(description: "new narrative line")

我仍然不太喜欢它,因为它仍然允许外部代码指示内部行为,并且它使对象的状态发生变化,而其他代码可能会遇到这种情况。这是一个更安全、更 ruby 色的变体:

class InvoiceLine < ActiveRecord::Base
def force_update(&block)
saved_force_update = @_force_update
@_force_update = true
result = yield
@_force_update = saved_force_update
result
end

def readonly?
invoice.readonly? unless @_force_update
end
end

然后客户端代码可以写:

line.force_update do
line.update(description: "new description")
end

最后,这可能是最精确的机制,您可以只允许更改某些属性。您可以在 before_save 回调中执行此操作并抛出异常,但我非常喜欢使用依赖于 ActiveRecord 脏属性模块的验证:

class InvoiceLine < ActiveRecord::Base
validate :readonly_policy

def readonly_policy
if invoice.readonly?
(changed - ["description", "amount"]).each do |attr|
errors.add(attr, "is a read-only attribute")
end
end
end
end

我很喜欢这个;它将所有领域知识放入模型中,它使用支持的和内置的机制,不需要任何猴子修补或元编程,不避免其他验证,并为您提供可以一直传播回来的漂亮错误消息到 View 。

关于ruby-on-rails - 绕过只读?保存 ActiveRecord 时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27723375/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com