Browse files

Define ActiveModel API Compliance

  - Define to_model on AR
  - Define to_model on ActiveModel::APICompliant
  - Update test fixtures to be API Compliant
  - Start using to_model in AP
  • Loading branch information...
1 parent 45d41d8 commit 5ffaaa71d149c9807260c950c9a61d01fe734827 @wycats wycats committed Jul 20, 2009
View
1 actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
@@ -77,6 +77,7 @@ def polymorphic_url(record_or_hash_or_array, options = {})
end
record = extract_record(record_or_hash_or_array)
+ record = record.to_model if record.respond_to?(:to_model)
namespace = extract_namespace(record_or_hash_or_array)
args = case record_or_hash_or_array
View
24 actionpack/lib/action_view/helpers/active_record_helper.rb
@@ -77,6 +77,7 @@ def input(record_name, method, options = {})
# * <tt>:submit_value</tt> - The text of the submit button (default: "Create" if a new record, otherwise "Update").
def form(record_name, options = {})
record = instance_variable_get("@#{record_name}")
+ record = record.to_model if record.respond_to?(:to_model)
options = options.symbolize_keys
options[:action] ||= record.new_record? ? "create" : "update"
@@ -121,6 +122,8 @@ def error_message_on(object, method, *args)
end
options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
+ object = object.to_model if object.respond_to?(:to_model)
+
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
(errors = obj.errors[method])
content_tag("div",
@@ -179,6 +182,8 @@ def error_messages_for(*params)
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
end
+ objects.map! {|o| o.respond_to?(:to_model) ? o.to_model : o }
+
count = objects.inject(0) {|sum, object| sum + object.errors.count }
unless count.zero?
html = {}
@@ -226,7 +231,14 @@ def default_input_block
end
end
- class InstanceTag #:nodoc:
+ module ActiveRecordInstanceTag
+ def object
+ @active_model_object ||= begin
+ object = super
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+ end
+
def to_tag(options = {})
case column_type
when :string
@@ -248,11 +260,9 @@ def to_tag(options = {})
end
%w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- without = "#{meth}_without_error_wrapping"
- define_method "#{meth}_with_error_wrapping" do |*args|
- error_wrapping(send(without, *args))
+ define_method meth do |*|
+ error_wrapping(super)
end
- alias_method_chain meth, :error_wrapping
end
def error_wrapping(html_tag)
@@ -267,5 +277,9 @@ def column_type
object.send(:column_for_attribute, @method_name).type
end
end
+
+ class InstanceTag
+ include ActiveRecordInstanceTag
+ end
end
end
View
13 actionpack/lib/action_view/helpers/form_helper.rb
@@ -626,8 +626,8 @@ def text_area(object_name, method, options = {})
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
- # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
- # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
+ # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
+ # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
#
# ==== Gotcha
@@ -709,7 +709,8 @@ def radio_button(object_name, method, tag_value, options = {})
end
end
- class InstanceTag #:nodoc:
+ module InstanceTagMethods #:nodoc:
+ extend ActiveSupport::Concern
include Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :method_name, :object_name
@@ -832,7 +833,7 @@ def value_before_type_cast(object)
self.class.value_before_type_cast(object, @method_name)
end
- class << self
+ module ClassMethods
def value(object, method_name)
object.send method_name unless object.nil?
end
@@ -918,6 +919,10 @@ def sanitized_method_name
end
end
+ class InstanceTag
+ include InstanceTagMethods
+ end
+
class FormBuilder #:nodoc:
# The methods which wrap a form helper call.
class_inheritable_accessor :field_helpers
View
1 actionpack/test/abstract_unit.rb
@@ -20,6 +20,7 @@
require 'action_view/test_case'
require 'action_controller/testing/integration'
require 'active_support/dependencies'
+require 'active_model'
$tags[:new_base] = true
View
2 actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -126,7 +126,7 @@ def render_with_record_collection
end
class Game < Struct.new(:name, :id)
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
def to_param
id.to_s
end
View
2 actionpack/test/controller/record_identifier_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
class Comment
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
attr_reader :id
def save; @id = 1 end
View
2 actionpack/test/controller/redirect_test.rb
@@ -4,7 +4,7 @@ class WorkshopsController < ActionController::Base
end
class Workshop
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
attr_accessor :id, :new_record
def initialize(id, new_record)
View
4 actionpack/test/lib/controller/fake_models.rb
@@ -1,7 +1,7 @@
require "active_model"
class Customer < Struct.new(:name, :id)
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
def to_param
id.to_s
@@ -16,7 +16,7 @@ class GoodCustomer < Customer
module Quiz
class Question < Struct.new(:name, :id)
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
def to_param
id.to_s
View
16 actionpack/test/template/active_record_helper_test.rb
@@ -4,19 +4,17 @@ class ActiveRecordHelperTest < ActionView::TestCase
tests ActionView::Helpers::ActiveRecordHelper
silence_warnings do
- Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
- Post.class_eval do
- alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
- alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
- alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
+ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
+ extend ActiveModel::APICompliant
end
- User = Struct.new("User", :email)
- User.class_eval do
- alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast)
+ class User < Struct.new(:email)
+ extend ActiveModel::APICompliant
end
- Column = Struct.new("Column", :type, :name, :human_name)
+ class Column < Struct.new(:type, :name, :human_name)
+ extend ActiveModel::APICompliant
+ end
end
class DirtyPost
View
9 actionpack/test/template/atom_feed_helper_test.rb
@@ -1,7 +1,12 @@
require 'abstract_unit'
-Scroll = Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
-Scroll.extend ActiveModel::Naming
+class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
+ extend ActiveModel::APICompliant
+
+ def new_record?
+ true
+ end
+end
class ScrollsController < ActionController::Base
FEEDS = {}
View
8 actionpack/test/template/prototype_helper_test.rb
@@ -1,10 +1,12 @@
require 'abstract_unit'
+require 'active_model'
Bunny = Struct.new(:Bunny, :id)
-Bunny.extend ActiveModel::Naming
+Bunny.extend ActiveModel::APICompliant
class Author
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
+
attr_reader :id
def save; @id = 1 end
def new_record?; @id.nil? end
@@ -14,7 +16,7 @@ def name
end
class Article
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
attr_reader :id
attr_reader :author_id
def save; @id = 1; @author_id = 1 end
View
2 actionpack/test/template/record_tag_helper_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
class Post
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
def id
45
end
View
2 actionpack/test/template/test_test.rb
@@ -41,7 +41,7 @@ def test_homepage_url
def test_link_to_person
person = mock(:name => "David")
- person.class.extend ActiveModel::Naming
+ person.class.extend ActiveModel::APICompliant
expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
end
View
4 actionpack/test/template/url_helper_test.rb
@@ -494,7 +494,7 @@ def with_restful_routing
end
class Workshop
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
attr_accessor :id, :new_record
def initialize(id, new_record)
@@ -511,7 +511,7 @@ def to_s
end
class Session
- extend ActiveModel::Naming
+ extend ActiveModel::APICompliant
attr_accessor :id, :workshop_id, :new_record
def initialize(id, new_record)
View
1 activemodel/lib/active_model.rb
@@ -26,6 +26,7 @@
require 'active_support'
module ActiveModel
+ autoload :APICompliant, 'active_model/api_compliant'
autoload :Attributes, 'active_model/attributes'
autoload :Base, 'active_model/base'
autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
View
25 activemodel/lib/active_model/api_compliant.rb
@@ -0,0 +1,25 @@
+module ActiveModel
+ module APICompliant
+ include Naming
+
+ def self.extended(klass)
+ klass.class_eval do
+ include Validations
+ include InstanceMethods
+ end
+ end
+
+ module InstanceMethods
+ def to_model
+ if respond_to?(:new_record?)
+ self.class.class_eval { def to_model() self end }
+ to_model
+ else
+ raise "In order to be ActiveModel API compliant, you need to define " \
+ "a new_record? method, which should return true if it has not " \
+ "yet been persisted."
+ end
+ end
+ end
+ end
+end
View
7 activerecord/lib/active_record/base.rb
@@ -2511,6 +2511,13 @@ def to_param
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
end
+ # Returns the ActiveRecord object when asked for its
+ # ActiveModel-compliant representation, because ActiveRecord is
+ # ActiveModel-compliant.
+ def to_model
+ self
+ end
+
# Returns a cache key that can be used to identify this record.
#
# ==== Examples

0 comments on commit 5ffaaa7

Please sign in to comment.