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.
@@ -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