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