forked from carlosbrando/remarkable
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge commit 'bgentry/master' into dm
- Loading branch information
Showing
15 changed files
with
1,590 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# encoding: utf-8 | ||
PROJECT_SUMMARY = "Remarkable DataMapper: collection of matchers and macros with I18n for DataMapper" | ||
PROJECT_DESCRIPTION = PROJECT_SUMMARY | ||
|
||
GEM_NAME = "remarkable_datamapper" | ||
GEM_AUTHOR = [ "Carlos Brando", "José Valim", "Diego Carrion", "Blake Gentry" ] | ||
GEM_EMAIL = [ "eduardobrando@gmail.com", "jose.valim@gmail.com", "dc.rec1@gmail.com", "blakesgentry@gmail.com" ] | ||
|
||
EXTRA_RDOC_FILES = ["README", "LICENSE", "CHANGELOG"] | ||
|
||
require File.join(File.dirname(__FILE__), "..", "rake_helpers.rb") | ||
|
||
########### Package && release | ||
|
||
configure_gemspec! do |s| | ||
s.add_dependency('remarkable', "~> #{GEM_VERSION}") | ||
end | ||
|
||
########### Specs | ||
|
||
RAILS_VERSIONS = ['2.1.2', '2.2.2', '2.3.2', '2.3.3'] | ||
|
||
desc "Run the specs under spec with supported Rails versions" | ||
task :pre_commit do | ||
RAILS_VERSIONS.each do |version| | ||
ENV['RAILS_VERSION'] = version | ||
puts "\n=> #{GEM_NAME}: rake spec RAILS_VERSION=#{version}" | ||
Rake::Task[:spec].execute | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Load Remarkable | ||
unless Object.const_defined?('Remarkable') | ||
begin | ||
require 'remarkable' | ||
rescue LoadError | ||
require 'rubygems' | ||
gem 'remarkable' | ||
require 'remarkable' | ||
end | ||
end | ||
|
||
# Load Remarkable DataMapper files | ||
dir = File.dirname(__FILE__) | ||
require File.join(dir, 'remarkable_datamapper', 'base') | ||
require File.join(dir, 'remarkable_datamapper', 'describe') | ||
require File.join(dir, 'remarkable_datamapper', 'human_names') | ||
|
||
# Add locale | ||
Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml') | ||
|
||
# Add matchers | ||
Dir[File.join(dir, 'remarkable_datamapper', 'matchers', '*.rb')].each do |file| | ||
require file | ||
end | ||
|
||
# By default, ActiveRecord matchers are not included in any example group. | ||
# The responsable for this is RemarkableRails. If you are using ActiveRecord | ||
# without Rails, put the line below in your spec_helper to include ActiveRecord | ||
# matchers into rspec globally. | ||
# Remarkable.include_matchers!(Remarkable::ActiveRecord, Spec::Example::ExampleGroup) |
245 changes: 245 additions & 0 deletions
245
remarkable_datamapper/lib/remarkable_datamapper/base.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
module Remarkable | ||
module DataMapper | ||
class Base < Remarkable::Base | ||
I18N_COLLECTION = [ :attributes, :associations ] | ||
|
||
# Provides a way to send options to all DataMapper matchers. | ||
# | ||
# validates_presence_of(:name).with_options(:nullable => false) | ||
# | ||
# Is equivalent to: | ||
# | ||
# validates_presence_of(:name, :nullable => false) | ||
# | ||
def with_options(opts={}) | ||
@options.merge!(opts) | ||
self | ||
end | ||
|
||
protected | ||
|
||
# Overwrite subject_name to provide I18n. | ||
# | ||
def subject_name | ||
nil unless @subject | ||
if subject_class.respond_to?(:human_name) | ||
subject_class.human_name(:locale => Remarkable.locale) | ||
else | ||
subject_class.name | ||
end | ||
end | ||
|
||
# Checks for the given key in @options, if it exists and it's true, | ||
# tests that the value is bad, otherwise tests that the value is good. | ||
# | ||
# It accepts the key to check for, the value that is used for testing | ||
# and an @options key where the message to search for is. | ||
# | ||
def assert_bad_or_good_if_key(key, value, message_key=:message) #:nodoc: | ||
return positive? unless @options.key?(key) | ||
|
||
if @options[key] | ||
return bad?(value, message_key), :not => not_word | ||
else | ||
return good?(value, message_key), :not => '' | ||
end | ||
end | ||
|
||
# Checks for the given key in @options, if it exists and it's true, | ||
# tests that the value is good, otherwise tests that the value is bad. | ||
# | ||
# It accepts the key to check for, the value that is used for testing | ||
# and an @options key where the message to search for is. | ||
# | ||
def assert_good_or_bad_if_key(key, value, message_key=:message) #:nodoc: | ||
return positive? unless @options.key?(key) | ||
|
||
if @options[key] | ||
return good?(value, message_key), :not => '' | ||
else | ||
return bad?(value, message_key), :not => not_word | ||
end | ||
end | ||
|
||
# Default nullable? validation. It accepts the message_key which is | ||
# the key which contain the message in @options. | ||
# | ||
# It also gets an nullable message on remarkable.data_mapper.nullable | ||
# to be used as default. | ||
# | ||
def nullable?(message_key=:message) #:nodoc: | ||
assert_good_or_bad_if_key(:nullable, nil, message_key) | ||
end | ||
|
||
# Default allow_blank? validation. It accepts the message_key which is | ||
# the key which contain the message in @options. | ||
# | ||
# It also gets an allow_blank message on remarkable.data_mapper.allow_blank | ||
# to be used as default. | ||
# | ||
def allow_blank?(message_key=:message) #:nodoc: | ||
assert_good_or_bad_if_key(:allow_blank, '', message_key) | ||
end | ||
|
||
# Shortcut for assert_good_value. | ||
# | ||
def good?(value, message_sym=:message) #:nodoc: | ||
assert_good_value(@subject, @attribute, value, @options[message_sym]) | ||
end | ||
|
||
# Shortcut for assert_bad_value. | ||
# | ||
def bad?(value, message_sym=:message) #:nodoc: | ||
assert_bad_value(@subject, @attribute, value, @options[message_sym]) | ||
end | ||
|
||
# Asserts that a DataMapper model validates with the passed | ||
# <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not | ||
# contained within the list of errors for that attribute. | ||
# | ||
# assert_good_value(User.new, :email, "user@example.com") | ||
# assert_good_value(User.new, :ssn, "123456789", /length/) | ||
# | ||
# If a class is passed as the first argument, a new object will be | ||
# instantiated before the assertion. If an instance variable exists with | ||
# the same name as the class (underscored), that object will be used | ||
# instead. | ||
# | ||
# assert_good_value(User, :email, "user@example.com") | ||
# | ||
# @product = Product.new(:tangible => false) | ||
# assert_good_value(Product, :price, "0") | ||
# | ||
def assert_good_value(model, attribute, value, error_message_to_avoid=//) # :nodoc: | ||
model.send("#{attribute}=", value) | ||
|
||
return true if model.valid? | ||
|
||
error_message_to_avoid = error_message_from_model(model, attribute, error_message_to_avoid) | ||
assert_does_not_contain(model.errors.on(attribute), error_message_to_avoid) | ||
end | ||
|
||
# Asserts that a DataMapper model invalidates the passed | ||
# <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is | ||
# contained within the list of errors for that attribute. | ||
# | ||
# assert_bad_value(User.new, :email, "invalid") | ||
# assert_bad_value(User.new, :ssn, "123", /length/) | ||
# | ||
# If a class is passed as the first argument, a new object will be | ||
# instantiated before the assertion. If an instance variable exists with | ||
# the same name as the class (underscored), that object will be used | ||
# instead. | ||
# | ||
# assert_bad_value(User, :email, "invalid") | ||
# | ||
# @product = Product.new(:tangible => true) | ||
# assert_bad_value(Product, :price, "0") | ||
# | ||
def assert_bad_value(model, attribute, value, error_message_to_expect=:invalid) #:nodoc: | ||
model.send("#{attribute}=", value) | ||
|
||
return false if model.valid? || model.errors.on(attribute).blank? | ||
|
||
error_message_to_expect = error_message_from_model(model, attribute, error_message_to_expect) | ||
assert_contains(model.errors.on(attribute), error_message_to_expect) | ||
end | ||
|
||
# Return the error message to be checked. If the message is not a Symbol | ||
# neither a Hash, it returns the own message. | ||
# | ||
# But the nice thing is that when the message is a Symbol we get the error | ||
# messsage from within the model, using already existent structure inside | ||
# DataMapper. | ||
# | ||
# This allows a couple things from the user side: | ||
# | ||
# 1. Specify symbols in their tests: | ||
# | ||
# should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => :inclusion) | ||
# | ||
# As we know, allow_values_for searches for a :invalid message. So if we | ||
# were testing a validates_inclusion_of with allow_values_for, previously | ||
# we had to do something like this: | ||
# | ||
# should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => 'not included in list') | ||
# | ||
# Now everything gets resumed to a Symbol. | ||
# | ||
# 2. Do not worry with specs if their are using I18n API properly. | ||
# | ||
# As we know, I18n API provides several interpolation options besides | ||
# fallback when creating error messages. If the user changed the message, | ||
# macros would start to pass when they shouldn't. | ||
# | ||
# Using the underlying mechanism inside DataMapper makes us free from | ||
# all those errors. | ||
# | ||
# We replace {{count}} interpolation for 12345 which later is replaced | ||
# by a regexp which contains \d+. | ||
# | ||
def error_message_from_model(model, attribute, message) #:nodoc: | ||
if message.is_a? Symbol | ||
# TODO: No Internationalization yet. | ||
message = ::DataMapper::Validate::ValidationErrors.default_error_message(message, attribute, '12345') | ||
|
||
if message =~ /12345/ | ||
message = Regexp.escape(message) | ||
message.gsub!('12345', '\d+') | ||
message = /#{message}/ | ||
end | ||
end | ||
|
||
message | ||
end | ||
|
||
# Asserts that the given collection does not contain item x. If x is a | ||
# regular expression, ensure that none of the elements from the collection | ||
# match x. | ||
# | ||
def assert_does_not_contain(collection, x) #:nodoc: | ||
!assert_contains(collection, x) | ||
end | ||
|
||
# Changes how collection are interpolated to provide localized names | ||
# whenever is possible. | ||
# | ||
def collection_interpolation #:nodoc: | ||
described_class = if @subject | ||
subject_class | ||
elsif @spec | ||
@spec.send(:described_class) | ||
end | ||
|
||
if i18n_collection? && described_class.respond_to?(:human_attribute_name) | ||
options = {} | ||
|
||
collection_name = self.class.matcher_arguments[:collection].to_sym | ||
if collection = instance_variable_get("@#{collection_name}") | ||
collection = collection.map do |attr| | ||
described_class.human_attribute_name(attr.to_s, :locale => Remarkable.locale).downcase | ||
end | ||
options[collection_name] = array_to_sentence(collection) | ||
end | ||
|
||
object_name = self.class.matcher_arguments[:as] | ||
if object = instance_variable_get("@#{object_name}") | ||
object = described_class.human_attribute_name(object.to_s, :locale => Remarkable.locale).downcase | ||
options[object_name] = object | ||
end | ||
|
||
options | ||
else | ||
super | ||
end | ||
end | ||
|
||
# Returns true if the given collection should be translated. | ||
# | ||
def i18n_collection? #:nodoc: | ||
RAILS_I18N && I18N_COLLECTION.include?(self.class.matcher_arguments[:collection]) | ||
end | ||
|
||
end | ||
end | ||
end |
Oops, something went wrong.