Permalink
Browse files

Copy more useful things from Remarkable ActiveRecord.

  • Loading branch information...
1 parent c6c1d39 commit dde1ddcdec7f8134437a5e3ce4aac8786ab6e71f @nmerouze nmerouze committed Jul 31, 2009
@@ -14,7 +14,9 @@
Remarkable.add_locale File.join(dir, '..', 'locales', 'en.yml')
require File.join(dir, 'remarkable_mongomapper', 'base')
-
+require File.join(dir, 'remarkable_mongomapper', 'describe')
+# require File.join(dir, 'remarkable_mongomapper', 'human_names')
+
# Add matchers
Dir[File.join(dir, 'remarkable_mongomapper', 'matchers', '*.rb')].each do |file|
require file
@@ -0,0 +1,199 @@
+module Remarkable
+ module ActiveRecord
+
+ def self.after_include(target) #:nodoc:
+ target.class_inheritable_reader :describe_subject_attributes, :default_subject_attributes
+ target.send :include, Describe
+ end
+
+ # Overwrites describe to provide quick way to configure your subject:
+ #
+ # describe Post
+ # should_validate_presente_of :title
+ #
+ # describe :published => true do
+ # should_validate_presence_of :published_at
+ # end
+ # end
+ #
+ # This is the same as:
+ #
+ # describe Post
+ # should_validate_presente_of :title
+ #
+ # describe "when published is true" do
+ # subject { Post.new(:published => true) }
+ # should_validate_presence_of :published_at
+ # end
+ # end
+ #
+ # The string can be localized using I18n. An example yml file is:
+ #
+ # locale:
+ # remarkable:
+ # mongo_mapper:
+ # describe:
+ # each: "{{key}} is {{value}}"
+ # prepend: "when "
+ # connector: " and "
+ #
+ # You can also call subject attributes to set the default attributes for a
+ # subject. You can even mix with a fixture replacement tool:
+ #
+ # describe Post
+ # # Fixjour example
+ # subject_attributes { valid_post_attributes }
+ #
+ # describe :published => true do
+ # should_validate_presence_of :published_at
+ # end
+ # end
+ #
+ # You can retrieve the merged result of all attributes given using the
+ # subject_attributes instance method:
+ #
+ # describe Post
+ # # Fixjour example
+ # subject_attributes { valid_post_attributes }
+ #
+ # describe :published => true do
+ # it "should have default subject attributes" do
+ # subject_attributes.should == { :title => 'My title', :published => true }
+ # end
+ # end
+ # end
+ #
+ module Describe
+
+ def self.included(base) #:nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+
+ # Overwrites describe to provide quick way to configure your subject:
+ #
+ # describe Post
+ # should_validate_presente_of :title
+ #
+ # describe :published => true do
+ # should_validate_presence_of :published_at
+ # end
+ # end
+ #
+ # This is the same as:
+ #
+ # describe Post
+ # should_validate_presente_of :title
+ #
+ # describe "when published is true" do
+ # subject { Post.new(:published => true) }
+ # should_validate_presence_of :published_at
+ # end
+ # end
+ #
+ # The string can be localized using I18n. An example yml file is:
+ #
+ # locale:
+ # remarkable:
+ # mongo_mapper:
+ # describe:
+ # each: "{{key}} is {{value}}"
+ # prepend: "when "
+ # connector: " and "
+ #
+ # See also subject_attributes instance and class methods for more
+ # information.
+ #
+ def describe(*args, &block)
+ if described_class && args.first.is_a?(Hash)
+ attributes = args.shift
+
+ connector = Remarkable.t "remarkable.mongo_mapper.describe.connector", :default => " and "
+
+ description = if self.describe_subject_attributes.blank?
+ Remarkable.t("remarkable.mongo_mapper.describe.prepend", :default => "when ")
+ else
+ connector.lstrip
+ end
+
+ pieces = []
+ attributes.each do |key, value|
+ translated_key = if described_class.respond_to?(:human_attribute_name)
+ described_class.human_attribute_name(key.to_s, :locale => Remarkable.locale)
+ else
+ key.to_s.humanize
+ end
+
+ pieces << Remarkable.t("remarkable.mongo_mapper.describe.each",
+ :default => "{{key}} is {{value}}",
+ :key => translated_key.downcase, :value => value.inspect)
+ end
+
+ description << pieces.join(connector)
+ args.unshift(description)
+
+ # Creates an example group, set the subject and eval the given block.
+ #
+ example_group = super(*args) do
+ write_inheritable_hash(:describe_subject_attributes, attributes)
+ set_described_subject!
+ instance_eval(&block)
+ end
+ else
+ super(*args, &block)
+ end
+ end
+
+ # Sets default attributes for the subject. You can use this to set up
+ # your subject with valid attributes. You can even mix with a fixture
+ # replacement tool and still use quick subjects:
+ #
+ # describe Post
+ # # Fixjour example
+ # subject_attributes { valid_post_attributes }
+ #
+ # describe :published => true do
+ # should_validate_presence_of :published_at
+ # end
+ # end
+ #
+ def subject_attributes(options=nil, &block)
+ write_inheritable_attribute(:default_subject_attributes, options || block)
+ set_described_subject!
+ end
+
+ def set_described_subject!
+ subject {
+ record = self.class.described_class.new
+ record.send(:attributes=, subject_attributes, false)
+ record
+ }
+ end
+ end
+
+ # Returns a hash with the subject attributes declared using the
+ # subject_attributes class method and the attributes given using the
+ # describe method.
+ #
+ # describe Post
+ # subject_attributes { valid_post_attributes }
+ #
+ # describe :published => true do
+ # it "should have default subject attributes" do
+ # subject_attributes.should == { :title => 'My title', :published => true }
+ # end
+ # end
+ # end
+ #
+ def subject_attributes
+ default = self.class.default_subject_attributes
+ default = self.instance_eval(&default) if default.is_a?(Proc)
+ default ||= {}
+
+ default.merge(self.class.describe_subject_attributes || {})
+ end
+
+ end
+ end
+end
@@ -0,0 +1,37 @@
+if defined?(Spec)
+ module Spec #:nodoc:
+ module Example #:nodoc:
+ module ExampleGroupMethods #:nodoc:
+
+ # This allows "describe User" to use the I18n human name of User.
+ #
+ def self.build_description_with_i18n(*args)
+ args.inject("") do |description, arg|
+ arg = if arg.respond_to?(:human_name)
+ arg.human_name(:locale => Remarkable.locale)
+ else
+ arg.to_s
+ end
+
+ description << " " unless (description == "" || arg =~ /^(\s|\.|#)/)
+ description << arg
+ end
+ end
+
+ # This is for rspec <= 1.1.12.
+ #
+ def self.description_text(*args)
+ self.build_description_with_i18n(*args)
+ end
+
+ # This is for rspec >= 1.2.0.
+ #
+ def self.build_description_from(*args)
+ text = ExampleGroupMethods.build_description_with_i18n(*args)
+ text == "" ? nil : text
+ end
+
+ end
+ end
+ end
+end
@@ -1,12 +1,24 @@
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe 'allow_values_for' do
- subject do
- Site.new
+ include ModelBuilder
+
+ # Defines a model, create a validation and returns a raw matcher
+ def define_and_validate(options={})
+ @model = define_model :product do
+ include MongoMapper::Document
+
+ key :title, String
+ key :category, String
+
+ validates_format_of :title, options
+ end
+
+ allow_values_for(:title)
end
describe 'messages' do
- before(:each){ @model = subject; @matcher = allow_values_for(:title) }
+ before(:each){ @matcher = define_and_validate(:with => /X|Y|Z/) }
it 'should contain a description' do
@matcher = allow_values_for(:title, "X", "Y", "Z")
@@ -15,40 +27,44 @@
it 'should set is_valid? message' do
@matcher.in("A").matches?(subject)
- @matcher.failure_message.should == 'Expected Site to be valid when title is set to "A"'
+ @matcher.failure_message.should == 'Expected Product to be valid when title is set to "A"'
end
it 'should set allow_nil? message' do
@matcher.allow_nil.matches?(subject)
- @matcher.failure_message.should == 'Expected Site to allow nil values for title'
+ @matcher.failure_message.should == 'Expected Product to allow nil values for title'
end
it 'should set allow_blank? message' do
@matcher.allow_blank.matches?(subject)
- @matcher.failure_message.should == 'Expected Site to allow blank values for title'
+ @matcher.failure_message.should == 'Expected Product to allow blank values for title'
end
end
describe 'matchers' do
- it { should allow_values_for(:title).in('X', 'Y', 'Z') }
- it { should_not allow_values_for(:title).in('A') }
+ it { should define_and_validate(:with => /X|Y|Z/).in('X', 'Y', 'Z') }
+ it { should_not define_and_validate(:with => /X|Y|Z/).in('A') }
- # it { should define_and_validate(:with => /X|Y|Z/, :message => 'valid').in('X', 'Y', 'Z').message('valid') }
+ it { should define_and_validate(:with => /X|Y|Z/, :message => 'valid').in('X', 'Y', 'Z').message('valid') }
- # create_optional_boolean_specs(:allow_nil, self, :with => /X|Y|Z/)
- # create_optional_boolean_specs(:allow_blank, self, :with => /X|Y|Z/)
+ create_optional_boolean_specs(:allow_nil, self, :with => /X|Y|Z/)
+ create_optional_boolean_specs(:allow_blank, self, :with => /X|Y|Z/)
end
describe 'macros' do
+ before(:each){ define_and_validate(:with => /X|Y|Z/) }
+
should_allow_values_for :title, 'X'
should_not_allow_values_for :title, 'A'
end
describe 'failures' do
it "should fail if any of the values are valid on invalid cases" do
+ define_and_validate(:with => /X|Y|Z/)
+
lambda {
- should_not allow_values_for :title, 'A', 'X', 'B'
- }.should raise_error(Spec::Expectations::ExpectationNotMetError, /Did not expect Site to be valid/)
+ should_not allow_values_for(:title, 'A', 'X', 'B')
+ }.should raise_error(Spec::Expectations::ExpectationNotMetError, /Did not expect Product to be valid/)
end
end
end
View
@@ -0,0 +1,64 @@
+# This is based on Shoulda model builder for Test::Unit.
+#
+module ModelBuilder
+ def self.included(base)
+ return unless base.name =~ /^Spec/
+
+ base.class_eval do
+ after(:each) do
+ if @defined_constants
+ @defined_constants.each do |class_name|
+ Object.send(:remove_const, class_name)
+ end
+ end
+ end
+ end
+
+ base.extend ClassMethods
+ end
+
+ def define_constant(class_name, &block)
+ class_name = class_name.to_s.camelize
+
+ klass = Class.new
+ Object.const_set(class_name, klass)
+
+ klass.class_eval(&block) if block_given?
+
+ @defined_constants ||= []
+ @defined_constants << class_name
+
+ klass
+ end
+
+ def define_model(name, columns = {}, &block)
+ instance = define_constant(name.to_s.classify, &block).new
+
+ self.class.subject { instance } if self.class.respond_to?(:subject)
+ instance
+ end
+
+ module ClassMethods
+ # This is a macro to run validations of boolean optionals such as :allow_nil
+ # and :allow_blank. This macro tests all scenarios. The specs must have a
+ # define_and_validate method defined.
+ #
+ def create_optional_boolean_specs(optional, base, options={})
+ base.describe "with #{optional} option" do
+ it { should define_and_validate(options.merge(optional => true)).send(optional) }
+ it { should define_and_validate(options.merge(optional => false)).send(optional, false) }
+ it { should_not define_and_validate(options.merge(optional => true)).send(optional, false) }
+ it { should_not define_and_validate(options.merge(optional => false)).send(optional) }
+ end
+ end
+
+ def create_message_specs(base)
+ base.describe "with message option" do
+ it { should define_and_validate(:message => 'valid_message').message('valid_message') }
+ it { should_not define_and_validate(:message => 'not_valid').message('valid_message') }
+ end
+ end
+ end
+
+end
+
Oops, something went wrong.

0 comments on commit dde1ddc

Please sign in to comment.