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' }