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
146 def self.extended(db)
147   db.constraint_validations_table ||= DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
148 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
307 def alter_table_generator(&block)
308   super do
309     extend AlterTableGeneratorMethods
310     @validations = []
311     instance_exec(&block) if block
312   end
313 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
245 def create_constraint_validations_table
246   create_table(constraint_validations_table) do
247     String :table, :null=>false
248     String :constraint_name
249     String :validation_type, :null=>false
250     String :column, :null=>false
251     String :argument
252     String :message
253     TrueClass :allow_nil
254   end
255 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
259 def create_table_generator(&block)
260   super do
261     extend CreateTableGeneratorMethods
262     @validations = []
263     instance_exec(&block) if block
264   end
265 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
288 def drop_constraint_validations_for(opts=OPTS)
289   ds = from(constraint_validations_table)
290   if table = opts[:table]
291     ds = ds.where(:table=>constraint_validations_literal_table(table))
292   end
293   if column = opts[:column]
294     ds = ds.where(:column=>column.to_s)
295   end
296   if constraint = opts[:constraint]
297     ds = ds.where(:constraint_name=>constraint.to_s)
298   end
299   unless table || column || constraint
300     raise Error, "must specify :table, :column, or :constraint when dropping constraint validations"
301   end
302   ds.delete
303 end
drop_constraint_validations_table() click to toggle source

Drop the constraint validations table.

    # File lib/sequel/extensions/constraint_validations.rb
268 def drop_constraint_validations_table
269   drop_table(constraint_validations_table)
270 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
323 def apply_alter_table_generator(name, generator)
324   super
325   unless generator.validations.empty?
326     gen = alter_table_generator
327     process_generator_validations(name, gen, generator.validations)
328     apply_alter_table(name, gen.operations)
329   end
330 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
334 def blank_string_value
335   if database_type == :oracle
336     nil
337   else
338     ''
339   end
340 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
345 def constraint_validations_literal_table(table)
346   dataset.with_quote_identifiers(false).literal(table)
347 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
351 def create_table_from_generator(name, generator, options)
352   unless generator.validations.empty?
353     process_generator_validations(name, generator, generator.validations)
354   end
355   super
356 end
generator_add_constraint_from_validation(generator, val, cons) click to toggle source

Add the constraint to the generator, including a NOT NULL constraint for all columns unless the :allow_nil option is given.

    # File lib/sequel/extensions/constraint_validations.rb
443 def generator_add_constraint_from_validation(generator, val, cons)
444   if val[:allow_nil]
445     nil_cons = Sequel[val[:columns].map{|c| [c, nil]}]
446     cons = Sequel.|(nil_cons, cons) if cons
447   else
448     nil_cons = Sequel.negate(val[:columns].map{|c| [c, nil]})
449     cons = cons ? Sequel.&(nil_cons, cons) : nil_cons
450   end
451 
452   if cons
453     generator.constraint(val[:name], cons)
454   end
455 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
460 def generator_string_column?(generator, table, c)
461   if generator.is_a?(Sequel::Schema::AlterTableGenerator)
462     # This is the alter table case, which runs after the
463     # table has been altered, so just check the database
464     # schema for the column.
465     schema(table).each do |col, sch|
466       if col == c
467         return sch[:type] == :string
468       end
469     end
470     false
471   else
472     # This is the create table case, check the metadata
473     # for the column to be created to see if it is a string.
474     generator.columns.each do |col|
475       if col[:name] == c
476         return [String, :text, :varchar].include?(col[:type])
477       end
478     end
479     false
480   end
481 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
361 def process_generator_validations(table, generator, validations)
362   drop_rows = []
363   rows = validations.map do |val|
364     columns, arg, constraint, validation_type, message, allow_nil = val.values_at(:columns, :arg, :name, :type, :message, :allow_nil)
365 
366     case validation_type
367     when :presence
368       string_check = columns.select{|c| generator_string_column?(generator, table, c)}.map{|c| [Sequel.trim(c), blank_string_value]}
369       generator_add_constraint_from_validation(generator, val, (Sequel.negate(string_check) unless string_check.empty?))
370     when :exact_length
371       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {Sequel.char_length(c) => arg}}))
372     when :min_length
373       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.char_length(c) >= arg}))
374     when :max_length
375       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.char_length(c) <= arg}))
376     when *REVERSE_OPERATOR_MAP.keys
377       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.identifier(c).public_send(REVERSE_OPERATOR_MAP[validation_type], arg)}))
378     when :length_range
379       op = arg.exclude_end? ? :< : :<=
380       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| (Sequel.char_length(c) >= arg.begin) & Sequel.char_length(c).public_send(op, arg.end)}))
381       arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
382     when :format
383       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
384       if arg.casefold?
385         validation_type = :iformat
386       end
387       arg = arg.source
388     when :includes
389       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| {c => arg}}))
390       if arg.is_a?(Range)
391         if arg.begin.is_a?(Integer) && arg.end.is_a?(Integer)
392           validation_type = :includes_int_range
393           arg = "#{arg.begin}..#{'.' if arg.exclude_end?}#{arg.end}"
394         else
395           raise Error, "validates includes with a range only supports integers currently, cannot handle: #{arg.inspect}"
396         end
397       elsif arg.is_a?(Array)
398         if arg.all?{|x| x.is_a?(Integer)}
399           validation_type = :includes_int_array
400         elsif arg.all?{|x| x.is_a?(String)}
401           validation_type = :includes_str_array
402         else
403           raise Error, "validates includes with an array only supports strings and integers currently, cannot handle: #{arg.inspect}"
404         end
405         arg = arg.join(',')
406       else
407         raise Error, "validates includes only supports arrays and ranges currently, cannot handle: #{arg.inspect}"
408       end
409     when :like, :ilike
410       generator_add_constraint_from_validation(generator, val, Sequel.&(*columns.map{|c| Sequel.public_send(validation_type, c, arg)}))
411     when :unique
412       generator.unique(columns, :name=>constraint)
413       columns = [columns.join(',')]
414     when :drop
415       if generator.is_a?(Sequel::Schema::AlterTableGenerator)
416         unless constraint
417           raise Error, 'cannot drop a constraint validation without a constraint name'
418         end
419         generator.drop_constraint(constraint)
420         drop_rows << [constraint_validations_literal_table(table), constraint.to_s]
421         columns = []
422       else
423         raise Error, 'cannot drop a constraint validation in a create_table generator'
424       end
425     else
426       raise Error, "invalid or missing validation type: #{val.inspect}"
427     end
428 
429     columns.map do  |column|
430       {: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}
431     end
432   end
433 
434   ds = from(:sequel_constraint_validations)
435   unless drop_rows.empty?
436     ds.where([:table, :constraint_name]=>drop_rows).delete
437   end
438   ds.multi_insert(rows.flatten)
439 end