Bivio::SQL::FormSupport
# Copyright (c) 1999-2012 bivio Software, Inc. All rights reserved. # $Id$ package Bivio::SQL::FormSupport; use strict; use Bivio::Base 'SQL.Support'; b_use('IO.Trace'); # C<Bivio::SQL::FormSupport> is the meta-data model for # L<Bivio::Biz::FormModel>s. This module does not execute SQL. # # # See also L<Bivio::SQL::Support|Bivio::SQL::Support> for more attributes. # # # auth_id : array_ref # # A field or field identity which must be equal to # request's I<auth_id> attribute. # # file_fields : array_ref # # The columns which are L<Bivio::Type::FileField|Bivio::Type::FileField> # type. C<undef> if no file fields. # # file_field_names : array_ref # # Names of I<file_fields> columns. # # has_secure_data : boolean # # One of the fields contains secure_data # (L<Bivio::Type::is_secure_data|Bivio::Type/"is_secure_data">). # # hidden : array_ref # # List of columns which are to be sent to and returned from the user, unmodified. # # hidden_field_names : array_ref # # Names of I<hidden> columns. # # in_list : array_ref # # List of columns for which I<in_list> is true. # # in_list_field_names : array_ref # # Names of I<in_list> columns. # # list_class : string # # If set, fields are repeatable. Primary key fields of the list model are # added, automatically as hidden fields. # # It may be the simple model name or full ListModel on declaration. # Will be prefixed with C<Bivio::Biz::ListModel::> and loaded # dynamically. # # These fields are also added to I<hidden>. # # other : array_ref # # A list of fields and field identities. These are not output # with the form. They are used for internal communication between # the form and the UI. # # primary_key : array_ref # # The list of fields and field identities that uniquely identifies a # form. # # require_context : boolean # # True if the form expects to have context when operating. # # require_validate : boolean # # True if the form requires validate to be called even on direct execution. # # version : int # # The version of this particular combination of fields. It will be # set in all outgoing forms. It should be changed whenever the # declaration changes. It is used to reject an out-of-date form. # # visible : array_ref # # List of columns to be made visible to the user. # # visible_field_names : array_ref # # Names of I<visible> columns. # # # # # default_value : any [undef] # # Specifies the default value. Currently doesn't handle reference types # properly. # # in_list : boolean # # True if the field is repeatable. # See L<Bivio::Biz::ListFormModel|Bivio::Biz::ListFormModel>. # All these columns are in I<in_list> global attribute. # # is_file_field : boolean # # True if the field is a file field. # # is_visible : boolean # # True if the field is visible. # # form_name : string # # Let's you specify form names explicitly for special cases, e.g. # incoming mail via b-sendmail-agent. my($_M) = b_use('Biz.Model'); our($_TRACE); my($_CLASSES) = [qw(auth_id visible hidden primary_key other)]; sub extract_column_from_classes { my($proto, $decls, $column_name) = @_; foreach my $c (map(@{$decls->{$_} || []}, @$_CLASSES)) { if (ref($c)) { return $c if $c->{name} eq $column_name; } else { return {name => $c} if $c eq $column_name; } } Bivio::Die->die($column_name, ': not found in ', $decls); # DOES NOT RETURN } sub get_column_name_for_html { # (self, string) : string # Returns the name of the column for an HTML form. my($columns) = shift->get('columns'); my($name) = shift; my($col) = $columns->{$name}; die("$name: no such column") unless $col; return $col->{form_name}; } sub new { # (proto, hash_ref) : SQL.FormSupport # Creates a SQL support instance from a declaration. A I<decl> is a list of # keyed categories. The keys are described below. The values are either an # array_ref, a string (except I<version>), or a hash. # The array_ref may contain strings # (property fields), hash_refs (local fields), # or array_refs of strings (field identities). # # A I<property model field> is composed of a # table qualifier and the column name. The first field in a field identity is # a property of the form model. # # A I<local field> is defined as a hash_ref containing one or more # of the following attributes: # # # name : string (required) # # name : array_ref (required) # # is a perl identifier (\w+) or a property model field identifier (\w+.\w+). # If the I<name> is an array_ref, the first element is the property name. # # type : Bivio::Type # # The type of the local field. You may override the type of a # property model field with this field. # # constraint : Bivio::SQL::Constraint # # The constraint of the local field. You may override the constraint of a # property model field with this field. # # # The types of the property fields will be extracted from the property # models corresponding to the table names unless overridden my($proto, $decl) = @_; my($attrs) = { # All columns by qualified name columns => {}, # All models by qualified name models => {}, # All fields and field identities by qualified name column_aliases => {}, # Columns which have no corresponding property model field local_columns => [], map(($_ => $decl->{$_} ? 1 : 0), qw(require_validate require_context)), has_secure_data => 0, }; $proto->init_common_attrs($attrs, $decl); # Modify the declarations to include the list model primary key _init_list_class($attrs, $decl); _init_column_classes($attrs, $decl); # Finish up by creating in_list_columns _init_list_columns($attrs, $decl); return $proto->SUPER::new($attrs); } sub _form_name { my($col, $i, $form_names) = @_; if ($col->{form_name}) { b_die( $col->{name}, q{: form name cannot be fNN. You probably have a field in both the 'visible' and 'hidden' sections of your form definition. OR, you may be trying to edit the primary key field of a ListFormModel's ListModel.} ) if $col->{form_name} =~ /^f\d+$/; } else { $col->{form_name} = 'f' . $i++; } b_die( $col->{name}, ': duplicate form name (', $col->{form_name}, ')', ) if $form_names->{$col->{form_name}}++; ($col->{json_form_name} = lc($col->{name})) =~ s/\W/_/g; return $i; } sub _init_column_classes { # (hash_ref, hash_ref) : undef # Initialize the column classes (auth_id, visible, hidden, etc.) my($attrs, $decl) = @_; my($column_aliases) = $attrs->{column_aliases}; __PACKAGE__->init_column_classes($attrs, $decl, $_CLASSES); # auth_id must be at most one column. die('too many auth_id fields') if int(@{$attrs->{auth_id}}) > 1; # Will set to undef if no auth_id. $attrs->{auth_id} = $attrs->{auth_id}->[0]; # Ensure that (qual) columns defined for all (qual) models and their # primary keys and initialize primary_key_map. __PACKAGE__->init_model_primary_key_maps($attrs); # These lists are sorted in keeping with other Support modules $attrs->{primary_key_names} = [map {$_->{name}} @{$attrs->{primary_key}}]; $attrs->{primary_key_types} = [map {$_->{type}} @{$attrs->{primary_key}}]; $attrs->{column_names} = [sort(keys(%{$attrs->{columns}}))]; # Assign form_name to each of the fields that can be in the form # Add it as an alias for easy input parsing. my($i) = 0; $attrs->{file_fields} = undef; #TODO: These must agree with FormModel my($form_names) = {(Bivio::Biz::FormModel->VERSION_FIELD() => 1, Bivio::Biz::FormModel->CONTEXT_FIELD() => 1, Bivio::Biz::FormModel->TIMEZONE_FIELD() => 1,)}; $attrs->{json_form_name_map} = {}; foreach my $col (@{$attrs->{visible}}, @{$attrs->{hidden}}) { $i = _form_name($col, $i, $form_names); $attrs->{json_form_name_map}->{$col->{json_form_name}} = $col; $attrs->{column_aliases}->{$col->{form_name}} = $col; push(@{$attrs->{file_fields} ||= []}, $col) if $col->{is_file_field} = b_use('Type.FileField')->is_super_of($col->{type}); $attrs->{has_secure_data} = 1 if $col->{type}->is_secure_data; # Defaults to false and overwritten below $col->{is_hidden} = 0; } # Reset is_visible for visible fields. foreach my $col (@{$attrs->{visible}}) { $col->{is_visible} = 1; } # Map field name lists $attrs->{visible_field_names} = [sort(map { $_->{name} } @{$attrs->{visible}})]; $attrs->{hidden_field_names} = [sort(map { $_->{name} } @{$attrs->{hidden}})]; $attrs->{file_field_names} = [sort(map { $_->{name} } @{$attrs->{file_fields}})] if $attrs->{file_fields}; return; } sub _init_list_class { # (hash_ref, hash_ref) : undef # Initialize the list_class and primary_key attributes by copying # list_class's primary_key to this model. my($attrs, $decl) = @_; # No list return unless $decl->{list_class}; my($lm) = $_M->get_instance($decl->{list_class}); $attrs->{list_class} = ref($lm); # Set the primary_key by copying the list_class's primary key $decl->{hidden} = [] unless $decl->{hidden}; foreach my $col (@{$lm->get_info('primary_key')}) { # Copy all the attributes, because list_class may override # the attributes or have local fields. my($c) = { name => $col->{name}, type => $col->{type}, constraint => $col->{constraint}, in_list => 1, }; push(@{$decl->{hidden}}, $c); } return; } sub _init_list_columns { # (hash_ref, hash_ref) : undef # Initialize in_list and in_list_field_names my($attrs, $decl) = @_; $attrs->{in_list} = []; $attrs->{in_list_field_names} = []; foreach my $cn (@{$attrs->{column_names}}) { my($col) = $attrs->{columns}->{$cn}; next unless $col->{in_list}; push(@{$attrs->{in_list}}, $col); push(@{$attrs->{in_list_field_names}}, $cn); } return; } 1;