mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
initial experimental commit of active_model
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8118 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
fcfcc707d4
commit
5dc3f91832
10 changed files with 304 additions and 0 deletions
12
activemodel/CHANGES
Normal file
12
activemodel/CHANGES
Normal file
|
@ -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
|
21
activemodel/README
Normal file
21
activemodel/README
Normal file
|
@ -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.
|
4
activemodel/Rakefile
Normal file
4
activemodel/Rakefile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
$LOAD_PATH << File.join(File.dirname(__FILE__), 'vendor', 'rspec', 'lib')
|
||||||
|
require 'rake'
|
||||||
|
require 'spec/rake/spectask'
|
17
activemodel/lib/active_model.rb
Normal file
17
activemodel/lib/active_model.rb
Normal file
|
@ -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
|
4
activemodel/lib/active_model/base.rb
Normal file
4
activemodel/lib/active_model/base.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module ActiveModel
|
||||||
|
class Base
|
||||||
|
end
|
||||||
|
end
|
5
activemodel/lib/active_model/callbacks.rb
Normal file
5
activemodel/lib/active_model/callbacks.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module ActiveModel
|
||||||
|
module Callbacks
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
100
activemodel/lib/active_model/observing.rb
Normal file
100
activemodel/lib/active_model/observing.rb
Normal file
|
@ -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
|
4
activemodel/lib/active_model/validations.rb
Normal file
4
activemodel/lib/active_model/validations.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module ActiveModel
|
||||||
|
module Validations
|
||||||
|
end
|
||||||
|
end
|
120
activemodel/spec/observing_spec.rb
Normal file
120
activemodel/spec/observing_spec.rb
Normal file
|
@ -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
|
17
activemodel/spec/spec_helper.rb
Normal file
17
activemodel/spec/spec_helper.rb
Normal file
|
@ -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
|
Loading…
Reference in a new issue