且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

rails将类常量重构到数据库对应的表中之一

更新时间:2022-06-28 19:41:12

    问题是这样:原来代码.html.erb页面中有一个select元素,其每个item对应的是model中的类常量:

<%= f.select :pay_type,Order::PAYMENT_TYPES,prompt:'Select a payment method' %>

类中的常量定义如下:

class Order < ActiveRecord::Base
  PAYMENT_TYPES = ["Check","Credit card","Purchase order"]
end

现在想把PAYMENT_TYPES重构至数据库中的表里去,于是有了尝试性的第一步,首先创建一个model如下:

rails g model payment_types type:string

rake db:migrate

第二步是写一个脚本将常量导入至表中:

PaymentType.transaction do
  Order::PAYMENT_TYPES.each do |type|
    PaymentType.create(type:type)
  end
end

但是运行rails runner script/load_payment_types.rb时出错了,提示如下:

/var/lib/gems/2.1.0/gems/activerecord-4.2.0/lib/active_record/inheritance.rb:215:in `subclass_from_attributes': Invalid single-table inheritance type: Check is not a subclass of PaymentType (ActiveRecord::SubclassNotFound)

确认没有语法上的错误后推测,可能type名称和框架中某个方法或属性冲突了,这样只有更改该名称了:

rails g migration rename_type_to_payment_types

在migrate目录中生成的.rb文件中修改type的名称:

class RenameTypeToPaymentTypes < ActiveRecord::Migration
  def change
    rename_column :payment_types,:type,:pay_type
  end
end

然后rake db:migrate,接下来再修改load_payment_types.rb中的代码以顺应更改:

PaymentType.transaction do
  PaymentType.delete_all
  Order::PAYMENT_TYPES.each do |type|
    PaymentType.create(pay_type:type)
  end
end

接着再执行rails runner script/load_payment_types.rb,这时没有问题了:) ,第三步是修改控制器中的new方法,添加以下一行:

    @payment_types = PaymentType.all.map {|type|type.pay_type}

第四步是修改.html.erb中的代码如下:

<%= f.select :pay_type,@payment_types,prompt:'Select a payment method' %>

第五步别忘了修改model的验证代码:

class Order < ActiveRecord::Base
  validates :name,:address,:email,:pay_type,presence:true
  #validates :pay_type,inclusion:PAYMENT_TYPES
  validates :pay_type,inclusion:PaymentType.all.map {|type|type.pay_type}
end

运行一下貌似没有问题.可是等等!如果new.html.erb全部留空提交订单,则会报错,提示nil对象没有empty?方法!稍微想一下可知,提交订单会转至Order#create方法,在order.save时会调用Order类的验证方法,因为前面留空,所以验证失败,save方法会出错;这时会重新render到new.html.erb中去,但这时@payment_types不存在其值当然为空喽!于是乎在create方法中也加上new方法中的那一句吧!

    还有神马呢?代码中有这么多@payment_types的重复,违反了DRY原则哦!我们可以考虑将其放到Order控制器的类变量中去,不过这还要考虑如果数据库中的pay_types有修改怎么及时反应到类变量中去的问题.我们简单起见,payment_types表中的pay_type很少修改,如果修改可以重启rails服务器来应用变更;于是可以进一步重构:

首先在Order控制器中加入类变量以及类变量属性:

class OrdersController < ApplicationController
  @@payment_types = PaymentType.all.map {|type|type.pay_type}

  def self.payment_types
    @@payment_types
  end
end

然后在new.html.erb和Order model中做如下修改

#in new.html.erb
<%= f.select :pay_type,OrdersController.payment_types,prompt:'Select a payment method' %>

#in Order.rb
validates :pay_type,inclusion:OrdersController.payment_types

这时原先new和create中的变量@payment_types都可以删掉鸟.至此重构告一段落! :