activemodel-error 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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