schlick-pickle 0.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,71 @@
1
+ == edge
2
+
3
+ * 1 major enhancement
4
+ * generate page steps with script/generate pickle page
5
+ page steps allow you to do things like this
6
+ When I go to the comment's page
7
+ Then I should be at the user's new comment page
8
+
9
+ * 3 minor enhancements
10
+ * Improved documentation
11
+ * absract models no longer kill pickle
12
+ * Actually test that the generators work
13
+
14
+ == 0.1.5
15
+
16
+ * API change
17
+ * CaptureModel, etc are now 'capture_model' methods
18
+
19
+ * 3 major enhancements
20
+ * Steps for asserting that <n> models exist, matching certain criteria
21
+ * Steps for asserting associations added to generated pickle steps
22
+ 'Then the user should be in the post's commenters'
23
+ 'Then the forum: "awesome" should be the 2nd post's forum'
24
+ * configuration can now occur any time before a step is defined, which makes
25
+ for much more intuitive env.rb
26
+
27
+ * 2 minor enhancement
28
+ * predicate matching is less prone to step conflicts because we preload a
29
+ big list of all the predicate and column methods
30
+ * field values now handle booleans and numerics
31
+
32
+
33
+ == 0.1.4
34
+
35
+ * 1 major enhancement
36
+ * You can create multiple models with ease, for eg.
37
+ 'Given 10 users exist with role: "admin"'
38
+
39
+ * 1 minor enhancement
40
+ * You can do Pickle.configure (just like Webrat.configure)
41
+
42
+
43
+ == 0.1.3 - Bugfix release
44
+
45
+ * 1 minor enhancement
46
+ * make generated steps compatible with Rails 2.1
47
+
48
+
49
+ == 0.1.2
50
+
51
+ * 2 major enhancements
52
+ * create your pickle steps with script/generate pickle
53
+ * Adapter based architecture, supports Machinist, FactoryGirl, and vanilla ActiveRecord
54
+
55
+ * 1 minor enhancement
56
+ * model_names now defaults to subclasses of AR::Base
57
+ * #original_model => #created_model
58
+
59
+
60
+ == 0.1.1
61
+
62
+ * 1 major enhancement:
63
+ * made pickle a github gem
64
+
65
+ * 1 minor enhancement:
66
+ * Added intentions for pickle in README.textile
67
+
68
+
69
+ == Prior to gems
70
+
71
+ * Initial release: everything is subject to sweeping change
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 Ian White - ian.w.white@gmail.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Todo.txt ADDED
File without changes
@@ -0,0 +1,85 @@
1
+ module Pickle
2
+ # Abstract Factory adapter class, if you have a factory type setup, you
3
+ # can easily create an adaptor to make it work with Pickle.
4
+ #
5
+ # The factory adaptor must have a #factories class method that returns
6
+ # its instances, and each instance must respond to:
7
+ #
8
+ # #name : identifies the factory by name (default is attr_reader)
9
+ # #klass : returns the associated model class for this factory (default is attr_reader)
10
+ # #create(attrs = {}) : returns a newly created object
11
+ class Adapter
12
+ attr_reader :name, :klass
13
+
14
+ def self.factories
15
+ raise NotImplementedError, "return an array of factory adapter objects"
16
+ end
17
+
18
+ def create(attrs = {})
19
+ raise NotImplementedError, "create and return an object with the given attributes"
20
+ end
21
+
22
+ cattr_writer :model_classes
23
+ self.model_classes = nil
24
+
25
+ def self.model_classes
26
+ @@model_classes ||= ::ActiveRecord::Base.send(:subclasses).reject do |klass|
27
+ klass.abstract_class? || [CGI::Session::ActiveRecordStore::Session].include?(klass)
28
+ end
29
+ end
30
+
31
+ # machinist adapter
32
+ class Machinist < Adapter
33
+ def self.factories
34
+ factories = []
35
+ model_classes.each do |klass|
36
+ factories << new(klass, "make") if klass.instance_variable_get('@blueprint')
37
+ # if there are make_<special> methods, add blueprints for them
38
+ klass.methods.select{|m| m =~ /^make_/ && m !~ /_unsaved$/}.each do |method|
39
+ factories << new(klass, method)
40
+ end
41
+ end
42
+ factories
43
+ end
44
+
45
+ def initialize(klass, method)
46
+ @klass, @method = klass, method
47
+ @name = (@method =~ /make_/ ? "#{@method.sub('make_','')}_" : "") + @klass.name.underscore.gsub('/','_')
48
+ end
49
+
50
+ def create(attrs = {})
51
+ @klass.send(@method, attrs)
52
+ end
53
+ end
54
+
55
+ # factory-girl adapter
56
+ class FactoryGirl < Adapter
57
+ def self.factories
58
+ (::Factory.factories.values rescue []).map {|factory| new(factory)}
59
+ end
60
+
61
+ def initialize(factory)
62
+ @klass, @name = factory.build_class, factory.factory_name.to_s
63
+ end
64
+
65
+ def create(attrs = {})
66
+ Factory.create(@name, attrs)
67
+ end
68
+ end
69
+
70
+ # fallback active record adapter
71
+ class ActiveRecord < Adapter
72
+ def self.factories
73
+ model_classes.map {|klass| new(klass) }
74
+ end
75
+
76
+ def initialize(klass)
77
+ @klass, @name = klass, klass.name.underscore.gsub('/','_')
78
+ end
79
+
80
+ def create(attrs = {})
81
+ @klass.send(:create!, attrs)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,48 @@
1
+ require 'ostruct'
2
+
3
+ module Pickle
4
+ class Config
5
+ attr_writer :adapters, :factories, :mappings, :predicates
6
+
7
+ def initialize(&block)
8
+ configure(&block) if block_given?
9
+ end
10
+
11
+ def configure(&block)
12
+ yield(self)
13
+ end
14
+
15
+ def adapters
16
+ @adapters ||= [:machinist, :factory_girl, :active_record]
17
+ end
18
+
19
+ def adapter_classes
20
+ adapters.map {|a| a.is_a?(Class) ? a : "pickle/adapter/#{a}".classify.constantize}
21
+ end
22
+
23
+ def factories
24
+ @factories ||= adapter_classes.reverse.inject({}) do |factories, adapter|
25
+ factories.merge(adapter.factories.inject({}){|h, f| h.merge(f.name => f)})
26
+ end
27
+ end
28
+
29
+ def predicates
30
+ @predicates ||= Pickle::Adapter.model_classes.map do |k|
31
+ k.public_instance_methods.select{|m| m =~ /\?$/} + k.column_names
32
+ end.flatten.uniq
33
+ end
34
+
35
+ def mappings
36
+ @mappings ||= []
37
+ end
38
+
39
+ # Usage: map 'me', 'myself', 'I', :to => 'user: "me"'
40
+ def map(*args)
41
+ options = args.extract_options!
42
+ raise ArgumentError, "Usage: map 'search' [, 'search2', ...] :to => 'replace'" unless args.any? && options[:to].is_a?(String)
43
+ args.each do |search|
44
+ self.mappings << OpenStruct.new(:search => search, :replace => options[:to])
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ module Pickle
2
+ module Injector
3
+ def self.inject(delegate_class, options = {})
4
+ target_class = options[:into] || raise('inject requires a target class specified with :into')
5
+ delegate_name = options[:name] || delegate_class.name.underscore.gsub('/','_')
6
+ init_delegate = options[:init] || lambda { new }
7
+
8
+ # create a session object on demand (in target)
9
+ target_class.send(:define_method, delegate_name) do
10
+ instance_variable_get("@#{delegate_name}") || instance_variable_set("@#{delegate_name}", delegate_class.instance_eval(&init_delegate))
11
+ end
12
+
13
+ # in the target, delegate the public instance methods of delegate_class to the delegate_name method
14
+ delegate_methods = delegate_class.public_instance_methods - Object.instance_methods
15
+ target_class.delegate *(delegate_methods + [{:to => delegate_name}])
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,87 @@
1
+ module Pickle
2
+ class Parser
3
+ module Matchers
4
+ def match_ordinal
5
+ '(?:\d+(?:st|nd|rd|th))'
6
+ end
7
+
8
+ def match_index
9
+ "(?:first|last|#{match_ordinal})"
10
+ end
11
+
12
+ def match_prefix
13
+ '(?:(?:1|a|an|another|the|that) )'
14
+ end
15
+
16
+ def match_quoted
17
+ '(?:[^\\"]|\\.)*'
18
+ end
19
+
20
+ def match_label
21
+ "(?::? \"#{match_quoted}\")"
22
+ end
23
+
24
+ def match_value
25
+ "(?:\"#{match_quoted}\"|true|false|\\d+(?:\\.\\d+)?)"
26
+ end
27
+
28
+ def match_field
29
+ "(?:\\w+: #{match_value})"
30
+ end
31
+
32
+ def match_fields
33
+ "(?:#{match_field}, )*#{match_field}"
34
+ end
35
+
36
+ def match_mapping
37
+ "(?:#{config.mappings.map(&:search).join('|')})"
38
+ end
39
+
40
+ def match_factory
41
+ "(?:#{config.factories.keys.map{|n| n.gsub('_','[_ ]')}.join('|')})"
42
+ end
43
+
44
+ def match_plural_factory
45
+ "(?:#{config.factories.keys.map{|n| n.pluralize.gsub('_','[_ ]')}.join('|')})"
46
+ end
47
+
48
+ def match_indexed_model
49
+ "(?:(?:#{match_index} )?#{match_factory})"
50
+ end
51
+
52
+ def match_labeled_model
53
+ "(?:#{match_factory}#{match_label})"
54
+ end
55
+
56
+ def match_model
57
+ "(?:#{match_mapping}|#{match_prefix}?(?:#{match_indexed_model}|#{match_labeled_model}))"
58
+ end
59
+
60
+ def match_predicate
61
+ "(?:#{config.predicates.map{|m| m.sub(/\?$/,'').gsub('_','[_ ]')}.join('|')})"
62
+ end
63
+
64
+ # create capture analogues of match methods
65
+ instance_methods.select{|m| m =~ /^match_/}.each do |method|
66
+ eval <<-end_eval
67
+ def #{method.sub('match_', 'capture_')} # def capture_field
68
+ "(" + #{method} + ")" # "(" + match_field + ")"
69
+ end # end
70
+ end_eval
71
+ end
72
+
73
+ # special capture methods
74
+ def capture_number_in_ordinal
75
+ '(?:(\d+)(?:st|nd|rd|th))'
76
+ end
77
+
78
+ def capture_name_in_label
79
+ "(?::? \"(#{match_quoted})\")"
80
+ end
81
+
82
+ def capture_key_and_value_in_field
83
+ "(?:(\\w+): #{capture_value})"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,24 @@
1
+ module Pickle
2
+ class Parser
3
+ # add ability to parse model names as fields, using a session
4
+ module WithSession
5
+ def self.included(parser_class)
6
+ parser_class.alias_method_chain :parse_field, :model
7
+ end
8
+
9
+ attr_accessor :session
10
+
11
+ def match_field
12
+ "(?:\\w+: (?:#{match_model}|#{match_value}))"
13
+ end
14
+
15
+ def parse_field_with_model(field)
16
+ if session && field =~ /^(\w+): #{capture_model}$/
17
+ {$1 => session.model($2)}
18
+ else
19
+ parse_field_without_model(field)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,65 @@
1
+ require 'pickle/parser/matchers'
2
+
3
+ module Pickle
4
+ class Parser
5
+ include Matchers
6
+
7
+ attr_reader :config
8
+
9
+ def initialize(options = {})
10
+ @config = options[:config] || raise(ArgumentError, "Parser.new requires a :config")
11
+ end
12
+
13
+ # given a string like 'foo: "bar", bar: "baz"' returns {"foo" => "bar", "bar" => "baz"}
14
+ def parse_fields(fields)
15
+ if fields.blank?
16
+ {}
17
+ elsif fields =~ /^#{match_fields}$/
18
+ fields.scan(/(#{match_field})(?:,|$)/).inject({}) do |m, match|
19
+ m.merge(parse_field(match[0]))
20
+ end
21
+ else
22
+ raise ArgumentError, "The fields string is not in the correct format.\n\n'#{fields}' did not match: #{match_fields}"
23
+ end
24
+ end
25
+
26
+ # given a string like 'foo: expr' returns {key => value}
27
+ def parse_field(field)
28
+ if field =~ /^#{capture_key_and_value_in_field}$/
29
+ { $1 => eval($2) }
30
+ else
31
+ raise ArgumentError, "The field argument is not in the correct format.\n\n'#{field}' did not match: #{match_field}"
32
+ end
33
+ end
34
+
35
+ # returns really underscored name
36
+ def canonical(str)
37
+ str.to_s.underscore.gsub(' ','_').gsub('/','_')
38
+ end
39
+
40
+ # return [factory_name, name or integer index]
41
+ def parse_model(model_name)
42
+ apply_mappings!(model_name)
43
+ if /#{capture_index} #{capture_factory}$/ =~ model_name
44
+ [canonical($2), parse_index($1)]
45
+ elsif /#{capture_factory}#{capture_name_in_label}?$/ =~ model_name
46
+ [canonical($1), canonical($2)]
47
+ end
48
+ end
49
+
50
+ def parse_index(index)
51
+ case index
52
+ when '', /last/ then -1
53
+ when /#{capture_number_in_ordinal}/ then $1.to_i - 1
54
+ when /first/ then 0
55
+ end
56
+ end
57
+
58
+ private
59
+ def apply_mappings!(string)
60
+ config.mappings.each do |mapping|
61
+ string.sub! /^#{mapping.search}$/, mapping.replace
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,109 @@
1
+ module Pickle
2
+ class Session
3
+ def initialize(options = {})
4
+ self.parser = options[:parser] || Pickle.parser
5
+ @config = parser.config
6
+ end
7
+
8
+ def create_model(a_model_name, fields = nil)
9
+ factory, label = *parser.parse_model(a_model_name)
10
+ raise ArgumentError, "Can't create with an ordinal (e.g. 1st user)" if label.is_a?(Integer)
11
+ record = config.factories[factory].create(parser.parse_fields(fields))
12
+ store_model(factory, label, record)
13
+ end
14
+
15
+ def find_model(a_model_name, fields = nil)
16
+ factory, name = *parser.parse_model(a_model_name)
17
+ raise ArgumentError, "Can't find a model with an ordinal (e.g. 1st user)" if name.is_a?(Integer)
18
+ model_class = config.factories[factory].klass
19
+ if record = model_class.find(:first, :conditions => convert_models_to_attributes(model_class, parser.parse_fields(fields)))
20
+ store_model(factory, name, record)
21
+ end
22
+ end
23
+
24
+ def find_models(factory, fields = nil)
25
+ models_by_index(factory).clear
26
+ model_class = config.factories[factory].klass
27
+ records = model_class.find(:all, :conditions => convert_models_to_attributes(model_class, parser.parse_fields(fields)))
28
+ records.each {|record| store_model(factory, nil, record)}
29
+ end
30
+
31
+ # return the original model stored by create_model or find_model
32
+ def created_model(name)
33
+ factory, name_or_index = *parser.parse_model(name)
34
+
35
+ if name_or_index.blank?
36
+ models_by_index(factory).last
37
+ elsif name_or_index.is_a?(Integer)
38
+ models_by_index(factory)[name_or_index]
39
+ else
40
+ models_by_name(factory)[name_or_index] or raise "model: #{name} does not refer to known model in this scenario"
41
+ end
42
+ end
43
+
44
+ # predicate version which raises no errors
45
+ def created_model?(name)
46
+ (created_model(name) rescue nil) ? true : false
47
+ end
48
+
49
+ # return a newly selected model
50
+ def model(name)
51
+ (model = created_model(name)) && model.class.find(model.id)
52
+ end
53
+
54
+ # predicate version which raises no errors
55
+ def model?(name)
56
+ (model(name) rescue nil) ? true : false
57
+ end
58
+
59
+ # return all original models of specified type
60
+ def created_models(factory)
61
+ models_by_index(factory)
62
+ end
63
+
64
+ # return all models of specified type (freshly selected from the database)
65
+ def models(factory)
66
+ created_models(factory).map{|model| model.class.find(model.id) }
67
+ end
68
+
69
+ protected
70
+ attr_reader :parser, :config
71
+ delegate :canonical, :to => :parser
72
+
73
+ def parser=(parser)
74
+ parser.session = self
75
+ @parser = parser
76
+ end
77
+
78
+ def convert_models_to_attributes(ar_class, attrs)
79
+ attrs.each do |key, val|
80
+ if val.is_a?(ActiveRecord::Base) && ar_class.column_names.include?("#{key}_id")
81
+ attrs["#{key}_id"] = val.id
82
+ attrs["#{key}_type"] = val.class.name if ar_class.column_names.include?("#{key}_type")
83
+ attrs.delete(key)
84
+ end
85
+ end
86
+ end
87
+
88
+ def models_by_name(factory)
89
+ @models_by_name ||= {}
90
+ @models_by_name[canonical(factory)] ||= {}
91
+ end
92
+
93
+ def models_by_index(factory)
94
+ @models_by_index ||= {}
95
+ @models_by_index[canonical(factory)] ||= []
96
+ end
97
+
98
+ # if the factory name != the model name, store under both names
99
+ def store_model(factory, name, record)
100
+ store_record(record.class.name, name, record) unless canonical(factory) == canonical(record.class.name)
101
+ store_record(factory, name, record)
102
+ end
103
+
104
+ def store_record(factory, name, record)
105
+ models_by_name(factory)[name] = record
106
+ models_by_index(factory) << record
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,9 @@
1
+ module Pickle
2
+ module Version
3
+ Major = 0
4
+ Minor = 1
5
+ Tiny = 5
6
+
7
+ String = [Major, Minor, Tiny].join('.')
8
+ end
9
+ end
data/lib/pickle.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'active_support'
2
+ require 'pickle/version'
3
+ require 'pickle/adapter'
4
+ require 'pickle/config'
5
+ require 'pickle/parser'
6
+ require 'pickle/parser/with_session'
7
+ require 'pickle/session'
8
+ require 'pickle/injector'
9
+ require 'pickle/page'
10
+
11
+ # make the parser aware of models in the session (for fields refering to models)
12
+ Pickle::Parser.send :include, Pickle::Parser::WithSession
13
+
14
+ module Pickle
15
+ class << self
16
+ def config
17
+ @config ||= Config.new
18
+ end
19
+
20
+ def configure(&block)
21
+ config.configure(&block)
22
+ end
23
+
24
+ def parser(options = {})
25
+ @parser ||= Parser.new({:config => config}.merge(options))
26
+ end
27
+ end
28
+ end
29
+
30
+ # shortcuts to regexps for use in step definition regexps, *and* in steps
31
+ delegations = %w(capture_model capture_fields capture_factory capture_plural_factory capture_predicate)
32
+ (class << self; self; end).delegate *(delegations + [{:to => 'Pickle.parser'}])
33
+ Pickle::Session.delegate *(delegations + [{:to => :parser}])
34
+
35
+ # inject the pickle session into integration session if we have one (TODO: inject into merb etc?)
36
+ if defined?(ActionController::Integration::Session)
37
+ Pickle::Injector.inject Pickle::Session, :into => ActionController::Integration::Session
38
+ ActionController::Integration::Session.send :include, Pickle::Page
39
+ end
@@ -0,0 +1,23 @@
1
+ class PickleGenerator < Rails::Generator::Base
2
+ def initialize(args, options)
3
+ super(args, options)
4
+ @generate_page_steps = args.include?('page')
5
+ File.exists?('features/support/env.rb') or raise "features/support/env.rb not found, try running script/generate cucumber"
6
+ end
7
+
8
+ def manifest
9
+ record do |m|
10
+ m.directory File.join('features/step_definitions')
11
+
12
+ current_env = File.read('features/support/env.rb')
13
+ if current_env.include?("require 'pickle'")
14
+ logger.skipped "features/support/env.rb, as it already requires pickle"
15
+ else
16
+ m.template 'env.rb', File.join('features/support', "env.rb"), :assigns => {:current_env => current_env}, :collision => :force
17
+ end
18
+
19
+ m.template 'pickle_steps.rb', File.join('features/step_definitions', "pickle_steps.rb")
20
+ m.template 'page_steps.rb', File.join('features/step_definitions', "page_steps.rb") if @generate_page_steps
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ <%= current_env %>
2
+ require 'pickle'
3
+
4
+ # Example of configuring pickle:
5
+ #
6
+ # Pickle.configure do |config|
7
+ # config.adaptors = [:machinist]
8
+ # config.map 'I', 'myself', 'me', 'my', :to => 'user: "me"'
9
+ # end
10
+
@@ -0,0 +1,41 @@
1
+ # this file generated by script/generate pickle
2
+
3
+ # create a model
4
+ Given(/^#{capture_model} exists?(?: with #{capture_fields})?$/) do |name, fields|
5
+ create_model(name, fields)
6
+ end
7
+
8
+ # create n models
9
+ Given(/^(\d+) #{capture_plural_factory} exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
10
+ count.to_i.times { create_model(plural_factory.singularize, fields) }
11
+ end
12
+
13
+ # find a model
14
+ Then(/^#{capture_model} should exist(?: with #{capture_fields})?$/) do |name, fields|
15
+ find_model(name, fields).should_not be_nil
16
+ end
17
+
18
+ # find exactly n models
19
+ Then(/^(\d+) #{capture_plural_factory} should exist(?: with #{capture_fields})?$/) do |count, plural_factory, fields|
20
+ find_models(plural_factory.singularize, fields).size.should == count.to_i
21
+ end
22
+
23
+ # assert model is in another model's has_many assoc
24
+ Then(/^#{capture_model} should be (?:in|one of|amongst) #{capture_model}'s (\w+)$/) do |target, owner, association|
25
+ model(owner).send(association).should include(model(target))
26
+ end
27
+
28
+ # assert model is another model's has_one/belongs_to assoc
29
+ Then(/^#{capture_model} should be #{capture_model}'s (\w+)$/) do |target, owner, association|
30
+ model(owner).send(association).should == model(target)
31
+ end
32
+
33
+ # assert model.predicate?
34
+ Then(/^#{capture_model} should (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
35
+ model(name).should send("be_#{predicate.gsub(' ', '_')}")
36
+ end
37
+
38
+ # assert not model.predicate?
39
+ Then(/^#{capture_model} should not (?:be|have) (?:an? )?#{capture_predicate}$/) do |name, predicate|
40
+ model(name).should_not send("be_#{predicate.gsub(' ', '_')}")
41
+ end