module Sequel::ConstraintValidations

Constants

DEFAULT_CONSTRAINT_VALIDATIONS_TABLE

The default table name used for the validation metadata.

OPERATORS
REVERSE_OPERATOR_MAP

Attributes

constraint_validations_table[RW]

The name of the table storing the validation metadata. If modifying this from the default, this should be changed directly after loading the extension into the database

Public Class Methods

extended(db) click to toggle source

Set the default validation metadata table name if it has not already been set.

    # File lib/sequel/extensions/constraint_validations.rb
150 def self.extended(db)
151   db.constraint_validations_table ||= DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
152 end

Public Instance Methods

alter_table_generator(&block) click to toggle source

Modify the default alter_table generator to include the constraint validation methods.

Calls superclass method
    # File lib/sequel/extensions/constraint_validations.rb
321 def alter_table_generator(&block)
322   super do
323     extend AlterTableGeneratorMethods
324     @validations = []
325     instance_exec(&block) if block
326   end
327 end
create_constraint_validations_table() click to toggle source

Create the table storing the validation metadata for all of the constraints created by this extension.

    # File lib/sequel/extensions/constraint_validations.rb
249 def create_constraint_validations_table
250   create_table(constraint_validations_table) do
251     String :table, :null=>false
252     String :constraint_name
253     String :validation_type, :null=>false
254     String :column, :null=>false
255     String :argument
256     String :message
257     TrueClass :allow_nil
258   end
259 end
create_table_generator(&block) click to toggle source

Modify the default create_table generator to include the constraint validation methods.

Calls superclass method
    # File lib/sequel/extensions/constraint_validations.rb
263 def create_table_generator(&block)
264   super do
265     extend CreateTableGeneratorMethods
266     @validations = []
267     instance_exec(&block) if block
268   end
269 end
drop_constraint_validations_for(opts=OPTS) click to toggle source

Delete validation metadata for specific constraints. At least one of the following options should be specified:

:table

The table containing the constraint

:column

The column affected by the constraint

:constraint

The name of the related constraint

The main reason for this method is when dropping tables or columns. If you have previously defined a constraint validation on the table or column, you should delete the related metadata when dropping the table or column. For a table, this isn’t a big issue, as it will just result in some wasted space, but for columns, if you don’t drop the related metadata, it could make it impossible to save rows, since a validation for a nonexistent column will be created.

    # File lib/sequel/extensions/constraint_validations.rb
302 def drop_constraint_validations_for(opts=OPTS)
303   ds = from(constraint_validations_table)
304   if table = opts[:table]
305     ds = ds.where(:table=>constraint_validations_literal_table(table))
306   end
307   if column = opts[:column]
308     ds = ds.where(:column=>column.to_s)
309   end
310   if constraint = opts[:constraint]
311     ds = ds.where(:constraint_name=>constraint.to_s)
312   end
313   unless table || column || constraint
314     raise Error, "must specify :table, :column, or :constraint when dropping constraint validations"
315   end
316   ds.delete
317 end
drop_constraint_validations_table() click to toggle source

Drop the constraint validations table.

    # File lib/sequel/extensions/constraint_validations.rb
282 def drop_constraint_validations_table
283   drop_table(constraint_validations_table)
284 end
drop_table(*names) click to toggle source

Drop all constraint validations for a table if dropping the table.

Calls superclass method
    # File lib/sequel/extensions/constraint_validations.rb
272 def drop_table(*names)
273   names.each do |name|
274     if !name.is_a?(Hash) && table_exists?(constraint_validations_table)
275       drop_constraint_validations_for(:table=>name)
276     end
277   end
278   super
279 end

Private Instance Methods

apply_alter_table_generator(name, generator) click to toggle source

After running all of the table alteration statements, if there were any constraint validations, run table alteration statements to create related constraints. This is purposely run after the other statements, as the presence validation in alter table requires introspecting the modified model schema.

Calls superclass method
    # File lib/sequel/extensions/constraint_validations.rb
337 def apply_alter_table_generator(name, generator)
338   super
339   unless generator.validations.empty?
340     gen = alter_table_generator
341     process_generator_validations(name, gen, generator.validations)
342     apply_alter_table(name, gen.operations)
343   end
344 end
blank_string_value() click to toggle source

The value of a blank string. An empty string by default, but nil on Oracle as Oracle treats the empty string as NULL.

    # File lib/sequel/extensions/constraint_validations.rb
348 def blank_string_value
349   if database_type == :oracle
350     nil
351   else
352     ''
353   end
354 end
constraint_validation_expression(cols, allow_nil) { |c| ... } click to toggle source
    # File lib/sequel/extensions/constraint_validations.rb
372 def constraint_validation_expression(cols, allow_nil)
373   exprs = cols.map do |c|
374     expr = yield c
375     if allow_nil
376       Sequel.|({c=>nil}, expr)
377     else
378       Sequel.&(Sequel.~(c=>nil), expr)
379     end
380   end
381   Sequel.&(*exprs)
382 end
constraint_validations_literal_table(table) click to toggle source

Return an unquoted literal form of the table name. This allows the code to handle schema qualified tables, without quoting all table names.

    # File lib/sequel/extensions/constraint_validations.rb
359 def constraint_validations_literal_table(table)
360   dataset.with_quote_identifiers(false).literal(table)
361 end
create_table_from_generator(name, generator, options) click to toggle source

Before creating the table, add constraints for all of the generators validations to the generator.

Calls superclass method
    # File lib/sequel/extensions/constraint_validations.rb
365 def create_table_from_generator(name, generator, options)
366   unless generator.validations.empty?
367     process_generator_validations(name, generator, generator.validations)
368   end
369   super
370 end
generator_string_column?(generator, table, c) click to toggle source

Introspect the generator to determine if column created is a string or not.

    # File lib/sequel/extensions/constraint_validations.rb
485 def generator_string_column?(generator, table, c)
486   if generator.is_a?(Sequel::Schema::AlterTableGenerator)
487     # This is the alter table case, which runs after the
488     # table has been altered, so just check the database
489     # schema for the column.
490     schema(table).each do |col, sch|
491       if col == c
492         return sch[:type] == :string
493       end
494     end
495     false
496   else
497     # This is the create table case, check the metadata
498     # for the column to be created to see if it is a string.
499     generator.columns.each do |col|
500       if col[:name] == c
501         return [String, :text, :varchar].include?(col[:type])
502       end
503     end
504     false
505   end
506 end
process_generator_validations(table, generator, validations) click to toggle source

For the given table, generator, and validations, add constraints to the generator for each of the validations, as well as adding validation metadata to the constraint validations table.

    # File lib/sequel/extensions/constraint_validations.rb
387 def process_generator_validations(table, generator, validations)
388   drop_rows = []
389   rows = validations.map do |val|
390     columns, arg, constraint, validation_type, message, allow_nil = val.values_at(:columns, :arg, :name, :type, :message, :allow_nil)
391 
392     case validation_type
393     when :presence
394       strings, non_strings = columns.partition{|c| generator_string_column?(generator, table, c)}
395       if !non_strings.empty? && !allow_nil
396         non_strings_expr = Sequel.&(*non_strings.map{|c| Sequel.~(c=>nil)})
397       end
398 
399       unless strings.empty?
400         strings_expr = constraint_validation_expression(strings, allow_nil){|c| Sequel.~(Sequel.trim(c) => blank_string_value)}
401       end
402 
403       expr = if non_strings_expr && strings_expr
404         Sequel.&(strings_expr, non_strings_expr)
405       else
406         strings_expr || non_strings_expr
407       end
408 
409       if expr
410         generator.constraint(constraint, expr)
411       end
412     when :exact_length
413       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| {Sequel.char_length(c) => arg}})
414     when :min_length
415       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.char_length(c) >= arg})
416     when :max_length
417       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.char_length(c) <= arg})
418     when *REVERSE_OPERATOR_MAP.keys
419       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.identifier(c).public_send(REVERSE_OPERATOR_MAP[validation_type], arg)})
420     when :length_range
421       op = arg.exclude_end? ? :< : :<=
422       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| (Sequel.char_length(c) >= arg.begin) & Sequel.char_length(c).public_send(op, arg.end)})
423       arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
424     when :format
425       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| {c => arg}})
426       if arg.casefold?
427         validation_type = :iformat
428       end
429       arg = arg.source
430     when :includes
431       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| {c => arg}})
432       if arg.is_a?(Range)
433         if arg.begin.is_a?(Integer) && arg.end.is_a?(Integer)
434           validation_type = :includes_int_range
435           arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
436         else
437           raise Error, "validates includes with a range only supports integers currently, cannot handle: #{arg.inspect}"
438         end
439       elsif arg.is_a?(Array)
440         if arg.all?{|x| x.is_a?(Integer)}
441           validation_type = :includes_int_array
442         elsif arg.all?{|x| x.is_a?(String)}
443           validation_type = :includes_str_array
444         else
445           raise Error, "validates includes with an array only supports strings and integers currently, cannot handle: #{arg.inspect}"
446         end
447         arg = arg.join(',')
448       else
449         raise Error, "validates includes only supports arrays and ranges currently, cannot handle: #{arg.inspect}"
450       end
451     when :like, :ilike
452       generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.public_send(validation_type, c, arg)})
453     when :unique
454       generator.unique(columns, :name=>constraint)
455       columns = [columns.join(',')]
456     when :drop
457       if generator.is_a?(Sequel::Schema::AlterTableGenerator)
458         unless constraint
459           raise Error, 'cannot drop a constraint validation without a constraint name'
460         end
461         generator.drop_constraint(constraint)
462         drop_rows << [constraint_validations_literal_table(table), constraint.to_s]
463         columns = []
464       else
465         raise Error, 'cannot drop a constraint validation in a create_table generator'
466       end
467     else
468       raise Error, "invalid or missing validation type: #{val.inspect}"
469     end
470 
471     columns.map do  |column|
472       {:table=>constraint_validations_literal_table(table), :constraint_name=>(constraint.to_s if constraint), :validation_type=>validation_type.to_s, :column=>column.to_s, :argument=>(arg.to_s if arg), :message=>(message.to_s if message), :allow_nil=>allow_nil}
473     end
474   end
475 
476   ds = from(constraint_validations_table)
477   unless drop_rows.empty?
478     ds.where([:table, :constraint_name]=>drop_rows).delete
479   end
480   ds.multi_insert(rows.flatten)
481 end