Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #861: js_validation.diff

File js_validation.diff, 8.1 kB (added by sebastian.kanthak@muehlheim.de, 3 years ago)

proof-of-concept patch for support of javascript validation

  • activerecord/test/validation_reflection_test.rb

    old new  
     1require 'abstract_unit' 
     2require 'fixtures/topic' 
     3require 'set' 
     4 
     5class ValidationReflectionTest < Test::Unit::TestCase 
     6  fixtures :topics 
     7   
     8  def setup 
     9    Topic.write_inheritable_attribute(:validate, nil) 
     10    Topic.write_inheritable_attribute(:validate_on_create, nil) 
     11    Topic.write_inheritable_attribute(:validate_on_update, nil) 
     12    Topic.write_inheritable_attribute("validations", nil) 
     13  end 
     14   
     15  def test_reflect_on_all_validations 
     16    Topic.validates_presence_of(:title, :content) 
     17    Topic.validates_acceptance_of(:terms_of_service) 
     18     
     19    assert_equal 3, Topic.reflect_on_all_validations.size 
     20    attr_names = Topic.reflect_on_all_validations.map { |v| v.name } 
     21    [:title,:content,:terms_of_service].each { |s| assert attr_names.include?(s) } 
     22     
     23    macro_names = Topic.reflect_on_all_validations.map { |v| v.macro } 
     24    assert_equal 2, macro_names.select{ |m| m == :validates_presence_of }.size 
     25    assert_equal 1, macro_names.select{ |m| m == :validates_acceptance_of }.size 
     26  end 
     27 
     28  def test_reflect_on_validations_for 
     29    Topic.validates_presence_of(:title, :content) 
     30    Topic.validates_length_of(:title, :within => 1...10) 
     31     
     32    title_validations = Topic.reflect_on_validations_for(:title) 
     33    assert_equal 2, title_validations.length 
     34    assert title_validations.find { |v| v.macro == :validates_presence_of } 
     35    assert title_validations.find { |v| v.macro == :validates_length_of } 
     36    assert !title_validations.find { |v| v.name != :title } 
     37 
     38    assert_equal 1, Topic.reflect_on_validations_for(:content).size 
     39     
     40    assert_equal [], Topic.reflect_on_validations_for(:does_not_exist) 
     41  end 
     42 
     43  # test whether options passed to validation macros can be recovered correctly 
     44  def test_reflect_options 
     45    Topic.validates_length_of(:title, :within => 1...10, :msg => "should be one digit") 
     46     
     47    validation = Topic.reflect_on_all_validations.first 
     48    assert_equal :title, validation.name 
     49    assert_equal 1...10, validation.options[:within] 
     50    assert_equal "should be one digit", validation.options[:msg] 
     51  end 
     52   
     53end 
  • activerecord/lib/active_record/reflection.rb

    old new  
    3131          end 
    3232        end_eval 
    3333      end 
     34       
     35      for validation_type in %w( validates_presence_of validates_acceptance_of validates_length_of ) 
     36        base.module_eval <<-"end_eval" 
     37            class << self 
     38                alias_method :#{validation_type}_without_reflection, :#{validation_type} 
     39                 
     40                def #{validation_type}_with_reflection(*attr_names) 
     41                    #{validation_type}_without_reflection(*attr_names) 
     42                    configuration = attr_names.last.is_a?(Hash) ? attr_names.pop : nil 
     43                    for attr_name in attr_names 
     44                        write_inheritable_array "validations", [ MacroReflection.new(:#{validation_type}, attr_name, configuration, self) ] 
     45                    end 
     46                end 
     47             
     48                alias_method :#{validation_type}, :#{validation_type}_with_reflection 
     49            end 
     50        end_eval 
     51      end 
    3452    end 
    3553 
    3654    # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.  
     
    6078      def reflect_on_association(association) 
    6179        reflect_on_all_associations.find { |reflection| reflection.name == association } unless reflect_on_all_associations.nil? 
    6280      end 
     81 
     82      # Returns an array of MacroReflection objects for all validations in the class 
     83      def reflect_on_all_validations 
     84        read_inheritable_attribute "validations" 
     85      end 
     86       
     87      # Returns an array of MacroReflection objects for all validations defined for the field +attr_name+ (expects a symbol) 
     88      def reflect_on_validations_for(attr_name) 
     89        reflect_on_all_validations.find_all { |reflection| reflection.name == attr_name } unless reflect_on_all_validations.nil? 
     90      end 
    6391    end 
    6492     
    6593 
     
    128156          return class_name || name 
    129157        end 
    130158    end 
     159 
    131160  end 
    132161end 
  • actionpack/lib/action_view/helpers/javascript_validation_helper.rb

    old new  
     1require File.dirname(__FILE__) + '/form_helper' 
     2 
     3module ActionView 
     4  module Helpers 
     5    module FormHelper 
     6      # method names in FormHelper that will automatically register fields for javascript validation 
     7      VALIDATION_FIELD_TYPES = %w(text_field password_field file_field text_area check_box radio_button) 
     8       
     9      for field_type in VALIDATION_FIELD_TYPES 
     10        module_eval <<-ENDEVAL 
     11            alias_method :#{field_type}_without_js_validation, :#{field_type} 
     12             
     13            def #{field_type}_with_js_validation(object,method,*args) 
     14                register_for_javascript_validation(object,method) 
     15                #{field_type}_without_js_validation(object,method,*args) 
     16            end 
     17             
     18            alias_method :#{field_type}, :#{field_type}_with_js_validation 
     19        ENDEVAL 
     20      end 
     21    end 
     22     
     23    module JavascriptValidationHelper 
     24      def register_for_javascript_validation(object_name,method_name) 
     25        @rails_instance_tags ||= [] 
     26        @rails_instance_tags << InstanceTag.new(object_name,method_name,self) 
     27      end 
     28 
     29      # Generate a javascript function for field validations. This will generate 
     30      # code to validate all fields that have been included via FormHelper methods and for 
     31      # which a validation with a supported validates_... macro has been defined. 
     32      # The javascript code returned will be wrapped in a proper +script+ tag and 
     33      # will be named as the value of the +function_name+ parameter (defaults to 
     34      # +validateForm+. 
     35      # To active validation before form submission, you should add the property 
     36      # <tt>onsubmit="return validateForm(this)"</tt> to your form tag. 
     37      def javascript_validation_for_fields(function_name="validateForm") 
     38        js_calls = @rails_instance_tags.inject([]) { |memo,tag| memo += function_names_for(tag) } 
     39        <<-ENDJS 
     40        <script type="text/javascript" language="javascript"> 
     41        function #{function_name}(form) { 
     42            return #{js_calls.join(" && ")}; 
     43        } 
     44        </script> 
     45        ENDJS 
     46      end 
     47 
     48      private 
     49       
     50      def function_names_for(instance_tag) 
     51        instance_tag.object.class.reflect_on_validations_for(instance_tag.method_name.to_sym). 
     52            inject([]) do |js_calls,validation| 
     53          case validation.macro 
     54            when :validates_presence_of 
     55                js_calls << "validatePresence(form['#{instance_tag.js_tag_name}'])" 
     56            when :validates_length_of 
     57                js_calls += js_validate_length(validation,instance_tag) 
     58          end 
     59        end 
     60      end       
     61 
     62      def js_validate_length(validation,tag) 
     63        js = [] 
     64        if validation.options[:maximum] 
     65          js << "validateLength(form['#{tag.js_tag_name}'],#{validation.options[:maximum]})" 
     66        end 
     67        js 
     68      end 
     69             
     70    end 
     71  end 
     72end 
  • actionpack/lib/action_view/helpers/form_helper.rb

    old new  
    244244        end 
    245245      end 
    246246 
     247      def js_tag_name(options = {}) 
     248        add_default_name_and_id(options) 
     249        options["name"] 
     250      end 
     251       
    247252      private 
    248253        def add_default_name_and_id(options) 
    249254          if options.has_key?("index")