test/conducer_test.rb in dao-4.2.1 vs test/conducer_test.rb in dao-4.4.2
- old
+ new
@@ -1,54 +1,331 @@
+# -*- encoding : utf-8 -*-
+
+
Testing Dao::Conducer do
##
#
- testing 'that base classes can be constructed and named' do
- new_foo_conducer_class()
+ context :teh_ctor do
+ #
+ testing 'conducers have a POLS .new method' do
+ [
+ {:key => :val, :array => [0,1,2]},
+ {}
+ ].each do |attributes|
+ c = new_conducer(attributes)
+ assert{ c.attributes =~ attributes }
+ end
+ end
+
+ #
+ testing 'models passed to .new are automatically tracked' do
+ user = User.new
+ post = Post.new
+ comment = Comment.new
+ params = {}
+
+ args = [comment, post, user, params]
+
+ c = new_conducer(*args)
+
+ assert{ c.models == [comment, post, user] }
+ assert{ c.model == comment }
+ assert{ c.model == c.conduces }
+
+ assert{ c.conduces(post) }
+ assert{ c.models == [post, comment, user] }
+ assert{ c.model == post }
+ assert{ c.model == c.conduces }
+ end
end
##
#
- testing 'that basic validations/errors work' do
- c =
- assert{
- new_foo_conducer_class do
- validates_presence_of :bar
- validates_presence_of :foo, :bar
+ context :teh_default_initialize do
+ #
+ testing 'that the last mode determines the lifecycle state when a models are passed in' do
+ user = User.new
+ post = Post.new
+ comment = Comment.new
+ params = {}
+
+ args = [comment, post, user, params]
+
+ c = new_conducer(*args)
+
+ %w( new_record? persisted? destroyed? ).each do |state|
+ assert{ comment.send(state) == c.send(state) }
+ end
+
+ comment.new_record = false
+ comment.persisted = true
+ assert{ c.new_record? == false }
+ assert{ c.persisted == true }
+ end
+
+ #
+ testing 'that passed in models/params are sanely ker-sploded onto the attributes' do
+ user = User.new :k => 1
+ post = Post.new :k => 2
+ comment = Comment.new :k => 3, :x => 4
+ params = {:x => 5}
+
+ args = [comment, post, user, params]
+
+ c = new_conducer(*args)
+
+ assert{ c.attributes[:user] =~ {:k => 1} }
+ assert{ c.instance_variable_get('@user') == user }
+
+ assert{ c.attributes[:post] =~ {:k => 2} }
+ assert{ c.instance_variable_get('@post') == post }
+
+ expected = Map.new
+ expected.update :user => user.attributes
+ expected.update :post => post.attributes
+ expected.update comment.attributes
+ expected.update params
+
+ assert{ c.attributes =~ expected }
+ assert{ c.instance_variable_get('@comment') == comment }
+ end
+
+ #
+ testing 'that .new specialises based on current action' do
+ conducer_class =
+ new_conducer_class do
+ def initialize_for_new
+ attributes.update(:new => Time.now)
+ end
+ def initialize_for_create
+ attributes.update(:create => Time.now)
+ end
+ def initialize_for_edit
+ attributes.update(:edit => Time.now)
+ end
end
- }
+
+ post = Post.new
- o = assert{ c.new }
- assert{ !o.valid? }
+ %w( new create edit ).each do |action|
+ c = conducer_class.for(action, post)
+ assert{ c.action == action }
+ assert{ c.respond_to?("initialize_for_#{ action }") }
+ assert{ c.attributes[action].is_a?(Time) }
+ assert{ c.attributes.size == 1 }
+ end
+ end
- assert{ !Array(o.errors.get(:bar)).empty? }
- assert{ !Array(o.errors.get(:foo, :bar)).empty? }
+ #
+ testing 'that conducers can build a highly specialized .new method based on action' do
+ c =
+ new_conducer_class do
+ def initialize(a, b, c, params)
+ @a, @b, @c = a, b, c
- o.attributes.set :foo, :bar, 42
- assert{ !o.valid? }
+ update_attributes(
+ :foo => :bar
+ )
- assert{ Array(o.errors.get(:foo, :bar)).empty? }
+ case action
+ when 'new'
+ update_attributes(:action => :new)
+ when 'edit'
+ update_attributes(:action => :edit)
+ else
+ update_attributes(:action => nil)
+ end
+
+ update_attributes(params)
+ end
+ end
+
+ params = {:key => :val}
+
+ %w( new edit ).each do |action|
+ o = assert{ c.for(action, :a, :b, :c, params) }
+ assert{ o.action == action }
+ assert{ o.key == :val }
+ assert{ o.foo == :bar }
+ %w( a b c ).each{|var| assert{ o.instance_variable_get("@#{ var }") == var.to_sym } }
+ end
+ end
end
##
#
- testing 'that basic form elements work' do
- c =
- assert{
- new_foo_conducer_class do
- validates_presence_of :bar
+ context :teh_default_save do
+ #
+ testing 'is sane and based solely on the last model' do
+ user = User.new
+ post = Post.new
+ comment = Comment.new
+ params = {:text => 'hai!', :user => {:params => 'are ignored'}, :post => {:params => 'are ignored'}}
+
+ args = [comment, post, user, params]
+
+ c = new_conducer(*args)
+
+ assert{ comment[:text].nil? }
+ assert{ c.save }
+ assert{ comment.text == 'hai!' }
+ assert{ comment[:user].nil? }
+ assert{ comment[:post].nil? }
+ end
+
+ #
+ testing 'halts when the conducer is invalid with errors' do
+ conducer_class =
+ new_conducer_class do
+ validates_presence_of(:foo)
end
- }
- o = assert{ c.new }
- assert{ o.form }
- assert{ o.form.input(:foo) }
- assert{ o.form.input(:bar) }
+ c = conducer_class.new
+
+ assert{ !c.save }
+ assert{ !c.valid? }
+ assert{ !c.errors.empty? }
+ end
+
+ #
+ testing 'halts when the model is invalid and relays errors' do
+ post = Post.new
+ post.errors[:foo] = 'is fucked'
+ c = new_conducer(post)
+ assert{ !c.save }
+ assert{ c.errors[:foo] == Array(post.errors[:foo]) }
+ end
+
+ #
+ testing 'raises a validation error on #save!' do
+ post = Post.new
+ post.errors[:foo] = 'is fucked'
+ c = new_conducer(post)
+
+ error = assert{ begin; c.save!; rescue Object => e; e; end; }
+
+ assert{ error.errors == c.errors }
+ assert{ error.errors[:foo] = Array(post.errors[:foo]) }
+ assert{ c.errors[:foo] = Array(post.errors[:foo]) }
+ end
end
##
#
- context 'class endpoint' do
+ context :validations do
+ #
+ testing 'that simple validations/errors work' do
+ c =
+ assert{
+ new_foo_conducer_class do
+ validates_presence_of :bar
+ validates_presence_of :foo, :bar
+ end
+ }
+
+ o = assert{ c.new }
+ assert{ !o.valid? }
+
+ assert{ Array(o.errors.get(:bar)).size == 1 }
+ assert{ Array(o.errors.get(:foo, :bar)).size == 1 }
+
+ o.attributes.set :foo, :bar, 42
+ assert{ !o.valid? }
+
+ assert{ Array(o.errors.get(:bar)).size == 1 }
+ assert{ Array(o.errors.get(:foo, :bar)).size == 0 }
+
+ assert{ Array(o.errors.get(:foo, :bar)).empty? }
+ end
+
+ #
+ testing 'that validations are evaluated in the context of the object' do
+ c =
+ assert{
+ new_foo_conducer_class do
+ klass = self
+ validates(:a){ klass == self.class }
+
+ validates(:b){ value != 42.0 }
+
+ def value() 42 end
+ end
+ }
+
+ o = assert{ c.new }
+ assert{ !o.valid? }
+ assert{ o.errors.get(:a).empty? }
+ assert{ !o.errors.get(:b).empty? }
+ end
+
+ #
+ testing 'that validates_each werks at the class and instance level' do
+ conducer_class =
+ new_conducer_class do
+ validates_each :a do |item|
+ validated.push(item)
+ true
+ end
+
+ def save
+ validates_each :b do |item|
+ validated.push(item)
+ true
+ end
+ return valid?
+ end
+
+ def validated
+ @validated ||= []
+ end
+ end
+
+ a = %w( a b c )
+ b = %w( 1 2 3 )
+
+ c = assert{ conducer_class.new(:a => a, :b => b) }
+ assert{ c.run_validations }
+ assert{ c.validated == %w( a b c ) }
+
+
+ c = conducer_class.new(:a => a, :b => b)
+ assert{ c.save }
+ assert{ c.validated == %w( a b c 1 2 3 ) }
+ end
+ end
+
+##
+#
+ context :forms do
+ #
+ testing 'that basic form helpers work' do
+ c =
+ assert{
+ new_foo_conducer_class do
+ validates_presence_of :bar
+ end
+ }
+
+ o = assert{ c.new }
+ assert{ !o.valid? } # make validations run...
+ assert{ o.form }
+ assert{ o.form.input(:foo) =~ /\<input/ }
+ assert{ o.form.input(:foo) !~ /errors/ }
+ assert{ o.form.textarea(:bar) =~ /\<textarea/ }
+ assert{ o.form.textarea(:bar) =~ /errors/ }
+ end
+ end
+
+##
+#
+ context :class_methods do
+ #
+ testing 'that base classes can be constructed and named' do
+ new_foo_conducer_class()
+ end
+
+ #
testing '.new' do
c = assert{ new_foo_conducer_class }
controller = assert{ Dao.mock_controller }
check = proc do |args|
@@ -68,80 +345,35 @@
check[ [controller, params] ]
check[ [params, controller] ]
end
end
- testing '.all' do
- c = assert{ new_foo_conducer_class }
- assert{ c.all().is_a?(Array) }
- assert{ c.all(nil).is_a?(Array) }
- assert{ c.all({}).is_a?(Array) }
- end
-
- testing '.find' do
- c = assert{ new_foo_conducer_class }
- o = assert{ c.new }
- assert{ c.find(o.id).is_a?(Dao::Conducer) }
- end
-
+ #
testing '.model_name' do
c = assert{ new_foo_conducer_class }
assert{ c.model_name }
o = assert{ c.new }
assert{ o.model_name }
end
end
- context 'current' do
- testing 'class and instance endpoints' do
- c = assert{ new_foo_conducer_class }
- o = c.new
- %w(
- current_controller
- current_request
- current_response
- current_session
- current_user
- ).each do |method|
- assert{ o.respond_to?(method) }
- assert{ c.respond_to?(method) }
+##
+#
+ context :instance_methods do
+ testing '#id' do
+ [:_id, :id].each do |id_key|
+ o = assert{ new_foo_conducer() }
+ assert{ o.id.nil? }
+ o.attributes.update(id_key => 42)
+ assert{ o.id==42 }
+ assert{ o.id = nil; true }
+ assert{ !o.id }
+ assert{ o.attributes[:id].nil? }
+ assert{ o.attributes[:_id].nil? }
end
end
- end
- context 'instance endpoint' do
- testing '#save' do
- params = {:k => :v}
- o = assert{ new_foo_conducer(params) }
- assert{ o.save }
- id = assert{ o.id }
- assert{ db.foos.find(id)[:k] == o.attributes[:k] }
- assert{ id == o.id }
- assert{ o.attributes =~ params.merge(:id => id) }
- end
-
- testing '#update_attributes' do
- params = {:k => :v}
- o = assert{ new_foo_conducer(params) }
- t = Time.now
- assert{ o.update_attributes :t => t }
- assert{ o.save }
- id = assert{ o.id }
- assert{ db.foos.find(id).id == o.id }
- assert{ db.foos.find(id) =~ params.merge(:id => id, :t => t) }
- end
-
- testing '#destroy' do
- params = {:k => :v}
- o = assert{ new_foo_conducer(params) }
- assert{ o.save }
- id = assert{ o.id }
- assert{ db.foos.find(id).id == o.id }
- assert{ o.destroy }
- assert{ db.foos.find(id).nil? }
- end
-
testing '#to_param' do
o = assert{ new_foo_conducer() }
assert{ o.to_param.nil? }
o.id = 42
assert{ o.to_param }
@@ -149,28 +381,185 @@
testing '#errors' do
o = assert{ new_foo_conducer() }
assert{ o.errors.respond_to?(:[]) }
end
+
+=begin
+ testing 'that conducers can register handlers for setting deeply nested attributes' do
+ c =
+ new_conducer_class do
+ def _update_attributes(attributes = {})
+ attributes.each do |key, value|
+ case Array(key).join('.')
+ when 'user.first_name'
+ set(key, value.to_s.upcase)
+ return true
+ else
+ return false
+ end
+ end
+ end
+ end
+
+ o = assert{ c.new :user => {:first_name => 'ara', :last_name => 'howard'} }
+ assert{ o.user.first_name == 'ARA' }
+ assert{ o.user.last_name == 'howard' }
+
+ o = assert{ c.new :name => 'ara howard' }
+ assert{ o.attributes.get(:name) == 'ara howard' }
+ end
+=end
end
-
+##
+#
+ context :teh_mount do
+ #
+ testing 'that mounted objects can be declared at the class level' do
+ conducer_class =
+ new_conducer_class do
+ mount Dao::Upload, :a, :b, :placeholder => '/images/foo.jpg'
+ end
+
+ assert{ !conducer_class.mounted.empty? }
+
+ c = conducer_class.new
+
+ assert{ c.mounted.first.is_a?(Dao::Upload) }
+ assert{ c.mounted.first._key.join('.') == 'a.b' }
+ assert{ c.mounted.first._value.nil? }
+ end
+
+ #
+ testing 'that mounted objects replace their location in attributes' do
+ conducer_class =
+ new_conducer_class do
+ mount Dao::Upload, :a, :b, :placeholder => '/images/foo.jpg'
+ end
+
+ path = __FILE__
+ up = Upload.new(path)
+
+ c = conducer_class.new( :a => {:b => {:file => up}} )
+
+ upload = assert{ c.get(:a, :b) }
+
+ assert{ upload.is_a?(Dao::Upload) }
+ assert{ test(?f, upload._value) }
+ end
+
+ #
+ testing 'that the default save uses the mounted _value and _clears it' do
+ conducer_class =
+ new_conducer_class do
+ mount Dao::Upload, :up, :placeholder => '/images/foo.jpg'
+ end
+
+ path = File.join(File.dirname(__FILE__), 'data/han-solo.jpg')
+ assert{ test(?s, path) }
+ up = Upload.new(path)
+ comment = Comment.new
+
+ c = conducer_class.new( comment, :up => {:file => up} )
+
+ upload = assert{ c.get(:up) }
+ assert{ upload.is_a?(Dao::Upload) }
+
+ assert{ test(?f, upload.path) }
+ assert{ File.basename(upload.path) == File.basename(path) }
+ assert{ IO.read(upload.path) == IO.read(path) }
+
+ assert{ c.save }
+
+ value_was_relayed = assert{ comment.attributes[:up] == upload._value }
+ value_was_cleared = assert{ !test(?f, upload.path) }
+
+ assert{ test(?s, path) }
+ end
+ end
+
+##
+#
+ context :collections do
+ testing 'can be created from page-y blessed arrays' do
+ paginated = Paginated[Post.new, Post.new, Post.new]
+ paginated.limit = 42
+ paginated.offset = 42.0
+ paginated.total_count = 420
+
+ conducer_class = new_conducer_class
+
+ conducer_class.collection_for(paginated)
+ collection = assert{ conducer_class.collection_for(paginated) }
+ assert{ collection.models == paginated }
+ assert{ collection.limit == 42 }
+ assert{ collection.offset == 42.0 }
+ assert{ collection.total_count == 420 }
+
+ user = User.new
+ collection = assert{ conducer_class.collection_for(paginated){|model| conducer_class.for(:show, user, model)} }
+ assert{ collection.all?{|conducer| conducer.action == :show} }
+ assert{ collection.all?{|conducer| conducer.models.first==user} }
+ assert{ collection.all?{|conducer| conducer.models.last.is_a?(Post)} }
+ end
+ end
+
+##
+#
+ context :callbacks do
+ testing 'can be added lazily in an ad-hoc fashion' do
+ callbacks = []
+
+ conducer_class =
+ new_conducer_class do
+ before_initialize do
+ callbacks.push(:before_initialize)
+ end
+
+ after_initialize do
+ callbacks.push(:after_initialize)
+ end
+
+ define_method(:foobar){ 42 }
+
+ before :foobar do
+ callbacks.push(:before_foobar)
+ end
+
+ after :foobar do
+ callbacks.push(:after_foobar)
+ end
+ end
+
+ c = assert{ conducer_class.new }
+ assert{ callbacks == [:before_initialize, :after_initialize] }
+ assert{ c.foobar; true }
+ assert{ callbacks == [:before_initialize, :after_initialize, :before_foobar, :after_foobar] }
+ end
+ end
+
protected
def new_foo_conducer_class(&block)
- name = 'FooConducer'
- c = assert{ Class.new(Dao::Conducer){ self.name = name; crud! } }
+ const = :FooConducer
+ Object.send(:remove_const, const) if Object.send(:const_defined?, const)
+ name = const.to_s
+ c = assert{ Class.new(Dao::Conducer){ self.name = name } }
+ Object.send(:const_set, const, c)
assert{ c.name == 'FooConducer' }
assert{ c.model_name == 'Foo' }
assert{ c.table_name == 'foos' && c.collection_name == 'foos' }
- assert{ c.module_eval(&block); true } if block
+ assert{ c.class_eval(&block); true } if block
c
end
+ alias_method :new_conducer_class, :new_foo_conducer_class
def new_foo_conducer(*args, &block)
assert{ new_foo_conducer_class(&block).new(*args) }
end
+ alias_method :new_conducer, :new_foo_conducer
prepare do
$db = Dao::Db.new(:path => 'test/db.yml')
Dao::Db.instance = $db
collection = $db['foos']
@@ -191,15 +580,144 @@
end
def collection
$db[:foos]
end
+
+ class Mounted
+ def Mounted.mount(*args, &block)
+ new(*args, &block)
+ end
+
+ def _set
+ end
+
+ def _key
+ end
+
+ def _value
+ end
+
+ def _clear
+ end
+ end
+
+ class Upload < StringIO
+ attr_accessor :path
+
+ def initialize(path)
+ super(IO.read(@path = path))
+ end
+ end
+
+ class Model
+ class << self
+ def model_name
+ name = self.name.split(/::/).last
+ ActiveModel::Name.new(Map[:name, name])
+ end
+ end
+
+ {
+ :new_record => true,
+ :persisted => false,
+ :destroyed => false,
+ }.each do |state, value|
+ class_eval <<-__
+ attr_writer :#{ state }
+
+ def #{ state }
+ @#{ state } = #{ value } unless defined?(@#{ state })
+ @#{ state }
+ end
+
+ def #{ state }?
+ #{ state }
+ end
+ __
+ end
+
+ def initialize(attributes = {})
+ self.attributes.update(attributes)
+ end
+
+ def attributes
+ @attributes ||= Map.new
+ end
+
+ def [](key)
+ attributes[key]
+ end
+
+ def []=(key, val)
+ attributes[key] = val
+ end
+
+ def update_attributes(hash = {})
+ hash.each{|k,v| attributes[k] = v }
+ end
+
+ def method_missing(method, *args, &block)
+ re = /^([^=!?]+)([=!?])?$/imox
+ matched, key, suffix = re.match(method.to_s).to_a
+
+ case suffix
+ when '=' then attributes.set(key, args.first)
+ when '!' then attributes.set(key, args.size > 0 ? args.first : true)
+ when '?' then attributes.has?(key)
+ else
+ attributes.has?(key) ? attributes.get(key) : super
+ end
+ end
+
+ def inspect(*args, &block)
+ "#{ self.class.name }( #{ attributes.inspect } )"
+ end
+
+ def errors
+ @errors ||= Map.new
+ end
+
+ def valid?
+ errors.empty?
+ end
+
+ def save
+ return false unless valid?
+ self.new_record = false
+ self.persisted = true
+ return true
+ end
+
+ def destroy
+ true
+ ensure
+ self.new_record = false
+ self.destroyed = true
+ end
+ end
+
+ class Paginated < ::Array
+ attr_accessor :limit
+ attr_accessor :offset
+ attr_accessor :total_count
+ end
+
+ class User < Model
+ end
+
+ class Post < Model
+ end
+
+ class Comment < Model
+ end
end
BEGIN {
testdir = File.dirname(File.expand_path(__FILE__))
rootdir = File.dirname(testdir)
libdir = File.join(rootdir, 'lib')
require File.join(libdir, 'dao')
require File.join(testdir, 'testing')
+ require 'stringio'
}