diff options
Diffstat (limited to 'activemodel')
| -rw-r--r-- | activemodel/CHANGELOG.md | 15 | ||||
| -rw-r--r-- | activemodel/README.rdoc | 19 | ||||
| -rw-r--r-- | activemodel/activemodel.gemspec | 2 | ||||
| -rw-r--r-- | activemodel/lib/active_model.rb | 1 | ||||
| -rw-r--r-- | activemodel/lib/active_model/conversion.rb | 4 | ||||
| -rw-r--r-- | activemodel/lib/active_model/mass_assignment_security.rb | 18 | ||||
| -rw-r--r-- | activemodel/lib/active_model/model.rb | 76 | ||||
| -rw-r--r-- | activemodel/lib/active_model/naming.rb | 1 | ||||
| -rw-r--r-- | activemodel/lib/active_model/serialization.rb | 16 | ||||
| -rw-r--r-- | activemodel/test/cases/conversion_test.rb | 2 | ||||
| -rw-r--r-- | activemodel/test/cases/model_test.rb | 26 | ||||
| -rw-r--r-- | activemodel/test/cases/serialization_test.rb | 26 |
12 files changed, 170 insertions, 36 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index a6d3afe653..c6d8eae46c 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,11 +1,22 @@ +## Rails 4.0.0 (unreleased) ## + +* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran* + * `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev* * Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim* + +## Rails 3.2.2 (March 1, 2012) ## + +* No changes. + + ## Rails 3.2.1 (January 26, 2012) ## * No changes. + ## Rails 3.2.0 (January 20, 2012) ## * Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to @@ -21,14 +32,17 @@ * Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev* + ## Rails 3.1.3 (November 20, 2011) ## * No changes + ## Rails 3.1.2 (November 18, 2011) ## * No changes + ## Rails 3.1.1 (October 7, 2011) ## * Remove hard dependency on bcrypt-ruby to avoid make ActiveModel dependent on a binary library. @@ -38,6 +52,7 @@ See GH #2687. *Guillermo Iguaran* + ## Rails 3.1.0 (August 30, 2011) ## * Alternate I18n namespace lookup is no longer supported. diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index a7ba27ba73..4861b0a002 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -13,6 +13,25 @@ in code duplication and fragile applications that broke on upgrades. Active Model solves this by defining an explicit API. You can read more about the API in ActiveModel::Lint::Tests. +Active Model provides a default module that implements the basic API required +to integrate with Action Pack out of the box: +ActiveModel::Model+. + + class Person + include ActiveModel::Model + + attr_accessor :name, :age + validates_presence_of :name + end + + person = Person.new(:name => 'bob', :age => '18') + person.name # => 'bob' + person.age # => 18 + person.valid? # => false + +It includes model name introspections, conversions, translations and +validations, resulting in a class suitable to be used with Action Pack. +See +ActiveModel::Model+ for more examples. + Active Model also provides the following functionality to have ORM-like behavior out of the box: diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 60c1d16934..f2d004fb0a 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = 'activemodel' s.version = version s.summary = 'A toolkit for building modeling frameworks (part of Rails).' - s.description = 'A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' + s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' s.required_ruby_version = '>= 1.9.3' diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 85514e63fd..2586147a20 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -39,6 +39,7 @@ module ActiveModel autoload :Errors autoload :Lint autoload :MassAssignmentSecurity + autoload :Model autoload :Name, 'active_model/naming' autoload :Naming autoload :Observer, 'active_model/observing' diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index c7c805f1a2..d7f30f0920 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -21,7 +21,7 @@ module ActiveModel # cm.to_model == self # => true # cm.to_key # => nil # cm.to_param # => nil - # cm.to_path # => "contact_messages/contact_message" + # cm.to_partial_path # => "contact_messages/contact_message" # module Conversion extend ActiveSupport::Concern @@ -57,7 +57,7 @@ module ActiveModel end module ClassMethods #:nodoc: - # Provide a class level cache for the to_path. This is an + # Provide a class level cache for #to_partial_path. This is an # internal method and should not be accessed directly. def _to_partial_path #:nodoc: @_to_partial_path ||= begin diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 95de039676..5e5405fe27 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -85,7 +85,7 @@ module ActiveModel # end # end # - # When using the :default role : + # When using the :default role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default) @@ -93,7 +93,7 @@ module ActiveModel # customer.email # => "a@b.com" # customer.logins_count # => nil # - # And using the :admin role : + # And using the :admin role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin) @@ -107,8 +107,9 @@ module ActiveModel # To start from an all-closed default and enable attributes as needed, # have a look at +attr_accessible+. # - # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+ - # to sanitize attributes won't provide sufficient protection. + # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of + # +attr_protected+ to sanitize attributes provides basically the same + # functionality, but it makes a bit tricky to deal with nested attributes. def attr_protected(*args) options = args.extract_options! role = options[:as] || :default @@ -152,7 +153,7 @@ module ActiveModel # end # end # - # When using the :default role : + # When using the :default role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) @@ -162,15 +163,16 @@ module ActiveModel # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # - # And using the :admin role : + # And using the :admin role: # # customer = Customer.new # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) # customer.name # => "David" # customer.credit_rating # => "Excellent" # - # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+ - # to sanitize attributes won't provide sufficient protection. + # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of + # +attr_accessible+ to sanitize attributes provides basically the same + # functionality, but it makes a bit tricky to deal with nested attributes. def attr_accessible(*args) options = args.extract_options! role = options[:as] || :default diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb new file mode 100644 index 0000000000..6825fdc653 --- /dev/null +++ b/activemodel/lib/active_model/model.rb @@ -0,0 +1,76 @@ +module ActiveModel + + # == Active Model Basic Model + # + # Includes the required interface for an object to interact with +ActionPack+, + # using different +ActiveModel+ modules. It includes model name introspections, + # conversions, translations and validations. Besides that, it allows you to + # initialize the object with a hash of attributes, pretty much like + # +ActiveRecord+ does. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(:name => 'bob', :age => '18') + # person.name # => 'bob' + # person.age # => 18 + # + # Note that, by default, +ActiveModel::Model+ implements +persisted?+ to + # return +false+, which is the most common case. You may want to override it + # in your class to simulate a different scenario: + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name + # + # def persisted? + # self.id == 1 + # end + # end + # + # person = Person.new(:id => 1, :name => 'bob') + # person.persisted? # => true + # + # Also, if for some reason you need to run code on +initialize+, make sure you + # call super if you want the attributes hash initialization to happen. + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name, :omg + # + # def initialize(attributes) + # super + # @omg ||= true + # end + # end + # + # person = Person.new(:id => 1, :name => 'bob') + # person.omg # => true + # + # For more detailed information on other functionalities available, please refer + # to the specific modules included in +ActiveModel::Model+ (see below). + module Model + def self.included(base) + base.class_eval do + extend ActiveModel::Naming + extend ActiveModel::Translation + include ActiveModel::Validations + include ActiveModel::Conversion + end + end + + def initialize(params={}) + params.each do |attr, value| + self.public_send("#{attr}=", value) + end if params + end + + def persisted? + false + end + end +end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 755e54efcd..fd0bc4e8e9 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -2,6 +2,7 @@ require 'active_support/inflector' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/deprecation' +require 'active_support/core_ext/object/blank' module ActiveModel class Name < String diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 51f078e662..4323ee1e09 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -1,7 +1,5 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/array/wrap' - module ActiveModel # == Active Model Serialization @@ -11,7 +9,6 @@ module ActiveModel # A minimal implementation could be: # # class Person - # # include ActiveModel::Serialization # # attr_accessor :name @@ -19,7 +16,6 @@ module ActiveModel # def attributes # {'name' => nil} # end - # # end # # Which would provide you with: @@ -43,7 +39,6 @@ module ActiveModel # So a minimal implementation including XML and JSON would be: # # class Person - # # include ActiveModel::Serializers::JSON # include ActiveModel::Serializers::Xml # @@ -52,7 +47,6 @@ module ActiveModel # def attributes # {'name' => nil} # end - # # end # # Which would provide you with: @@ -88,7 +82,7 @@ module ActiveModel method_names.each { |n| hash[n.to_s] = send(n) } serializable_add_includes(options) do |association, records, opts| - hash[association] = if records.is_a?(Enumerable) + hash[association.to_s] = if records.is_a?(Enumerable) records.map { |a| a.serializable_hash(opts) } else records.serializable_hash(opts) @@ -126,13 +120,13 @@ module ActiveModel # +records+ - the association record(s) to be serialized # +opts+ - options for the association records def serializable_add_includes(options = {}) #:nodoc: - return unless include = options[:include] + return unless includes = options[:include] - unless include.is_a?(Hash) - include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }] + unless includes.is_a?(Hash) + includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }] end - include.each do |association, opts| + includes.each do |association, opts| if records = send(association) yield association, records, opts end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index 24552bcaf2..d679ad41aa 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -24,7 +24,7 @@ class ConversionTest < ActiveModel::TestCase assert_equal "1", Contact.new(:id => 1).to_param end - test "to_path default implementation returns a string giving a relative path" do + test "to_partial_path default implementation returns a string giving a relative path" do assert_equal "contacts/contact", Contact.new.to_partial_path assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path, "ActiveModel::Conversion#to_partial_path caching should be class-specific" diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb new file mode 100644 index 0000000000..d93fd96b88 --- /dev/null +++ b/activemodel/test/cases/model_test.rb @@ -0,0 +1,26 @@ +require 'cases/helper' + +class ModelTest < ActiveModel::TestCase + include ActiveModel::Lint::Tests + + class BasicModel + include ActiveModel::Model + attr_accessor :attr + end + + def setup + @model = BasicModel.new + end + + def test_initialize_with_params + object = BasicModel.new(:attr => "value") + assert_equal object.attr, "value" + end + + def test_initialize_with_nil_or_empty_hash_params_does_not_explode + assert_nothing_raised do + BasicModel.new() + BasicModel.new({}) + end + end +end diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index 3b201a70f5..66b18d65e5 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -88,62 +88,62 @@ class SerializationTest < ActiveModel::TestCase def test_include_option_with_singular_association expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} + "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} assert_equal expected, @user.serializable_hash(:include => :address) end def test_include_option_with_plural_association expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(:include => :friends) end def test_include_option_with_empty_association @user.friends = [] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]} assert_equal expected, @user.serializable_hash(:include => :friends) end def test_multiple_includes expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(:include => [:address, :friends]) end def test_include_with_options expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}} + "address"=>{"street"=>"123 Lane"}} assert_equal expected, @user.serializable_hash(:include => {:address => {:only => "street"}}) end def test_nested_include @user.friends.first.friends = [@user] expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', - :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', + "friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', "friends"=> []}]} assert_equal expected, @user.serializable_hash(:include => {:friends => {:include => :friends}}) end def test_only_include - expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]} + expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]} assert_equal expected, @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) end def test_except_include expected = {"name"=>"David", "email"=>"david@example.com", - :friends => [{"name" => 'Joe', "email" => 'joe@example.com'}, + "friends"=> [{"name" => 'Joe', "email" => 'joe@example.com'}, {"name" => "Sue", "email" => 'sue@example.com'}]} assert_equal expected, @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) end def test_multiple_includes_with_options expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + "address"=>{"street"=>"123 Lane"}, + "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} assert_equal expected, @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) end |
