The intent is as follows: for each request to a Rails controller, redirect unless all of the URL query parameters you get are valid.
And this is how it actually works:
- When Rails loads the FooController class:
- FooController mixes in the ParamsFilter module, resulting in it getting the known_params and parse_known_params class methods and the redirect_if_unknown_params and select_unknown instance methods (et al.).
- FooController calls the known_params class method to always accept the "limit" and "offset" params, but only accept the "bar" param when the Foo object is of type "bar".
- On each request:
- FooController's before_filter calls its redirect_if_unknown_params instance method.
- redirect_if_unknown_params rejects unknown parameters using the private select_unknown instance method.
- select_unknown calls known_param? on each param.
- If the param has a :when component, known_param? executes its value (which is a block)--and here's the cool part--in the context of the controller instance! This is cool because Proc objects normally operate in the context in which they were defined (i.e. they are closures). Calling instance_eval or instance_exec with a Proc argument (which is not the same thing as a block!) results in the Proc's binding being changed to context where it is called. This results in self, which was the FooController class when the Proc was defined, being set to the FooController instance on which known_param? is executing.
And now, the code:
app/controllers/foo_controller.rb
class FooController < ApplicationController
include ParamsFilter
before_filter :redirect_if_unknown_params
known_params :limit, :offset, :only => :bar, :when => lambda {@type == 'bar'}
# [...]
lib/params_filter.rb
module ParamsFilter
def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
attr_reader :known_params
def known_params(*args, &block)
@@known_params ||= HashWithIndifferentAccess.new
@@known_params.merge! parse_known_params(*args, &block)
end
def parse_known_params(*args, &block)
HashWithIndifferentAccess.new.tap do |known_params|
options = (args.last.is_a? Hash) ? args.pop.dup : {}
options[:when] = block if block_given?
args.each do |param|
known_params[param] = options
end
end
end
end
module InstanceMethods
def known_params(*args)
@known_params ||= HashWithIndifferentAccess.new
@known_params.merge! self.class.parse_known_params(*args)
end
def redirect_if_unknown_params
unknown = select_unknown request.query_parameters, request.path_parameters
redirect_without_unknown_params unknown unless unknown.empty?
end
private
def select_unknown(query_params = {}, path_params = {})
query_params.reject {|param, _| known_param? param, path_params}
end
def known_param?(param, params)
if options = param_options(param)
if action_matches_params? options[:only], params
options[:when].nil? || instance_exec(param, &options[:when])
end
end
end
def redirect_without_unknown_params(unknown_params)
# Actually perform redirect
end
# [...]
end
end
No comments:
Post a Comment