module Sequel::ConstraintValidations
Constants
- DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
The default table name used for the validation metadata.
- OPERATORS
- REVERSE_OPERATOR_MAP
Attributes
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
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
Modify the default alter_table generator to include the constraint validation methods.
# 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 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
Modify the default create_table generator to include the constraint validation methods.
# 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
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 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
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.
# 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
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
Before creating the table, add constraints for all of the generators validations to the generator.
# 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
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
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
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