Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit 61b1f107febec0f3ca146188612c77a368ebda7a 0 parents
@augustl authored
Showing with 593 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +20 −0 MIT-LICENCE
  3. +1 −0  README
  4. +22 −0 Rakefile
  5. +10 −0 init.rb
  6. +11 −0 lib/live_validations.rb
  7. +36 −0 lib/live_validations/active_record_hooks.rb
  8. +67 −0 lib/live_validations/adapter.rb
  9. +34 −0 lib/live_validations/adapter/adapter_methods.rb
  10. +36 −0 lib/live_validations/adapters/jquery_validations.rb
  11. +14 −0 lib/live_validations/form_builder.rb
  12. +22 −0 lib/live_validations/view_helpers.rb
  13. +55 −0 test/active_record_hooks_test.rb
  14. +24 −0 test/form_builder_output_test.rb
  15. +3 −0  test/rails_root/app/controllers/application.rb
  16. +9 −0 test/rails_root/app/controllers/posts_controller.rb
  17. +2 −0  test/rails_root/app/helpers/application_helper.rb
  18. +3 −0  test/rails_root/app/models/post.rb
  19. +5 −0 test/rails_root/app/views/posts/new.html.erb
  20. +109 −0 test/rails_root/config/boot.rb
  21. +3 −0  test/rails_root/config/database.yml
  22. +8 −0 test/rails_root/config/environment.rb
  23. 0  test/rails_root/config/environments/sqlite3.rb
  24. +7 −0 test/rails_root/config/initializers/live_validations.rb
  25. +17 −0 test/rails_root/config/initializers/new_rails_defaults.rb
  26. +3 −0  test/rails_root/config/routes.rb
  27. +13 −0 test/rails_root/db/migrate/20081027115539_create_posts.rb
  28. 0  test/rails_root/vendor/plugins/.gitignore
  29. +48 −0 test/test_helper.rb
  30. +10 −0 test/the_meat_test.rb
1  .gitignore
@@ -0,0 +1 @@
+*.log
20 MIT-LICENCE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 August Lilleaas
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1  README
@@ -0,0 +1 @@
+Re-implementation of some old code. A sensible readme comes when the code gets closer to being usable by the public.
22 Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the translated plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the translated plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'Translated'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
10 init.rb
@@ -0,0 +1,10 @@
+require 'live_validations'
+
+# Dynamic adapter loading.
+ActiveSupport::Dependencies.load_paths.unshift(File.join(File.dirname(__FILE__), 'lib'))
+
+# Hook into ActiveRecord.
+ActiveRecord::Base.class_eval { include LiveValidations::ActiveRecordHooks }
+
+# Hook view helpers
+ActionView::Base.class_eval { include LiveValidations::ViewHelpers }
11 lib/live_validations.rb
@@ -0,0 +1,11 @@
+module LiveValidations
+ # Set which adapter to use. Pass the module itself. Example:
+ #
+ # LiveValidation.use(LiveValidations::Adapters::JQueryValidations)
+ def use(adapter_klass)
+ self.current_adapter = adapter_klass
+ end
+ mattr_accessor :current_adapter
+
+ extend self
+end
36 lib/live_validations/active_record_hooks.rb
@@ -0,0 +1,36 @@
+module LiveValidations
+ module ActiveRecordHooks
+ VALIDATION_METHODS = %w(confirmation acceptance presence length uniqueness format inclusion exclusion numericality)
+
+ def self.included(base)
+ base.class_eval { include InstanceMethods }
+
+ # Add some extra data to the validation stacks, so that we can pull out the validation method
+ # and which attributes it is going to validate.
+ class << base
+ VALIDATION_METHODS.each do |validation_method|
+ class_eval %{
+ def validates_#{validation_method}_of_with_live_validation_hooks(*args, &block)
+ options = args.extract_options!
+ options.merge!(:validation_method => :#{validation_method}, :attributes => args.dup)
+ validates_#{validation_method}_of_without_live_validation_hooks(*(args << options), &block)
+ end
+
+ alias_method_chain :validates_#{validation_method}_of, :live_validation_hooks
+ }
+ end
+ end
+ end
+
+ module InstanceMethods
+ def validation_callback_for(attribute)
+ validation_callbacks.select {|callback| callback.options[:attributes].include?(attribute) }
+ end
+
+ def validation_callbacks
+ on = [:save, (new_record? ? :create : :update)]
+ self.class.validate_callback_chain.select {|callback| callback.kind == :validate && on.include?(callback.options[:on]) }
+ end
+ end
+ end
+end
67 lib/live_validations/adapter.rb
@@ -0,0 +1,67 @@
+module LiveValidations
+ # The base class of an adapter.
+ class Adapter
+ cattr_accessor :json_proc, :validation_hooks
+ extend AdapterMethods
+
+ attr_reader :active_record_instance
+
+ def initialize(active_record_instance)
+ @active_record_instance = active_record_instance
+ iterate!
+ end
+
+ # Called by the form builder if JSON data is present for this validation.
+ def render_json
+ self.class.json_proc.call(self) if json_proc && !json_data.blank?
+ end
+
+ def json_data
+ @json_data ||= Hash.new {|hash, key| hash[key] = {} }
+ end
+
+ # Utility method, so that adapters can call this method directly instead of explicitly
+ # doing what this method does -- converting the json_data to actual JSON data.
+ def json
+ json_data.to_json
+ end
+
+ private
+
+ def iterate!
+ active_record_instance.validation_callbacks.each do |callback|
+ method = callback.options[:validation_method]
+ self.class.validation_hooks[method].run_validation(self, callback)
+ end
+ end
+
+ # One of these are created for each of the 'validates' blocks in the adapter.
+ class ValidationHook
+ attr_reader :json, :callback
+ def initialize(&block)
+ @json = {}
+ @proc = block
+ end
+
+ def run_validation(adapter_instance, callback)
+ @adapter_instance = adapter_instance
+ @callback = callback
+
+ @proc.call(self)
+
+ pass_json_hooks_to_adapter_instance
+ end
+
+ private
+
+ # Adds stuff to the adapter instance's @json_data
+ def pass_json_hooks_to_adapter_instance
+ return if @json.blank?
+
+ @callback.options[:attributes].each do |attribute|
+ @adapter_instance.json_data[attribute.to_s].merge!(@json)
+ end
+ end
+ end
+ end
+end
34 lib/live_validations/adapter/adapter_methods.rb
@@ -0,0 +1,34 @@
+module LiveValidations
+ class Adapter
+ # The methods individual adapters uses to implement itself.
+ module AdapterMethods
+ # Hooks into a validation method. Example:
+ #
+ # validates :presence do |v|
+ # v.json['required'] = true
+ # end
+ #
+ # Yields the validation hook.
+ def validates(name, &block)
+ self.validation_hooks ||= {}
+ self.validation_hooks[name] = ValidationHook.new(&block)
+ end
+
+ # Defines the JSON output, if your adapters uses JSON data to define validations (some
+ # adapters uses class names directly on form fields etc.).
+ #
+ # Should return a string. This string is added below the form, inside a <script> tag.
+ #
+ # Implementation example:
+ #
+ # json do |a|
+ # "doSomethingFancy(#{a.json})"
+ # end
+ #
+ # Yields the adapter.
+ def json(&block)
+ self.json_proc = block
+ end
+ end
+ end
+end
36 lib/live_validations/adapters/jquery_validations.rb
@@ -0,0 +1,36 @@
+module LiveValidations
+ module Adapters
+ class JqueryValidations < LiveValidations::Adapter
+ validates :presence do |v|
+ v.json['required'] = true
+ end
+
+ validates :length do |v|
+ v.json['minlength'] = v.callback.options[:minimum]
+ v.json['maxlength'] = v.callback.options[:maximum]
+ v.json['range'] = [v.callback.options[:within].first, v.callback.options[:within].last] if v.callback.options[:within]
+ end
+
+ validates :numericality do |v|
+ v.json['digits'] = true
+ end
+
+ validates :confirmation do |v|
+ # hmm..
+ end
+
+ validates :format do |v|
+ # Probably something like validates_format_of :foo, :with => /bar/, :live_validation => 'js regex here'
+ end
+
+ validates :uniqueness do |v|
+ # Next version. We need to do AJAX callbacks here.
+ end
+
+ json do |a|
+ dom_id = ActionController::RecordIdentifier.dom_id(a.active_record_instance)
+ "$('##{dom_id}').validate(#{{'rules' => a.json_data}.to_json})"
+ end
+ end
+ end
+end
14 lib/live_validations/form_builder.rb
@@ -0,0 +1,14 @@
+module LiveValidations
+ class FormBuilder < ActionView::Helpers::FormBuilder
+ helpers = field_helpers +
+ %w(date_select datetime_select time_select) +
+ %w(collection_select select country_select time_zone_select) -
+ %w(hidden_field label fields_for)
+
+ helpers.each do |field_helper|
+ define_method(field_helper) do |attribute, *args|
+ super
+ end
+ end
+ end
+end
22 lib/live_validations/view_helpers.rb
@@ -0,0 +1,22 @@
+module LiveValidations
+ module ViewHelpers
+ def live_validation_form_for(record_name_or_array, *args, &block)
+ options = args.extract_options!
+ options.merge!(:builder => LiveValidations::FormBuilder)
+
+ record = case record_name_or_array
+ when Array
+ array.last
+ when ActiveRecord::Base
+ record_name_or_array
+ else
+ raise ArgumentError, 'live_validation_form_for only supports an array (e.g. [:admin, @post]) or an active record instance (e.g. @post) as its first argument.'
+ end
+
+ adapter_instance = LiveValidations.current_adapter.new(record)
+
+ form_for(record_name_or_array, *(args << options), &block)
+ concat(%{<script type="text/javascript">#{adapter_instance.render_json}</script>}, block.binding)
+ end
+ end
+end
55 test/active_record_hooks_test.rb
@@ -0,0 +1,55 @@
+require File.join(File.dirname(__FILE__), "test_helper")
+
+class ActiveRecordHooksTest < Test::Unit::TestCase
+ def setup
+ reset_callbacks Post
+ end
+
+ def test_validation_callbacks_for_on_new_record
+ post = new_post
+ Post.validates_presence_of :title
+ Post.validates_length_of :title, :maximum => 50
+ # This one is ignored, as it's on update, and the post is a new record.
+ Post.validates_format_of :body, :with => /silly/, :on => :update
+
+ validators = post.validation_callback_for(:title)
+ assert_equal 2, validators.size
+
+ assert validators.detect {|v| v.options[:validation_method] == :length }
+ assert validators.detect {|v| v.options[:validation_method] == :presence }
+
+ assert !post.validation_callback_for(:body).detect {|v| v.options[:validation_method] == :format}
+ end
+
+ def test_validation_callbacks_for_on_existing_record
+ post = create_post
+ Post.validates_presence_of :title
+ Post.validates_length_of :title, :maximum => 50
+ # This one is ignored, as it's on update, and the post is a new record.
+ Post.validates_format_of :body, :with => /silly/, :on => :create
+
+ validators = post.validation_callback_for(:title)
+ assert_equal 2, validators.size
+
+ assert validators.detect {|v| v.options[:validation_method] == :length }
+ assert validators.detect {|v| v.options[:validation_method] == :presence }
+
+ assert !post.validation_callback_for(:body).detect {|v| v.options[:validation_method] == :format}
+ end
+
+ def teardown
+ restore_callbacks Post
+ end
+
+ private
+
+ def new_post
+ Post.new(:title => "Yarr", :excerpt => "Yep", :body => "Yap")
+ end
+
+ def create_post
+ p = new_post
+ p.save
+ p
+ end
+end
24 test/form_builder_output_test.rb
@@ -0,0 +1,24 @@
+require File.join(File.dirname(__FILE__), "test_helper")
+
+class FormBuilderOutputTest < Test::Unit::TestCase
+ def setup
+ @controller = PostsController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_json_output
+ get :new
+ assert_response :success
+
+ assert_select 'script[type=text/javascript]'
+ assert @response.body.include?("$('#new_post').validate")
+ assert @response.body.include?({'rules' => {'title' => {'required' => true}}}.to_json)
+ end
+
+ private
+
+ def render(template)
+ @output = @view.render_template(ActionView::InlineTemplate.new(@view, template))
+ end
+end
3  test/rails_root/app/controllers/application.rb
@@ -0,0 +1,3 @@
+class ApplicationController < ActionController::Base
+ session :session_key => '_live_validations_test_session_id'
+end
9 test/rails_root/app/controllers/posts_controller.rb
@@ -0,0 +1,9 @@
+class PostsController < ApplicationController
+ def index
+ @posts = Post.find(:all)
+ end
+
+ def new
+ @post = Post.new
+ end
+end
2  test/rails_root/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
3  test/rails_root/app/models/post.rb
@@ -0,0 +1,3 @@
+class Post < ActiveRecord::Base
+ validates_presence_of :title
+end
5 test/rails_root/app/views/posts/new.html.erb
@@ -0,0 +1,5 @@
+<% live_validation_form_for(@post) do |f| %>
+ <%= f.text_field :title %>
+ <%= f.text_area :excerpt %>
+ <%= f.text_area :body %>
+<% end %>
109 test/rails_root/config/boot.rb
@@ -0,0 +1,109 @@
+# Don't change this file!
+# Configure your app in config/environment.rb and config/environments/*.rb
+
+RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
+
+module Rails
+ class << self
+ def boot!
+ unless booted?
+ preinitialize
+ pick_boot.run
+ end
+ end
+
+ def booted?
+ defined? Rails::Initializer
+ end
+
+ def pick_boot
+ (vendor_rails? ? VendorBoot : GemBoot).new
+ end
+
+ def vendor_rails?
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
+ end
+
+ def preinitialize
+ load(preinitializer_path) if File.exist?(preinitializer_path)
+ end
+
+ def preinitializer_path
+ "#{RAILS_ROOT}/config/preinitializer.rb"
+ end
+ end
+
+ class Boot
+ def run
+ load_initializer
+ Rails::Initializer.run(:set_load_path)
+ end
+ end
+
+ class VendorBoot < Boot
+ def load_initializer
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+ Rails::Initializer.run(:install_gem_spec_stubs)
+ end
+ end
+
+ class GemBoot < Boot
+ def load_initializer
+ self.class.load_rubygems
+ load_rails_gem
+ require 'initializer'
+ end
+
+ def load_rails_gem
+ if version = self.class.gem_version
+ gem 'rails', version
+ else
+ gem 'rails'
+ end
+ rescue Gem::LoadError => load_error
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
+ exit 1
+ end
+
+ class << self
+ def rubygems_version
+ Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
+ end
+
+ def gem_version
+ if defined? RAILS_GEM_VERSION
+ RAILS_GEM_VERSION
+ elsif ENV.include?('RAILS_GEM_VERSION')
+ ENV['RAILS_GEM_VERSION']
+ else
+ parse_gem_version(read_environment_rb)
+ end
+ end
+
+ def load_rubygems
+ require 'rubygems'
+ min_version = '1.1.1'
+ unless rubygems_version >= min_version
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
+ exit 1
+ end
+
+ rescue LoadError
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ exit 1
+ end
+
+ def parse_gem_version(text)
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
+ end
+
+ private
+ def read_environment_rb
+ File.read("#{RAILS_ROOT}/config/environment.rb")
+ end
+ end
+ end
+end
+
+# All that for this:
+Rails.boot!
3  test/rails_root/config/database.yml
@@ -0,0 +1,3 @@
+sqlite3:
+ :adapter: sqlite3
+ :dbfile: ":memory:"
8 test/rails_root/config/environment.rb
@@ -0,0 +1,8 @@
+RAILS_GEM_VERSION = '2.1.1' unless defined? RAILS_GEM_VERSION
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+ config.log_level = :debug
+ config.cache_classes = false
+ config.whiny_nils = true
+end
0  test/rails_root/config/environments/sqlite3.rb
No changes.
7 test/rails_root/config/initializers/live_validations.rb
@@ -0,0 +1,7 @@
+# Makes rails aware of the plugin.
+lib_path = File.join(Rails.root, '..', '..')
+$LOAD_PATH << lib_path
+load File.join(lib_path, 'init.rb')
+
+# Pretend like nothing happened.
+LiveValidations.use LiveValidations::Adapters::JqueryValidations
17 test/rails_root/config/initializers/new_rails_defaults.rb
@@ -0,0 +1,17 @@
+# These settings change the behavior of Rails 2 apps and will be defaults
+# for Rails 3. You can remove this initializer when Rails 3 is released.
+
+if defined?(ActiveRecord)
+ # Include Active Record class name as root for JSON serialized output.
+ ActiveRecord::Base.include_root_in_json = true
+
+ # Store the full class name (including module namespace) in STI type column.
+ ActiveRecord::Base.store_full_sti_class = true
+end
+
+# Use ISO 8601 format for JSON serialized times and dates.
+ActiveSupport.use_standard_json_time_format = true
+
+# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
+# if you're including raw json in an HTML page.
+ActiveSupport.escape_html_entities_in_json = false
3  test/rails_root/config/routes.rb
@@ -0,0 +1,3 @@
+ActionController::Routing::Routes.draw do |map|
+ map.resources :posts
+end
13 test/rails_root/db/migrate/20081027115539_create_posts.rb
@@ -0,0 +1,13 @@
+class CreatePosts < ActiveRecord::Migration
+ def self.up
+ create_table :posts do |t|
+ t.string :title
+ t.text :excerpt, :body
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :posts
+ end
+end
0  test/rails_root/vendor/plugins/.gitignore
No changes.
48 test/test_helper.rb
@@ -0,0 +1,48 @@
+# Rip-off from the Shoulda test suite. Thanks, Shoulda!
+
+ENV['RAILS_ENV'] = 'sqlite3'
+rails_root = File.join(File.dirname(__FILE__), 'rails_root')
+
+# Load the rails environment
+require File.join(rails_root, 'config', 'environment.rb')
+
+# Load the testing framework
+require 'test_help'
+silence_warnings { RAILS_ENV = ENV['RAILS_ENV'] }
+
+# Run the migrations
+ActiveRecord::Migration.verbose = false
+ActiveRecord::Migrator.migrate File.join(rails_root, 'db', 'migrate')
+
+Test::Unit::TestCase.fixture_path = File.join(File.dirname(__FILE__), 'fixtures')
+
+class Test::Unit::TestCase #:nodoc:
+ def create_fixtures(*table_names)
+ if block_given?
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
+ else
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
+ end
+ end
+
+ self.use_transactional_fixtures = false
+ self.use_instantiated_fixtures = false
+
+ def reset_callbacks(model)
+ @_original_callbacks = {}
+
+ [:validate_callbacks, :validate_on_create_callbacks, :validate_on_update_callbacks].each do |callback|
+ # Store it for restore later on
+ @_original_callbacks[callback] = model.instance_variable_get("@#{callback}")
+ # Remove it
+ model.instance_variable_set("@#{callback}", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+ end
+
+ # Be nice to other test files and run this in teardown.
+ def restore_callbacks(model)
+ @_original_callbacks.each do |name, callback|
+ model.instance_variable_set("@#{name}", callback)
+ end
+ end
+end
10 test/the_meat_test.rb
@@ -0,0 +1,10 @@
+require File.join(File.dirname(__FILE__), "test_helper")
+
+class TheMeatTest < Test::Unit::TestCase
+ def test_render_json
+ validator = LiveValidations.current_adapter.new(Post.new)
+
+ expected_json_data = {"title" => {"required" => true}}
+ assert_equal expected_json_data, validator.json_data
+ end
+end

0 comments on commit 61b1f10

Please sign in to comment.
Something went wrong with that request. Please try again.