forked from marklar/action_args
-
Notifications
You must be signed in to change notification settings - Fork 0
glyde/action-args
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
= ActionArgs : RoR plugin =
== Motivation ==
Controller actions are special methods in that they interface with
external programs. In particular:
- They have no formal parameters to allow one to declare what input
they expect. Rather, they're provided a special hash-like object
called 'params' comprising all supplied arguments from the client.
- And all those arguments arrive as Strings (or collections of
same).
This means that the action has to do work to:
- Sort out what it was given.
- Determine whether it's sufficient and valid.
- Turn it into what it actually needs.
That's a lot of work for each action to have to do.
=== What Is Expected? ===
The client-supplied arguments are not guaranteed to match the action's
expectations. From the action's perspective, these arguments may fall
in one of three varieties:
- Required. Necessary for the action's proper functioning.
- Optional. Their mere presence or absence may impact the action's
behavior in addition to their value.
- Unused. From the action's perspective, they're spurious. (They
may be used only for logging, for example.)
The action must deal with these different varieties of arguments
differently.
=== Writing Actions ===
When writing controller actions, dealing with Unused arguments is
usually easy (ignore 'em), but with an important caveat: one must be
careful not to pass along the params hash intact to other code, as the
presence of spurious key-value pairs may induce improper behavior.
Dealing with Required and Optional arguments, however, is trickier.
- For Required args, one must explicitly check for their presence
before using them, or exception-handle if absent.
- For Optional args, one must:
- likewise check for their presence, and either
- possibly branch on their presence or absence, or
- supply a default value if absent.
- For both Required and Optional args, one
- must (usually) convert the Strings into values of the type one
really needs (such as ints or booleans or what-have-you).
- may need to validate those values before using them.
So writing good, robust actions is difficult work.
=== Reading Actions ===
Controller code is not only difficult to write, it can also be
difficult to understand.
First, this is because so much of the action code may be dedicated to
digesting the parameters that the (other) "real" work of the
controller is obscured.
Second, the action's expectations about arguments are not made
immediately apparent. Sometimes, an argument is expect from the
client but not used directly in the controller and instead passed
along to another object (or chain of them) which may ignore it, or
convert it to another type, or otherwise modify its value, and then
perhaps validate it before using it. Considerable detective work may
be necessary to determine which arguments are actually used and how.
=== ActionArgs to the Rescue ===
The goal of ActionArgs is to solve these problems. It's a
work-in-progress, but already it should prove useful for mitigating
some of these problems.
== How to Use It ==
Or, "Pulling Args from Params" (http://www.youtube.com/watch?v=3WngGeI9lnA).
=== Plugin ===
First, include the plugin:
<pre>
#!ruby
# app/controllers/application.rb
class ApplicationController < ActionController::Base
include ActionController::ArgyBargy
end
</pre>
=== Declare Your Args ===
Then, in your controller, you may:
- declare for each action:
- which arguments you expect, and
- some info about each
- access them via {{{params}}}' evil twin, {{{args}}}
Here's an excessively-commented example:
<pre>
#!ruby
# app/controllers/bojacks_controller.rb
class BojacksController < ApplicationController
VERTICALS = [:books, :games, :other_crap]
args_for :my_action do
# Required arg called :vertical. (If absent, raises ActionArgs::ArgumentError.)
# Value is a Symbol (converted from supplied String).
# Ensure value's validity or raise ActionArgs::ArgumentError.
req(:vertical).as(:symbol).validate {|s| VERTICALS.include? s }
# Optional string arg called :filter.
# If provided, its value is downcased automatically.
# If not provided, its value is nil.
opt(:filter).as(:string).munge(:downcase)
# Optional hash called :paging.
# If not provided, :paging is nil.
#
# One may not supply a default value for an entire (optional) hash.
# Instead:
# - make the hash required, but
# - make each of its k:v pairs optional (with defaults).
#
# (Here, however, the hash is optional, but if present,
# its members are required.)
opt_hash(:paging) do
# If :paging hash is present...
# Required int (Fixnum, really) called :offset.
# Must be non-negative (or raises ActionArgs::ArgumentError).
req(:offset).as(:int).validate {|i| i >= 0 }
# Required int called :limit.
# Must be positive (or raises ActionArgs::ArgumentError).
req(:limit).as(:int). validate {|i| i > 0 }
end
# Optional boolean arg called :show_related_p.
# If absent, default value is false.
opt(:show_related_p).as(:bool).default(false)
end
def my_action
# you can use...
args[:vertical] # a Symbol, one of VERTICALS
args[:filter] # nil -or- a downcased String
args[:paging] # nil -or- {:offset => <non-neg. int>, :limit => <pos. int>}
args[:show_related_p] # true -or- false
end
end
</pre>
== Exceptions ==
But wait, what if something goes wrong? What if a declaration makes no sense?
=== ActionArgs::ConfigError ===
If there's an error in an args_for() method -- rather, an error which
is detectable by the library --, then your app server will never start
up. ActionArgs will raise an exception of type ConfigError,
explaining what it thinks you did wrong. Some bojacked examples:
<pre>
#!ruby
args_for :action1
req(:foo).as(:int)
opt(:foo).as(:int) # repeated arg.
end
args_for :action2
req(:foo).default('bar') # required args cannot have defaults.
end
args_for :action3
opt(:id).as(:int).default(true) # default of wrong type.
end
args_for :action4
# default does not validate. (or really, the validate method is janky.)
opt(:vertical).as(:symbol).default(:books).validate {|sym| ['books', 'games'].include? sym }
end
</pre>
So that's good.
=== ActionArgs::ArgumentError ===
If your declaration seems sound, but the supplied parameters are not,
then the library will raise an exception of type
ActionArgs::ArgumentError. How to handle this? There are two
options:
- By default, the plugin will handle the exception for you in a
standard way, using ApplicationController#rescue_action_locally.
Your controller action code will never get called. If that's good
by you, you're golden.
- If you want your controller action to get called regardless of
ActionArgs errors, then you'll need to add to your
args_for() declaration, like this:
<pre>
#!ruby
args_for :my_action, :raise_p => false do
...
end
</pre>
If you tell #args_for not to raise, then your action code will be
called, and you may ask of the args object what happened by inspecting
the exceptions it gathered up (args.errors -- should it be named
args.exceptions instead?):
<pre>
#!ruby
def my_action
if !args.valid?
# action-specific exception-handling code...
errors_str = args.errors.map(&:to_s).join("\n")
render_json(:success => false,
:exception => "Some bojackedness occurred: #{errors_str}")
else
# "real" action code...
end
end
</pre>
And that's it.
About
Ruby on Rails. Wrangle the params to your controller actions.
Resources
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published