专属于 PostgreSQL 给定一些 ’约束‘constraints
作用于 db/migration (DB迁移文件),其实它只不过是一个方法(constrain)一个文件而矣,你能很方便找出和编写代码。
class AddConstraintsToBooks < ActiveRecord::Migration
def self.up
constrain :books do |t|
t.title :not_blank => true, :alphanumeric => true, :length_within => 3..50
t.isbn :unique => true, :blacklist => %w(badbook1 badbook2)
end
end
def self.down
deconstrain :books do |t|
t.title :not_blank, :alphanumeric, :length_within
t.isbn :unique, :blacklist
end
end
end
如果你还没想清楚,不能够一次性的设置所有 属性 的可选 options 可以这么做
constrain :books, :title, :not_blank => true, :length_within => 3..50
这等同于
constrain :books do |t|
t.title :not_blank => true, :length_within => 3..50
end
上面所列举的每次都只 约束 一个属性, 如果它们有相同的 约束时 就会存在重复,还好我们可以以 数组 的形式来 DRY
class AddConstraintsToBooks < ActiveRecord::Migration
def self.up
constrain :books do |t|
t[:title, :author_id].all :unique => true # Notice how multiple columns are listed in brackets.
end
end
def self.down
deconstrain :books do |t|
t[:title, :author_id].all :unique
end
end
end
不同一 rails 的约定,sexy_pg_constraints 可以有 ‘外键’
# 这个例子并不能够很好的说明问题
class AddConstraintsToBooks < ActiveRecord::Migration
def self.up
constrain :books do |t|
t.author_id :reference => {:authors => :id, :on_delete => :cascade} # :on_delete 可选
end
end
def self.down
deconstrain :books do |t|
t.author_id :reference
end
end
end
如下面的代码所示,它支持多达 16 种的约束(原谅我的 cpoy + parse)
module SexyPgConstraints
module Constraints
module_function
##
# Only allow listed values.
#
# Example:
# constrain :books, :variation, :whitelist => %w(hardcover softcover)
#
def whitelist(table, column, options)
"check (#{table}.#{column} in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
end
##
# Prohibit listed values.
#
# Example:
# constrain :books, :isbn, :blacklist => %w(invalid_isbn1 invalid_isbn2)
#
def blacklist(table, column, options)
"check (#{table}.#{column} not in (#{ options.collect{|v| "'#{v}'"}.join(',') }))"
end
##
# The value must have at least 1 non-space character.
#
# Example:
# constrain :books, :title, :not_blank => true
#
def not_blank(table, column, options)
"check ( length(trim(both from #{table}.#{column})) > 0 )"
end
##
# The numeric value must be within given range.
#
# Example:
# constrain :books, :year, :within => 1980..2008
# constrain :books, :year, :within => 1980...2009
# (the two lines above do the same thing)
#
def within(table, column, options)
column_ref = column.to_s.include?('.') ? column : "#{table}.#{column}"
"check (#{column_ref} >= #{options.begin} and #{column_ref} #{options.exclude_end? ? ' < ' : ' <= '} #{options.end})"
end
##
# Check the length of strings/text to be within the range.
#
# Example:
# constrain :books, :author, :length_within => 4..50
#
def length_within(table, column, options)
within(table, "length(#{table}.#{column})", options)
end
##
# Allow only valid email format.
#
# Example:
# constrain :books, :author, :email => true
#
def email(table, column, options)
"check (((#{table}.#{column})::text ~ E'^([-a-z0-9]+)@([-a-z0-9]+[.]+[a-z]{2,4})$'::text))"
end
##
# Allow only alphanumeric values.
#
# Example:
# constrain :books, :author, :alphanumeric => true
#
def alphanumeric(table, column, options)
"check (((#{table}.#{column})::text ~* '^[a-z0-9]+$'::text))"
end
##
# Allow only lower case values.
#
# Example:
# constrain :books, :author, :lowercase => true
#
def lowercase(table, column, options)
"check (#{table}.#{column} = lower(#{table}.#{column}))"
end
##
# Allow only positive values.
#
# Example:
# constrain :books, :quantity, :positive => true
#
def positive(table, column, options)
"check (#{table}.#{column} >= 0)"
end
##
# Allow only odd values.
#
# Example:
# constrain :books, :quantity, :odd => true
#
def odd(table, column, options)
"check (mod(#{table}.#{column}, 2) != 0)"
end
##
# Allow only even values.
#
# Example:
# constrain :books, :quantity, :even => true
#
def even(table, column, options)
"check (mod(#{table}.#{column}, 2) = 0)"
end
##
# Make sure every entry in the column is unique.
#
# Example:
# constrain :books, :isbn, :unique => true
#
def unique(table, column, options)
column = Array(column).map {|c| %{"#{c}"} }.join(', ')
"unique (#{column})"
end
##
# Allow only one of the values in the given columns to be true.
# Only reasonable with more than one column.
# See Enterprise Rails, Chapter 10 for details.
#
# Example:
# constrain :books, [], :xor => true
#
def xor(table, column, options)
addition = Array(column).map {|c| %{("#{c}" is not null)::integer} }.join(' + ')
"check (#{addition} = 1)"
end
##
# Allow only text/strings of the exact length specified, no more, no less.
#
# Example:
# constrain :books, :hash, :exact_length => 32
#
def exact_length(table, column, options)
"check ( length(trim(both from #{table}.#{column})) = #{options} )"
end
##
# Allow only values that match the regular expression.
#
# Example:
# constrain :orders, :visa, :format => /^([4]{1})([0-9]{12,15})$/
#
def format(table, column, options)
"check (((#{table}.#{column})::text #{options.casefold? ? '~*' : '~'} E'#{options.source}'::text ))"
end
##
# Add foreign key constraint.
#
# Example:
# constrain :books, :author_id, :reference => {:authors => :id, :on_delete => :cascade}
#
def reference(table, column, options)
on_delete = options.delete(:on_delete)
fk_table = options.keys.first
fk_column = options[fk_table]
on_delete = "on delete #{on_delete}" if on_delete
%{foreign key ("#{column}") references #{fk_table} (#{fk_column}) #{on_delete}}
end
end
end