Skip to content
Browse files

Worked a little bit on Remarkable core documentation.

  • Loading branch information...
1 parent 6cc527d commit f42b25637b142d2ceaee039f4c5469ea80c3db5a @josevalim josevalim committed
View
42 README
@@ -1,2 +1,40 @@
-Remarkable
-==========
+= Remarkable 3.0
+
+Remarkable is a framework for rspec matchers that supports macros and I18n. It's
+constituted of three pieces:
+
+ + Remarkable: the framework with helpers, DSL, I18n and rspec features;
+
+ + Remarkable ActiveRecord: a collection of matchers for ActiveRecord. It
+ supports all ActiveRecord validations, associations and some extra matchers.
+
+ + Remarkable Rails: a collection of matchers for ActionController. It also
+ includes MacroStubs, which is a clean DSL for stubbing your controller methods.
+
+In each folder above, you can find a README more detailed description of each piece.
+
+== Install & Upgrade
+
+Install the gem:
+
+ sudo gem install remarkable_rails
+
+This will install remarkable, remarkable_activerecord and remarkable_rails gems.
+
+If you are developing matchers, for example hpricot matchers, you should install
+only the remarkable "core" gem:
+
+ sudo gem install remarkable
+
+Users who are upgrading to Remarkable 3.0, should not find any problem if their
+tests are running without deprecation warnings.
+
+== More information
+
+Google group: http://groups.google.com/group/remarkable-core
+Bug tracking: http://carlosbrando.lighthouseapp.com/projects/19775-remarkable/overview
+
+== LICENSE
+
+All projects are under MIT LICENSE.
+
View
201 remarkable/README
@@ -1,2 +1,199 @@
-Remarkable
-==========
+= Remarkable
+
+This is the core package of Remarkable. It provides a DSL for creating matchers
+with I18n support, decoupling messages from matcher's logic and adding rspec
+extra features.
+
+== Macros
+
+Each matcher in Remarkable is also available as a macro. So this matcher:
+
+ it { should validate_presence_of(:name) }
+
+Can also be written as:
+
+ should_validate_presence_of :name
+
+Remarkable adds the possibility to disable macros. So as you could do:
+
+ xit { should validate_presence_of(:name) }
+
+You can also do:
+
+ xshould_validate_presence_of :name
+
+And it will show in your specs output:
+
+ "Example disabled: require name to be set"
+
+== Pending macros
+
+In Rspec you can mark some examples as pending:
+
+ it "should have one manager" do
+ pending("create managers resource")
+ end
+
+ it "should validate associated manager" do
+ pending("create managers resource")
+ end
+
+To allow this to work with macros, we created the pending group:
+
+ pending "create managers resource" do
+ should_have_one :manager
+ should_validate_associated :manager
+ end
+
+This outputs the same as above.
+
+== I18n
+
+All matchers come with I18n support. You can find an example locale file under
+the locale folder of each project.
+
+To change the locale, you have first to add your locale file:
+
+ Remarkable.add_locale 'path/to/my_locale.yml'
+
+And then:
+
+ Remarkable.locale = :my_locale
+
+Internationalization is powered by the I18n gem. If you are using it with Rails,
+it will use the built in gem, otherwise you will have to install the gem by hand:
+
+ gem sources -a http://gems.github.com
+ sudo gem install svenfuchs-i18n
+
+== Creating you own matcher
+
+Create a new matcher is easy. Let's create validate_inclusion_of matcher for
+ActiveRecord as an example. A first matcher version would be:
+
+module Remarkable
+ module ActiveRecord
+ module Matchers
+ class ValidateInclusionOfMatcher < Remarkable::ActiveRecord::Base
+ arguments :attribute
+ assertion :is_valid?
+
+ optional :in
+ optional :allow_blank, :allow_nil, :default => true
+
+ protected
+
+ def is_valid?
+ @options[:in].each do |value|
+ @subject.send(:"#{@attribute}=", value)
+ return false, :value => value unless @subject.valid?
+ end
+ true
+ end
+ end
+
+ def validate_inclusion_of(*args)
+ ValidateInclusionOfMatcher.new(*args).spec(self)
+ end
+ end
+ end
+end
+
+This creates a matcher which requires one attribute and has :in, :allow_blank
+and :allow_nil as options. So you can call the matcher in the following way:
+
+ should_validate_inclusion_of :size, :in => %w(S M L XL)
+ should_validate_inclusion_of :size, :in => %w(S M L XL), :allow_blank => true
+
+ it { should validate_inclusion_of(:size, :in => %w(S M L XL)).allow_nil(true) }
+ it { should validate_inclusion_of(:size, :in => %w(S M L XL)).allow_nil }
+
+ it { should validate_inclusion_of(:size, :in => %w(S M L XL)) }
+ it { should validate_inclusion_of(:size, :in => %w(S M L XL), :allow_nil => true) }
+
+The assertions methods (in this case, :is_valid?) makes the matcher pass when
+it returns true and fail when returns false.
+
+As you noticed, the matcher doesn't have any message on it. You add them on I18n
+file. A file for this example would be:
+
+ remarkable:
+ active_record:
+ validate_inclusion_of:
+ description: "validate inclusion of {{attribute}}"
+ expectations:
+ is_valid: "to be valid when {{attribute}} is {{value}}"
+ optionals:
+ in:
+ positive: "in {{inspect}}"
+ allow_nil:
+ positive: "allowing nil values"
+ negative: "not allowing nil values"
+ allow_blank:
+ positive: "allowing blank values"
+ negative: "allowing blank values"
+
+The optionals are just added to the description message when they are supplied.
+Look some description messages examples:
+
+ should_validate_inclusion_of :size, :in => %w(S M L XL)
+ #=> should validate inclusion of size in ["S", "M", "L", "XL"]
+
+ should_validate_inclusion_of :size, :in => %w(S M L XL), :allow_nil => true
+ #=> should validate inclusion of size in ["S", "M", "L", "XL"] and allowing nil values
+
+ should_validate_inclusion_of :size, :in => %w(S M L XL), :allow_nil => false
+ #=> should validate inclusion of size in ["S", "M", "L", "XL"] and not allowing nil values
+
+Please notice that the arguments are available as interpolation option, as well
+as the optionals.
+
+The expectations message are set whenever one of the assertions returns false.
+In this case, whenever the assertion fails, we are also returning a hash, with
+the value that failed:
+
+ return false, :value => value
+
+This will tell remarkable to make value as interpolation option too.
+
+Whenever you create all your matchers, you tell remarkable to add them to the
+desired rspec example group:
+
+ Remarkable.include_matchers!(Remarkable::ActiveRecord, Spec::Example::ExampleGroup)
+
+== Working with collections
+
+Finally, Remarkable also makes easy to deal with collections. The same matcher
+could be easily extended to accept a collection of attributes instead of just one:
+
+ should_validate_inclusion_of :first_size, :second_size, :in => %w(S M L XL)
+
+For this we have just those two lines:
+
+ arguments :attribute
+ assertion :is_valid?
+
+For:
+
+ arguments :collection => :attributes, :as => :attribute
+ collection_assertion :is_valid?
+
+This means that the collection will be kept in the @attributes instance variable
+and for each value in the collection, it will run the :is_valid? assertion.
+
+Whenever running the assertion, it will also set the @attribute (in singular)
+variable. In your I18n files, you just need to change your description:
+
+ validate_inclusion_of:
+ description: "validate inclusion of {{attributes}}"
+
+And this will output:
+
+ should_validate_inclusion_of :first_size, :second_size, :in => %w(S M L XL)
+ #=> should validate inclusion of first size and second size in ["S", "M", "L", "XL"]
+
+== More
+
+This is just an overview of the API. You can add extra options to interpolation
+by overwriting the interpolation_options methods, you can add callbacks after
+initialize your matcher or before asserting and much more!
View
2 remarkable/lib/remarkable.rb
@@ -11,10 +11,8 @@
require File.join(dir, 'remarkable', 'pending')
require File.join(dir, 'remarkable', 'core_ext', 'array')
-# Loads rspec files only if spec is defined
if defined?(Spec)
require File.join(dir, 'remarkable', 'rspec')
end
-# Add Remarkable default locale file
Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
View
2 remarkable/lib/remarkable/base.rb
@@ -10,7 +10,7 @@ def spec(binding)
private
- # Returns the subject class if it's not one.
+ # Returns the subject class unless it's a class object.
def subject_class
nil unless @subject
@subject.is_a?(Class) ? @subject : @subject.class
View
3 remarkable/lib/remarkable/dsl.rb
@@ -11,13 +11,12 @@ module DSL
] unless self.const_defined?(:ATTR_READERS)
def self.extended(base)
- # Load modules
base.extend Assertions
base.send :include, Callbacks
base.send :include, Matches
base.send :include, Optionals
- # Set the default value for matcher_arguments
+ # Initialize matcher_arguments hash with names as an empty array
base.instance_variable_set('@matcher_arguments', { :names => [] })
end
View
7 remarkable/lib/remarkable/dsl/assertions.rb
@@ -24,7 +24,7 @@ module Assertions
# validate_presence_of is a matcher declared as:
#
# class ValidatePresenceOfMatcher < Remarkable::Base
- # arguments :collection => :attributes
+ # arguments :collection => :attributes, :as => :attribute
# end
#
# In this case, Remarkable provides an API that enables you to easily
@@ -107,7 +107,7 @@ def initialize(#{args.join(',')})
# For example, validate_presence_of can be written as:
#
# class ValidatePresenceOfMatcher < Remarkable::Base
- # arguments :collection => :attributes
+ # arguments :collection => :attributes, :as => :attribute
# collection_assertions :allow_nil?
#
# protected
@@ -168,8 +168,7 @@ def assertions(*methods, &block)
end
alias :assertion :assertions
- # Class method that accepts a block or a Hash that will overwrite
- # instance method default_options.
+ # Class method that accepts a block or a hash to set matcher's default options.
#
def default_options(hash = {}, &block)
if block_given?
View
4 remarkable/lib/remarkable/dsl/callbacks.rb
@@ -8,7 +8,7 @@ def self.included(base)
module ClassMethods
protected
- # Class method that accepts a block which is called after initialization.
+ # Class method that accepts a block or a symbol which is called after initialization.
#
def after_initialize(symbol=nil, &block)
if block_given?
@@ -18,7 +18,7 @@ def after_initialize(symbol=nil, &block)
end
end
- # Class method that accepts a block which is called before assertion.
+ # Class method that accepts a block or a symbol which is called before assertion.
#
def before_assert(symbol=nil, &block)
if block_given?
View
20 remarkable/lib/remarkable/dsl/matches.rb
@@ -3,7 +3,7 @@ module DSL
module Matches
# For each instance under the collection declared in <tt>arguments</tt>,
- # this method will call each method declared in <tt>assertions</tt>.
+ # this method will call each method declared in <tt>collection_assertions</tt>.
#
# As an example, let's assume you have the following matcher:
#
@@ -49,14 +49,8 @@ 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.
+ # Overwrites default_i18n_options to provide arguments and optionals
+ # to interpolation options.
#
# If you still need to provide more other interpolation options, you can
# do that in two ways:
@@ -95,13 +89,11 @@ def default_i18n_options
i18n_options.update(super)
end
- # Methods that return collection_name and object_name as a Hash for
- # interpolation.
+ # Method responsible to add collection as 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}")
@@ -115,8 +107,8 @@ def collection_interpolation
options
end
- # Helper that send the methods given and create a expectation message if
- # any returns false.
+ # Send the assertion methods given and create a expectation message
+ # if any of those methods 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
View
1 remarkable/lib/remarkable/matchers.rb
@@ -6,7 +6,6 @@ module Matchers; end
# Helper that includes required Remarkable modules into the given klass.
def self.include_matchers!(base, klass)
- # Add Remarkable macros core module
klass.send :extend, Remarkable::Macros
if defined?(base::Matchers)

0 comments on commit f42b256

Please sign in to comment.
Something went wrong with that request. Please try again.