module Remarkable module ActiveRecord module Matchers class AcceptNestedAttributesForMatcher < Remarkable::ActiveRecord::Base #:nodoc: arguments :collection => :associations, :as => :association collection_assertions :association_exists?, :is_autosave?, :responds_to_attributes?, :allows_destroy?, :accepts?, :rejects? optionals :allow_destroy, :default => true optionals :accept, :reject, :splat => true protected def association_exists? reflection end def is_autosave? reflection.options[:autosave] == true end def responds_to_attributes? @subject.respond_to?(:"#{@association}_attributes=", true) end def allows_destroy? return true unless @options.key?(:allow_destroy) @subject.instance_eval <<-ALLOW_DESTROY def assign_nested_attributes_for_#{reflection_type}_association(association, attrs, allow) return allow end ALLOW_DESTROY actual = @subject.send(:"#{@association}_attributes=", {}) return actual == @options[:allow_destroy], :actual => actual end def accepts? return true unless @options.key?(:accept) [@options[:accept]].flatten.each do |attributes| return false, :attributes => attributes.inspect if reject_if_proc.call(attributes) end true end def rejects? return true unless @options.key?(:reject) [@options[:reject]].flatten.each do |attributes| return false, :attributes => attributes.inspect unless reject_if_proc.call(attributes) end true end private def reflection @reflection ||= subject_class.reflect_on_association(@association.to_sym) end def reflection_type case reflection.macro when :has_one, :belongs_to :one_to_one when :has_many, :has_and_belongs_to_many :collection end end def reject_if_proc subject_class.reject_new_nested_attributes_procs[@association.to_sym] end end # Ensures that the model accepts nested attributes for the given associations. # # == Options # # * allow_destroy - When true allows the association to be destroyed # * accept - attributes that should be accepted by the :reject_if proc # * reject - attributes that should be rejected by the :reject_if proc # # == Examples # # should_accept_nested_attributes_for :tasks # should_accept_nested_attributes_for :tasks, :allow_destroy => true # # :accept and :reject takes objects that are verified against the proc. So # having a model: # # class Projects < ActiveRecord::Base # has_many :tasks # accepts_nested_attributes_for :tasks, :reject_if => proc { |a| a[:title].blank? } # end # # You can have the following specs: # # should_accept_nested_attributes_for :tasks, :reject => { :title => '' } # Passes # should_accept_nested_attributes_for :tasks, :accept => { :title => 'My task' } # Passes # # should_accept_nested_attributes_for :tasks, :accept => { :title => 'My task' }, # :reject => { :title => '' } # Passes # # should_accept_nested_attributes_for :tasks, :accept => { :title => '' } # Fail # should_accept_nested_attributes_for :tasks, :reject => { :title => 'My task' } # Fail # # You can also give arrays to :accept and :reject to verify multiple attributes. # In such cases the block syntax is more recommended for readability: # # should_accept_nested_attributes_for :tasks do # m.allow_destroy(false) # m.accept :title => 'My task' # m.accept :title => 'Another task' # m.reject :title => nil # m.reject :title => '' # end # def accept_nested_attributes_for(*args, &block) AcceptNestedAttributesForMatcher.new(*args, &block).spec(self) end end end end