所以我的表中有一个字段-exposure
。该字段的名称称为setup_code
,
每次创建exposure
时,我想从A-Z递增setup_code
怎么实现?
到目前为止,这是我所做的,根据this other answer,我看到可以使用next
class Exposure < ApplicationRecord
before_create :create_setup_code
def create_setup_code
last_setup_code = self.last.setup_code
last_setup_code.next
end
end
如果我已经在数据库中至少有一个setup_code,例如setup_code ='A',则此方法将起作用,然后对于下一个setup_code,当调用before_create
时它将为'B'。
如何使用第一个setup_code = 'A'
进行初始化?
答案 0 :(得分:2)
为避免出现竞争情况,您可能需要使用自动递增列。
首先创建一个自动递增的列:
class AddSetupCodeToExposures < ActiveRecord::Migration[5.2]
def change
add_column :exposures, :setup_code, :serial
end
end
此示例适用于PostgreSQL。在MySQL上,仅当您使用MyISAM而不是INNODB时,才可以创建辅助自动递增列(为什么?),因此您需要找到其他解决方案。
由于ID列会自动递增,因此您可以使用它。
在Ruby中将整数映射到字母是非常简单的:
irb(main):008:0> ALPHABET = ('A'..'Z').to_a.unshift(nil)
=> [nil, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
irb(main):009:0> ALPHABET[1]
=> "A"
irb(main):010:0> ALPHABET[26]
=> "Z"
在模型中,我们可以使用自定义的setter和getter处理值的铸造:
class Exposure < ApplicationRecord
# This creates a array of the 24 ASCII letters A..Z
# adding null to the beginning lets us treat it as a 1 indexed array
ALPHABET = ('A'..'Z').to_a.unshift(nil)
def setup_code
# the default value here handles out of range values
self.class.integer_to_letter(super || 0) || "default_value"
end
def setup_code=(value)
super self.class.integer_to_letter(value)
end
def self.integer_to_letter(integer)
ALPHABET[integer]
end
def self.letter_to_integer(letter)
ALPHABET.index(letter)
end
end
具有自动递增的列和数据库默认值的陷阱是插入记录时未填充该列:
irb(main):005:0> e = Exposure.create
(0.2ms) BEGIN
Exposure Create (0.7ms) INSERT INTO "exposures" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", "2018-09-27 12:26:24.672016"], ["updated_at", "2018-09-27 12:26:24.672016"]]
(0.6ms) COMMIT
=> #<Exposure id: 3, created_at: "2018-09-27 12:26:24", updated_at: "2018-09-27 12:26:24", setup_code: nil>
irb(main):006:0> e.setup_code
=> "default_value"
irb(main):007:0> e.reload
Exposure Load (0.7ms) SELECT "exposures".* FROM "exposures" WHERE "exposures"."id" = $1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
=> #<Exposure id: 3, created_at: "2018-09-27 12:26:24", updated_at: "2018-09-27 12:26:24", setup_code: 3>
irb(main):008:0> e.setup_code
=> "C"
由于ActiveRecord在插入时仅返回id
列。
这并没有真正改变列-而是使用旧的重命名技巧来创建新列并将旧值移动到新列。
# rename the old column
class RenameExposuresSequenceCode < ActiveRecord::Migration[5.2]
def change
rename_column :exposures, :setup_code, :old_setup_code
end
end
# add the new column
class AddSequencedSetupCodeToExposures < ActiveRecord::Migration[5.2]
def change
add_column :exposures, :setup_code, :serial
end
end
# convert the existing values
class ConvertOldSetupCodes < ActiveRecord::Migration[5.2]
def up
Exposure.find_each do |e|
converted_code = Exposure.letter_to_integer(e.old_setup_code)
e.update_attribute(setup_code: converted_code) if converted_code
end
end
def down
Exposure.find_each do |e|
converted_code = Exposure.integer_to_letter(e.setup_code)
e.update_attribute(old_setup_code: converted_code) if converted_code
end
end
end
# remove the old column
class RemoveOldSetupCodeFromExposures < ActiveRecord::Migration[5.2]
def change
remove_column :exposures, :old_setup_code, :string
end
end
答案 1 :(得分:2)
在Postgres中,实际上有一种很酷的方法,可以将计算出的默认值应用于列。
首先,我们要定义一个Postgres函数,该函数为您提供一个整数(1 = A,26 = Z)的字母,以便我们可以得到一个给定ID的字母。
class CreateIdToLetterFunction < ActiveRecord::Migration[5.2]
def up
execute <<-SQL
CREATE OR REPLACE FUNCTION id_to_letter(integer) RETURNS varchar
AS 'select chr(64 + $1)'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
SQL
end
def down
execute <<-SQL
DROP FUNCTION id_to_letter(integer);
SQL
end
end
chr(int)
返回具有给定ASCII码的字符。在ASCII中,A的值为65,因此我们需要将该值填充为64。
然后我们添加对曝光量进行计数的功能,以便我们可以使用默认值:
class AddExposureCountFunction < ActiveRecord::Migration[5.2]
def up
execute <<-SQL
CREATE OR REPLACE FUNCTION count_exposures() RETURNS bigint
AS 'select count(*) from exposures'
LANGUAGE SQL
VOLATILE;
SQL
end
def down
execute <<-SQL
DROP FUNCTION count_exposures();
SQL
end
end
然后,我们要更改Exposures.setup_code列以添加默认值。
class AddDefaultSetupCodeToExposures < ActiveRecord::Migration[5.2]
def change
change_column_default(
:exposures,
:setup_code,
from: nil,
to: -> {"id_to_letter(CAST(count_exposures() AS integer) + 1)"}
)
end
end
我们将新的默认值包装在lambda(->{}
)中,因为这告诉ActiveRecord该值不是文字值,应作为SQL添加到语句中。
由于这是在数据库级别处理的,因此不需要其他模型代码。请注意,有关数据库设置的默认值的警告适用-在从数据库重新加载实例之前,该值将为nil。
但是,如果您希望从注释中获得功能上等同于不良代码的功能,则只需:
class Exposure < ApplicationRecord
SETUP_CODES = %w{ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A2 B2 C2 D2 E2 F2 G2 H2 }
def self.next_setup_code(current_code)
if !SETUP_CODES.index(current_code) || current_code == SETUP_CODES.last
"error"
else
SETUP_CODES[ SETUP_CODES.index(current_code) + 1 ]
end
end
end
答案 2 :(得分:0)
class Exposure < ApplicationRecord
NO_FORMAT = "A"
before_create :create_setup_code
def next_setup_code
(Exposure.count > 0 ? Exposure.last.setup_code : Exposure::NO_FORMAT).succ
end
def create_setup_code
self.setup_code = next_setup_code
end
end
您可以创建任意数量的
。