activemodel-error 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sven Fuchs & Mateo Murphy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,87 @@
1
+ h1. Proposal: validation error messages in ActiveModel
2
+
3
+ (requires I18n current master)
4
+
5
+ This stuff is a very rough draft of how I propose to deal with validation
6
+ error messages (and potentially other usecases that need I18n support, such as
7
+ form labels).
8
+
9
+ It might go into Rails or end up in some plugin or gem.
10
+
11
+ I'm not looking so much at the actual integration to ActiveModel, yet. So far
12
+ I'm just trying to sketch out the underlying stuff so that it covers all
13
+ requirements in a consistent way.
14
+
15
+ I want this stuff to be re-usable in other places where similar patterns occur
16
+ when it comes to I18n, such as form labels and potentially other stuff.
17
+
18
+ h2. Problems solved
19
+
20
+ * Messages in class method calls are treated as plain text by default
21
+ (users might optionally want to treat them as translation keys for
22
+ gettext-style class method calls).
23
+ * Symbols in class method calls are treated as translation keys.
24
+ * Use messages from class method calls consistently with translation keys that
25
+ are implicitly used for a particular validation.
26
+ * Different translation variants (such as :full_message) for the same message
27
+ must be possible.
28
+ * Translation calls should optionally be able to cascade over scopes.
29
+ * Support format strings, e.g. "{{attribute}} {{message}}", transparently
30
+
31
+ h2. Classes
32
+
33
+ The Error class is the meant to work as a default Error class in ActiveModel.
34
+
35
+ I18n::Message is the main base class and is supposed to work situations such as
36
+ error messages, form labels etc.
37
+
38
+ The Format class is used internally to format other strings (see below).
39
+
40
+ h2. Features
41
+
42
+ The Message class comes with six modules that each provide a single feature.
43
+ This way everything's completely optional and pluggable (thus extensible and
44
+ easy to customize) even though we might choose to not impose this complexity
45
+ on the enduser (and maybe pick a default configuration/provide a configuration
46
+ dsl instead).
47
+
48
+ h3. I18n::Message::Base
49
+
50
+ This module just takes a Message OR translation key (Symbol) and interpolation
51
+ values. Calling #to_s will use the string or translate the key and interpolate
52
+ the values. The module is organized in a way so that other modules can hook in
53
+ easily.
54
+
55
+ h3. I18n::Message::Gettext
56
+
57
+ This module will also translate Messages. This behavior is frequently asked for
58
+ by people who want to use Gettext-style translation keys in class-level calls
59
+ as in validates_presence_of :foo, :message => 'The message'
60
+
61
+ h3. I18n::Message::Variants
62
+
63
+ This module adds the ability to specify Hashes as strings both at class-level
64
+ and in translation data. E.g.:
65
+
66
+ validates_presence_of :foo, :message => { :short => 'Short!', :full => 'The full message!' }
67
+
68
+ Calling #to_s on the I18n::Message instance will take a variant key and try to
69
+ find and use the variant. It defaults to the :short variant.
70
+
71
+ h3. I18n::Message::Formatted
72
+
73
+ This module adds the ability to specify format strings both at class-level and
74
+ in translation data. E.g.:
75
+
76
+ validates_presence_of :foo, :format => 'The formatted {{message}}.'
77
+
78
+ This module also works in combination with the Variants module in the way one
79
+ would expect:
80
+
81
+ validates_presence_of :foo, :message => { :short => 'foo', :full => 'FOO' }
82
+ :format => { :full => 'The formatted {{message}}.' }
83
+
84
+ message.to_s(:full) would then wrap the :full variant 'FOO' into the :full
85
+ format string and yield 'The formatted FOO'. message.to_s (defaults to :short)
86
+ would just yield 'foo' as expected.
87
+
@@ -0,0 +1,27 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => [:test]
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.pattern = "#{File.dirname(__FILE__)}/test/all.rb"
7
+ t.verbose = true
8
+ end
9
+ Rake::Task['test'].comment = "Run all tests"
10
+
11
+ begin
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |s|
14
+ s.name = "activemodel-error"
15
+ s.summary = "I18n for validation error messages in ActiveModel"
16
+ s.description = %(Provides I18n support for validation error messages in ActiveModel.
17
+ With Rails 3 ActiveModel validation error messages are not backwards
18
+ compatible with ActiveRecord 2.3.x. This Gem aims to restore this
19
+ backwards compatiblity and provide a richer feature set and better
20
+ implementation compared to ActiveRecord 2.3.x.)
21
+ s.homepage = "http://github.com/svenfuchs/activemodel-error"
22
+ s.authors = ["Sven Fuchs", "Mateo Murphy"]
23
+ s.files = FileList["[A-Z]*", "{lib,test}/**/*"]
24
+ end
25
+ rescue LoadError
26
+ puts "Jeweler not available. Install it with: gem install jeweler"
27
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,44 @@
1
+ module ActiveModel
2
+ class AttributeErrors
3
+ include Enumerable
4
+
5
+ attr_accessor :attribute, :errors
6
+ delegate :size, :empty?, :first, :second, :last, :to => :errors
7
+
8
+ def initialize(base, attribute)
9
+ @base = base
10
+ @attribute = attribute
11
+ @errors = []
12
+ end
13
+
14
+ def add(message = nil, options = {})
15
+ message = generate_message(message, options) if message.is_a?(Symbol)
16
+ @errors << message
17
+ end
18
+
19
+ alias :<< :add
20
+
21
+ def each
22
+ @errors.each { |error| yield error }
23
+ end
24
+
25
+ def include?(message)
26
+ case message
27
+ when Symbol
28
+ @errors.any? { |error| error.subject == message }
29
+ when String
30
+ @errors.any? { |error| error.to_s == message }
31
+ else
32
+ @errors.include?(message)
33
+ end
34
+ end
35
+
36
+ def generate_message(type = :invalid, options = {})
37
+ @base.generate_message(attribute, type, options)
38
+ end
39
+
40
+ def to_a
41
+ @errors
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ require 'i18n/message'
2
+
3
+ module ActiveModel
4
+ class Error < I18n::Message
5
+ autoload :Base, 'active_model/error/base'
6
+ autoload :Format, 'active_model/error/format'
7
+ autoload :Legacy, 'active_model/error/legacy'
8
+
9
+ include Base, Formatted, Variants, Legacy
10
+
11
+ self.format_class = ActiveModel::Error::Format
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveModel
2
+ class Error < I18n::Message
3
+ module Base
4
+ attr_reader :base, :attribute, :value
5
+
6
+ def initialize(subject = nil, options = {})
7
+ @base, @attribute, @value = options.values_at(:model, :attribute, :value)
8
+ @attribute = attribute
9
+ super(subject, options)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module ActiveModel
2
+ class Error
3
+ class Format < I18n::Message::Format
4
+ protected
5
+
6
+ def scope
7
+ :"errors.#{super}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveModel
2
+ class Error < I18n::Message
3
+ module Legacy
4
+ protected
5
+
6
+ def translate(subject, options = {})
7
+ options = translate_options(subject)
8
+ super(options[:default].shift, options)
9
+ end
10
+
11
+ def translate_options(subject)
12
+ defaults = base.class.lookup_ancestors.map do |klass|
13
+ [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{subject}",
14
+ :"models.#{klass.name.underscore}.#{subject}" ]
15
+ end.flatten
16
+
17
+ defaults << :"messages.#{subject}"
18
+
19
+ options = @options.merge(
20
+ :scope => :errors,
21
+ :default => defaults,
22
+ :model => base.class.model_name.human,
23
+ :attribute => base.class.human_attribute_name(attribute),
24
+ :value => value,
25
+ :raise => true
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_support/core_ext/class/attribute_accessors'
2
+ require 'active_support/core_ext/module/delegation'
3
+ require 'active_model'
4
+
5
+ module ActiveModel
6
+ autoload :Error, 'active_model/error'
7
+ autoload :AttributeErrors, 'active_model/attribute_errors'
8
+
9
+ class Errors
10
+ cattr_accessor :error_class
11
+ @@error_class = ActiveModel::Error
12
+
13
+ def [](attribute)
14
+ if errors = get(attribute.to_sym)
15
+ errors
16
+ else
17
+ set(attribute.to_sym, AttributeErrors.new(self, attribute.to_sym))
18
+ end
19
+ end
20
+
21
+ def add(attribute, message = nil, options = {})
22
+ self[attribute].add(message, options)
23
+ end
24
+
25
+ def generate_message(attribute, type = :invalid, options = {})
26
+ options.update(:model => @base, :attribute => attribute)
27
+ error_class.new(options.delete(:default) || type, options)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1 @@
1
+ require 'active_model/errors_ext'
@@ -0,0 +1,25 @@
1
+ class Array
2
+ def equals_with_active_model_errors(other)
3
+ other = other.map { |e| e.to_s } if ActiveModel::AttributeErrors === other
4
+ equals_without_active_model_errors(other)
5
+ end
6
+ alias equals_without_active_model_errors ==
7
+ alias == equals_with_active_model_errors
8
+ end
9
+
10
+ class Message
11
+ def equals_with_active_model_error(other)
12
+ other = other.to_s if ActiveModel::Error === other
13
+ equals_without_active_model_error(other)
14
+ end
15
+ alias equals_without_active_model_error ==
16
+ alias == equals_with_active_model_error
17
+
18
+ def compare_with_active_model_error(other)
19
+ other = other.to_s if ActiveModel::Error === other
20
+ compare_without_active_model_error(other)
21
+ end
22
+ alias compare_without_active_model_error <=>
23
+ alias <=> compare_with_active_model_error
24
+ end
25
+
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../../test_helper', __FILE__)
3
+
4
+ class ActiveModelApiFormatTest < Test::Unit::TestCase
5
+ class Model
6
+ include ActiveModel::Validations
7
+ attr_reader :foo
8
+ def self.name; 'Model'; end
9
+ end
10
+
11
+ def setup
12
+ I18n.backend.send(:init_translations) # i.e. don't overwrite our translations
13
+ end
14
+
15
+ def teardown
16
+ I18n.backend = nil
17
+ Model.reset_callbacks(:validate)
18
+ end
19
+
20
+ def error_on(attribute)
21
+ model = Model.new
22
+ model.valid?
23
+ model.errors[attribute].first
24
+ end
25
+
26
+ # FIXME this currently can't be supported because rails does not pass the
27
+ # format option through
28
+ #
29
+ # test "uses a format given at class level to format a message" do
30
+ # Model.validates_presence_of :foo, :message => 'message', :format => 'formatted %{message}'
31
+ # assert_equal "formatted message", error_on(:foo).to_s
32
+ # end
33
+
34
+ test "uses a format stored as a translation to wrap a message (messages namespace)" do
35
+ Model.validates_presence_of :foo, :message => 'message'
36
+ store_translations(:'errors.formats.default' => 'formatted %{message}')
37
+ assert_equal "formatted message", error_on(:foo).to_s
38
+ end
39
+
40
+ # FIXME currently not supported and probably should be optional
41
+ #
42
+ # test "uses a format stored as a translation to wrap a message (models namespace)" do
43
+ # Model.validates_presence_of :foo, :message => 'message'
44
+ # store_translations(:'errors.formats.models.model.default' => 'formatted %{message}')
45
+ # assert_equal "formatted message", error_on(:foo).to_s
46
+ # end
47
+ #
48
+ # test "uses a format stored as a translation to wrap a message (attributes namespace)" do
49
+ # Model.validates_presence_of :foo, :message => 'message'
50
+ # store_translations(:'errors.formats.models.model.attributes.foo.default' => 'formatted %{message}')
51
+ # assert_equal "formatted message", error_on(:foo).to_s
52
+ # end
53
+
54
+ test "uses a format stored as a translation to wrap a message variant (messages namespace)" do
55
+ Model.validates_presence_of :foo, :message => 'message'
56
+ store_translations(:'errors.formats.full' => 'full %{message}')
57
+ assert_equal "full message", error_on(:foo).to_s(:full)
58
+ end
59
+
60
+ # FIXME currently not supported and probably should be optional
61
+ #
62
+ # test "uses a format stored as a translation to wrap a message variant (models namespace)" do
63
+ # Model.validates_presence_of :foo, :message => 'message'
64
+ # store_translations(:'errors.formats.models.model.default' => 'formatted %{message}')
65
+ # assert_equal "formatted message", error_on(:foo).to_s
66
+ # end
67
+ #
68
+ # test "uses a format stored as a translation to wrap a message variant (attributes namespace)" do
69
+ # Model.validates_presence_of :foo, :message => 'message'
70
+ # store_translations(:'errors.formats.models.model.attributes.foo.default' => 'formatted %{message}')
71
+ # assert_equal "formatted message", error_on(:foo).to_s
72
+ # end
73
+
74
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../../test_helper', __FILE__)
3
+
4
+ class ActiveModelApiInterpolationTest < Test::Unit::TestCase
5
+ class Model
6
+ include ActiveModel::Validations
7
+ attr_reader :foo
8
+ def self.name; 'Model'; end
9
+ end
10
+
11
+ def setup
12
+ I18n.backend.send(:init_translations) # i.e. don't overwrite our translations
13
+ end
14
+
15
+ def teardown
16
+ I18n.backend = nil
17
+ Model.reset_callbacks(:validate)
18
+ end
19
+
20
+ def error_on(attribute)
21
+ model = Model.new
22
+ model.valid?
23
+ model.errors[attribute].first
24
+ end
25
+
26
+ test "interpolates validation data to a translation from the messages namespace" do
27
+ store_translations(:'errors.models.model.too_short' => '%{count}')
28
+ Model.validates_length_of :foo, :minimum => 1
29
+ assert_equal '1', error_on(:foo).to_s
30
+ end
31
+
32
+ test "interpolation: given a class level :message Message it interpolates validation data to the Message" do
33
+ Model.validates_length_of :foo, :minimum => 1, :message => '%{count}'
34
+ assert_equal '1', error_on(:foo).to_s
35
+ end
36
+
37
+ test "interpolation: given a class level :message Symbol it interpolates validation data to the Symbol's translation" do
38
+ store_translations(:'errors.messages.broken' => '%{count}')
39
+ Model.validates_length_of :foo, :minimum => 1, :message => :broken
40
+ assert_equal '1', error_on(:foo).to_s
41
+ end
42
+
43
+ test "interpolation: given a class level :message Proc returning a Message it interpolates validation data to the Message" do
44
+ Model.validates_length_of :foo, :minimum => 1, :message => proc { '%{count}' }
45
+ assert_equal '1', error_on(:foo).to_s
46
+ end
47
+
48
+ test "interpolation: given a class level :message Proc returning a Symbol it interpolates validation data to the Symbol's translation" do
49
+ store_translations(:'errors.messages.broken' => '%{count}')
50
+ Model.validates_length_of :foo, :minimum => 1, :message => proc { :broken }
51
+ assert_equal '1', error_on(:foo).to_s
52
+ end
53
+
54
+ end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../../test_helper', __FILE__)
3
+
4
+ class ActiveModelApiLookupTest < Test::Unit::TestCase
5
+ class Model
6
+ include ActiveModel::Validations
7
+ attr_reader :foo
8
+ def self.name; 'Model'; end
9
+ end
10
+
11
+ def setup
12
+ I18n.backend.send(:init_translations) # i.e. don't overwrite our translations
13
+ end
14
+
15
+ def teardown
16
+ I18n.backend = nil
17
+ Model.reset_callbacks(:validate)
18
+ end
19
+
20
+ def error_on(attribute)
21
+ model = Model.new
22
+ model.valid?
23
+ model.errors[attribute].first
24
+ end
25
+
26
+ # LOOKUP
27
+
28
+ test "lookup: it returns a translation from the messages namespace" do
29
+ Model.validates_presence_of :foo
30
+ store_translations(:'errors.messages.blank' => 'message')
31
+ assert_equal "message", error_on(:foo).to_s
32
+ end
33
+
34
+ test "lookup: it returns a translation from the model namespace" do
35
+ Model.validates_presence_of :foo
36
+ store_translations(:'errors.models.model.blank' => 'message')
37
+ assert_equal "message", error_on(:foo).to_s
38
+ end
39
+
40
+ test "lookup: it returns a translation from the attribute namespace" do
41
+ Model.validates_presence_of :foo
42
+ store_translations(:'errors.models.model.attributes.foo.blank' => 'message')
43
+ assert_equal "message", error_on(:foo).to_s
44
+ end
45
+
46
+ # given a :message Message in class level validation macros
47
+
48
+ test "lookup: given a class level :message Message it returns the Message" do
49
+ Model.validates_presence_of :foo, :message => 'message'
50
+ assert_equal 'message', error_on(:foo).to_s
51
+ end
52
+
53
+ # given a :message Symbol in class level validation macros
54
+
55
+ test "lookup: given a class level :message Symbol it translates the Symbol (message namespace)" do
56
+ store_translations(:'errors.messages.broken' => 'translated')
57
+ Model.validates_presence_of :foo, :message => :broken
58
+ assert_equal 'translated', error_on(:foo).to_s
59
+ end
60
+
61
+ test "lookup: given a class level :message Symbol it translates the Symbol (model namespace)" do
62
+ store_translations(:'errors.models.model.broken' => 'translated')
63
+ Model.validates_presence_of :foo, :message => :broken
64
+ assert_equal 'translated', error_on(:foo).to_s
65
+ end
66
+
67
+ test "lookup: given a class level :message Symbol it translates the Symbol (attribute namespace)" do
68
+ store_translations(:'errors.models.model.attributes.foo.broken' => 'translated')
69
+ Model.validates_presence_of :foo, :message => :broken
70
+ assert_equal 'translated', error_on(:foo).to_s
71
+ end
72
+
73
+ # given a :message Proc returning a Message in class level validation macros
74
+
75
+ test "lookup: given a class level :message Proc returning a Message it returns the Message" do
76
+ Model.validates_presence_of :foo, :message => proc { 'message' }
77
+ assert_equal 'message', error_on(:foo).to_s
78
+ end
79
+
80
+ # given a :message Proc returning a Symbol in class level validation macros
81
+
82
+ test "lookup: given a class level :message Proc returning a Symbol it translates the Symbol (message namespace)" do
83
+ store_translations(:'errors.messages.broken' => 'translated')
84
+ Model.validates_presence_of :foo, :message => proc { :broken }
85
+ assert_equal 'translated', error_on(:foo).to_s
86
+ end
87
+
88
+ test "lookup: given a class level :message Proc returning a Symbol it translates the Symbol (model namespace)" do
89
+ store_translations(:'errors.models.model.broken' => 'translated')
90
+ Model.validates_presence_of :foo, :message => proc { :broken }
91
+ assert_equal 'translated', error_on(:foo).to_s
92
+ end
93
+
94
+ test "lookup: given a class level :message Proc returning a Symbol it translates the Symbol (attribute namespace)" do
95
+ store_translations(:'errors.models.model.attributes.foo.broken' => 'translated')
96
+ Model.validates_presence_of :foo, :message => proc { :broken }
97
+ assert_equal 'translated', error_on(:foo).to_s
98
+ end
99
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../../test_helper', __FILE__)
3
+
4
+ class ActiveModelApiVariantsTest < Test::Unit::TestCase
5
+ class Model
6
+ include ActiveModel::Validations
7
+ attr_reader :foo
8
+ def self.name; 'Model'; end
9
+ end
10
+
11
+ def setup
12
+ I18n.backend.send(:init_translations) # i.e. don't overwrite our translations
13
+ end
14
+
15
+ def teardown
16
+ I18n.backend = nil
17
+ Model.reset_callbacks(:validate)
18
+ end
19
+
20
+ def error_on(attribute)
21
+ model = Model.new
22
+ model.valid?
23
+ model.errors[attribute].first
24
+ end
25
+
26
+ test "variants: it picks a translation message variant from the messages namespace" do
27
+ Model.validates_presence_of :foo
28
+ store_translations(:'errors.messages.blank' => { :short => 'short', :full => 'full' })
29
+ assert_equal "short", error_on(:foo).to_s
30
+ assert_equal "short", error_on(:foo).to_s(:short)
31
+ assert_equal "full", error_on(:foo).to_s(:full)
32
+ end
33
+
34
+ test "variants: it picks a translation message variant from the models namespace" do
35
+ Model.validates_presence_of :foo
36
+ store_translations(:'errors.models.model.blank' => { :short => 'short', :full => 'full' })
37
+ assert_equal "short", error_on(:foo).to_s
38
+ assert_equal "short", error_on(:foo).to_s(:short)
39
+ assert_equal "full", error_on(:foo).to_s(:full)
40
+ end
41
+
42
+ test "variants: it picks a translation message variant from the attributes namespace" do
43
+ Model.validates_presence_of :foo
44
+ store_translations(:'errors.models.model.attributes.foo.blank' => { :short => 'short', :full => 'full' })
45
+ assert_equal "short", error_on(:foo).to_s
46
+ assert_equal "short", error_on(:foo).to_s(:short)
47
+ assert_equal "full", error_on(:foo).to_s(:full)
48
+ end
49
+
50
+ # given a :message Message in class level validation macros
51
+
52
+ test "variants: given a class level :message Hash of Messages it picks a variant" do
53
+ Model.validates_presence_of :foo, :message => { :short => 'short', :full => 'full' }
54
+ assert_equal "short", error_on(:foo).to_s
55
+ assert_equal "short", error_on(:foo).to_s(:short)
56
+ assert_equal "full", error_on(:foo).to_s(:full)
57
+ end
58
+
59
+ # given a :message Symbol in class level validation macros
60
+
61
+ test "variants: given a class level :message Symbol it translates the Symbol (message namespace) and picks a variant" do
62
+ store_translations(:'errors.messages.broken' => { :short => 'short', :full => 'full' })
63
+ Model.validates_presence_of :foo, :message => :broken
64
+ assert_equal "short", error_on(:foo).to_s
65
+ assert_equal "short", error_on(:foo).to_s(:short)
66
+ assert_equal "full", error_on(:foo).to_s(:full)
67
+ end
68
+
69
+ test "variants: given a class level :message Symbol it translates the Symbol (model namespace) and picks a variant" do
70
+ store_translations(:'errors.models.model.broken' => { :short => 'short', :full => 'full' })
71
+ Model.validates_presence_of :foo, :message => :broken
72
+ assert_equal "short", error_on(:foo).to_s
73
+ assert_equal "short", error_on(:foo).to_s(:short)
74
+ assert_equal "full", error_on(:foo).to_s(:full)
75
+ end
76
+
77
+ test "variants: given a class level :message Symbol it translates the Symbol (attribute namespace) and picks a variant" do
78
+ store_translations(:'errors.models.model.attributes.foo.broken' => { :short => 'short', :full => 'full' })
79
+ Model.validates_presence_of :foo, :message => :broken
80
+ assert_equal "short", error_on(:foo).to_s
81
+ assert_equal "short", error_on(:foo).to_s(:short)
82
+ assert_equal "full", error_on(:foo).to_s(:full)
83
+ end
84
+
85
+ # given a :message Proc returning a Message in class level validation macros
86
+
87
+ # FIXME does not pass
88
+ #
89
+ # test "variants: given a class level :message Proc returning a Hash of variant Messages it picks a variant" do
90
+ # Model.validates_presence_of :foo, :message => proc { { :short => 'short', :full => 'full' } }
91
+ # assert_equal "short", error_on(:foo).to_s
92
+ # assert_equal "short", error_on(:foo).to_s(:short)
93
+ # assert_equal "full", error_on(:foo).to_s(:full)
94
+ # end
95
+
96
+ # given a :message Proc returning a Symbol in class level validation macros
97
+
98
+ test "variants: given a class level :message Proc returning a Symbol it translates the Symbol (message namespace) and picks a variant" do
99
+ store_translations(:'errors.messages.broken' => { :short => 'short', :full => 'full' })
100
+ Model.validates_presence_of :foo, :message => proc { :broken }
101
+ assert_equal "short", error_on(:foo).to_s
102
+ assert_equal "short", error_on(:foo).to_s(:short)
103
+ assert_equal "full", error_on(:foo).to_s(:full)
104
+ end
105
+
106
+ test "variants: given a class level :message Proc returning a Symbol it translates the Symbol (model namespace) and picks a variant" do
107
+ store_translations(:'errors.models.model.broken' => { :short => 'short', :full => 'full' })
108
+ Model.validates_presence_of :foo, :message => proc { :broken }
109
+ assert_equal "short", error_on(:foo).to_s
110
+ assert_equal "short", error_on(:foo).to_s(:short)
111
+ assert_equal "full", error_on(:foo).to_s(:full)
112
+ end
113
+
114
+ test "variants: given a class level :message Proc returning a Symbol it translates the Symbol (attribute namespace) and picks a variant" do
115
+ store_translations(:'errors.models.model.attributes.foo.broken' => { :short => 'short', :full => 'full' })
116
+ Model.validates_presence_of :foo, :message => proc { :broken }
117
+ assert_equal "short", error_on(:foo).to_s
118
+ assert_equal "short", error_on(:foo).to_s(:short)
119
+ assert_equal "full", error_on(:foo).to_s(:full)
120
+ end
121
+ end
@@ -0,0 +1,175 @@
1
+ # encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__)) + '/test_helper'
3
+
4
+ $:.unshift('~/Development/shared/rails/rails-master/activemodel/lib')
5
+ $:.unshift('~/Development/shared/rails/rails-master/activesupport/lib')
6
+ $:.unshift('~/Projects/Ruby/rails/activemodel/lib')
7
+ $:.unshift('~/Projects/Ruby/rails/activesupport/lib')
8
+
9
+ require 'active_model/errors_ext'
10
+
11
+ class Model
12
+ include ActiveModel::Validations
13
+ attr_reader :foo
14
+ end
15
+
16
+ module ActiveModelValidationMessageTestSetup
17
+
18
+ def setup
19
+ I18n.backend = CascadingBackend.new
20
+ I18n.backend.send(:init_translations) # so our translations won't be overwritten by Rails
21
+ end
22
+
23
+ def teardown
24
+ I18n.backend = nil
25
+ Model.reset_callbacks(:validate)
26
+
27
+ end
28
+
29
+ def model
30
+ model = Model.new
31
+ model.valid?
32
+ model
33
+ end
34
+
35
+ end
36
+
37
+ class LegacyActiveModelValidationMessageTest < Test::Unit::TestCase
38
+ include ActiveModelValidationMessageTestSetup
39
+
40
+ class Error < ActiveModel::Error
41
+ include Legacy
42
+ end
43
+
44
+ def setup
45
+ super
46
+ ActiveModel::Errors.error_class = Error
47
+ end
48
+
49
+ test "uses a translation from the messages namespace" do
50
+ Model.validates_presence_of :foo
51
+ store_translations(:'errors.messages.blank' => 'message')
52
+ assert_equal "message", model.errors[:foo].first.to_s
53
+ end
54
+
55
+ test "uses a translation from the messages namespace with a Proc" do
56
+ Model.validates_presence_of :foo, :message => proc { :foo }
57
+ store_translations(:'errors.messages.foo' => 'message')
58
+ assert_equal "message", model.errors[:foo].first.to_s
59
+ end
60
+
61
+ test "uses a translation from a model namespace" do
62
+ Model.validates_presence_of :foo
63
+ store_translations(:'errors.models.model.blank' => 'message')
64
+ assert_equal "message", model.errors[:foo].first.to_s
65
+ end
66
+
67
+ test "uses a translation from an attribute namespace" do
68
+ Model.validates_presence_of :foo
69
+ store_translations(:'errors.models.model.attributes.foo.blank' => 'message')
70
+ assert_equal "message", model.errors[:foo].first.to_s
71
+ end
72
+
73
+ test "interpolates validation data to a default message" do
74
+ Model.validates_length_of :foo, :minimum => 1
75
+ assert_equal "is too short (minimum is 1 characters)", model.errors[:foo].first.to_s
76
+ end
77
+
78
+ test "AttributeErrors supports common methods" do
79
+ store_translations(:'errors.messages.blank' => 'message')
80
+
81
+ errors = model.errors[:foo]
82
+ assert errors.empty?
83
+ assert !errors.any?
84
+
85
+ errors << :blank
86
+ assert !errors.empty?
87
+ assert errors.any?
88
+ assert errors.include?(:blank)
89
+ assert errors.include?("message")
90
+ assert_equal "message", errors.first.to_s
91
+ end
92
+
93
+ test "errors added directly to AttributeErrors are accesible via Errors" do
94
+ foo = model
95
+ foo.errors[:bar] << :blank
96
+
97
+ assert_equal 1, foo.errors.size
98
+ assert_equal ['Bar can\'t be blank'], foo.errors.to_a
99
+ end
100
+
101
+
102
+ =begin
103
+ test "uses translation message formats from a model namespace" do
104
+ Model.validates_presence_of :foo
105
+ store_translations(:'errors.models.model.blank' => { :short => 'short', :full => 'full' })
106
+ assert_equal "short", model.errors[:foo].first.to_s
107
+ assert_equal "short", model.errors[:foo].first.to_s(:short)
108
+ assert_equal "full", model.errors[:foo].first.to_s(:full)
109
+ end
110
+
111
+
112
+ test "uses translation message formats from an attribute namespace" do
113
+ Model.validates_presence_of :foo
114
+ store_translations(:'errors.models.model.attributes.foo.blank' => { :short => 'short', :full => 'full' })
115
+ assert_equal "short", model.errors[:foo].first.to_s
116
+ assert_equal "short", model.errors[:foo].first.to_s(:short)
117
+ assert_equal "full", model.errors[:foo].first.to_s(:full)
118
+ end
119
+ =end
120
+
121
+ end
122
+
123
+ class ActiveModelValidationMessageTest < Test::Unit::TestCase
124
+
125
+ include ActiveModelValidationMessageTestSetup
126
+
127
+ class Error < ActiveModel::Error
128
+ include Cascade, Variants, Formatted
129
+
130
+ self.cascade_options = { :step => 2, :skip_root => false, :scopes => [:model, :attribute] }
131
+ end
132
+
133
+ def setup
134
+ super
135
+ ActiveModel::Errors.error_class = Error
136
+ end
137
+
138
+ test "uses a class level Message message" do
139
+ Model.validates_presence_of :foo, :message => 'message'
140
+ assert_equal "message", model.errors[:foo].first.to_s
141
+ end
142
+
143
+ test "uses a class level Proc message" do
144
+ Model.validates_presence_of :foo, :message => proc {'message'}
145
+ assert_equal "message", model.errors[:foo].first.to_s
146
+ end
147
+
148
+ test "uses class level Message message formats" do
149
+ Model.validates_presence_of :foo, :message => { :short => 'short', :full => 'full'}
150
+ assert_equal "short", model.errors[:foo].first.to_s
151
+ assert_equal "short", model.errors[:foo].first.to_s(:short)
152
+ assert_equal "full", model.errors[:foo].first.to_s(:full)
153
+ end
154
+
155
+ =begin
156
+ test "uses translation message formats the messages namespace" do
157
+ Model.validates_presence_of :foo
158
+ store_translations(:'errors.messages.blank' => { :short => 'short', :full => 'full' })
159
+ assert_equal "short", model.errors[:foo].first.to_s
160
+ assert_equal "short", model.errors[:foo].first.to_s(:short)
161
+ assert_equal "full", model.errors[:foo].first.to_s(:full)
162
+ end
163
+ =end
164
+
165
+ test "interpolates validation data to a class level Message message" do
166
+ Model.validates_length_of :foo, :minimum => 1, :message => '%{count}'
167
+ assert_equal "1", model.errors[:foo].first.to_s
168
+ end
169
+
170
+ test "returns an instance of AttributeErrors" do
171
+ Model.validates_presence_of :foo
172
+ assert model.errors[:foo].is_a?(ActiveModel::AttributeErrors)
173
+ end
174
+
175
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ dir = File.dirname(__FILE__)
4
+ $LOAD_PATH.unshift(dir)
5
+
6
+ Dir["#{dir}/**/*_test.rb"].sort.each do |file|
7
+ require file.sub(/^#{dir}\/(.*)\.rb$/, '\1')
8
+ end
@@ -0,0 +1,32 @@
1
+ # setup:
2
+ #
3
+ # git clone rails
4
+ # gem install bundler
5
+ # gem bundle (i had to comment out gem "pg", ">= 0.8.0" from rails/Gemfile)
6
+ # cd activemodel
7
+ # rake
8
+ # => should run the activemodel tests (outputs a ton of warnings for me)
9
+ #
10
+ # update line the following line, then running this file should run validations tests
11
+
12
+ dirs = %w(
13
+ ~/Development/shared/rails/rails-master/activemodel
14
+ ~/Projects/Ruby/rails/activemodel/
15
+ ).map { |dir| File.expand_path(dir) }
16
+
17
+ dirs.each { |dir| $:.unshift "#{dir}/lib", "#{dir}/test" }
18
+ $:.unshift('~/Projects/Ruby/rails/activesupport/lib')
19
+
20
+ require File.expand_path('../test_helper', __FILE__)
21
+ require 'active_model/errors_ext'
22
+ require 'core_ext/error_comparsion'
23
+
24
+ class ActiveModel::Error
25
+ include Legacy
26
+ end
27
+
28
+ tests = dirs.map { |dir| Dir["#{dir}/test/cases/validations{/**/*,}_test.rb"] }.flatten
29
+ # tests = Dir["#{dir}/test/cases/validations_test.rb"]
30
+ # tests.reject! { |test| test.include?('i18n_validation_test.rb') }
31
+ tests.each { |test| require test }
32
+
@@ -0,0 +1,23 @@
1
+ module Declarative
2
+ def self.included(base)
3
+ base.class_eval do
4
+ def test(name, &block)
5
+ name = "test_#{name.gsub(/\s+/,'_')}".to_sym
6
+ block ||= lambda { flunk "No implementation provided for #{name}" }
7
+
8
+ defined = instance_method(name) rescue false
9
+ raise("#{name} is already defined in #{self}") if defined
10
+
11
+ define_method(name, &block)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ class Test::Unit::TestCase
18
+ include Declarative
19
+ end
20
+
21
+ class Module
22
+ include Declarative
23
+ end
@@ -0,0 +1,29 @@
1
+ # FIXME personal load paths
2
+ $:.unshift('~/Development/shared/rails/rails-master/activemodel/lib')
3
+ $:.unshift('~/Development/shared/rails/rails-master/activesupport/lib')
4
+ $:.unshift('~/Projects/Ruby/rails/activemodel/lib')
5
+ $:.unshift('~/Projects/Ruby/rails/activesupport/lib')
6
+
7
+
8
+ $:.unshift File.expand_path("../lib", File.dirname(__FILE__))
9
+ $:.unshift(File.expand_path(File.dirname(__FILE__)))
10
+
11
+ require 'rubygems'
12
+ require 'test/unit'
13
+ require 'test_case_declarative'
14
+
15
+ require 'activemodel_error'
16
+
17
+ class Test::Unit::TestCase
18
+ def teardown
19
+ I18n.backend = nil
20
+ end
21
+
22
+ def store_translations(data)
23
+ I18n.backend.store_translations(:en, data)
24
+ end
25
+ end
26
+
27
+ class CascadingBackend < I18n::Backend::Simple
28
+ include I18n::Backend::Cascade
29
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activemodel-error
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sven Fuchs
8
+ - Mateo Murphy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2010-02-25 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: |-
18
+ Provides I18n support for validation error messages in ActiveModel.
19
+ With Rails 3 ActiveModel validation error messages are not backwards
20
+ compatible with ActiveRecord 2.3.x. This Gem aims to restore this
21
+ backwards compatiblity and provide a richer feature set and better
22
+ implementation compared to ActiveRecord 2.3.x.
23
+ email:
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.textile
30
+ files:
31
+ - MIT-LICENSE
32
+ - README.textile
33
+ - RakeFile
34
+ - VERSION
35
+ - lib/active_model/attribute_errors.rb
36
+ - lib/active_model/error.rb
37
+ - lib/active_model/error/base.rb
38
+ - lib/active_model/error/format.rb
39
+ - lib/active_model/error/legacy.rb
40
+ - lib/active_model/errors_ext.rb
41
+ - lib/activemodel_error.rb
42
+ - lib/core_ext/error_comparsion.rb
43
+ - test/active_model/format_test.rb
44
+ - test/active_model/interpolation_test.rb
45
+ - test/active_model/lookup_test.rb
46
+ - test/active_model/variants_test.rb
47
+ - test/active_model_test.rb
48
+ - test/all.rb
49
+ - test/run_active_model_tests.rb
50
+ - test/test_case_declarative.rb
51
+ - test/test_helper.rb
52
+ has_rdoc: true
53
+ homepage: http://github.com/svenfuchs/activemodel-error
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.5
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: I18n for validation error messages in ActiveModel
80
+ test_files:
81
+ - test/active_model/format_test.rb
82
+ - test/active_model/interpolation_test.rb
83
+ - test/active_model/lookup_test.rb
84
+ - test/active_model/variants_test.rb
85
+ - test/active_model_test.rb
86
+ - test/all.rb
87
+ - test/run_active_model_tests.rb
88
+ - test/test_case_declarative.rb
89
+ - test/test_helper.rb