remarkable_mongo 0.1.2
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/.gitignore +1 -0
- data/LICENSE +20 -0
- data/README.md +23 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/remarkable/mongo_mapper.rb +30 -0
- data/lib/remarkable/mongo_mapper/base.rb +223 -0
- data/lib/remarkable/mongo_mapper/describe.rb +199 -0
- data/lib/remarkable/mongo_mapper/human_names.rb +37 -0
- data/lib/remarkable/mongo_mapper/matchers/allow_values_for_matcher.rb +86 -0
- data/lib/remarkable/mongo_mapper/matchers/association_matcher.rb +105 -0
- data/lib/remarkable/mongo_mapper/matchers/have_key_matcher.rb +38 -0
- data/lib/remarkable/mongo_mapper/matchers/validate_confirmation_of_matcher.rb +44 -0
- data/lib/remarkable/mongo_mapper/matchers/validate_length_of_matcher.rb +106 -0
- data/lib/remarkable/mongo_mapper/matchers/validate_presence_of_matcher.rb +37 -0
- data/locales/en.yml +135 -0
- data/spec/matchers/allow_values_for_matcher_spec.rb +71 -0
- data/spec/matchers/association_matcher_spec.rb +104 -0
- data/spec/matchers/have_key_matcher_spec.rb +32 -0
- data/spec/matchers/validate_confirmation_of_matcher_spec.rb +64 -0
- data/spec/matchers/validate_length_of_matcher_spec.rb +147 -0
- data/spec/matchers/validate_presence_of_matcher_spec.rb +33 -0
- data/spec/model_builder.rb +64 -0
- data/spec/models.rb +42 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +17 -0
- metadata +108 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Nicolas Mérouze
|
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/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Remarkable MongoMapper
|
2
|
+
|
3
|
+
Remarkable matchers for [MongoMapper](http://github.com/jnunemaker/mongomapper).
|
4
|
+
|
5
|
+
## Matchers
|
6
|
+
|
7
|
+
<pre><code>it { should have_key(:name, String) }
|
8
|
+
it { should have_keys(:name, :phone_number, String) }
|
9
|
+
it { should validate_presence_of(:name, :phone_number, :message => "not there!") }
|
10
|
+
it { should belong_to(:user, :class_name => 'Person') }
|
11
|
+
it { should have_many(:users, :class_name => 'Person', :polymorphic => true) }</code></pre>
|
12
|
+
|
13
|
+
## TODO
|
14
|
+
|
15
|
+
* Finish validate_length_of
|
16
|
+
|
17
|
+
## Contributions
|
18
|
+
|
19
|
+
It is far from complete! It'd be very helpful to have some help.
|
20
|
+
|
21
|
+
## Contributors
|
22
|
+
|
23
|
+
* Nicolas Mérouze
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "remarkable_mongo"
|
9
|
+
gem.summary = %Q{Remarkable Matchers for MongoDB ORMs}
|
10
|
+
gem.email = "nicolas.merouze@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/nmerouze/remarkable_mongo"
|
12
|
+
gem.authors = ["Nicolas Mérouze"]
|
13
|
+
|
14
|
+
gem.add_dependency('remarkable', '~> 3.1.8')
|
15
|
+
gem.add_dependency('mongo_mapper', '~> 0.6.1')
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Default: run specs.'
|
22
|
+
task :default => :spec
|
23
|
+
|
24
|
+
desc 'Run all the specs for the machinist plugin.'
|
25
|
+
Spec::Rake::SpecTask.new do |t|
|
26
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
t.rcov = false
|
28
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Load Remarkable
|
2
|
+
unless Object.const_defined?('Remarkable')
|
3
|
+
begin
|
4
|
+
require 'remarkable'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
gem 'remarkable'
|
8
|
+
require 'remarkable'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Add locale
|
13
|
+
dir = File.dirname(__FILE__)
|
14
|
+
Remarkable.add_locale File.join(dir, '..', '..', 'locales', 'en.yml')
|
15
|
+
|
16
|
+
require File.join(dir, 'mongo_mapper', 'base')
|
17
|
+
require File.join(dir, 'mongo_mapper', 'describe')
|
18
|
+
# require File.join(dir, 'remarkable_mongomapper', 'human_names')
|
19
|
+
|
20
|
+
# Add matchers
|
21
|
+
Dir[File.join(dir, 'mongo_mapper', 'matchers', '*.rb')].each do |file|
|
22
|
+
require file
|
23
|
+
end
|
24
|
+
|
25
|
+
# Include Remarkable MongoMapper matcher in appropriate ExampleGroup
|
26
|
+
if defined?(Spec::Rails)
|
27
|
+
Remarkable.include_matchers!(Remarkable::MongoMapper, Spec::Rails::Example::ModelExampleGroup)
|
28
|
+
else
|
29
|
+
Remarkable.include_matchers!(Remarkable::MongoMapper, Spec::Example::ExampleGroup)
|
30
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module MongoMapper
|
3
|
+
class Base < Remarkable::Base
|
4
|
+
I18N_COLLECTION = [ :attributes, :associations ]
|
5
|
+
|
6
|
+
# Provides a way to send options to all MongoMapper matchers.
|
7
|
+
#
|
8
|
+
# validates_presence_of(:name).with_options(:allow_nil => false)
|
9
|
+
#
|
10
|
+
# Is equivalent to:
|
11
|
+
#
|
12
|
+
# validates_presence_of(:name, :allow_nil => false)
|
13
|
+
#
|
14
|
+
def with_options(opts={})
|
15
|
+
@options.merge!(opts)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
# Checks for the given key in @options, if it exists and it's true,
|
22
|
+
# tests that the value is bad, otherwise tests that the value is good.
|
23
|
+
#
|
24
|
+
# It accepts the key to check for, the value that is used for testing
|
25
|
+
# and an @options key where the message to search for is.
|
26
|
+
#
|
27
|
+
def assert_bad_or_good_if_key(key, value, message_key=:message) #:nodoc:
|
28
|
+
return positive? unless @options.key?(key)
|
29
|
+
|
30
|
+
if @options[key]
|
31
|
+
return bad?(value, message_key), :not => not_word
|
32
|
+
else
|
33
|
+
return good?(value, message_key), :not => ''
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Checks for the given key in @options, if it exists and it's true,
|
38
|
+
# tests that the value is good, otherwise tests that the value is bad.
|
39
|
+
#
|
40
|
+
# It accepts the key to check for, the value that is used for testing
|
41
|
+
# and an @options key where the message to search for is.
|
42
|
+
#
|
43
|
+
def assert_good_or_bad_if_key(key, value, message_key=:message) #:nodoc:
|
44
|
+
return positive? unless @options.key?(key)
|
45
|
+
|
46
|
+
if @options[key]
|
47
|
+
return good?(value, message_key), :not => ''
|
48
|
+
else
|
49
|
+
return bad?(value, message_key), :not => not_word
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Default allow_nil? validation. It accepts the message_key which is
|
54
|
+
# the key which contain the message in @options.
|
55
|
+
#
|
56
|
+
# It also gets an allow_nil message on remarkable.mongo_mapper.allow_nil
|
57
|
+
# to be used as default.
|
58
|
+
#
|
59
|
+
def allow_nil?(message_key=:message) #:nodoc:
|
60
|
+
assert_good_or_bad_if_key(:allow_nil, nil, message_key)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Default allow_blank? validation. It accepts the message_key which is
|
64
|
+
# the key which contain the message in @options.
|
65
|
+
#
|
66
|
+
# It also gets an allow_blank message on remarkable.mongo_mapper.allow_blank
|
67
|
+
# to be used as default.
|
68
|
+
#
|
69
|
+
def allow_blank?(message_key=:message) #:nodoc:
|
70
|
+
assert_good_or_bad_if_key(:allow_blank, '', message_key)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Shortcut for assert_good_value.
|
74
|
+
#
|
75
|
+
def good?(value, message_sym=:message) #:nodoc:
|
76
|
+
assert_good_value(@subject, @attribute, value, @options[message_sym])
|
77
|
+
end
|
78
|
+
|
79
|
+
# Shortcut for assert_bad_value.
|
80
|
+
#
|
81
|
+
def bad?(value, message_sym=:message) #:nodoc:
|
82
|
+
assert_bad_value(@subject, @attribute, value, @options[message_sym])
|
83
|
+
end
|
84
|
+
|
85
|
+
# Asserts that an MongoMapper model validates with the passed
|
86
|
+
# <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
|
87
|
+
# contained within the list of errors for that attribute.
|
88
|
+
#
|
89
|
+
# assert_good_value(User.new, :email, "user@example.com")
|
90
|
+
# assert_good_value(User.new, :ssn, "123456789", /length/)
|
91
|
+
#
|
92
|
+
# If a class is passed as the first argument, a new object will be
|
93
|
+
# instantiated before the assertion. If an instance variable exists with
|
94
|
+
# the same name as the class (underscored), that object will be used
|
95
|
+
# instead.
|
96
|
+
#
|
97
|
+
# assert_good_value(User, :email, "user@example.com")
|
98
|
+
#
|
99
|
+
# @product = Product.new(:tangible => false)
|
100
|
+
# assert_good_value(Product, :price, "0")
|
101
|
+
#
|
102
|
+
def assert_good_value(model, attribute, value, error_message_to_avoid=//) # :nodoc:
|
103
|
+
model.send("#{attribute}=", value)
|
104
|
+
|
105
|
+
return true if model.valid?
|
106
|
+
|
107
|
+
error_message_to_avoid = error_message_from_model(model, attribute, error_message_to_avoid)
|
108
|
+
assert_does_not_contain(model.errors.on(attribute), error_message_to_avoid)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Asserts that an MongoMapper model invalidates the passed
|
112
|
+
# <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
|
113
|
+
# contained within the list of errors for that attribute.
|
114
|
+
#
|
115
|
+
# assert_bad_value(User.new, :email, "invalid")
|
116
|
+
# assert_bad_value(User.new, :ssn, "123", /length/)
|
117
|
+
#
|
118
|
+
# If a class is passed as the first argument, a new object will be
|
119
|
+
# instantiated before the assertion. If an instance variable exists with
|
120
|
+
# the same name as the class (underscored), that object will be used
|
121
|
+
# instead.
|
122
|
+
#
|
123
|
+
# assert_bad_value(User, :email, "invalid")
|
124
|
+
#
|
125
|
+
# @product = Product.new(:tangible => true)
|
126
|
+
# assert_bad_value(Product, :price, "0")
|
127
|
+
#
|
128
|
+
def assert_bad_value(model, attribute, value, error_message_to_expect=:invalid) #:nodoc:
|
129
|
+
model.send("#{attribute}=", value)
|
130
|
+
|
131
|
+
return false if model.valid? || model.errors.on(attribute).blank?
|
132
|
+
|
133
|
+
error_message_to_expect = error_message_from_model(model, attribute, error_message_to_expect)
|
134
|
+
assert_contains(model.errors.on(attribute), error_message_to_expect)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return the error message to be checked. If the message is not a Symbol
|
138
|
+
# neither a Hash, it returns the own message.
|
139
|
+
#
|
140
|
+
# But the nice thing is that when the message is a Symbol we get the error
|
141
|
+
# messsage from within the model, using already existent structure inside
|
142
|
+
# MongoMapper.
|
143
|
+
#
|
144
|
+
# This allows a couple things from the user side:
|
145
|
+
#
|
146
|
+
# 1. Specify symbols in their tests:
|
147
|
+
#
|
148
|
+
# should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => :inclusion)
|
149
|
+
#
|
150
|
+
# As we know, allow_values_for searches for a :invalid message. So if we
|
151
|
+
# were testing a validates_inclusion_of with allow_values_for, previously
|
152
|
+
# we had to do something like this:
|
153
|
+
#
|
154
|
+
# should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => 'not included in list')
|
155
|
+
#
|
156
|
+
# Now everything gets resumed to a Symbol.
|
157
|
+
#
|
158
|
+
# 2. Do not worry with specs if their are using I18n API properly.
|
159
|
+
#
|
160
|
+
# As we know, I18n API provides several interpolation options besides
|
161
|
+
# fallback when creating error messages. If the user changed the message,
|
162
|
+
# macros would start to pass when they shouldn't.
|
163
|
+
#
|
164
|
+
# Using the underlying mechanism inside ActiveRecord makes us free from
|
165
|
+
# all thos errors.
|
166
|
+
#
|
167
|
+
# We replace {{count}} interpolation for 12345 which later is replaced
|
168
|
+
# by a regexp which contains \d+.
|
169
|
+
#
|
170
|
+
def error_message_from_model(model, attribute, message) #:nodoc:
|
171
|
+
# FIXME
|
172
|
+
message
|
173
|
+
end
|
174
|
+
|
175
|
+
# Asserts that the given collection does not contain item x. If x is a
|
176
|
+
# regular expression, ensure that none of the elements from the collection
|
177
|
+
# match x.
|
178
|
+
#
|
179
|
+
def assert_does_not_contain(collection, x) #:nodoc:
|
180
|
+
!assert_contains(collection, x)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Changes how collection are interpolated to provide localized names
|
184
|
+
# whenever is possible.
|
185
|
+
#
|
186
|
+
def collection_interpolation #:nodoc:
|
187
|
+
described_class = if @subject
|
188
|
+
subject_class
|
189
|
+
elsif @spec
|
190
|
+
@spec.send(:described_class)
|
191
|
+
end
|
192
|
+
|
193
|
+
if i18n_collection? && described_class.respond_to?(:human_attribute_name)
|
194
|
+
options = {}
|
195
|
+
|
196
|
+
collection_name = self.class.matcher_arguments[:collection].to_sym
|
197
|
+
if collection = instance_variable_get("@#{collection_name}")
|
198
|
+
collection = collection.map do |attr|
|
199
|
+
described_class.human_attribute_name(attr.to_s, :locale => Remarkable.locale).downcase
|
200
|
+
end
|
201
|
+
options[collection_name] = array_to_sentence(collection)
|
202
|
+
end
|
203
|
+
|
204
|
+
object_name = self.class.matcher_arguments[:as]
|
205
|
+
if object = instance_variable_get("@#{object_name}")
|
206
|
+
object = described_class.human_attribute_name(object.to_s, :locale => Remarkable.locale).downcase
|
207
|
+
options[object_name] = object
|
208
|
+
end
|
209
|
+
|
210
|
+
options
|
211
|
+
else
|
212
|
+
super
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns true if the given collection should be translated.
|
217
|
+
#
|
218
|
+
def i18n_collection? #:nodoc:
|
219
|
+
RAILS_I18N && I18N_COLLECTION.include?(self.class.matcher_arguments[:collection])
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module Remarkable
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
def self.after_include(target) #:nodoc:
|
5
|
+
target.class_inheritable_reader :describe_subject_attributes, :default_subject_attributes
|
6
|
+
target.send :include, Describe
|
7
|
+
end
|
8
|
+
|
9
|
+
# Overwrites describe to provide quick way to configure your subject:
|
10
|
+
#
|
11
|
+
# describe Post
|
12
|
+
# should_validate_presente_of :title
|
13
|
+
#
|
14
|
+
# describe :published => true do
|
15
|
+
# should_validate_presence_of :published_at
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# This is the same as:
|
20
|
+
#
|
21
|
+
# describe Post
|
22
|
+
# should_validate_presente_of :title
|
23
|
+
#
|
24
|
+
# describe "when published is true" do
|
25
|
+
# subject { Post.new(:published => true) }
|
26
|
+
# should_validate_presence_of :published_at
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# The string can be localized using I18n. An example yml file is:
|
31
|
+
#
|
32
|
+
# locale:
|
33
|
+
# remarkable:
|
34
|
+
# mongo_mapper:
|
35
|
+
# describe:
|
36
|
+
# each: "{{key}} is {{value}}"
|
37
|
+
# prepend: "when "
|
38
|
+
# connector: " and "
|
39
|
+
#
|
40
|
+
# You can also call subject attributes to set the default attributes for a
|
41
|
+
# subject. You can even mix with a fixture replacement tool:
|
42
|
+
#
|
43
|
+
# describe Post
|
44
|
+
# # Fixjour example
|
45
|
+
# subject_attributes { valid_post_attributes }
|
46
|
+
#
|
47
|
+
# describe :published => true do
|
48
|
+
# should_validate_presence_of :published_at
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# You can retrieve the merged result of all attributes given using the
|
53
|
+
# subject_attributes instance method:
|
54
|
+
#
|
55
|
+
# describe Post
|
56
|
+
# # Fixjour example
|
57
|
+
# subject_attributes { valid_post_attributes }
|
58
|
+
#
|
59
|
+
# describe :published => true do
|
60
|
+
# it "should have default subject attributes" do
|
61
|
+
# subject_attributes.should == { :title => 'My title', :published => true }
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
module Describe
|
67
|
+
|
68
|
+
def self.included(base) #:nodoc:
|
69
|
+
base.extend ClassMethods
|
70
|
+
end
|
71
|
+
|
72
|
+
module ClassMethods
|
73
|
+
|
74
|
+
# Overwrites describe to provide quick way to configure your subject:
|
75
|
+
#
|
76
|
+
# describe Post
|
77
|
+
# should_validate_presente_of :title
|
78
|
+
#
|
79
|
+
# describe :published => true do
|
80
|
+
# should_validate_presence_of :published_at
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# This is the same as:
|
85
|
+
#
|
86
|
+
# describe Post
|
87
|
+
# should_validate_presente_of :title
|
88
|
+
#
|
89
|
+
# describe "when published is true" do
|
90
|
+
# subject { Post.new(:published => true) }
|
91
|
+
# should_validate_presence_of :published_at
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# The string can be localized using I18n. An example yml file is:
|
96
|
+
#
|
97
|
+
# locale:
|
98
|
+
# remarkable:
|
99
|
+
# mongo_mapper:
|
100
|
+
# describe:
|
101
|
+
# each: "{{key}} is {{value}}"
|
102
|
+
# prepend: "when "
|
103
|
+
# connector: " and "
|
104
|
+
#
|
105
|
+
# See also subject_attributes instance and class methods for more
|
106
|
+
# information.
|
107
|
+
#
|
108
|
+
def describe(*args, &block)
|
109
|
+
if described_class && args.first.is_a?(Hash)
|
110
|
+
attributes = args.shift
|
111
|
+
|
112
|
+
connector = Remarkable.t "remarkable.mongo_mapper.describe.connector", :default => " and "
|
113
|
+
|
114
|
+
description = if self.describe_subject_attributes.blank?
|
115
|
+
Remarkable.t("remarkable.mongo_mapper.describe.prepend", :default => "when ")
|
116
|
+
else
|
117
|
+
connector.lstrip
|
118
|
+
end
|
119
|
+
|
120
|
+
pieces = []
|
121
|
+
attributes.each do |key, value|
|
122
|
+
translated_key = if described_class.respond_to?(:human_attribute_name)
|
123
|
+
described_class.human_attribute_name(key.to_s, :locale => Remarkable.locale)
|
124
|
+
else
|
125
|
+
key.to_s.humanize
|
126
|
+
end
|
127
|
+
|
128
|
+
pieces << Remarkable.t("remarkable.mongo_mapper.describe.each",
|
129
|
+
:default => "{{key}} is {{value}}",
|
130
|
+
:key => translated_key.downcase, :value => value.inspect)
|
131
|
+
end
|
132
|
+
|
133
|
+
description << pieces.join(connector)
|
134
|
+
args.unshift(description)
|
135
|
+
|
136
|
+
# Creates an example group, set the subject and eval the given block.
|
137
|
+
#
|
138
|
+
example_group = super(*args) do
|
139
|
+
write_inheritable_hash(:describe_subject_attributes, attributes)
|
140
|
+
set_described_subject!
|
141
|
+
instance_eval(&block)
|
142
|
+
end
|
143
|
+
else
|
144
|
+
super(*args, &block)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Sets default attributes for the subject. You can use this to set up
|
149
|
+
# your subject with valid attributes. You can even mix with a fixture
|
150
|
+
# replacement tool and still use quick subjects:
|
151
|
+
#
|
152
|
+
# describe Post
|
153
|
+
# # Fixjour example
|
154
|
+
# subject_attributes { valid_post_attributes }
|
155
|
+
#
|
156
|
+
# describe :published => true do
|
157
|
+
# should_validate_presence_of :published_at
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
def subject_attributes(options=nil, &block)
|
162
|
+
write_inheritable_attribute(:default_subject_attributes, options || block)
|
163
|
+
set_described_subject!
|
164
|
+
end
|
165
|
+
|
166
|
+
def set_described_subject!
|
167
|
+
subject {
|
168
|
+
record = self.class.described_class.new
|
169
|
+
record.send(:attributes=, subject_attributes, false)
|
170
|
+
record
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns a hash with the subject attributes declared using the
|
176
|
+
# subject_attributes class method and the attributes given using the
|
177
|
+
# describe method.
|
178
|
+
#
|
179
|
+
# describe Post
|
180
|
+
# subject_attributes { valid_post_attributes }
|
181
|
+
#
|
182
|
+
# describe :published => true do
|
183
|
+
# it "should have default subject attributes" do
|
184
|
+
# subject_attributes.should == { :title => 'My title', :published => true }
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
def subject_attributes
|
190
|
+
default = self.class.default_subject_attributes
|
191
|
+
default = self.instance_eval(&default) if default.is_a?(Proc)
|
192
|
+
default ||= {}
|
193
|
+
|
194
|
+
default.merge(self.class.describe_subject_attributes || {})
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|