fixjour 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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
@@ -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
@@ -1,6 +1,6 @@
1
1
  $LOAD_PATH << File.dirname(__FILE__)
2
2
 
3
- require 'active_record'
3
+ require 'activerecord'
4
4
  require 'core_ext/hash'
5
5
  require 'core_ext/object'
6
6
  require 'fixjour/merging_proxy'
@@ -21,7 +21,7 @@ module Fixjour
21
21
  attrs = defaults.merge(@overrides)
22
22
  accessible, inaccessible = partition(attrs)
23
23
 
24
- returning @klass.new(accessible) do |instance|
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
@@ -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
@@ -0,0 +1,4 @@
1
+ git://github.com/rspec/rspec.git
2
+ git://github.com/btakita/rr.git
3
+ git://github.com/nakajima/acts_as_fu.git
4
+ faker
@@ -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