aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG5
-rw-r--r--activemodel/README.rdoc (renamed from activemodel/README)124
-rw-r--r--activemodel/Rakefile12
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/conversion.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb12
-rw-r--r--activemodel/lib/active_model/errors.rb34
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb3
-rw-r--r--activemodel/lib/active_model/naming.rb38
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb18
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb24
-rw-r--r--activemodel/lib/active_model/validations/length.rb11
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb6
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb90
-rw-r--r--activemodel/test/cases/errors_test.rb65
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb2
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb6
-rw-r--r--activemodel/test/cases/naming_test.rb39
-rw-r--r--activemodel/test/cases/serializeration/json_serialization_test.rb16
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations_test.rb8
-rw-r--r--activemodel/test/models/contact.rb1
-rw-r--r--activemodel/test/models/sheep.rb4
29 files changed, 392 insertions, 160 deletions
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index a5e7f300d9..8374853231 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis]
+
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
diff --git a/activemodel/README b/activemodel/README.rdoc
index 6f162ef408..89cacbcab4 100644
--- a/activemodel/README
+++ b/activemodel/README.rdoc
@@ -1,21 +1,21 @@
-= Active Model - defined interfaces for Rails
-
-Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have
-an object interact with Action Pack helpers, it was required to either
-copy chunks of code from Rails, or monkey patch entire helpers to make them
-handle objects that did not look like Active Record. This generated code
-duplication and fragile applications that broke on upgrades.
-
-Active Model is a solution for this problem.
-
-Active Model provides a known set of interfaces that your objects can implement
-to then present a common interface to the Action Pack helpers. You can include
-functionality from the following modules:
-
-* Adding attribute magic to your objects
-
- Add prefixes and suffixes to defined attribute methods...
-
+= Active Model -- model interfaces for Rails
+
+Active Model provides a known set of interfaces for usage in model classes.
+They allow for Action Pack helpers to interact with non-ActiveRecord models,
+for example. Active Model also helps building custom ORMs for use outside of
+the Rails framework.
+
+Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
+interact with Action Pack helpers, it was required to either copy chunks of
+code from Rails, or monkey patch entire helpers to make them handle objects
+that did not exacly conform to the Active Record interface. This would result
+in code duplication and fragile applications that broke on upgrades.
+
+Active Model solves this. You can include functionality from the following
+modules:
+
+* Add attribute magic to objects
+
class Person
include ActiveModel::AttributeMethods
@@ -23,17 +23,18 @@ functionality from the following modules:
define_attribute_methods [:name, :age]
attr_accessor :name, :age
-
+
def clear_attribute(attr)
send("#{attr}=", nil)
end
end
- ...gives you clear_name, clear_age.
+ person.clear_name
+ person.clear_age
{Learn more}[link:classes/ActiveModel/AttributeMethods.html]
-* Adding callbacks to your objects
+* Callbacks for certain operations
class Person
extend ActiveModel::Callbacks
@@ -45,26 +46,16 @@ functionality from the following modules:
end
end
end
-
- ...gives you before_create, around_create and after_create class methods that
- wrap your create method.
-
+
+ This generates +before_create+, +around_create+ and +after_create+
+ class methods that wrap your create method.
+
{Learn more}[link:classes/ActiveModel/CallBacks.html]
-* For classes that already look like an Active Record object
+* Tracking value changes
- class Person
- include ActiveModel::Conversion
- end
-
- ...returns the class itself when sent :to_model
-
- {Learn more}[link:classes/ActiveModel/Conversion.html]
+ The ActiveModel::Dirty module allows for tracking attribute changes:
-* Tracking changes in your object
-
- Provides all the value tracking features implemented by ActiveRecord...
-
person = Person.new
person.name # => nil
person.changed? # => false
@@ -75,14 +66,14 @@ functionality from the following modules:
person.name = 'robert'
person.save
person.previous_changes # => {'name' => ['bob, 'robert']}
-
+
{Learn more}[link:classes/ActiveModel/Dirty.html]
-* Adding +errors+ support to your object
+* Adding +errors+ interface to objects
- Provides the error messages to allow your object to interact with Action Pack
- helpers seamlessly...
-
+ Exposing error messages allows objects to interact with Action Pack
+ helpers seamlessly.
+
class Person
def initialize
@@ -102,51 +93,38 @@ functionality from the following modules:
end
- ... gives you...
-
person.errors.full_messages
# => ["Name Can not be nil"]
+
person.errors.full_messages
# => ["Name Can not be nil"]
{Learn more}[link:classes/ActiveModel/Errors.html]
-* Testing the compliance of your object
+* Model name introspection
- Use ActiveModel::Lint to test the compliance of your object to the
- basic ActiveModel API...
-
- {Learn more}[link:classes/ActiveModel/Lint/Tests.html]
-
-* Providing a human face to your object
-
- ActiveModel::Naming provides your model with the model_name convention
- and a human_name attribute...
-
class NamedPerson
extend ActiveModel::Naming
end
- ...gives you...
-
NamedPerson.model_name #=> "NamedPerson"
NamedPerson.model_name.human #=> "Named person"
{Learn more}[link:classes/ActiveModel/Naming.html]
-* Adding observer support to your objects
+* Observer support
- ActiveModel::Observers allows your object to implement the Observer
- pattern in a Rails App and take advantage of all the standard observer
- functions.
+ ActiveModel::Observers allows your object to implement the Observer
+ pattern in a Rails App and take advantage of all the standard observer
+ functions.
{Learn more}[link:classes/ActiveModel/Observer.html]
-* Making your object serializable
+* Making objects serializable
- ActiveModel::Serialization provides a standard interface for your object
- to provide to_json or to_xml serialization...
-
+ ActiveModel::Serialization provides a standard interface for your object
+ to provide +to_json+ or +to_xml+ serialization.
+
s = SerialPerson.new
s.serializable_hash # => {"name"=>nil}
s.to_json # => "{\"name\":null}"
@@ -154,36 +132,36 @@ functionality from the following modules:
{Learn more}[link:classes/ActiveModel/Serialization.html]
-* Integrating with Rail's internationalization (i18n) handling through
- ActiveModel::Translations...
+* Internationalization (i18n) support
class Person
extend ActiveModel::Translation
end
+
+ Person.human_attribute_name('my_attribute')
+ #=> "My attribute"
{Learn more}[link:classes/ActiveModel/Translation.html]
-* Providing a full Validation stack for your objects...
+* Validation support
class Person
include ActiveModel::Validations
attr_accessor :first_name, :last_name
-
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
end
end
-
person = Person.new
person.first_name = 'zoolander'
- person.valid? #=> false
+ person.valid? #=> false
{Learn more}[link:classes/ActiveModel/Validations.html]
-* Make custom validators
+* Custom validators
class Person
include ActiveModel::Validations
@@ -196,7 +174,7 @@ functionality from the following modules:
record.errors[:name] = "must exist" if record.name.blank?
end
end
-
+
p = ValidatorPerson.new
p.valid? #=> false
p.errors.full_messages #=> ["Name must exist"]
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 1dba664539..3fffc0d021 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,6 +1,6 @@
dir = File.dirname(__FILE__)
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake/testtask'
@@ -23,16 +23,16 @@ namespace :test do
end
-require 'rake/rdoctask'
+require 'rdoc/task'
# Generate the RDoc documentation
-Rake::RDocTask.new do |rdoc|
+RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = "doc"
rdoc.title = "Active Model"
- rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include("README", "CHANGELOG")
+ rdoc.options << '--main' << 'README.rdoc'
+ rdoc.rdoc_files.include("README.rdoc", "CHANGELOG")
rdoc.rdoc_files.include("lib/**/*.rb")
end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 6bd6fe49ff..c483ecbc3c 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'activemodel'
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.has_rdoc = true
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 817640b178..a43436e008 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -283,7 +283,7 @@ module ActiveModel
@attribute_methods_generated = true
end
- # Removes all the preiously dynamically defined methods from the class
+ # Removes all the previously dynamically defined methods from the class
def undefine_attribute_methods
generated_attribute_methods.module_eval do
instance_methods.each { |m| undef_method(m) }
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index e7aad17021..8c10c54b54 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -19,7 +19,7 @@ module ActiveModel
#
# define_model_callbacks :create, :update
#
- # This will provide all three standard callbacks (before, around and after) around
+ # This will provide all three standard callbacks (before, around and after) for
# both the :create and :update methods. To implement, you need to wrap the methods
# you want callbacks on in a block so that the callbacks get a chance to fire:
#
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 2a1650faa9..d2bd160dc7 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -17,9 +17,9 @@ module ActiveModel
# end
#
# cm = ContactMessage.new
- # cm.to_model == self #=> true
- # cm.to_key #=> nil
- # cm.to_param #=> nil
+ # cm.to_model == self # => true
+ # cm.to_key # => nil
+ # cm.to_param # => nil
#
module Conversion
# If your object is already designed to implement all of the Active Model
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 4c80863e3a..2516377afd 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -37,12 +37,13 @@ module ActiveModel
# end
#
# def name=(val)
- # name_will_change!
+ # name_will_change! unless val == @name
# @name = val
# end
#
# def save
# @previously_changed = changes
+ # @changed_attributes.clear
# end
#
# end
@@ -77,13 +78,10 @@ module ActiveModel
# person.changed # => ['name']
# person.changes # => { 'name' => ['Bill', 'Bob'] }
#
- # Resetting an attribute returns it to its original state:
- # person.reset_name! # => 'Bill'
- # person.changed? # => false
- # person.name_changed? # => false
- # person.name # => 'Bill'
+ # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
+ # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
+ # in-place attributes.
#
- # Before modifying an attribute in-place:
# person.name_will_change!
# person.name << 'y'
# person.name_change # => ['Bill', 'Billy']
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 482b3dac47..272ddb1554 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -37,11 +37,11 @@ module ActiveModel
# send(attr)
# end
#
- # def ErrorsPerson.human_attribute_name(attr, options = {})
+ # def Person.human_attribute_name(attr, options = {})
# attr
# end
#
- # def ErrorsPerson.lookup_ancestors
+ # def Person.lookup_ancestors
# [self]
# end
#
@@ -83,20 +83,16 @@ module ActiveModel
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
- # p.errors[:name] #=> ["can not be nil"]
- # p.errors['name'] #=> ["can not be nil"]
+ # p.errors[:name] # => ["can not be nil"]
+ # p.errors['name'] # => ["can not be nil"]
def [](attribute)
- if errors = get(attribute.to_sym)
- errors
- else
- set(attribute.to_sym, [])
- end
+ get(attribute.to_sym) || set(attribute.to_sym, [])
end
# Adds to the supplied attribute the supplied error message.
#
# p.errors[:name] = "must be set"
- # p.errors[:name] #=> ['must be set']
+ # p.errors[:name] # => ['must be set']
def []=(attribute, error)
self[attribute.to_sym] << error
end
@@ -124,9 +120,9 @@ module ActiveModel
# Returns the number of error messages.
#
# p.errors.add(:name, "can't be blank")
- # p.errors.size #=> 1
+ # p.errors.size # => 1
# p.errors.add(:name, "must be specified")
- # p.errors.size #=> 2
+ # p.errors.size # => 2
def size
values.flatten.size
end
@@ -135,16 +131,16 @@ module ActiveModel
#
# p.errors.add(:name, "can't be blank")
# p.errors.add(:name, "must be specified")
- # p.errors.to_a #=> ["name can't be blank", "name must be specified"]
+ # p.errors.to_a # => ["name can't be blank", "name must be specified"]
def to_a
full_messages
end
# Returns the number of error messages.
# p.errors.add(:name, "can't be blank")
- # p.errors.count #=> 1
+ # p.errors.count # => 1
# p.errors.add(:name, "must be specified")
- # p.errors.count #=> 2
+ # p.errors.count # => 2
def count
to_a.size
end
@@ -158,8 +154,8 @@ module ActiveModel
#
# p.errors.add(:name, "can't be blank")
# p.errors.add(:name, "must be specified")
- # p.errors.to_xml #=> Produces:
- #
+ # p.errors.to_xml
+ # # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
# # <errors>
# # <error>name can't be blank</error>
@@ -169,9 +165,9 @@ module ActiveModel
to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
end
- # Returns an array as JSON representation for this object.
+ # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
def as_json(options=nil)
- to_a
+ self
end
# Adds +message+ to the error messages on +attribute+, which will be returned on a call to
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 7c48472799..9fcb94d48a 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -1,3 +1,4 @@
+require 'set'
require 'active_model/mass_assignment_security/sanitizer'
module ActiveModel
@@ -36,4 +37,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index ca1e9f0ee8..b74d669f0a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -17,7 +17,10 @@ module ActiveModel
end
# Transform the model name into a more humane format, using I18n. By default,
- # it will underscore then humanize the class name (BlogPost.model_name.human #=> "Blog post").
+ # it will underscore then humanize the class name
+ #
+ # BlogPost.model_name.human # => "Blog post"
+ #
# Specify +options+ with additional translating options.
def human(options={})
return @human unless @klass.respond_to?(:lookup_ancestors) &&
@@ -45,8 +48,8 @@ module ActiveModel
# extend ActiveModel::Naming
# end
#
- # BookCover.model_name #=> "BookCover"
- # BookCover.model_name.human #=> "Book cover"
+ # BookCover.model_name # => "BookCover"
+ # BookCover.model_name.human # => "Book cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the Active Model Lint test. So either extending the provided
@@ -57,6 +60,35 @@ module ActiveModel
def model_name
@_model_name ||= ActiveModel::Name.new(self)
end
+
+ # Returns the plural class name of a record or class. Examples:
+ #
+ # ActiveModel::Naming.plural(post) # => "posts"
+ # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
+ def self.plural(record_or_class)
+ model_name_from_record_or_class(record_or_class).plural
+ end
+
+ # Returns the singular class name of a record or class. Examples:
+ #
+ # ActiveModel::Naming.singular(post) # => "post"
+ # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
+ def self.singular(record_or_class)
+ model_name_from_record_or_class(record_or_class).singular
+ end
+
+ # Identifies whether the class name of a record or class is uncountable. Examples:
+ #
+ # ActiveModel::Naming.uncountable?(Sheep) # => true
+ # ActiveModel::Naming.uncountable?(Post) => false
+ def self.uncountable?(record_or_class)
+ plural(record_or_class) == singular(record_or_class)
+ end
+
+ private
+ def self.model_name_from_record_or_class(record_or_class)
+ (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
+ end
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 5670ec74cb..e675937f4d 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -61,6 +61,8 @@ module ActiveModel
# person.serializable_hash # => {"name"=>"Bob"}
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
+ #
+ # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
module Serialization
def serializable_hash(options = nil)
options ||= {}
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 918cd0ab76..e1dbc522de 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -19,8 +19,8 @@ module ActiveModel
# passed through +options+.
#
# The option <tt>ActiveModel::Base.include_root_in_json</tt> controls the
- # top-level behavior of to_json. It is true by default. When it is <tt>true</tt>,
- # to_json will emit a single root node named after the object's type. For example:
+ # top-level behavior of +to_json+. If true (the default) +to_json+ will
+ # emit a single root node named after the object's type. For example:
#
# konata = User.find(1)
# konata.to_json
@@ -32,11 +32,11 @@ module ActiveModel
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
# "created_at": "2006/08/01", "awesome": true}
#
- # The remainder of the examples in this section assume include_root_in_json is set to
- # <tt>false</tt>.
+ # The remainder of the examples in this section assume +include_root_in_json+
+ # is false.
#
- # Without any +options+, the returned JSON string will include all
- # the model's attributes. For example:
+ # Without any +options+, the returned JSON string will include all the model's
+ # attributes. For example:
#
# konata = User.find(1)
# konata.to_json
@@ -52,14 +52,14 @@ module ActiveModel
# konata.to_json(:except => [ :id, :created_at, :age ])
# # => {"name": "Konata Izumi", "awesome": true}
#
- # To include any methods on the model, use <tt>:methods</tt>.
+ # To include the result of some method calls on the model use <tt>:methods</tt>:
#
# konata.to_json(:methods => :permalink)
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
# "created_at": "2006/08/01", "awesome": true,
# "permalink": "1-konata-izumi"}
#
- # To include associations, use <tt>:include</tt>.
+ # To include associations use <tt>:include</tt>:
#
# konata.to_json(:include => :posts)
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
@@ -67,7 +67,7 @@ module ActiveModel
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
# {"id": 2, author_id: 1, "title": "So I was thinking"}]}
#
- # 2nd level and higher order associations work as well:
+ # Second level and higher order associations work as well:
#
# konata.to_json(:include => { :posts => {
# :include => { :comments => {
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 0554677296..0facbd6ce1 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -14,7 +14,7 @@ module ActiveModel
# end
#
# TranslatedPerson.human_attribute_name('my_attribute')
- # #=> "My attribute"
+ # # => "My attribute"
#
# This also provides the required class methods for hooking into the
# Rails internationalization API, including being able to define a
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 5779ba3b29..3407c59e7a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -24,20 +24,16 @@ module ActiveModel
# end
#
# Which provides you with the full standard validation stack that you
- # know from ActiveRecord.
+ # know from Active Record:
#
# person = Person.new
- # person.valid?
- # #=> true
- # person.invalid?
- # #=> false
+ # person.valid? # => true
+ # person.invalid? # => false
+ #
# person.first_name = 'zoolander'
- # person.valid?
- # #=> false
- # person.invalid?
- # #=> true
- # person.errors
- # #=> #<OrderedHash {:first_name=>["starts with z."]}>
+ # person.valid? # => false
+ # person.invalid? # => true
+ # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}>
#
# Note that ActiveModel::Validations automatically adds an +errors+ method
# to your instances initialized with a new ActiveModel::Errors object, so
@@ -122,11 +118,13 @@ module ActiveModel
# end
#
def validate(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options.key?(:on)
+ options = args.extract_options!
+ if options.key?(:on)
+ options = options.dup
options[:if] = Array.wrap(options[:if])
options[:if] << "validation_context == :#{options[:on]}"
end
+ args << options
set_callback(:validate, *args, &block)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index c8a77ad666..a7af4f2b4d 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -40,8 +40,6 @@ module ActiveModel
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
- default_message = options[MESSAGES[key]]
- options[:message] ||= default_message if default_message
valid_value = if key == :maximum
value.nil? || value.size.send(validity_check, check_value)
@@ -51,8 +49,13 @@ module ActiveModel
next if valid_value
- record.errors.add(attribute, MESSAGES[key],
- options.except(*RESERVED_OPTIONS).merge!(:count => check_value))
+ errors_options = options.except(*RESERVED_OPTIONS)
+ errors_options[:count] = check_value
+
+ default_message = options[MESSAGES[key]]
+ errors_options[:message] ||= default_message if default_message
+
+ record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 0674640925..3260e6bc5a 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -40,7 +40,7 @@ module ActiveModel
# validates :email, :presence => true, :email => true
# end
#
- # Validator classes my also exist within the class being validated
+ # Validator classes may also exist within the class being validated
# allowing custom modules of validators to be included as needed e.g.
#
# class Film
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 689c617177..163124d531 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -102,8 +102,8 @@ module ActiveModel #:nodoc:
#
# == Examples
#
- # PresenceValidator.kind #=> :presence
- # UniquenessValidator.kind #=> :uniqueness
+ # PresenceValidator.kind # => :presence
+ # UniquenessValidator.kind # => :uniqueness
#
def self.kind
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
@@ -111,7 +111,7 @@ module ActiveModel #:nodoc:
# Accepts options that will be made available through the +options+ reader.
def initialize(options)
- @options = options
+ @options = options.freeze
end
# Return the kind for this validator.
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index c36fc8b1f7..f2f4b15520 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module ActiveModel
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index e1a35be384..858ae9cb69 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -3,10 +3,11 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods [:name]
+ define_attribute_methods [:name, :color]
def initialize
@name = nil
+ @color = nil
end
def name
@@ -17,13 +18,92 @@ class DirtyTest < ActiveModel::TestCase
name_will_change!
@name = val
end
+
+ def color
+ @color
+ end
+
+ def color=(val)
+ color_will_change! unless val == @color
+ @color = val
+ end
+
+ def save
+ @previously_changed = changes
+ @changed_attributes.clear
+ end
+ end
+
+ setup do
+ @model = DirtyModel.new
+ end
+
+ test "setting attribute will result in change" do
+ assert !@model.changed?
+ assert !@model.name_changed?
+ @model.name = "Ringo"
+ assert @model.changed?
+ assert @model.name_changed?
+ end
+
+ test "list of changed attributes" do
+ assert_equal [], @model.changed
+ @model.name = "Paul"
+ assert_equal ['name'], @model.changed
+ end
+
+ test "changes to attribute values" do
+ assert !@model.changes['name']
+ @model.name = "John"
+ assert_equal [nil, "John"], @model.changes['name']
end
test "changes accessible through both strings and symbols" do
- model = DirtyModel.new
- model.name = "David"
- assert_not_nil model.changes[:name]
- assert_not_nil model.changes['name']
+ @model.name = "David"
+ assert_not_nil @model.changes[:name]
+ assert_not_nil @model.changes['name']
+ end
+
+ test "attribute mutation" do
+ @model.instance_variable_set("@name", "Yam")
+ assert !@model.name_changed?
+ @model.name.replace("Hadad")
+ assert !@model.name_changed?
+ @model.name_will_change!
+ @model.name.replace("Baal")
+ assert @model.name_changed?
+ end
+
+ test "resetting attribute" do
+ @model.name = "Bob"
+ @model.reset_name!
+ assert_nil @model.name
+ #assert !@model.name_changed #Doesn't work yet
+ end
+
+ test "setting color to same value should not result in change being recorded" do
+ @model.color = "red"
+ assert @model.color_changed?
+ @model.save
+ assert !@model.color_changed?
+ assert !@model.changed?
+ @model.color = "red"
+ assert !@model.color_changed?
+ assert !@model.changed?
+ end
+
+ test "saving should reset model's changed status" do
+ @model.name = "Alf"
+ assert @model.changed?
+ @model.save
+ assert !@model.changed?
+ assert !@model.name_changed?
+ end
+
+ test "saving should preserve previous changes" do
+ @model.name = "Jericho Cane"
+ @model.save
+ assert_equal [nil, "Jericho Cane"], @model.previous_changes['name']
end
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
new file mode 100644
index 0000000000..79b45bb298
--- /dev/null
+++ b/activemodel/test/cases/errors_test.rb
@@ -0,0 +1,65 @@
+require "cases/helper"
+
+class ErrorsTest < ActiveModel::TestCase
+ class Person
+ extend ActiveModel::Naming
+ def initialize
+ @errors = ActiveModel::Errors.new(self)
+ end
+
+ attr_accessor :name
+ attr_reader :errors
+
+ def validate!
+ errors.add(:name, "can not be nil") if name == nil
+ end
+
+ def read_attribute_for_validation(attr)
+ send(attr)
+ end
+
+ def self.human_attribute_name(attr, options = {})
+ attr
+ end
+
+ def self.lookup_ancestors
+ [self]
+ end
+
+ end
+
+ test "method validate! should work" do
+ person = Person.new
+ person.validate!
+ assert_equal ["name can not be nil"], person.errors.full_messages
+ assert_equal ["can not be nil"], person.errors[:name]
+
+ end
+
+ test 'should be able to assign error' do
+ person = Person.new
+ person.errors[:name] = 'should not be nil'
+ assert_equal ["should not be nil"], person.errors[:name]
+ end
+
+ test 'should be able to add an error on an attribute' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal ["can not be blank"], person.errors[:name]
+ end
+
+ test 'should respond to size' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal 1, person.errors.size
+ end
+
+ test 'to_a should return an array' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
+
+ end
+
+end
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 367207aab3..015153ec7c 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -31,7 +31,7 @@ class SanitizerTest < ActiveModel::TestCase
log = StringIO.new
@sanitizer.logger = Logger.new(log)
@sanitizer.sanitize(original_attributes)
- assert (log.string =~ /admin/), "Should log removed attributes: #{log.string}"
+ assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}")
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index 0f7a38b0bc..c25b0fdf00 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -35,10 +35,10 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
'Running attr_protected twice in one class should merge the protections'
- assert (TightPerson.protected_attributes - TightPerson.attributes_protected_by_default).blank?
+ assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default
assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
- assert (TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default).blank?
+ assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default
assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
end
@@ -49,4 +49,4 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal sanitized, { }
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index dc39b84ed8..5a8bff378a 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -1,4 +1,6 @@
require 'cases/helper'
+require 'models/contact'
+require 'models/sheep'
require 'models/track_back'
class NamingTest < ActiveModel::TestCase
@@ -26,3 +28,40 @@ class NamingTest < ActiveModel::TestCase
assert_equal 'post/track_backs/track_back', @model_name.partial_path
end
end
+
+class NamingHelpersTest < Test::Unit::TestCase
+ def setup
+ @klass = Contact
+ @record = @klass.new
+ @singular = 'contact'
+ @plural = 'contacts'
+ @uncountable = Sheep
+ end
+
+ def test_singular
+ assert_equal @singular, singular(@record)
+ end
+
+ def test_singular_for_class
+ assert_equal @singular, singular(@klass)
+ end
+
+ def test_plural
+ assert_equal @plural, plural(@record)
+ end
+
+ def test_plural_for_class
+ assert_equal @plural, plural(@klass)
+ end
+
+ def test_uncountable
+ assert uncountable?(@uncountable), "Expected 'sheep' to be uncoutable"
+ assert !uncountable?(@klass), "Expected 'contact' to be countable"
+ end
+
+ private
+ def method_missing(method, *args)
+ ActiveModel::Naming.send(method, *args)
+ end
+end
+
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializeration/json_serialization_test.rb
index 04b50e5bb8..1ac991a8f1 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -89,7 +89,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
- test "methds are called on object" do
+ test "methods are called on object" do
# Define methods on fixture.
def @contact.label; "Has cheezburger"; end
def @contact.favorite_quote; "Constraints are liberating"; end
@@ -102,4 +102,18 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"label":"Has cheezburger"}, methods_json
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
+
+ test "should return OrderedHash for errors" do
+ car = Automobile.new
+
+ # run the validation
+ car.valid?
+
+ hash = ActiveSupport::OrderedHash.new
+ hash[:make] = "can't be blank"
+ hash[:model] = "is too short (minimum is 2 characters)"
+ assert_equal hash.to_json, car.errors.to_json
+ end
+
+
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 012c5a2f37..1e6180a938 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -229,6 +229,20 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["hoo 5"], t.errors["title"]
end
+ def test_validates_length_of_custom_errors_for_both_too_short_and_too_long
+ Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long'
+
+ t = Topic.new(:title => 'a')
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ['too short'], t.errors['title']
+
+ t = Topic.new(:title => 'aaaaaa')
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ['too long'], t.errors['title']
+ end
+
def test_validates_length_of_custom_errors_for_is_with_message
Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index e94d8ce88c..8d6bdeb6a5 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -170,9 +170,11 @@ class ValidationsTest < ActiveModel::TestCase
assert_match %r{<errors>}, xml
assert_match %r{<error>Title can't be blank</error>}, xml
assert_match %r{<error>Content can't be blank</error>}, xml
-
- json = t.errors.to_json
- assert_equal t.errors.to_a.to_json, json
+
+ hash = ActiveSupport::OrderedHash.new
+ hash[:title] = "can't be blank"
+ hash[:content] = "can't be blank"
+ assert_equal t.errors.to_json, hash.to_json
end
def test_validation_order
diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb
index 605e435f39..f4f3078473 100644
--- a/activemodel/test/models/contact.rb
+++ b/activemodel/test/models/contact.rb
@@ -1,4 +1,5 @@
class Contact
+ extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
diff --git a/activemodel/test/models/sheep.rb b/activemodel/test/models/sheep.rb
new file mode 100644
index 0000000000..175dbe6477
--- /dev/null
+++ b/activemodel/test/models/sheep.rb
@@ -0,0 +1,4 @@
+class Sheep
+ extend ActiveModel::Naming
+end
+ \ No newline at end of file