Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 397 lines (284 sloc) 13.763 kB
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
1 == BasicAssumption
ec468eb @mattyoho Added the start of an RDoc README
authored
2
10692e7 @mattyoho README cleanup
authored
3 BasicAssumption is a gem that lets you declare resources inside of a class
f105399 @mattyoho Update README
authored
4 in a concise manner. It implements an idiom for writing certain kinds of code
5 in a declarative way. In particular, it's meant to make Rails controllers and
6 views cleaner.
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
7
8 == Install BasicAssumption
9
10 It's a gem, so do the usual:
11
12 [sudo] gem install basic_assumption
13
14 === Using it in a Rails app
15
16 For Rails 2, in environment.rb:
17
18 gem.config 'basic_assumption'
19
20 For Rails 3, in your Gemfile:
21
dd47a09 @mattyoho Update README
authored
22 gem 'basic_assumption'
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
23
24 To use the library in another context, it is enough to extend the
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
25 BasicAssumption module inside the class you would like it available.
ec468eb @mattyoho Added the start of an RDoc README
authored
26
27 == Examples
28
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
29 === Inside a Rails controller
30
31 The presumed most-common use case for BasicAssumption is in a Rails app. By
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
32 default, BasicAssumption is extended within ActionController::Base, making it
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
33 available inside your controllers.
34
35 The most important (of the few) methods made available in controller classes is
36 +assume+, which is used to declaratively define a resource of some kind in
37 controller instances and to make that resource available inside corresponding
d560fb1 @mattyoho Doc fix and addition
authored
38 views. For all the wordiness of the description, it's a simple concept, as
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
39 illustrated below. First, we will use +assume+ to expose a resource inside our
40 controller actions that will take the value resulting from the block passed to
41 +assume+. In this case, the resource will be called 'widget':
42
43 class WidgetController < ActionController::Base
44
45 assume(:widget) { Widget.find(params[:id]) }
46
47 ...
48
49 def purchase
50 current_user.purchase!(widget)
51 render :template => 'widgets/purchase_complete'
52 end
53 end
54
55 And then inside of the 'widgets/purchase_complete.html.haml' view:
56
57 %h2= "#{current_user.name}, your purchase is complete!
58 .widget
59 %span#thanks
60 = "Thank you for purchasing #{widget.name}!"
61 %table#details
62 %tr
63 %td Cost
64 %td= widget.cost
65 %tr
66 %td Manufacturer
67 %td= widget.manufacturer
68
69 By calling +assume+ with the symbol :widget and passing it a block, an instance
30a6d81 @mattyoho Update README
authored
70 method +widget+ is created on the controller that is also exposed as a helper
956734f @mattyoho Doc update
authored
71 inside views.
72
73 ==== Special cases in controllers
74
75 A named resource created with +assume+ may be used in multiple controller
76 actions, or the same view template or partial referencing the named
77 resource may be rendered by more than one action. There will be times when
4831398 @mattyoho README update/fixes
authored
78 the behavior given to +assume+ is correct for most cases save one or two. It's
10692e7 @mattyoho README cleanup
authored
79 possible to override the value returned by the resource method within a
80 particular action to accommodate an exceptional case more easily. For example:
956734f @mattyoho Doc update
authored
81
82 class WidgetController < ActionController::Base
83 assume :widget
84
85 def show
86 end
87
88 def show_mine
89 self.widget = current_user.widgets.find(params[:widget_id])
90 render :action => 'show'
91 end
92
93 def destroy
94 widget.destroy if widget.owned_by? current_user
95 end
96 end
97
98 In this case, the +show_mine+ action overrides the value of widget so that it
99 may reuse the view template for the regular +show+ action. Overriding the
100 assumed resource should be the exception, not the rule.
101
3e2c20c @mattyoho Update doc for context
authored
102 ==== Using an alternative model name
103
104 BasicAssumption tends to assume a lot of things, including the name of the model
105 class a default +assume+ call should try to load. If you want the name given to
106 +assume+ to differ from the name of the model, use the optional context hash to
107 pass an +as+ option:
108
109 class WidgetController < ApplicationController
110 assume :sprocket, :as => :widget
111 end
112
113 This will create a +sprocket+ method in your actions and views that will use the
114 Widget model for its lookup.
115
956734f @mattyoho Doc update
authored
116 For more details on how BasicAssumption is wired into your Rails
7580afd @mattyoho Add license and tweak documentation
authored
117 app, please see the BasicAssumption::Railtie documentation.
118
119 === When to use it
120
121 Whenever you find yourself writing a +before_filter+ in a Rails controller
122 that sets instance variables as part of the context of your request, you should
10692e7 @mattyoho README cleanup
authored
123 probably use an assumption instead.
7580afd @mattyoho Add license and tweak documentation
authored
124
d560fb1 @mattyoho Doc fix and addition
authored
125 For example, this:
126
127 class RecordController < ActionController::Base
128 before_filter :find_record, :only => [:show, :edit, :update, :destroy]
129
130 ...
131
132 protected
133 def find_record
c855488 @mattyoho Update documentation
authored
134 @record = Record.find(params[:record_id])
d560fb1 @mattyoho Doc fix and addition
authored
135 end
136 end
137
138 would become this:
139
140 class RecordController < ActionController::Base
141 assume :record
142 end
143
4831398 @mattyoho README update/fixes
authored
144 and would provide the added benefit of not tossing instance variables around.
10692e7 @mattyoho README cleanup
authored
145 Because BasicAssumption is written to use lazy evaluation, there's no need to
146 worry about avoiding calls on actions that don't need some particular setup.
147
7580afd @mattyoho Add license and tweak documentation
authored
148 If a controller has protected or hidden methods that find or create instance
149 variables used in actions and/or views, it might be cleaner to use an
10692e7 @mattyoho README cleanup
authored
150 assumption. This:
45a9f05 @mattyoho Doc update
authored
151
152 class CompanyController < ActionController::Base
153
154 def show
155 @company = Company.find(params[:company_id])
156 end
157
158 def unique_groups
159 @unique_groups = Group.unique_groups(@company)
160 end
161 helper_method :unique_groups
162 hide_action :unique_groups
163
164 end
165
166 could instead be written as:
167
168 class CompanyController < ActionController::Base
169
170 assume :company
171 assume(:unique_groups) { Group.unique_groups(company) }
172
173 end
7580afd @mattyoho Add license and tweak documentation
authored
174
175 BasicAssumption allows for a simple, declarative, and very lightweight approach
45a9f05 @mattyoho Doc update
authored
176 to RESTful controllers. It also tends to make for a cleaner, more testable
177 interface for controller or view testing. There may even be uses outside of
178 Rails apps. Give it a shot.
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
179
4831398 @mattyoho README update/fixes
authored
180 == Defaults
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
181
182 BasicAssumption allows for default behavior to be associated with methods
183 created by +assume+ whenever a block is not passed. Here is a simple example:
184
45a9f05 @mattyoho Doc update
authored
185 class MariosController < ActionController::Base
186 default_assumption { "It's a me, Mario!" }
187 assume :mario
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
188
189 ...
190 end
191
45a9f05 @mattyoho Doc update
authored
192 MariosController.new.mario #=> 'It's a me, Mario!'
193
4831398 @mattyoho README update/fixes
authored
194 In this case, any calls to +assume+ that don't provide a block will create
45a9f05 @mattyoho Doc update
authored
195 methods that return the string "It's a me, Mario!".
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
196
45a9f05 @mattyoho Doc update
authored
197 In addition to passing a default block, a symbol may be passed if it corresponds
198 to a specifically-defined helper class that came packaged with the
199 BasicAssumption library or was provided by the application as a custom default.
200 See below for more information on providing custom defaults.
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
201
db70943 @mattyoho Update specifying defaults at the #assume level
authored
202 Specifying a built-in or application-defined default can be done on +assume+
203 calls as well.
204
205 assume :luigi, :using => :luigi_strategy
206
3e2c20c @mattyoho Update doc for context
authored
207 === Passing context to defaults
208
209 BasicAssumption supports passing a hash of arbitrary context information when
210 +assume+ is called without a block. This allows configuration or optional data
211 to be made available in default blocks. The built-in Rails defaults use this
212 to override the name of the model that is being worked with via the :as option.
213
214 Here is an example:
215
216 class Widget < ActiveRecord::Base
c855488 @mattyoho Update documentation
authored
217 named_scope :shiny, where(:glossy => true)
3e2c20c @mattyoho Update doc for context
authored
218 end
219
220 class WidgetController < ActionController::Base
221 default_assumption do |name, context|
222 name.to_s.classify.constantize.send(context[:type]).find(params[:id])
223 end
224
225 assume :widget, :type => :shiny
226 end
227
228 In this case, the lookups for +widget+ are scoped to ones that are shiny.
229
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
230 === In Rails
231
232 In Rails, a useful default is already active out of the box. It attempts to
4831398 @mattyoho README update/fixes
authored
233 guess the name of a class derived from ActiveRecord::Base and perform a find on
234 it based on an id available in the +params+ of the request. Because of this, the
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
235 following two constructs would be equivalent in your controllers:
236
237 assume(:film) { Film.find(params[:film_id] || params[:id]) }
238 # The above line is exactly the same as:
3e2c20c @mattyoho Update doc for context
authored
239 assume :film
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
240
c855488 @mattyoho Update documentation
authored
241 Please see +Rails+ for implementation details. Though it could be considered
242 a bit more dangerous to do, this standard Rails default will accept an option
243 :find_on_id, that will find on params[:id] as well as params[:name_id].
244 Enable that for one of your controllers like so:
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
245
246 class FilmController < ActionController::Base
c855488 @mattyoho Update documentation
authored
247 assume :film, :find_on_id => true
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
248 end
249
c855488 @mattyoho Update documentation
authored
250 It's also possible to have this behavior turned on by default via a
251 configuration setting, which may be convenient for backwards compatibility
252 with versions of BasicAssumption prior to 0.5.0. Similarly, there is a
253 raise_error setting that will cause any errors that result from the attempt
254 to find the record to bubble up; otherwise, they will be swallowed and the
255 assumption method will return nil.
256
b867df8 @mattyoho Add note on restful_rails to README
authored
257 Another option is :restful_rails, which attempts to provide appropriate
258 behavior for the basic RESTful actions. Please see +RestfulRails+ for a
259 description of how it works.
260
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
261 Default assumptions are inherited by derived classes.
262
4831398 @mattyoho README update/fixes
authored
263 == Supplying custom default behavior classes
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
264
265 There is an ability to provide custom, modular default extensions to
7580afd @mattyoho Add license and tweak documentation
authored
266 BasicAssumption and then use them by passing a symbol, as in the following:
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
267
268 class WidgetController < ActionController::Base
30a6d81 @mattyoho Update README
authored
269 default_assumption :my_custom_rails_default
d40a2ef @mattyoho Additional documentation and clean up of existing
authored
270 end
271
30a6d81 @mattyoho Update README
authored
272 The symbol is converted to a class in the same manner as Rails
273 classify/constantize operates, but it is looked up in the
274 BasicAssumption::DefaultAssumption namespace. The following code implements the
275 custom default specified in the preceding example. It reimplements the behavior
276 that is active by default within Rails.
277
278 module BasicAssumption
279 module DefaultAssumption
280 class MyCustomRailsDefault
281
282 def initialize(name=nil, params={})
283 @name = name.to_s
284 @lookup = params['id']
285 end
286
287 def block
288 klass = self.class
3e2c20c @mattyoho Update doc for context
authored
289 Proc.new do |name, context|
30a6d81 @mattyoho Update README
authored
290 klass.new(name, params).result
291 end
292 end
293
294 def result
295 model_class.find(@lookup)
296 end
297
298 # Rely on ActiveSupport methods
299 def model_class
300 name.classify.constantize
301 end
302 end
303 end
304 end
305
306 The only method that BasicAssumption depends on in the interface of custom
3e2c20c @mattyoho Update doc for context
authored
307 default classes is the +block+ method. It should return a Proc that accepts a
308 a symbol/string name and a context hash. Note the hoops that have to be jumped
30a6d81 @mattyoho Update README
authored
309 through inside the implementation of +block+ in this example. Keep in mind the
310 implications of evaluating the +Proc+ returned by +block+ using +instance_eval+
3e2c20c @mattyoho Update doc for context
authored
311 (or +instance_exec+), and enclose any data the block may need at runtime.
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
312
4831398 @mattyoho README update/fixes
authored
313 == Configuration
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
314
30a6d81 @mattyoho Update README
authored
315 There are a couple of configuration settings that can be set inside of
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
316 a configuration block that can be used in places such as Rails initializer
c855488 @mattyoho Update documentation
authored
317 blocks. #alias_assume_to will alias +assume+ to other names. The example
318 below would alias it to +expose+ and +reveal+. You can also set the
319 app-wide default behavior. For more information, see
320 BasicAssumption::Configuration.
321
322 BasicAssumption::Configuration.configure do |conf|
323 conf.default_assumption = Proc.new { "I <3 GitHub." }
324
325 conf.alias_assume_to :expose, :reveal
326 end
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
327
4831398 @mattyoho README update/fixes
authored
328 == Issues to note
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
329
330 === Memoization
331
7580afd @mattyoho Add license and tweak documentation
authored
332 Methods that are created by BasicAssumption#assume memoize the result of the
333 block the invoke when they're called. Because of that, the block is only
334 evaluated once during the lifespan of each object of the class that used
335 +assume+. This means that a method created by assuming can be used multiple
336 times inside of a Rails controller object and associated view(s) without
337 invoking the associated block multiple times, but it also means that any
338 behavior of the block that is meant to vary over multiple invocations will not
339 be observed.
340
341 === Exceptions
342
343 Using BasicAssumption may change the exception handling strategy inside your
344 classes. In Rails, the +rescue_from+ method may be useful.
345
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
346 == Hacking/running specs
347
348 There is nothing special about running the specs, aside from ensuring the
349 RUBYOPT environment variable is set to your preferred Ruby dependency
4831398 @mattyoho README update/fixes
authored
350 manager. For example, if that's RubyGems:
82859a8 @mattyoho Switch to README.rdoc for intro documentation
authored
351
352 export RUBYOPT=rubygems
353
61ee28e @mattyoho Doc tweak
authored
354 If you're unfamiliar with why this is being done, take a look
355 {here for a start}[http://tomayko.com/writings/require-rubygems-antipattern].
dcf0c00 @mattyoho Update README to reference cukes for dev
authored
356
357 There is also a Cucumber suite that can be run to check BasicAssumption against
3506330 @mattyoho Use Bundler to manage dependencies
authored
358 an actual Rails app.
dcf0c00 @mattyoho Update README to reference cukes for dev
authored
359
360 There is an .rvmrc file in the repository that will require a basic_assumption
361 gemset if you're using RVM, which will help to manage the gem dependencies.
362
798c189 @mattyoho No need to reference old Bundler version
authored
363 The test suites are dependent on the Bundler gem.
3506330 @mattyoho Use Bundler to manage dependencies
authored
364
798c189 @mattyoho No need to reference old Bundler version
authored
365 gem install bundler
3506330 @mattyoho Use Bundler to manage dependencies
authored
366
5adcaf2 @mattyoho Doc update
authored
367 It is highly recommended to use RVM to manage development BasicAssumption against
368 various Rails and Ruby versions. Run the following command to create an
369 appropriate RVM gemset and receive a command to run manually that selects that
370 gemset:
371
372 rake rvm:gemset
373
3506330 @mattyoho Use Bundler to manage dependencies
authored
374 To run the Cucumber and spec suites for the first time, use these Rake tasks:
cda9b89 @mattyoho Update README for running cukes
authored
375
5adcaf2 @mattyoho Doc update
authored
376 rake init:rails2 #or rake init:rails3
3506330 @mattyoho Use Bundler to manage dependencies
authored
377 rake
378
379 Note that the +init+ task will +bundle+ +install+ the development dependencies,
380 which includes +basic_assumption+ itself. Using the RVM gemset is recommended.
cda9b89 @mattyoho Update README for running cukes
authored
381
3506330 @mattyoho Use Bundler to manage dependencies
authored
382 This will create an example Rails app in ./tmp and run the suites against it.
383 Use +rake+ +spec+ to run BasicAssumption's specs, +rake+ +cucumber+ to run the
384 cukes, or +rake+ to run specs and cukes.
cda9b89 @mattyoho Update README for running cukes
authored
385
386 Feel free to fork away and send back pull requests, including specs! Thanks.
7580afd @mattyoho Add license and tweak documentation
authored
387
388 == But should I use it?
389
390 Sure! Absolutely. I think it's a cool idea that lets you cut down on line
f105399 @mattyoho Update README
authored
391 noise, particularly in your Rails controllers. You may also want to look at
392 {DecentExposure}[http://github.com/voxdolo/decent_exposure], the
393 project BasicAssumption is based on, written by {Stephen Caudill}[http://voxdolo.me/]
394 of {Hashrocket}[http://www.hashrocket.com/]. Feel free to let me know
4831398 @mattyoho README update/fixes
authored
395 if you use it! Email mby [at] mattyoho [dot] com with questions, comments, or
396 non-sequiters.
Something went wrong with that request. Please try again.