fixjour 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +236 -0
- data/Rakefile +14 -0
- data/fixjour.gemspec +35 -0
- data/lib/fixjour.rb +1 -1
- data/lib/fixjour/merging_proxy.rb +1 -1
- data/spec/builder_spec.rb +24 -0
- data/spec/define_spec.rb +55 -0
- data/spec/dev.rip +4 -0
- data/spec/edge_cases_spec.rb +33 -0
- data/spec/fixjour_spec.rb +555 -0
- data/spec/merging_proxy_spec.rb +48 -0
- data/spec/overrides_hash_spec.rb +61 -0
- data/spec/spec_helper.rb +90 -0
- data/spec/verify_spec.rb +136 -0
- metadata +28 -5
data/README.textile
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
h1. fixjour
|
2
|
+
|
3
|
+
Another fixture replacement. Gets you some methods (@new_*@, @create_*@ and
|
4
|
+
@valid_*_attributes@) methods and some confidence.
|
5
|
+
|
6
|
+
"View the RDoc":http://gitrdoc.com/nakajima/fixjour/tree/master (still underway, but has a bit of helpful stuff)
|
7
|
+
|
8
|
+
"Recommend Me on the Working with Rails":http://www.workingwithrails.com/person/7973-pat-nakajima
|
9
|
+
|
10
|
+
h2. Contribute
|
11
|
+
|
12
|
+
Fixjour has some developer dependencies. Install them as shown below,
|
13
|
+
and run rake to make sure the tests pass. Then dive in!
|
14
|
+
|
15
|
+
<pre>
|
16
|
+
fixjour:$ gem build fixjour.gemspec
|
17
|
+
fixjour:$ sudo gem install --development fixjour
|
18
|
+
fixjour:$ rake
|
19
|
+
Do the tests pass?
|
20
|
+
</pre>
|
21
|
+
h2. The focus of this project is liberation through constraints.
|
22
|
+
|
23
|
+
It uses the bits of object mother systems that worked well for
|
24
|
+
me in the past, and actively discourages the bits that have caused
|
25
|
+
me pain.
|
26
|
+
|
27
|
+
The constraints:
|
28
|
+
|
29
|
+
h4. One builder per model
|
30
|
+
|
31
|
+
If you try to define a builder more than once per model, you'll
|
32
|
+
run into a @Fixjour::RedundantBuilder@ error. One builder per
|
33
|
+
model decreases confusion.
|
34
|
+
|
35
|
+
h4. No redundant object creation methods
|
36
|
+
|
37
|
+
If you try to define a method that's already been defined by
|
38
|
+
a Fixjour builder, you'll run into a @Fixjour::RedundantBuilder@
|
39
|
+
error. If you find the need to alter the behavior of a builder
|
40
|
+
for a particular set of tests, you should just wrap the creation
|
41
|
+
methods defined by Fixjour, preferably with a name that describes
|
42
|
+
how the new method is different from the Fixjour method.
|
43
|
+
|
44
|
+
h4. Processing the overrides hash is bad
|
45
|
+
|
46
|
+
If you want to mess with the overrides hash that can be passed
|
47
|
+
into any of the creation methods, you must use the @process@
|
48
|
+
method (see below). To enforce this, the @delete@ method is actually
|
49
|
+
private for the overrides hash.
|
50
|
+
|
51
|
+
h2. What it gets you:
|
52
|
+
|
53
|
+
With this setup:
|
54
|
+
|
55
|
+
<pre>
|
56
|
+
Fixjour do
|
57
|
+
define_builder(Person) do |klass, overrides|
|
58
|
+
klass.new(:name => 'Pat', :age => 22)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Fixjour
|
63
|
+
</pre>
|
64
|
+
|
65
|
+
You get:
|
66
|
+
|
67
|
+
h3. @new_person(overrides={})@
|
68
|
+
|
69
|
+
The @new_person@ method basically just returns the result
|
70
|
+
of your builder block, which should *always return an unsaved
|
71
|
+
instance of the model class*. You can pass it overrides in a
|
72
|
+
hash like so: @new_person(:name => nil)@.
|
73
|
+
|
74
|
+
h3. @create_person(overrides={})@
|
75
|
+
|
76
|
+
The @create_person@ method calls @new_person@, passing in any
|
77
|
+
overrides you pass it, calls @save!@ on the result, and returns
|
78
|
+
the saved object.
|
79
|
+
|
80
|
+
h3. @valid_person_attributes(overrides={})@
|
81
|
+
|
82
|
+
The @valid_person_attributes@ returns a hash of valid person
|
83
|
+
attributes that are derived from the @new_person@ method, and
|
84
|
+
ideal for things like testing controllers. It can also take
|
85
|
+
attribute override options like so: @valid_person_attributes(:name => nil)@.
|
86
|
+
|
87
|
+
h2. Usage:
|
88
|
+
|
89
|
+
You specify builder sets for your ActiveRecord models in a
|
90
|
+
@Fixjour@ block using the @define_builder@ helper, which can
|
91
|
+
be used in one of two ways:
|
92
|
+
|
93
|
+
h3. Using a builder block
|
94
|
+
|
95
|
+
Pass @define_builder@ a model class for which you want a new set
|
96
|
+
of creation methods, and a block which returns a new valid
|
97
|
+
model object. The block will be passed two arguments: a proxy
|
98
|
+
object for your class, and an overrides hash. If you call @new@
|
99
|
+
on the class proxy, it will return a new instance of the class,
|
100
|
+
with whatever attributes you specify as defaults. It will also
|
101
|
+
automatically merge any override options in all of the methods
|
102
|
+
generated by Fixjour.
|
103
|
+
|
104
|
+
Example:
|
105
|
+
|
106
|
+
<pre>
|
107
|
+
define_builder(Person) do |klass, overrides|
|
108
|
+
klass.new(:name => "Pat", :age => 22)
|
109
|
+
end
|
110
|
+
</pre>
|
111
|
+
|
112
|
+
If you want to process an option in the overrides hash, you can use
|
113
|
+
the @process@ method:
|
114
|
+
|
115
|
+
<pre>
|
116
|
+
define_builder(Person) do |klass, overrides|
|
117
|
+
overrides.process(:child) do |is_child|
|
118
|
+
overrides[:age] = 14 if is_child
|
119
|
+
end
|
120
|
+
|
121
|
+
klass.new(:name => "Pat", :age => 22)
|
122
|
+
end
|
123
|
+
|
124
|
+
# the default
|
125
|
+
person = new_person
|
126
|
+
person.age # => 22
|
127
|
+
|
128
|
+
# using the override
|
129
|
+
person = new_person(:child => true)
|
130
|
+
person.age # => 14
|
131
|
+
</pre>
|
132
|
+
|
133
|
+
In the above example, the @:child@ key will be deleted from the @overrides@
|
134
|
+
hash and made available as the @is_child@ block argument where you can handle
|
135
|
+
things accordingly.
|
136
|
+
|
137
|
+
*Note:* The @delete@ method is private on the overrides hash passed into the
|
138
|
+
builder block. This is meant to encourage you to only use the @process@ method
|
139
|
+
instead. Why? First, because processing the overrides hash is a smell. Deal
|
140
|
+
with it. Second, using the @process@ method provides some indication to readers
|
141
|
+
that you're screwing with the overrides hash, and that's a good thing.
|
142
|
+
|
143
|
+
h4. @attr_protected@ fields
|
144
|
+
|
145
|
+
If you have fields that cannot be mass-assigned, use the @protected@ helper:
|
146
|
+
|
147
|
+
define_builder(Article) do |klass, overrides|
|
148
|
+
klass.protected :author
|
149
|
+
klass.new :title => "The title", :body => "good", :author => new_user
|
150
|
+
end
|
151
|
+
|
152
|
+
If you use the @protected@ helper to declare @attr_protected@ fields, you can
|
153
|
+
then treat them the same as any other field in your test methods.
|
154
|
+
|
155
|
+
h4. With Associations
|
156
|
+
|
157
|
+
To specify an associated object, you can call that object's @new_*@ method:
|
158
|
+
|
159
|
+
<pre>
|
160
|
+
Fixjour do
|
161
|
+
define_builder(Post) do |klass, overrides|
|
162
|
+
klass.new(:name => 'a post', :body => 'texted')
|
163
|
+
end
|
164
|
+
|
165
|
+
define_builder(Comment) do |klass, overrides|
|
166
|
+
klass.new(:body => 'Oh ok!', :post => new_post)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
include Fixjour
|
171
|
+
|
172
|
+
new_comment.post.name # => 'a post'
|
173
|
+
</pre>
|
174
|
+
|
175
|
+
Note that it's never a good idea to use a @create_*@ method in a
|
176
|
+
build block.
|
177
|
+
|
178
|
+
h3. Verifying your setups
|
179
|
+
|
180
|
+
Fixjour requires more work on your part, so it also includes a way
|
181
|
+
to verify that your creation methods are behaving the way they should.
|
182
|
+
Call @Fixjour.verify!@ to ensure the following things:
|
183
|
+
|
184
|
+
# Creation methods are returning valid objects by default.
|
185
|
+
# @new_*@ methods are returning new records.
|
186
|
+
# @new_*@ and @create_*@ methods return instances of the correct class.
|
187
|
+
|
188
|
+
h3. Recommended usage with RSpec and Cucumber
|
189
|
+
|
190
|
+
If you want to use Fixjour with RSpec and Cucumber you probably want to avoid adding the builder methods onto Object directly. To do this you should first create a file where your Fixjour builder definitions can live. Say for example you put it at spec/fixjour_builders.rb. To take advantage of these builders from RSpec use the following code in your spec_helper.rb:
|
191
|
+
|
192
|
+
<pre>
|
193
|
+
require File.expand_path(File.dirname(__FILE__) + "/fixjour_builders.rb")
|
194
|
+
|
195
|
+
Spec::Runner.configure do |config|
|
196
|
+
config.include(Fixjour) # This will add the builder methods to your ExampleGroups and not pollute Object
|
197
|
+
...
|
198
|
+
end
|
199
|
+
</pre>
|
200
|
+
|
201
|
+
To use the same builders in Cucumber you simply need to include Fixjour into your World object from features/support/env.rb:
|
202
|
+
|
203
|
+
<pre>
|
204
|
+
require File.expand_path(File.dirname(__FILE__) +'/../../spec/fixjour_builders.rb')
|
205
|
+
World { |world| world.extend(Fixjour) }
|
206
|
+
</pre>
|
207
|
+
|
208
|
+
Be sure to do this after you define your World object. So, if you are using Rails you should include Fixjour after you require 'cucumber/rails/world'.
|
209
|
+
|
210
|
+
In *Cucumber version 0.2.3.2 and later* you need to pass in a module to the World method to extend it:
|
211
|
+
|
212
|
+
<pre>
|
213
|
+
require File.expand_path(File.dirname(__FILE__) +'/../../spec/fixjour_builders.rb')
|
214
|
+
World(Fixjour)
|
215
|
+
</pre>
|
216
|
+
|
217
|
+
(See the Cucumber::StepMother#World RDoc or "http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world":http://wiki.github.com/aslakhellesoy/cucumber/a-whole-new-world.)
|
218
|
+
|
219
|
+
h4. Contributors
|
220
|
+
|
221
|
+
* "Pat Maddox":http://github.com/pat-maddox - Sparked the original idea and fixed my bugs
|
222
|
+
* "Ben Mabey":http://github.com/bmabey - Added docs and fixed my bugs
|
223
|
+
* "Aaron Quint":http://github.com/quirkey - Pointed out valid attrs problem and fixed my bugs
|
224
|
+
|
225
|
+
h4. TODO
|
226
|
+
|
227
|
+
* There should be a @Builder@ class.
|
228
|
+
|
229
|
+
h4. "Join the mailing list.":http://groups.google.com/group/fixjour
|
230
|
+
|
231
|
+
I've talked to smart people who like these instead:
|
232
|
+
|
233
|
+
* "fixturereplacement":http://github.com/smtlaissezfaire/fixturereplacement/tree/master
|
234
|
+
* "factory girl":http://github.com/thoughtbot/factory_girl/tree
|
235
|
+
|
236
|
+
(c) Copyright 2008 Pat Nakajima, released under MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec/rake/spectask'
|
2
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
3
|
+
spec.libs << 'lib' << 'spec'
|
4
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
5
|
+
end
|
6
|
+
|
7
|
+
Spec::Rake::SpecTask.new("spec:rcov") do |spec|
|
8
|
+
spec.libs << 'lib' << 'spec'
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
spec.rcov = true
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
task :default => :spec
|
data/fixjour.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{fixjour}
|
5
|
+
s.version = "0.5.2"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Pat Nakajima"]
|
9
|
+
s.date = %q{2008-12-29}
|
10
|
+
s.email = %q{patnakajima@gmail.com}
|
11
|
+
s.files = ["README.textile", "Rakefile", "fixjour.gemspec", "lib/core_ext/hash.rb", "lib/core_ext/object.rb", "lib/fixjour.rb", "lib/fixjour/builder.rb", "lib/fixjour/builders.rb", "lib/fixjour/counter.rb", "lib/fixjour/define.rb", "lib/fixjour/definitions.rb", "lib/fixjour/deprecation.rb", "lib/fixjour/errors.rb", "lib/fixjour/generator.rb", "lib/fixjour/merging_proxy.rb", "lib/fixjour/overrides_hash.rb", "lib/fixjour/redundant_check.rb", "lib/fixjour/verify.rb", "spec/builder_spec.rb", "spec/define_spec.rb", "spec/dev.rip", "spec/edge_cases_spec.rb", "spec/fixjour_spec.rb", "spec/merging_proxy_spec.rb", "spec/overrides_hash_spec.rb", "spec/spec_helper.rb", "spec/verify_spec.rb"]
|
12
|
+
s.homepage = %q{http://github.com/nakajima/fixjour}
|
13
|
+
s.require_paths = ["lib"]
|
14
|
+
s.rubygems_version = %q{1.3.7}
|
15
|
+
s.summary = %q{Object creation methods everyone already has}
|
16
|
+
|
17
|
+
if s.respond_to? :specification_version then
|
18
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
19
|
+
s.specification_version = 2
|
20
|
+
|
21
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
22
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
23
|
+
s.add_development_dependency(%q<faker>, [">= 0"])
|
24
|
+
s.add_development_dependency(%q<acts_as_fu>, [">= 0"])
|
25
|
+
else
|
26
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
27
|
+
s.add_dependency(%q<faker>, [">= 0"])
|
28
|
+
s.add_dependency(%q<acts_as_fu>, [">= 0"])
|
29
|
+
end
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
32
|
+
s.add_dependency(%q<faker>, [">= 0"])
|
33
|
+
s.add_dependency(%q<acts_as_fu>, [">= 0"])
|
34
|
+
end
|
35
|
+
end
|
data/lib/fixjour.rb
CHANGED
@@ -21,7 +21,7 @@ module Fixjour
|
|
21
21
|
attrs = defaults.merge(@overrides)
|
22
22
|
accessible, inaccessible = partition(attrs)
|
23
23
|
|
24
|
-
|
24
|
+
@klass.new(accessible).tap do |instance|
|
25
25
|
inaccessible.each do |key,val|
|
26
26
|
instance.send("#{key}=", val)
|
27
27
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Fixjour::Builder do
|
4
|
+
def new_builder(*args)
|
5
|
+
Fixjour::Builder.new(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "takes a class" do
|
9
|
+
new_builder(Foo).klass.should == Foo
|
10
|
+
end
|
11
|
+
|
12
|
+
it "can have a name specified" do
|
13
|
+
new_builder(Foo, :as => :fooz).name.should == 'fooz'
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can infer name" do
|
17
|
+
new_builder(Foo).name.should == 'foo'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can infer nested name" do
|
21
|
+
class Foo; class Bar; end end
|
22
|
+
new_builder(Foo::Bar).name.should == 'foo_bar'
|
23
|
+
end
|
24
|
+
end
|
data/spec/define_spec.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe Fixjour, '.define' do
|
4
|
+
include Fixjour
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
Fixjour.builders.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
it "defines a valid builder" do
|
11
|
+
Fixjour.define(Person) do |person|
|
12
|
+
person.first_name = 'Pat'
|
13
|
+
end
|
14
|
+
|
15
|
+
person = new_person
|
16
|
+
person.first_name.should == 'Pat'
|
17
|
+
|
18
|
+
new_person(:first_name => 'Larry').first_name.should == 'Larry'
|
19
|
+
|
20
|
+
created_person = create_person
|
21
|
+
created_person.should_not be_new_record
|
22
|
+
created_person.first_name.should == 'Pat'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "allows 'scenario' builders from class name" do
|
26
|
+
Fixjour.define(Person) do |person|
|
27
|
+
person.first_name = 'Pat'
|
28
|
+
end
|
29
|
+
|
30
|
+
Fixjour.define(:person_with_last_name, :from => Person) do |person|
|
31
|
+
person.last_name = 'Nakajima'
|
32
|
+
end
|
33
|
+
|
34
|
+
new_person_with_last_name.first_name.should == 'Pat'
|
35
|
+
new_person_with_last_name.last_name.should == 'Nakajima'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "allows 'scenario' builders from other scenario" do
|
39
|
+
Fixjour.define(Person) do |person|
|
40
|
+
person.first_name = 'Pat'
|
41
|
+
end
|
42
|
+
|
43
|
+
Fixjour.define(:person_with_last_name, :from => Person) do |person|
|
44
|
+
person.last_name = 'Nakajima'
|
45
|
+
end
|
46
|
+
|
47
|
+
Fixjour.define(:person_with_bar_id, :from => :person_with_last_name) do |person|
|
48
|
+
person.bar_id = 123
|
49
|
+
end
|
50
|
+
|
51
|
+
new_person_with_bar_id.first_name.should == 'Pat'
|
52
|
+
new_person_with_bar_id.last_name.should == 'Nakajima'
|
53
|
+
new_person_with_bar_id.bar_id.should == 123
|
54
|
+
end
|
55
|
+
end
|
data/spec/dev.rip
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
module Mister
|
4
|
+
class Manager
|
5
|
+
def initialize(attrs)
|
6
|
+
@attributes = attrs
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Fixjour, 'edge cases' do
|
16
|
+
include Fixjour
|
17
|
+
|
18
|
+
describe "namespaced models" do
|
19
|
+
before(:each) do
|
20
|
+
Fixjour.builders.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows name to be specified" do
|
24
|
+
Fixjour do
|
25
|
+
define_builder(Mister::Manager, :as => :manager) do |klass|
|
26
|
+
klass.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
new_manager.should be_kind_of(Mister::Manager)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,555 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe Fixjour do
|
4
|
+
before(:each) do
|
5
|
+
define_all_builders
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "when Fixjour is not included" do
|
9
|
+
it "does not have access to creation methods" do
|
10
|
+
self.should_not respond_to(:new_foo)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "counter" do
|
15
|
+
include Fixjour
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
Fixjour::Counter.reset
|
19
|
+
end
|
20
|
+
|
21
|
+
it "provides default counter" do
|
22
|
+
counter.should == 1
|
23
|
+
counter.should == 2
|
24
|
+
counter.should == 3
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should increase" do
|
28
|
+
counter(:foo).should == 1
|
29
|
+
counter(:foo).should == 2
|
30
|
+
counter(:foo).should == 3
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should allow multiple counters" do
|
34
|
+
counter(:foo).should == 1
|
35
|
+
counter(:foo).should == 2
|
36
|
+
counter(:bar).should == 1
|
37
|
+
counter(:foo).should == 3
|
38
|
+
counter(:bar).should == 2
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow resetting a single counter" do
|
42
|
+
counter(:foo).should == 1
|
43
|
+
counter(:bar).should == 1
|
44
|
+
Fixjour::Counter.reset :foo
|
45
|
+
counter(:foo).should == 1
|
46
|
+
counter(:bar).should == 2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "when Fixjour is included" do
|
51
|
+
include Fixjour
|
52
|
+
|
53
|
+
describe "new_* methods" do
|
54
|
+
it "generates new_[model] method" do
|
55
|
+
proc {
|
56
|
+
new_foo
|
57
|
+
}.should_not raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should raise a helpful error when the class can't be found" do
|
61
|
+
lambda {
|
62
|
+
Fixjour { define_builder(WhereOWhereAmI) }
|
63
|
+
}.should raise_error(NameError)
|
64
|
+
end
|
65
|
+
|
66
|
+
context "passing a builder block with one arg" do
|
67
|
+
context "when it returns a model object" do
|
68
|
+
before(:each) do
|
69
|
+
Fixjour.remove(Foo)
|
70
|
+
Fixjour do
|
71
|
+
define_builder(Foo) do |klass|
|
72
|
+
klass.new(:name => 'Foo Namery', :bar => new_bar)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "returns a new model object" do
|
78
|
+
new_foo.should be_kind_of(Foo)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "is a new record" do
|
82
|
+
new_foo.should be_new_record
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns defaults specified in block" do
|
86
|
+
new_foo.name.should == 'Foo Namery'
|
87
|
+
end
|
88
|
+
|
89
|
+
it "merges overrides" do
|
90
|
+
new_foo(:name => nil).name.should be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "is indifferent" do
|
94
|
+
new_foo('name' => nil).name.should be_nil
|
95
|
+
end
|
96
|
+
|
97
|
+
it "can be made invalid associated objects" do
|
98
|
+
new_foo(:bar => nil).should_not be_valid
|
99
|
+
end
|
100
|
+
|
101
|
+
it "allows access to other builders" do
|
102
|
+
bar = new_bar
|
103
|
+
mock(self).new_bar { bar }
|
104
|
+
new_foo.bar.should == bar
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when passed a hash" do
|
109
|
+
before(:each) do
|
110
|
+
Fixjour.remove(Foo)
|
111
|
+
Fixjour do
|
112
|
+
define_builder(Foo, :name => 'Foo Namery')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns a new model object" do
|
117
|
+
new_foo.should be_kind_of(Foo)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "is a new record" do
|
121
|
+
new_foo.should be_new_record
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns defaults specified in block" do
|
125
|
+
new_foo.name.should == 'Foo Namery'
|
126
|
+
end
|
127
|
+
|
128
|
+
it "merges overrides" do
|
129
|
+
new_foo(:name => nil).name.should be_nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when it returns a hash" do
|
134
|
+
before(:each) do
|
135
|
+
Fixjour.remove(Foo)
|
136
|
+
Fixjour do
|
137
|
+
define_builder(Foo) do |overrides|
|
138
|
+
{ :name => 'Foo Namery', :bar => new_bar }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "returns a new model object" do
|
144
|
+
new_foo.should be_kind_of(Foo)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "is a new record" do
|
148
|
+
new_foo.should be_new_record
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns defaults specified in block" do
|
152
|
+
new_foo.name.should == 'Foo Namery'
|
153
|
+
end
|
154
|
+
|
155
|
+
it "merges overrides" do
|
156
|
+
new_foo(:name => nil).name.should be_nil
|
157
|
+
end
|
158
|
+
|
159
|
+
it "can be made invalid associated objects" do
|
160
|
+
new_foo(:bar => nil).should_not be_valid
|
161
|
+
end
|
162
|
+
|
163
|
+
it "allows access to other builders" do
|
164
|
+
bar = new_bar
|
165
|
+
mock(self).new_bar { bar }
|
166
|
+
new_foo.bar.should == bar
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "passing a builder block with two args" do
|
172
|
+
before(:each) do
|
173
|
+
Fixjour.remove(Foo)
|
174
|
+
Fixjour do
|
175
|
+
define_builder(Foo) do |klass, overrides|
|
176
|
+
klass.new({ :name => 'Foo Namery', :bar => new_bar })
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
it "returns a new model object" do
|
182
|
+
new_foo.should be_kind_of(Foo)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "is a new record" do
|
186
|
+
new_foo.should be_new_record
|
187
|
+
end
|
188
|
+
|
189
|
+
it "returns defaults specified in block" do
|
190
|
+
new_foo.name.should == 'Foo Namery'
|
191
|
+
end
|
192
|
+
|
193
|
+
it "merges overrides" do
|
194
|
+
new_foo(:name => nil).name.should be_nil
|
195
|
+
end
|
196
|
+
|
197
|
+
it "is indifferent" do
|
198
|
+
new_foo('name' => nil).name.should be_nil
|
199
|
+
end
|
200
|
+
|
201
|
+
it "can be made invalid associated objects" do
|
202
|
+
new_foo(:bar => nil).should_not be_valid
|
203
|
+
end
|
204
|
+
|
205
|
+
it "allows access to other builders" do
|
206
|
+
bar = new_bar
|
207
|
+
mock(self).new_bar { bar }
|
208
|
+
new_foo.bar.should == bar
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe "create_* methods" do
|
214
|
+
it "calls new_* method then saves the result" do
|
215
|
+
# mocking here to make sure it's still using the new_person helper
|
216
|
+
# as opposed to calling Foo.new again. We don't want to duplicate
|
217
|
+
# that sort of behavior
|
218
|
+
mock(foo = Object.new).save!
|
219
|
+
mock(self).new_foo { foo }
|
220
|
+
|
221
|
+
create_foo
|
222
|
+
end
|
223
|
+
|
224
|
+
context "declared with a block" do
|
225
|
+
it "saves the record" do
|
226
|
+
foo = create_foo
|
227
|
+
foo.should_not be_new_record
|
228
|
+
end
|
229
|
+
|
230
|
+
it "retains defaults" do
|
231
|
+
create_foo.name.should == 'Foo Namery'
|
232
|
+
end
|
233
|
+
|
234
|
+
it "still allows options override" do
|
235
|
+
create_foo(:name => "created").name.should == "created"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context "declared with a hash" do
|
240
|
+
it "saves the record" do
|
241
|
+
bazz = create_bazz
|
242
|
+
bazz.should_not be_new_record
|
243
|
+
end
|
244
|
+
|
245
|
+
it "retains defaults" do
|
246
|
+
create_bazz.name.should == 'Bazz Namery'
|
247
|
+
end
|
248
|
+
|
249
|
+
it "still allows options override" do
|
250
|
+
create_bazz(:name => "created").name.should == "created"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe "valid_*_attributes" do
|
256
|
+
it "returns a hash containing the valid attributes specified in the builder" do
|
257
|
+
valid_foo_attributes[:name].should == new_foo.name
|
258
|
+
end
|
259
|
+
|
260
|
+
it "does not include attributes that aren't defined in the builder block" do
|
261
|
+
valid_foo_attributes.should_not have_key(:age)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "allows overrides" do
|
265
|
+
valid_foo_attributes(:name => "as attr")[:name].should == "as attr"
|
266
|
+
end
|
267
|
+
|
268
|
+
it "is indifferent" do
|
269
|
+
valid_foo_attributes[:name].should == valid_foo_attributes['name']
|
270
|
+
end
|
271
|
+
|
272
|
+
it "overrides indifferently" do
|
273
|
+
valid_foo_attributes("name" => "as attr")[:name].should == "as attr"
|
274
|
+
valid_foo_attributes(:name => "as attr")["name"].should == "as attr"
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "singular association ids" do
|
278
|
+
it "sets appropriate foreign keys" do
|
279
|
+
valid_foo_attributes[:bar].should be_nil
|
280
|
+
valid_foo_attributes[:bar_id].should_not be_nil
|
281
|
+
end
|
282
|
+
|
283
|
+
it "respects custom foreign keys" do
|
284
|
+
valid_foo_attributes[:owner].should be_nil
|
285
|
+
valid_foo_attributes[:person_id].should_not be_nil
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe "plural association ids" do
|
290
|
+
it "sets appropriate foreign keys" do
|
291
|
+
valid_bar_attributes[:people].should be_nil
|
292
|
+
valid_bar_attributes[:person_ids].should_not be_empty
|
293
|
+
end
|
294
|
+
|
295
|
+
it "leaves out blank keys" do
|
296
|
+
valid_bar_attributes.should_not have_key('foo_ids')
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe "returning new values every time" do
|
301
|
+
before(:each) do
|
302
|
+
FooBar.validates_uniqueness_of :name
|
303
|
+
create_foo_bar(valid_foo_bar_attributes)
|
304
|
+
end
|
305
|
+
|
306
|
+
it "returns new values every time" do
|
307
|
+
new_foo_bar(valid_foo_bar_attributes).should be_valid
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
context "declared with a hash" do
|
312
|
+
it "works the same way as builder block style" do
|
313
|
+
valid_bazz_attributes[:name].should == new_bazz.name
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe "Fixjour.builders" do
|
319
|
+
it "contains the classes for which there are builders" do
|
320
|
+
Fixjour.should have(5).builders
|
321
|
+
Fixjour.builders.values.map(&:klass).should include(Foo, Bar, Bazz)
|
322
|
+
end
|
323
|
+
|
324
|
+
context "when defining multiple builders for same class" do
|
325
|
+
it "raises RedundantBuilder error" do
|
326
|
+
proc {
|
327
|
+
Fixjour do
|
328
|
+
define_builder(Foo) { |overrides| Foo.new(:name => 'bad!') }
|
329
|
+
end
|
330
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe "redundancy checker" do
|
335
|
+
context "when :allow_redundancy is true" do
|
336
|
+
before(:each) do
|
337
|
+
Fixjour.builders.clear
|
338
|
+
end
|
339
|
+
|
340
|
+
it "doesn't blow up" do
|
341
|
+
proc {
|
342
|
+
Fixjour :allow_redundancy => true do
|
343
|
+
define_builder(Bar) { Bar.new }
|
344
|
+
define_builder(Bar) { Bar.new }
|
345
|
+
end
|
346
|
+
}.should_not raise_error
|
347
|
+
end
|
348
|
+
|
349
|
+
it "resets the settings when done" do
|
350
|
+
Fixjour :allow_redundancy => true do
|
351
|
+
define_builder(Bar) { Bar.new }
|
352
|
+
define_builder(Bar) { Bar.new }
|
353
|
+
end
|
354
|
+
|
355
|
+
proc {
|
356
|
+
Fixjour do
|
357
|
+
define_builder(Bar) { Bar.new }
|
358
|
+
end
|
359
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
360
|
+
end
|
361
|
+
|
362
|
+
it "can always allow redundancy" do
|
363
|
+
Fixjour do
|
364
|
+
allow_redundancy!
|
365
|
+
define_builder(Bar) { Bar.new }
|
366
|
+
end
|
367
|
+
proc {
|
368
|
+
Class.new {
|
369
|
+
include Fixjour
|
370
|
+
def new_bar(*args) end
|
371
|
+
}
|
372
|
+
}.should_not raise_error
|
373
|
+
end
|
374
|
+
|
375
|
+
it "can prohibit redundancy" do
|
376
|
+
Fixjour do
|
377
|
+
allow_redundancy!
|
378
|
+
prohibit_redundancy!
|
379
|
+
define_builder(Bar) { Bar.new }
|
380
|
+
end
|
381
|
+
proc {
|
382
|
+
Class.new {
|
383
|
+
include Fixjour
|
384
|
+
def new_bar(*args) end
|
385
|
+
}
|
386
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
describe "method_added hook" do
|
391
|
+
context "when it's already defined for the class" do
|
392
|
+
before(:each) do
|
393
|
+
@klass = Class.new do
|
394
|
+
def self.added_methods
|
395
|
+
@added_methods ||= []
|
396
|
+
end
|
397
|
+
|
398
|
+
def self.method_added(name)
|
399
|
+
added_methods << name
|
400
|
+
end
|
401
|
+
|
402
|
+
include Fixjour
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
it "does not lose old behavior" do
|
407
|
+
@klass.class_eval { def foo; :foo end }
|
408
|
+
@klass.added_methods.should include(:foo)
|
409
|
+
end
|
410
|
+
|
411
|
+
it "gets Fixjour behavior" do
|
412
|
+
foo = @klass.new.new_foo
|
413
|
+
foo.should be_kind_of(Foo)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
context "when the method is redundant" do
|
419
|
+
it "raises RedundantBuilder for new_*" do
|
420
|
+
proc {
|
421
|
+
self.class.class_eval do
|
422
|
+
def new_foo(overrides={}); Foo.new end
|
423
|
+
end
|
424
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
425
|
+
end
|
426
|
+
|
427
|
+
it "raises RedundantBuilder for new_ when there's an underscore" do
|
428
|
+
proc {
|
429
|
+
self.class.class_eval do
|
430
|
+
def new_foo_bar(overrides={}); end
|
431
|
+
end
|
432
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
433
|
+
end
|
434
|
+
|
435
|
+
it "raises RedundantBuilder for create_*" do
|
436
|
+
proc {
|
437
|
+
self.class.class_eval do
|
438
|
+
def create_foo(overrides={}); Foo.new end
|
439
|
+
end
|
440
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
441
|
+
end
|
442
|
+
|
443
|
+
it "raises RedundantBuilder for valid_*_attributes" do
|
444
|
+
proc {
|
445
|
+
self.class.class_eval do
|
446
|
+
def valid_foo_attributes(overrides={}); end
|
447
|
+
end
|
448
|
+
}.should raise_error(Fixjour::RedundantBuilder)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
context "when the method is not redundant" do
|
453
|
+
it "handles *similar* names" do
|
454
|
+
proc {
|
455
|
+
self.class.class_eval do
|
456
|
+
def new_foo_source(overrides={}); end
|
457
|
+
def choice_new_foo(overrides={}); end
|
458
|
+
end
|
459
|
+
}.should_not raise_error(Fixjour::RedundantBuilder)
|
460
|
+
end
|
461
|
+
|
462
|
+
it "does not raise error" do
|
463
|
+
proc {
|
464
|
+
self.class.class_eval do
|
465
|
+
def valid_nothing_attributes(overrides={}); Foo.new end
|
466
|
+
end
|
467
|
+
}.should_not raise_error
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
describe "a virtual attribute" do
|
474
|
+
before(:each) do
|
475
|
+
Fixjour.remove(Foo)
|
476
|
+
Foo.class_eval { attr_accessor :bizzle }
|
477
|
+
Fixjour do
|
478
|
+
define_builder(Foo) do |klass, overrides|
|
479
|
+
klass.new(:bizzle => 'fizzle')
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
it "gets preserved" do
|
485
|
+
new_foo.bizzle.should == 'fizzle'
|
486
|
+
end
|
487
|
+
|
488
|
+
it "is overrideable" do
|
489
|
+
new_foo(:bizzle => 'bliggety').bizzle.should == 'bliggety'
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
describe "protected attributes" do
|
494
|
+
before(:each) do
|
495
|
+
Fixjour.remove(Bar)
|
496
|
+
Bar.attr_protected :name
|
497
|
+
Fixjour do
|
498
|
+
define_builder(Bar) do |klass, overrides|
|
499
|
+
klass.protected :name
|
500
|
+
klass.new :name => "Protect me!"
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
it "returns default value" do
|
506
|
+
new_bar.name.should == "Protect me!"
|
507
|
+
end
|
508
|
+
|
509
|
+
it "can be overridden" do
|
510
|
+
new_bar(:name => 'pwnd').name.should == 'pwnd'
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
describe "processing overrides" do
|
515
|
+
before(:each) do
|
516
|
+
Fixjour.remove(Foo)
|
517
|
+
end
|
518
|
+
|
519
|
+
context "when the builder block has one arg" do
|
520
|
+
it "is raises DeprecatedMergeAttempt when #process is called" do
|
521
|
+
proc {
|
522
|
+
Fixjour.define_builder(Foo) do |overrides|
|
523
|
+
overrides.process(:alias) { |v| overrides[:name] = v }
|
524
|
+
end
|
525
|
+
|
526
|
+
new_foo
|
527
|
+
}.should raise_error(Fixjour::DeprecatedMergeAttempt)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
context "when the builder block has two args" do
|
532
|
+
before(:each) do
|
533
|
+
Fixjour do
|
534
|
+
define_builder(Foo) do |klass, overrides|
|
535
|
+
overrides.process(:alias) do |value|
|
536
|
+
overrides[:name] = value
|
537
|
+
end
|
538
|
+
|
539
|
+
klass.new(:name => "El Nameo!")
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
it "returns a new Fixjour::OverridesHash" do
|
545
|
+
mock.proxy(Fixjour::OverridesHash).new(:alias => "Bart Simpson")
|
546
|
+
new_foo(:alias => "Bart Simpson")
|
547
|
+
end
|
548
|
+
|
549
|
+
it "merges overrides" do
|
550
|
+
new_foo(:alias => "Bart Simpson").name.should == "Bart Simpson"
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|