sexy_pg_constraints

专属于 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

你可能感兴趣的:(sexy_pg_constraints)