Permalink
Browse files

Pulled out of production code, and added the active_record_base_witho…

…ut_table code to make the specs nicer.
  • Loading branch information...
0 parents commit 7c00678e0305937a7c37d2e21feb52c0f9da4697 @notahat committed Jun 11, 2008
Showing with 234 additions and 0 deletions.
  1. +20 −0 MIT-LICENSE
  2. +63 −0 README.rdoc
  3. +13 −0 Rakefile
  4. +1 −0 init.rb
  5. +34 −0 lib/wraps_attribute.rb
  6. +31 −0 spec/active_record_base_without_table.rb
  7. +72 −0 spec/wraps_attribute_spec.rb
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Peter Yandell.
+
+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.
@@ -0,0 +1,63 @@
+= WrapsAttribute
+
+This makes it easier to handle data like phone numbers with ActiveRecord,
+
+Use a simple wrapper class to do validation, normalization, and formatting of ActiveRecord attributes.
+
+For example, here's a simple class to wrap around a phone number field.
+
+ class PhoneNumber < String
+ def initialize(value)
+ super(value || '')
+ end
+
+ def normalize
+ gsub(' ', '')
+ end
+
+ def valid?
+ normalize =~ /\A[0-9]+\Z/
+ end
+ end
+
+In your ActiveRecord class, include:
+
+ wraps_attribute :phone_number, PhoneNumber
+
+This does a few things.
+
+== Normalisation
+
+Before the attribute is validated, it will get run through the +normalize+ method.
+
+== Validation
+
+By default, the +valid?+ method will be called to validate the attribute.
+
+You can specify that another method by used by passing the :validate option to +wraps_attribute+.
+
+You can
+
+== Formatting
+
+
+
+
+By default, the +valid?+ method on the wrapper class will be called
+to validate the attribute. You can override this by passing the
++validate+ option to wraps_attribute:
+
+ wraps_attribute :phone, PhoneNumber, :validate => :valid_mobile?, :message => "must be a valid mobile number"
+
+You can also disable automatic validation and use the regular ActiveRecord
+validation methods:
+
+ wraps_attribute :phone, PhoneNumber, :validate => false
+ validates_length_of :phone, :minimum => 6
+
+== Installation
+
+ruby script/plugin install git://github.com/notahat/wraps_attribute.git
+
+
+Copyright (c) 2008 Peter Yandell, released under the MIT license
@@ -0,0 +1,13 @@
+require 'rake'
+require 'spec/rake/spectask'
+
+desc "Default: run specs"
+task :default => :spec
+
+desc "Run all the specs for the wraps_attribute plugin."
+Spec::Rake::SpecTask.new do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.spec_opts = ['--colour']
+ t.rcov = true
+ t.rcov_opts = ["--exclude \"spec/*,gems/*\""]
+end
@@ -0,0 +1 @@
+require 'wraps_attribute'
@@ -0,0 +1,34 @@
+module ActiveRecord
+ module WrapsAttribute
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods #:nodoc:
+ def wraps_attribute(attribute, wrapper_class, options = {})
+ options = { :validate => :valid?, :message => "must be valid" }.merge(options)
+
+ before_validation {|object| object.send(:write_attribute, attribute, object.send(attribute).normalize) }
+
+ if options[:validate]
+ validate do |object|
+ unless (options[:allow_blank] && object.send(attribute).blank?) || object.send(attribute).send(options[:validate])
+ object.errors.add(attribute, options[:message])
+ end
+ end
+ end
+
+ define_method(attribute) do
+ unless read_attribute(attribute).is_a?(wrapper_class)
+ write_attribute(attribute, wrapper_class.new(read_attribute(attribute)))
+ end
+ read_attribute(attribute)
+ end
+ end
+ end
+ end
+
+ class Base #:nodoc:
+ include WrapsAttribute
+ end
+end
@@ -0,0 +1,31 @@
+require 'active_record'
+
+# This is based on Jonathan Viney's active_record_base_without_table plugin:
+#
+# http://svn.viney.net.nz/things/rails/plugins/active_record_base_without_table/
+module ActiveRecord
+ class BaseWithoutTable < Base
+ self.abstract_class = true
+
+ def save
+ valid?
+ end
+
+ class << self
+ def columns()
+ @columns ||= []
+ end
+
+ def column(name, sql_type = nil, default = nil, null = true)
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
+ reset_column_information
+ end
+
+ # Do not reset @columns
+ def reset_column_information
+ generated_methods.each { |name| undef_method(name) }
+ @column_names = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
+ end
+ end
+ end
+end
@@ -0,0 +1,72 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
+require "#{File.dirname(__FILE__)}/active_record_base_without_table"
+require 'wraps_attribute'
+
+class Phone < String
+ def initialize(value)
+ super(value || '')
+ end
+
+ def normalize
+ gsub(' ', '')
+ end
+
+ def valid?
+ self =~ /\A[0-9 ]+\Z/
+ end
+
+ def valid_mobile?
+ normalize =~ /\A04[0-9]{8}\Z/
+ end
+end
+
+class MyModel < ActiveRecord::BaseWithoutTable
+ column :phone, :string
+ column :mobile, :string
+ column :home_phone, :string
+
+ wraps_attribute :phone, Phone
+ wraps_attribute :mobile, Phone, :validate => :valid_mobile?, :message => "must be a valid mobile number"
+ wraps_attribute :home_phone, Phone, :allow_blank => true
+end
+
+describe "An ActiveRecord model using wraps_attribute" do
+
+ before do
+ @object = MyModel.new(:phone => '12 3 45', :mobile => '0414 98 33 00', :home_phone => '')
+ end
+
+ it "should return an object of the wrapped class when asked for the attribute" do
+ @object.phone.class.should == Phone
+ end
+
+ it "should return the correct value when asked for the attribute" do
+ @object.phone.should == '12 3 45'
+ end
+
+ it "should normalize the attribute before saving" do
+ @object.save
+ @object.phone.should == '12345'
+ end
+
+ it "should return a mutable attribate" do
+ @object.phone << '6789'
+ @object.phone.should == '12 3 456789'
+ @object.phone.class.should == Phone
+ end
+
+ it "should validate the attribute using the valid? method by default" do
+ @object.should be_valid
+ @object[:phone] = "abcde"
+ @object.should_not be_valid
+ @object.errors["phone"].should == "must be valid"
+ end
+
+ it "should vaildate the attribute using the provided method" do
+ @object.should be_valid
+ @object[:mobile] = "12345"
+ @object.should_not be_valid
+ @object.errors["mobile"].should == "must be a valid mobile number"
+ end
+
+end

0 comments on commit 7c00678

Please sign in to comment.