fixjour 0.5.1 → 0.5.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/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
|