aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/README85
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb20
-rw-r--r--activemodel/lib/active_model/callbacks.rb7
-rw-r--r--activemodel/lib/active_model/dirty.rb35
-rw-r--r--activemodel/lib/active_model/errors.rb3
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb7
-rw-r--r--activemodel/lib/active_model/validations.rb4
-rw-r--r--activemodel/lib/active_model/validator.rb3
-rw-r--r--activemodel/test/cases/dirty_test.rb30
10 files changed, 115 insertions, 81 deletions
diff --git a/activemodel/README b/activemodel/README
index 0d6fd1f21c..3945a6da06 100644
--- a/activemodel/README
+++ b/activemodel/README
@@ -11,9 +11,9 @@ 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...
class Person
@@ -34,7 +34,7 @@ functionality from the following modules:
{Learn more}[link:classes/ActiveModel/AttributeMethods.html]
* Adding callbacks to your objects
-
+
class Person
extend ActiveModel::Callbacks
define_model_callbacks :create
@@ -50,19 +50,19 @@ functionality from the following modules:
wrap your create method.
{Learn more}[link:classes/ActiveModel/CallBacks.html]
-
+
* For classes that already look like an Active Record object
-
+
class Person
include ActiveModel::Conversion
end
...returns the class itself when sent :to_model
-
+
{Learn more}[link:classes/ActiveModel/Conversion.html]
-
+
* Tracking changes in your object
-
+
Provides all the value tracking features implemented by ActiveRecord...
person = Person.new
@@ -77,29 +77,29 @@ functionality from the following modules:
person.previous_changes # => {'name' => ['bob, 'robert']}
{Learn more}[link:classes/ActiveModel/Dirty.html]
-
+
* Adding +errors+ support to your object
-
+
Provides the error messages to allow your object to interact with Action Pack
helpers seamlessly...
class Person
-
+
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 ErrorsPerson.human_attribute_name(attr, options = {})
"Name"
end
-
+
end
... gives you...
@@ -108,18 +108,18 @@ functionality from the following modules:
# => ["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
-
+
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...
@@ -131,19 +131,19 @@ functionality from the following modules:
NamedPerson.model_name #=> "NamedPerson"
NamedPerson.model_name.human #=> "Named person"
-
+
{Learn more}[link:classes/ActiveModel/Naming.html]
-
+
* Adding observer support to your objects
-
+
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
-
+
ActiveModel::Serialization provides a standard interface for your object
to provide to_json or to_xml serialization...
@@ -153,48 +153,37 @@ functionality from the following modules:
s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
{Learn more}[link:classes/ActiveModel/Serialization.html]
-
-
-* Turning your object into a finite State Machine
-
- ActiveModel::StateMachine provides a clean way to include all the methods
- you need to transform your object into a finite State Machine...
-
- light = TrafficLight.new
- light.current_state #=> :red
- light.change_color! #=> true
- light.current_state #=> :green
-
- {Learn more}[link:classes/ActiveModel/StateMachine.html]
-
+
* Integrating with Rail's internationalization (i18n) handling through
ActiveModel::Translations...
-
+
class Person
extend ActiveModel::Translation
end
{Learn more}[link:classes/ActiveModel/Translation.html]
-
+
* Providing a full Validation stack for your objects...
-
+
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(:first_name => 'zoolander')
person.valid? #=> false
-
+
{Learn more}[link:classes/ActiveModel/Validations.html]
* Make custom validators
-
+
class Person
include ActiveModel::Validations
validates_with HasNameValidator
@@ -212,5 +201,5 @@ functionality from the following modules:
p.errors.full_messages #=> ["Name must exist"]
p.name = "Bob"
p.valid? #=> true
-
+
{Learn more}[link:classes/ActiveModel/Validator.html]
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 588976f1d4..f04829ef09 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -53,7 +53,6 @@ module ActiveModel
module AttributeMethods
extend ActiveSupport::Concern
- # Declare and check for suffixed attribute methods.
module ClassMethods
# Defines an "attribute" method (like +inheritance_column+ or
# +table_name+). A new (class) method will be created with the
@@ -90,13 +89,20 @@ module ActiveModel
# # => 'address_id'
def define_attr_method(name, value=nil, &block)
sing = singleton_class
- sing.send :alias_method, "original_#{name}", name
+ sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
+ if method_defined?(:original_#{name})
+ undef :original_#{name}
+ end
+ alias_method :original_#{name}, :#{name}
+ eorb
if block_given?
sing.send :define_method, name, &block
else
# use eval instead of a block to work around a memory leak in dev
# mode in fcgi
- sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
+ sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
+ def #{name}; #{value.to_s.inspect}; end
+ eorb
end
end
@@ -257,8 +263,13 @@ module ActiveModel
if respond_to?(generate_method)
send(generate_method, attr_name)
else
+ method_name = matcher.method_name(attr_name)
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__+1
- def #{matcher.method_name(attr_name)}(*args)
+ if method_defined?(:#{method_name})
+ undef :#{method_name}
+ end
+ def #{method_name}(*args)
send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
end
STR
@@ -286,6 +297,7 @@ module ActiveModel
end
end
+ # Returns true if the attribute methods defined have been generated.
def attribute_methods_generated?
@attribute_methods_generated ||= nil
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index a7e0cf90c1..d4e98de57b 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/wrap'
require 'active_support/callbacks'
module ActiveModel
@@ -91,7 +92,7 @@ module ActiveModel
options = callbacks.extract_options!
options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
- types = Array(options.delete(:only))
+ types = Array.wrap(options.delete(:only))
types = [:before, :around, :after] if types.empty?
callbacks.each do |callback|
@@ -124,10 +125,10 @@ module ActiveModel
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
- options[:if] = Array(options[:if]) << "!halted && value != false"
+ options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 5f02929a9d..cb67ef7270 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,3 +1,5 @@
+require 'active_support/hash_with_indifferent_access'
+
module ActiveModel
# <tt>ActiveModel::Dirty</tt> provides a way to track changes in your
# object in the same way as ActiveRecord does.
@@ -86,12 +88,17 @@ module ActiveModel
attribute_method_affix :prefix => 'reset_', :suffix => '!'
end
+ def initialize(*)
+ @changed_attributes = {}
+ super
+ end
+
# Do any attributes have unsaved changes?
# person.changed? # => false
# person.name = 'bob'
# person.changed? # => true
def changed?
- !changed_attributes.empty?
+ !@changed_attributes.empty?
end
# List of attributes with unsaved changes.
@@ -99,7 +106,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed # => ['name']
def changed
- changed_attributes.keys
+ @changed_attributes.keys
end
# Map of changed attrs => [original value, new value].
@@ -107,7 +114,7 @@ module ActiveModel
# person.name = 'bob'
# person.changes # => { 'name' => ['bill', 'bob'] }
def changes
- changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
+ changed.inject(HashWithIndifferentAccess.new){ |h, attr| h[attr] = attribute_change(attr); h }
end
# Map of attributes that were changed when the model was saved.
@@ -116,33 +123,23 @@ module ActiveModel
# person.save
# person.previous_changes # => {'name' => ['bob, 'robert']}
def previous_changes
- previously_changed_attributes
+ @previously_changed
end
private
- # Map of change <tt>attr => original value</tt>.
- def changed_attributes
- @changed_attributes ||= {}
- end
-
- # Map of fields that were changed when the model was saved
- def previously_changed_attributes
- @previously_changed || {}
- end
-
# Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr)
- changed_attributes.include?(attr)
+ @changed_attributes.include?(attr)
end
# Handle <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ [@changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
# Handle <tt>*_was</tt> for +method_missing+.
def attribute_was(attr)
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ attribute_changed?(attr) ? @changed_attributes[attr] : __send__(attr)
end
# Handle <tt>*_will_change!</tt> for +method_missing+.
@@ -153,12 +150,12 @@ module ActiveModel
rescue TypeError, NoMethodError
end
- changed_attributes[attr] = value
+ @changed_attributes[attr] = value
end
# Handle <tt>reset_*!</tt> for +method_missing+.
def reset_attribute!(attr)
- __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
+ __send__("#{attr}=", @changed_attributes[attr]) if attribute_changed?(attr)
end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index d8320275df..a9a54a90e0 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/string/inflections'
require 'active_support/ordered_hash'
@@ -206,7 +207,7 @@ module ActiveModel
full_messages = []
each do |attribute, messages|
- messages = Array(messages)
+ messages = Array.wrap(messages)
next if messages.empty?
if attribute == :base
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 39512a427b..8cdd3d2fe8 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,6 +1,7 @@
require 'active_support/inflector'
module ActiveModel
+
class Name < String
attr_reader :singular, :plural, :element, :collection, :partial_path
alias_method :cache_key, :collection
@@ -57,4 +58,5 @@ module ActiveModel
@_model_name ||= ActiveModel::Name.new(self)
end
end
+
end
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 86149f1e5f..c226359ea7 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/hash/conversions'
@@ -85,8 +86,8 @@ module ActiveModel
@options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
end
- # To replicate the behavior in ActiveRecord#attributes,
- # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
+ # To replicate the behavior in ActiveRecord#attributes, <tt>:except</tt>
+ # takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
# for a N level model but is set for the N+1 level models,
# then because <tt>:except</tt> is set to a default value, the second
# level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
@@ -108,7 +109,7 @@ module ActiveModel
end
def serializable_method_attributes
- Array(options[:methods]).inject([]) do |methods, name|
+ Array.wrap(options[:methods]).inject([]) do |methods, name|
methods << MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
methods
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index ba8648f8c9..708557f4ae 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_model/errors'
@@ -112,11 +113,10 @@ module ActiveModel
# end
# end
#
- # This usage applies to +validate_on_create+ and +validate_on_update as well+.
def validate(*args, &block)
options = args.last
if options.is_a?(Hash) && options.key?(:on)
- options[:if] = Array(options[:if])
+ options[:if] = Array.wrap(options[:if])
options[:if] << "@_on_validate == :#{options[:on]}"
end
set_callback(:validate, *args, &block)
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index b61f0cb266..b7c52be3f0 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/wrap'
require "active_support/core_ext/module/anonymous"
module ActiveModel #:nodoc:
@@ -130,7 +131,7 @@ module ActiveModel #:nodoc:
# +options+ reader, however the <tt>:attributes</tt> option will be removed
# and instead be made available through the +attributes+ reader.
def initialize(options)
- @attributes = Array(options.delete(:attributes))
+ @attributes = Array.wrap(options.delete(:attributes))
raise ":attributes cannot be blank" if @attributes.empty?
super
check_validity!
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
new file mode 100644
index 0000000000..0883363baf
--- /dev/null
+++ b/activemodel/test/cases/dirty_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class DirtyTest < ActiveModel::TestCase
+ class DirtyModel
+ include ActiveModel::Dirty
+ define_attribute_methods [:name]
+
+ def initialize
+ super
+ @name = nil
+ end
+
+ def name
+ @name
+ end
+
+ def name=(val)
+ name_will_change!
+ @name = val
+ end
+ end
+
+ test "changes accessible through both strings and symbols" do
+ model = DirtyModel.new
+ model.name = "David"
+ assert !model.changes[:name].nil?
+ assert !model.changes['name'].nil?
+ end
+
+end