Permalink
Browse files

initial experimental commit of active_model

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8118 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent fcfcc70 commit 5dc3f91832fd8580287e5cbeba478bb8b9580dc1 @technoweenie technoweenie committed Nov 9, 2007
View
@@ -0,0 +1,12 @@
+Changes from extracting bits to ActiveModel
+
+* ActiveModel::Observer#add_observer!
+
+ It has a custom hook to define after_find that should really be in a
+ ActiveRecord::Observer subclass:
+
+ def add_observer!(klass)
+ klass.add_observer(self)
+ klass.class_eval 'def after_find() end' unless
+ klass.respond_to?(:after_find)
+ end
View
@@ -0,0 +1,21 @@
+Active Model
+==============
+
+Totally experimental library that aims to extract common model mixins from
+ActiveRecord for use in ActiveResource (and other similar libraries).
+This is in a very rough state (no autotest or spec rake tasks set up yet),
+so please excuse the mess.
+
+Here's what I plan to extract:
+ * ActiveModel::Observing
+ * ActiveModel::Callbacks
+ * ActiveModel::Validations
+
+ # for ActiveResource params and ActiveRecord options
+ * ActiveModel::Scoping
+
+ # to_json, to_xml, etc
+ * ActiveModel::Serialization
+
+I'm trying to keep ActiveRecord compatibility where possible, but I'm
+annotating the spots where I'm diverging a bit.
View
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+$LOAD_PATH << File.join(File.dirname(__FILE__), 'vendor', 'rspec', 'lib')
+require 'rake'
+require 'spec/rake/spectask'
@@ -0,0 +1,17 @@
+$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', 'activesupport', 'lib')
+
+# premature optimization?
+require 'active_support/inflector'
+require 'active_support/core_ext/string/inflections'
+String.send :include, ActiveSupport::CoreExtensions::String::Inflections
+
+require 'active_model/base'
+require 'active_model/observing'
+require 'active_model/callbacks'
+require 'active_model/validations'
+
+ActiveModel::Base.class_eval do
+ include ActiveModel::Observing
+ include ActiveModel::Callbacks
+ include ActiveModel::Validations
+end
@@ -0,0 +1,4 @@
+module ActiveModel
+ class Base
+ end
+end
@@ -0,0 +1,5 @@
+module ActiveModel
+ module Callbacks
+
+ end
+end
@@ -0,0 +1,100 @@
+require 'observer'
+
+module ActiveModel
+ module Observing
+ module ClassMethods
+ def observers
+ @observers ||= []
+ end
+
+ def observers=(*values)
+ @observers = values.flatten
+ end
+
+ def instantiate_observers
+ observers.each { |o| instantiate_observer(o) }
+ end
+
+ protected
+ def instantiate_observer(observer)
+ # string/symbol
+ if observer.respond_to?(:to_sym)
+ observer = observer.to_s.camelize.constantize.instance
+ elsif observer.respond_to?(:instance)
+ observer.instance
+ else
+ raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
+ end
+ end
+
+ # Notify observers when the observed class is subclassed.
+ def inherited(subclass)
+ super
+ changed
+ notify_observers :observed_class_inherited, subclass
+ end
+ end
+
+ def self.included(receiver)
+ receiver.extend Observable, ClassMethods
+ end
+ end
+
+ class Observer
+ include Singleton
+ attr_writer :observed_classes
+
+ class << self
+ attr_accessor :models
+ # Attaches the observer to the supplied model classes.
+ def observe(*models)
+ @models = models.flatten
+ @models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
+ end
+
+ def observed_class_name
+ @observed_class_name ||=
+ if guessed_name = name.scan(/(.*)Observer/)[0]
+ @observed_class_name = guessed_name[0]
+ end
+ end
+
+ # The class observed by default is inferred from the observer's class name:
+ # assert_equal [Person], PersonObserver.observed_class
+ def observed_class
+ if observed_class_name
+ observed_class_name.constantize
+ else
+ nil
+ end
+ end
+ end
+
+ # Start observing the declared classes and their subclasses.
+ def initialize
+ self.observed_classes = self.class.models if self.class.models
+ observed_classes.each { |klass| add_observer! klass }
+ end
+
+ # Send observed_method(object) if the method exists.
+ def update(observed_method, object) #:nodoc:
+ send(observed_method, object) if respond_to?(observed_method)
+ end
+
+ # Special method sent by the observed class when it is inherited.
+ # Passes the new subclass.
+ def observed_class_inherited(subclass) #:nodoc:
+ self.class.observe(observed_classes + [subclass])
+ add_observer!(subclass)
+ end
+
+ protected
+ def observed_classes
+ @observed_classes ||= [self.class.observed_class]
+ end
+
+ def add_observer!(klass)
+ klass.add_observer(self)
+ end
+ end
+end
@@ -0,0 +1,4 @@
+module ActiveModel
+ module Validations
+ end
+end
@@ -0,0 +1,120 @@
+require File.join(File.dirname(__FILE__), 'spec_helper')
+
+class ObservedModel < ActiveModel::Base
+ class Observer
+ end
+end
+
+class FooObserver < ActiveModel::Observer
+ class << self
+ public :new
+ end
+
+ attr_accessor :stub
+
+ def on_spec(record)
+ stub.event_with(record) if stub
+ end
+end
+
+class Foo < ActiveModel::Base
+end
+
+module ActiveModel
+ describe Observing do
+ before do
+ ObservedModel.observers.clear
+ end
+
+ it "initializes model with no cached observers" do
+ ObservedModel.observers.should be_empty
+ end
+
+ it "stores cached observers in an array" do
+ ObservedModel.observers << :foo
+ ObservedModel.observers.should include(:foo)
+ end
+
+ it "flattens array of assigned cached observers" do
+ ObservedModel.observers = [[:foo], :bar]
+ ObservedModel.observers.should include(:foo)
+ ObservedModel.observers.should include(:bar)
+ end
+
+ it "instantiates observer names passed as strings" do
+ ObservedModel.observers << 'foo_observer'
+ FooObserver.should_receive(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ it "instantiates observer names passed as symbols" do
+ ObservedModel.observers << :foo_observer
+ FooObserver.should_receive(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ it "instantiates observer classes" do
+ ObservedModel.observers << ObservedModel::Observer
+ ObservedModel::Observer.should_receive(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ it "should pass observers to subclasses" do
+ FooObserver.instance
+ bar = Class.new(Foo)
+ bar.count_observers.should == 1
+ end
+ end
+
+ describe Observer do
+ before do
+ ObservedModel.observers = :foo_observer
+ FooObserver.models = nil
+ end
+
+ it "guesses implicit observable model name" do
+ FooObserver.observed_class_name.should == 'Foo'
+ end
+
+ it "tracks implicit observable models" do
+ instance = FooObserver.new
+ instance.send(:observed_classes).should include(Foo)
+ instance.send(:observed_classes).should_not include(ObservedModel)
+ end
+
+ it "tracks explicit observed model class" do
+ FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
+ FooObserver.observe ObservedModel
+ instance = FooObserver.new
+ instance.send(:observed_classes).should include(ObservedModel)
+ end
+
+ it "tracks explicit observed model as string" do
+ FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
+ FooObserver.observe 'observed_model'
+ instance = FooObserver.new
+ instance.send(:observed_classes).should include(ObservedModel)
+ end
+
+ it "tracks explicit observed model as symbol" do
+ FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
+ FooObserver.observe :observed_model
+ instance = FooObserver.new
+ instance.send(:observed_classes).should include(ObservedModel)
+ end
+
+ it "calls existing observer event" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub!(:stub)
+ FooObserver.instance.stub.should_receive(:event_with).with(foo)
+ Foo.send(:changed)
+ Foo.send(:notify_observers, :on_spec, foo)
+ end
+
+ it "skips nonexistent observer event" do
+ foo = Foo.new
+ Foo.send(:changed)
+ Foo.send(:notify_observers, :whatever, foo)
+ end
+ end
+end
@@ -0,0 +1,17 @@
+ENV['LOG_NAME'] = 'spec'
+$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'vendor', 'rspec', 'lib')
+$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
+require 'active_model'
+begin
+ require 'spec'
+rescue LoadError
+ require 'rubygems'
+ require 'spec'
+end
+
+begin
+ require 'ruby-debug'
+ Debugger.start
+rescue LoadError
+ # you do not know the ways of ruby-debug yet, what a shame
+end

0 comments on commit 5dc3f91

Please sign in to comment.