module Remarkable module DSL module Matches # For each instance under the collection declared in arguments, # this method will call each method declared in assertions. # # As an example, let's assume you have the following matcher: # # arguments :collection => :attributes # assertions :allow_nil?, :allow_blank? # # For each attribute in @attributes, we will set the instance variable # @attribute and then call allow_nil? and allow_blank?. Assertions should # return true if it pass or false if it fails. When it fails, it will use # I18n API to find the proper failure message: # # expectations: # allow_nil?: allowed the value to be nil # allow_blank?: allowed the value to be blank # # Or you can set the message in the instance variable @expectation in the # assertion method if you don't want to rely on I18n API. # # This method also call the methods declared in single_assertions. Which # work the same way as assertions, except it doesn't loop for each value in # the collection. # # It also provides a before_assert callback that you might want to use it # to manipulate the subject before the assertions start. # def matches?(subject) @subject = subject run_before_assert_callbacks send_methods_and_generate_message(self.class.matcher_single_assertions) && assert_matcher_for(instance_variable_get("@#{self.class.matcher_arguments[:collection]}") || []) do |value| instance_variable_set("@#{self.class.matcher_arguments[:as]}", value) send_methods_and_generate_message(self.class.matcher_collection_assertions) end end protected # Overwrite to provide default options. # def default_options {} end # Overwrites default_i18n_options to provide collection interpolation, # arguments and optionals to interpolation options. # # Their are appended in the reverse order above. So if you have an optional # with the same name as an argument, the argument overwrites the optional. # # All values are provided calling inspect, so what you will have in your # I18n available for interpolation is @options[:allow_nil].inspect. # # If you still need to provide more other interpolation options, you can # do that in two ways: # # 1. Overwrite interpolation_options: # # def interpolation_options # { :real_value => real_value } # end # # 2. Return a hash from your assertion method: # # def my_assertion # return true if real_value == expected_value # return false, :real_value => real_value # end # # In both cases, :real_value will be available as interpolation option. # def default_i18n_options i18n_options = {} @options.each do |key, value| i18n_options[key] = value.inspect end if @options # Also add arguments as interpolation options. self.class.matcher_arguments[:names].each do |name| i18n_options[name] = instance_variable_get("@#{name}").inspect end # Add collection interpolation options. i18n_options.update(collection_interpolation) # Add default options (highest priority). They should not be overwritten. i18n_options.update(super) end # Methods that return collection_name and object_name as a Hash for # interpolation. # def collection_interpolation options = {} # Add collection to options if collection_name = self.class.matcher_arguments[:collection] collection_name = collection_name.to_sym collection = instance_variable_get("@#{collection_name}") options[collection_name] = array_to_sentence(collection) if collection object_name = self.class.matcher_arguments[:as].to_sym object = instance_variable_get("@#{object_name}") options[object_name] = object if object end options end # Helper that send the methods given and create a expectation message if # any returns false. # # Since most assertion methods ends with an question mark and it's not # readable in yml files, we remove question and exclation marks at the # end of the method name before translating it. So if you have a method # called is_valid? on I18n yml file we will check for a key :is_valid. # def send_methods_and_generate_message(methods) methods.each do |method| bool, hash = send(method) unless bool @expectation ||= Remarkable.t "expectations.#{method.to_s.gsub(/(\?|\!)$/, '')}", default_i18n_options.merge(hash || {}) return false end end return true end end end end