# -*- encoding : utf-8 -*- require_relative 'test_helper' class Dao::ConducerTest < Dao::TestCase ## # context :teh_ctor do # test '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 # test '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 } end # test 'that the conduced model can be declared at the class level' do user = User.new post = Post.new comment = Comment.new params = {} args = [comment, post, user, params] c = new_conducer(*args){ conduces User } assert{ c.models == [comment, post, user] } assert{ c.model == user } assert{ c.model == c.conduces } end # test 'that the conduced model can be declared at the instance level' do user = User.new post = Post.new comment = Comment.new params = {} args = [comment, post, user, params] c = new_conducer(*args) c.conduces(user) assert{ c.models == [user, comment, post] } assert{ c.model == user } assert{ c.model == c.conduces } end end ## # context :teh_default_initialize do # test '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 # test '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.add :user => user.attributes expected.add :post => post.attributes expected.add comment.attributes expected.add params assert{ c.attributes =~ expected } assert{ c.instance_variable_get('@comment') == comment } end # test '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 %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 # test '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 update_attributes( :foo => :bar ) 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 # test 'that conducers *fold* in attributes' do c = new_conducer assert{ c.update_attributes :key => {:a => :b} } assert{ c.update_attributes :key => {:nested => {:a => :b}} } assert{ c.attributes =~ {:key => {:a => :b, :nested => {:a => :b}}} } end ## # context :teh_default_save do # test '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 # test 'halts when the conducer is invalid with errors' do conducer_class = new_conducer_class do validates_presence_of(:foo) end c = conducer_class.new assert{ !c.save } assert{ !c.valid? } assert{ !c.errors.empty? } end # test '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 # test '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 :validations do # test '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 # test '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 # test '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 # test '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) =~ /\ :v}, nil, ].each do |params| check[ [params] ] check[ [controller, params] ] check[ [params, controller] ] end end # test '.model_name' do c = assert{ new_foo_conducer_class } assert{ c.model_name } o = assert{ c.new } assert{ o.model_name } end end ## # context :instance_methods do test '#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 test '#to_param' do o = assert{ new_foo_conducer() } assert{ o.to_param.nil? } o.id = 42 assert{ o.to_param } end test '#errors' do o = assert{ new_foo_conducer() } assert{ o.errors.respond_to?(:[]) } end =begin test '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 # test '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 # test '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 # test 'that the default save uses the mounted _value and _clears it' do begin $pry=true 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 => Upload.new(path)} ) 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) } ensure $pry=false end end end ## # context :collections do test '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 test '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) 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.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 def setup $db = Dao::Db.new(:path => 'test/db.yml') Dao::Db.instance = $db collection = $db['foos'] %w( a b c ).each do |name| collection.save( :name => name, :created_at => Time.now.to_f, :a => %w( x y z ), :h => {:k => :v} ) end end def teardown $db = Dao::Db.new(:path => 'test/db.yml') $db.rm_f end def db $db 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 def dup self.class.new(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.strip })" 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