Ruby on rails 如何将逻辑从控制器移动到模型中?
一个池有许多地址。要基于提交的范围创建多个地址记录 我的控制器中有以下逻辑:Ruby on rails 如何将逻辑从控制器移动到模型中?,ruby-on-rails,ruby-on-rails-4,Ruby On Rails,Ruby On Rails 4,一个池有许多地址。要基于提交的范围创建多个地址记录 我的控制器中有以下逻辑: def create @pool = Pool.find(params[:pool_id]) unless address_params[:ipv4_range_start].blank? || address_params[:ipv4_range_stop].blank? (address_params[:ipv4_range_start]..address_params[:ipv4
def create
@pool = Pool.find(params[:pool_id])
unless address_params[:ipv4_range_start].blank? || address_params[:ipv4_range_stop].blank?
(address_params[:ipv4_range_start]..address_params[:ipv4_range_stop]).each do |octet|
params[:address][:ipv4_octet3] = octet
@address = @pool.addresses.build(address_params)
if !@address.save
render 'new'
end
end
redirect_to pool_path(@pool), notice: "Address range created."
else #something was missing
@address = @pool.addresses.build(address_params)
@address.errors.add_on_blank(:ipv4_range_start)
@address.errors.add_on_blank(:ipv4_range_stop)
render 'new'
end
end
# AddressesController
def create
@pool = Pool.find(params[:pool_id])
@address = @pool.addresses.build(address_params)
if @address.invalid?(:require_range)
render 'new' and return
end
# ...
end
def create
# Our code from before
@pool = Pool.find(params[:pool_id])
@address = @pool.addresses.build(address_params)
if @address.invalid?(:require_range)
render 'new' and return
end
# The new code
begin
@pool.addresses.create_from_range!(address_params)
rescue ActiveRecord::ActiveRecordError
flash[:error] = "Address range not created!"
render 'new' and return
end
redirect_to @pool, notice: "Address range created."
end
想知道我该如何将其移动到地址模型中?对于控制器来说似乎太多了,但我不知道如何迭代提交的范围,并从地址模型本身构建和保存每个地址
谢谢你的建议
吉姆我认为你的直觉是正确的,我们可以在模型中引入很多 免责声明:此代码未经测试;将其复制并直接粘贴到代码中可能会以眼泪告终 因此,我们需要处理您的逻辑的两个部分。第一个是确保提供了
:ipv4\u range\u start
和\u stop
。为此,我们可以使用验证。由于您似乎不希望所有地址都需要这些属性,因此我们可以使用:on
选项来提供验证上下文。():
expndtw-1::require\u range上的部分意味着此验证通常不会运行,它只会在我们告诉ActiveRecord使用:require\u range
上下文时运行
现在我们可以在控制器中执行此操作:
def create
@pool = Pool.find(params[:pool_id])
unless address_params[:ipv4_range_start].blank? || address_params[:ipv4_range_stop].blank?
(address_params[:ipv4_range_start]..address_params[:ipv4_range_stop]).each do |octet|
params[:address][:ipv4_octet3] = octet
@address = @pool.addresses.build(address_params)
if !@address.save
render 'new'
end
end
redirect_to pool_path(@pool), notice: "Address range created."
else #something was missing
@address = @pool.addresses.build(address_params)
@address.errors.add_on_blank(:ipv4_range_start)
@address.errors.add_on_blank(:ipv4_range_stop)
render 'new'
end
end
# AddressesController
def create
@pool = Pool.find(params[:pool_id])
@address = @pool.addresses.build(address_params)
if @address.invalid?(:require_range)
render 'new' and return
end
# ...
end
def create
# Our code from before
@pool = Pool.find(params[:pool_id])
@address = @pool.addresses.build(address_params)
if @address.invalid?(:require_range)
render 'new' and return
end
# The new code
begin
@pool.addresses.create_from_range!(address_params)
rescue ActiveRecord::ActiveRecordError
flash[:error] = "Address range not created!"
render 'new' and return
end
redirect_to @pool, notice: "Address range created."
end
这与else
块中的代码实现了相同的功能,但真正的逻辑在模型中,Rails为我们填充errors
对象
现在我们已经解决了这个问题,我们可以处理创建对象的问题了。为此,我们可以在Address中编写一个类方法。Rails模型中类方法的优点是它们在关联集合中自动可用,因此,例如,如果我们定义Address.foo
类方法,我们可以免费获得@pool.addresses.foo
。下面是一个将创建地址数组的类方法:
# Address model
def self.create_from_range!(attrs)
start = attrs.fetch(:ipv4_range_start)
stop = attrs.fetch(:ipv4_range_stop)
self.transaction do
(start..stop).map do |octet|
self.create!(attrs.merge ipv4_octet3: octet)
end
end
end
这与else
块几乎相同,只是稍微干净了一点。我们做self.create代码>在事务中,因此如果任何创建代码>s失败,它们将全部回滚。我们在映射
块中执行此操作,而不是在每个
块中执行此操作,因此,假设没有错误发生,该方法将返回所创建对象的数组
现在我们只需要在控制器中使用它:
def create
@pool = Pool.find(params[:pool_id])
unless address_params[:ipv4_range_start].blank? || address_params[:ipv4_range_stop].blank?
(address_params[:ipv4_range_start]..address_params[:ipv4_range_stop]).each do |octet|
params[:address][:ipv4_octet3] = octet
@address = @pool.addresses.build(address_params)
if !@address.save
render 'new'
end
end
redirect_to pool_path(@pool), notice: "Address range created."
else #something was missing
@address = @pool.addresses.build(address_params)
@address.errors.add_on_blank(:ipv4_range_start)
@address.errors.add_on_blank(:ipv4_range_stop)
render 'new'
end
end
# AddressesController
def create
@pool = Pool.find(params[:pool_id])
@address = @pool.addresses.build(address_params)
if @address.invalid?(:require_range)
render 'new' and return
end
# ...
end
def create
# Our code from before
@pool = Pool.find(params[:pool_id])
@address = @pool.addresses.build(address_params)
if @address.invalid?(:require_range)
render 'new' and return
end
# The new code
begin
@pool.addresses.create_from_range!(address_params)
rescue ActiveRecord::ActiveRecordError
flash[:error] = "Address range not created!"
render 'new' and return
end
redirect_to @pool, notice: "Address range created."
end
如您所见,我们使用了@pool.addresses.create\u from\u range代码>,因此将为我们填写关联(地址#池id
)。如果需要,我们可以将返回的数组分配给实例变量,并在视图中显示创建的记录
这应该就是你所需要的了
另外,值得注意的是Ruby有一个很好的内置类,而且因为IP地址只是一个数字(例如,3338456716
是198.252.206.140
的十进制形式),所以可以将每个IP地址存储为一个4字节的整数,而不是四个单独的列。大多数数据库都有处理IP地址的有用内置函数(PostgreSQL实际上有一个内置的inet
列类型)。然而,这实际上取决于您的用例,在这一点上,这样的优化可能还为时过早。干杯 这条线的目的是什么<代码>参数[:地址][:ipv4_octet3]=octet
因为它在循环中,所以它的最终值将只是上一次迭代中的octet
的值。这是预期的行为吗?如果您可以编辑您的答案,以包括参数
和地址参数
的预期内容,以及应保存的地址对象的属性,这将非常有用。地址参数
包含允许的参数:ipv4范围_开始/停止,:ipv4_八位字节0,:ipv4_八位字节1,和:ipv4_octet2
。:ipv4\u octet3
值是从范围中获得的,但由于它不是由表单提供的,因此我必须通过参数[:address][:ipv4\u octet3]=octet
行手动插入它。ipv4\u范围\u开始/停止
值是虚拟属性,不保存。希望这能有所帮助。非常感谢您给出如此完整的答案!这不仅解决了问题,而且我还学到了关于Rails的一些新的有用的东西。乔丹,你真是慷慨善良!很高兴它有帮助!另外,我做了一个小改动,就是从地址范围中删除Address.create\u代码>。这是不必要的,因为代码从不修改attrs
(merge
在执行合并之前复制散列;原始散列不变)。我还将替换为@address.valid?(…)
,而不是if@address.invalid?(…)
,只是因为它读起来容易一点。@Jordan,只是一个小建议。这里的Pool.find(params[:Pool_id])如果我们使用find方法,那么如果对象不存在,那么此行将抛出ActiveRecord::RecordNotFound。相反,我们可以使用Pool.where(id:params[:Pool_id]。在这种情况下,如果找不到记录,那么将返回nil而不是抛出异常。请分享您的想法:)@Ajay如果@Pool
是nil
,那么下一行将在@Pool.addresses
上引发一个NoMethodError,因此,无论哪种方式,我们都会得到一个例外。你是对的,这应该以某种方式处理,但适当的方式取决于OP想要的行为。可能没有池id
的地址仍然有效(例如,可能有另一个视图用于创建独立于池的地址),在这种情况下,需要添加逻辑来说明这一点。