BasicAssumption is a gem meant to let you declare resources inside of a class in a concise manner. It is targetted at making Rails controllers and views cleaner.
It is a clone of DecentExposure that adds a psuedo-modular interface for providing custom defaults and some useful defaults out of the box.
It’s a plugin written by Stephen Caudill that provides a way of cleaning up Rails controller and view code. Find it at its GitHub repository.
DecentExposure coins an idiom for writing certain kinds of code in a declarative way. Particularly for your Rails apps, it’s worth checking out. BasicAssumption is another implementation of the idiom. Check the BasicAssumption::Configuration options for enabling compatibility with the DecentExposure API.
It’s a gem, so do the usual:
[sudo] gem install basic_assumption
For Rails 2, in environment.rb:
gem.config 'basic_assumption'
For Rails 3, in your Gemfile:
gem 'basic_assumption', :require => ['basic_assumption', 'basic_assumption/rails']
It’s possible to use BasicAssumption in your Rails 3 app without requiring ‘basic_assumption/rails’, and will in fact allow more flexibility if you don’t, but will incur the cost of additional setup. See the file ‘lib/basic_assumption/rails.rb’ for guidance.
To use the library in another context, it is enough to extend the BasicAssumption module inside the class you would like it available.
The presumed most-common use case for BasicAssumption is in a Rails app. By default, BasicAssumption is extended within ActionController::Base, making it available inside your controllers.
The most important (of the few) methods made available in controller classes is assume
, which is used to declaratively define a resource of some kind in controller instances and to make that resource available inside corresponding views. For all the wordiness of the description, it’s a simple concept, as illustrated below. First, we will use assume
to expose a resource inside our controller actions that will take the value resulting from the block passed to assume
. In this case, the resource will be called ‘widget’:
class WidgetController < ActionController::Base assume(:widget) { Widget.find(params[:id]) } ... def purchase current_user.purchase!(widget) render :template => 'widgets/purchase_complete' end end
And then inside of the ‘widgets/purchase_complete.html.haml’ view:
%h2= "#{current_user.name}, your purchase is complete! .widget %span#thanks = "Thank you for purchasing #{widget.name}!" %table#details %tr %td Cost %td= widget.cost %tr %td Manufacturer %td= widget.manufacturer
By calling assume
with the symbol :widget and passing it a block, an instance method widget
is created on the controller that is also exposed as a helper inside views.
A named resource created with assume
may be used in multiple controller actions, or the same view template or partial referencing the named resource may be rendered by more than one action. There will be times when the behavior given to assume
is correct for most cases save one or two. It’s possible to override the value of the resource method within a particular action to accommodate an exceptional case more easily. For example:
class WidgetController < ActionController::Base assume :widget def show end def show_mine self.widget = current_user.widgets.find(params[:widget_id]) render :action => 'show' end def destroy widget.destroy if widget.owned_by? current_user end end
In this case, the show_mine
action overrides the value of widget so that it may reuse the view template for the regular show
action. Overriding the assumed resource should be the exception, not the rule.
For more details on how BasicAssumption is wired into your Rails app, please see the BasicAssumption::Railtie documentation.
Whenever you find yourself writing a before_filter
in a Rails controller that sets instance variables as part of the context of your request, you should probably use an assumption instead. Because BasicAssumption is written to use lazy evaluation, there’s no need to worry about avoiding calls on actions that don’t need some particular setup.
For example, this:
class RecordController < ActionController::Base before_filter :find_record, :only => [:show, :edit, :update, :destroy] ... protected def find_record @record = Record.find(params[:record_id] || params[:id]) end end
would become this:
class RecordController < ActionController::Base assume :record end
and would provide the added benefit of not tossing instance variables around. If a controller has protected or hidden methods that find or create instance variables used in actions and/or views, it might be cleaner to use an assumption instead. This:
class CompanyController < ActionController::Base def show @company = Company.find(params[:company_id]) end def unique_groups @unique_groups = Group.unique_groups(@company) end helper_method :unique_groups hide_action :unique_groups end
could instead be written as:
class CompanyController < ActionController::Base assume :company assume(:unique_groups) { Group.unique_groups(company) } end
BasicAssumption allows for a simple, declarative, and very lightweight approach to RESTful controllers. It also tends to make for a cleaner, more testable interface for controller or view testing. There may even be uses outside of Rails apps. Give it a shot.
BasicAssumption allows for default behavior to be associated with methods created by assume
whenever a block is not passed. Here is a simple example:
class MariosController < ActionController::Base default_assumption { "It's a me, Mario!" } assume :mario ... end MariosController.new.mario #=> 'It's a me, Mario!'
In this case, any calls to assume
that don’t provide a block will create methods that return the string “It’s a me, Mario!”.
In addition to passing a default block, a symbol may be passed if it corresponds to a specifically-defined helper class that came packaged with the BasicAssumption library or was provided by the application as a custom default. See below for more information on providing custom defaults.
Specifying a built-in or application-defined default can be done on assume
calls as well.
assume :luigi, :using => :luigi_strategy
In Rails, a useful default is already active out of the box. It attempts to guess the name of a class derived from ActiveRecord::Base and perform a find on it based on an id available in the params
of the request. Because of this, the following two constructs would be equivalent in your controllers:
assume(:film) { Film.find(params[:film_id] || params[:id]) } # The above line is exactly the same as: assume(:film)
Please see Rails
for implementation details . Because finding on :id could be considered dangerous, there is another default available for use, CautiousRails
, that will only find on :name_id. (In the example above, this would be :film_id.) Enable that for one of your controllers like so:
class FilmController < ActionController::Base default_assumption :cautious_rails end
Another option is :restful_rails, which attempts to provide appropriate behavior for the basic RESTful actions. Please see RestfulRails
for a description of how it works.
Default assumptions are inherited by derived classes.
There is an ability to provide custom, modular default extensions to BasicAssumption and then use them by passing a symbol, as in the following:
class WidgetController < ActionController::Base default_assumption :my_custom_rails_default end
The symbol is converted to a class in the same manner as Rails classify/constantize operates, but it is looked up in the BasicAssumption::DefaultAssumption namespace. The following code implements the custom default specified in the preceding example. It reimplements the behavior that is active by default within Rails.
module BasicAssumption module DefaultAssumption class MyCustomRailsDefault def initialize(name=nil, params={}) @name = name.to_s @lookup = params['id'] end def block klass = self.class Proc.new do |name| klass.new(name, params).result end end def result model_class.find(@lookup) end # Rely on ActiveSupport methods def model_class name.classify.constantize end end end end
The only method that BasicAssumption depends on in the interface of custom default classes is the block
method. Note the hoops that have to be jumped through inside the implementation of block
in this example. Keep in mind the implications of evaluating the Proc
returned by block
using instance_eval
(or instance_exec
).
There are a couple of configuration settings that can be set inside of a configuration block that can be used in places such as Rails initializer blocks. #emulate_exposure! will alias assume
and default_assumption
to expose
and default_exposure
, respectively. You can also set the app-wide default behavior. For more information, see BasicAssumption::Configuration.
Methods that are created by BasicAssumption#assume memoize the result of the block the invoke when they’re called. Because of that, the block is only evaluated once during the lifespan of each object of the class that used assume
. This means that a method created by assuming can be used multiple times inside of a Rails controller object and associated view(s) without invoking the associated block multiple times, but it also means that any behavior of the block that is meant to vary over multiple invocations will not be observed.
Using BasicAssumption may change the exception handling strategy inside your classes. In Rails, the rescue_from
method may be useful.
There is nothing special about running the specs, aside from ensuring the RUBYOPT environment variable is set to your preferred Ruby dependency manager. For example, if that’s RubyGems:
export RUBYOPT=rubygems
If you’re unfamiliar with why this is being done, take a look here for a start. Other than that, fork away and send back pull requests! Thanks.
Sure! Absolutely. I think it’s a cool idea that lets you cut down on line noise, particularly in your Rails controllers. You may want to consider going with DecentExposure instead, simply because it’s used by much of Hashrocket. But so far as I know, BasicAssumption is pretty solid. Feel free to let me know if you use it! Email mby [at] mattyoho [dot] com with questions, comments, or non-sequiters.