merb 0.3.4 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README +206 -197
- data/Rakefile +12 -21
- data/bin/merb +1 -1
- data/examples/skeleton/Rakefile +6 -20
- data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
- data/examples/skeleton/dist/conf/database.yml +23 -0
- data/examples/skeleton/dist/conf/environments/development.rb +1 -0
- data/examples/skeleton/dist/conf/environments/production.rb +1 -0
- data/examples/skeleton/dist/conf/environments/test.rb +1 -0
- data/examples/skeleton/dist/conf/merb.yml +32 -28
- data/examples/skeleton/dist/conf/merb_init.rb +16 -13
- data/examples/skeleton/dist/conf/router.rb +9 -9
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
- data/lib/merb.rb +23 -18
- data/lib/merb/caching/fragment_cache.rb +3 -7
- data/lib/merb/caching/store/memcache.rb +20 -0
- data/lib/merb/core_ext/merb_array.rb +0 -0
- data/lib/merb/core_ext/merb_class.rb +44 -4
- data/lib/merb/core_ext/merb_enumerable.rb +43 -1
- data/lib/merb/core_ext/merb_hash.rb +200 -122
- data/lib/merb/core_ext/merb_kernel.rb +2 -0
- data/lib/merb/core_ext/merb_module.rb +41 -0
- data/lib/merb/core_ext/merb_numeric.rb +57 -5
- data/lib/merb/core_ext/merb_object.rb +172 -6
- data/lib/merb/generators/merb_app/merb_app.rb +15 -9
- data/lib/merb/merb_abstract_controller.rb +193 -0
- data/lib/merb/merb_constants.rb +26 -1
- data/lib/merb/merb_controller.rb +143 -234
- data/lib/merb/merb_dispatcher.rb +28 -20
- data/lib/merb/merb_drb_server.rb +2 -3
- data/lib/merb/merb_exceptions.rb +194 -49
- data/lib/merb/merb_handler.rb +34 -26
- data/lib/merb/merb_mail_controller.rb +200 -0
- data/lib/merb/merb_mailer.rb +33 -13
- data/lib/merb/merb_part_controller.rb +42 -0
- data/lib/merb/merb_plugins.rb +293 -0
- data/lib/merb/merb_request.rb +6 -4
- data/lib/merb/merb_router.rb +99 -65
- data/lib/merb/merb_server.rb +65 -21
- data/lib/merb/merb_upload_handler.rb +2 -1
- data/lib/merb/merb_view_context.rb +36 -15
- data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
- data/lib/merb/mixins/controller_mixin.rb +67 -28
- data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
- data/lib/merb/mixins/form_control_mixin.rb +280 -42
- data/lib/merb/mixins/render_mixin.rb +127 -45
- data/lib/merb/mixins/responder_mixin.rb +5 -7
- data/lib/merb/mixins/view_context_mixin.rb +260 -94
- data/lib/merb/session.rb +23 -0
- data/lib/merb/session/merb_ar_session.rb +28 -16
- data/lib/merb/session/merb_mem_cache_session.rb +108 -0
- data/lib/merb/session/merb_memory_session.rb +65 -20
- data/lib/merb/template/erubis.rb +22 -13
- data/lib/merb/template/haml.rb +5 -16
- data/lib/merb/template/markaby.rb +5 -3
- data/lib/merb/template/xml_builder.rb +17 -5
- data/lib/merb/test/merb_fake_request.rb +63 -0
- data/lib/merb/test/merb_multipart.rb +58 -0
- data/lib/tasks/db.rake +2 -0
- data/lib/tasks/merb.rake +20 -8
- metadata +24 -25
- data/examples/skeleton.tar +0 -0
data/lib/merb/merb_constants.rb
CHANGED
@@ -21,5 +21,30 @@ module Merb
|
|
21
21
|
HOUR = 60*60
|
22
22
|
DAY = HOUR*24
|
23
23
|
WEEK = DAY*7
|
24
|
+
MULTIPART_REGEXP = /\Amultipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
|
25
|
+
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
26
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
27
|
+
APPLICATION_JSON = 'application/json'.freeze
|
28
|
+
TEXT_JSON = 'text/x-json'.freeze
|
29
|
+
APPLICATION_XML = 'application/xml'.freeze
|
30
|
+
TEXT_XML = 'text/xml'.freeze
|
31
|
+
UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
32
|
+
CONTENT_TYPE = "Content-Type".freeze
|
33
|
+
LAST_MODIFIED = "Last-Modified".freeze
|
34
|
+
SLASH = "/".freeze
|
35
|
+
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
36
|
+
GET = "GET".freeze
|
37
|
+
POST = "POST".freeze
|
38
|
+
HEAD = "HEAD".freeze
|
39
|
+
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
40
|
+
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
|
41
|
+
HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze
|
42
|
+
HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze
|
43
|
+
UPLOAD_ID = 'upload_id'.freeze
|
44
|
+
PATH_INFO="PATH_INFO".freeze
|
45
|
+
SCRIPT_NAME="SCRIPT_NAME".freeze
|
46
|
+
REQUEST_URI='REQUEST_URI'.freeze
|
47
|
+
REQUEST_PATH='REQUEST_PATH'.freeze
|
48
|
+
REMOTE_ADDR="REMOTE_ADDR".freeze
|
24
49
|
end
|
25
|
-
end
|
50
|
+
end
|
data/lib/merb/merb_controller.rb
CHANGED
@@ -1,297 +1,206 @@
|
|
1
1
|
require File.dirname(__FILE__)+'/mixins/controller_mixin'
|
2
|
-
require File.dirname(__FILE__)+'/mixins/render_mixin'
|
3
2
|
require File.dirname(__FILE__)+'/mixins/responder_mixin'
|
4
3
|
require File.dirname(__FILE__)+'/merb_request'
|
5
|
-
|
4
|
+
require File.dirname(__FILE__)+'/merb_exceptions'
|
5
|
+
require 'set'
|
6
6
|
module Merb
|
7
7
|
|
8
|
-
# All of your controllers will inherit from Merb::Controller. This
|
8
|
+
# All of your web controllers will inherit from Merb::Controller. This
|
9
9
|
# superclass takes care of parsing the incoming headers and body into
|
10
10
|
# params and cookies and headers. If the request is a file upload it will
|
11
11
|
# stream it into a tempfile and pass in the filename and tempfile object
|
12
12
|
# to your controller via params. It also parses the ?query=string and
|
13
13
|
# puts that into params as well.
|
14
|
-
class Controller
|
15
|
-
|
16
|
-
class_inheritable_accessor :
|
17
|
-
:_session_id_key,
|
18
|
-
:_template_extensions,
|
19
|
-
:_template_root,
|
20
|
-
:_layout_root
|
21
|
-
self._layout = :application
|
14
|
+
class Controller < AbstractController
|
15
|
+
|
16
|
+
class_inheritable_accessor :_session_id_key, :_session_expiry
|
22
17
|
self._session_id_key = :_session_id
|
23
|
-
self.
|
24
|
-
|
25
|
-
self._layout_root = File.expand_path(MERB_VIEW_ROOT / "layout")
|
26
|
-
|
18
|
+
self._session_expiry = Time.now + Merb::Const::WEEK * 2
|
19
|
+
|
27
20
|
include Merb::ControllerMixin
|
28
|
-
include Merb::RenderMixin
|
29
21
|
include Merb::ResponderMixin
|
30
|
-
|
31
|
-
|
22
|
+
include Merb::ControllerExceptions::HTTPErrors
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def callable_actions
|
26
|
+
@callable_actions ||= Set.new(public_instance_methods - hidden_actions)
|
27
|
+
end
|
28
|
+
|
29
|
+
def hidden_actions
|
30
|
+
write_inheritable_attribute(:hidden_actions, Merb::Controller.public_instance_methods) unless read_inheritable_attribute(:hidden_actions)
|
31
|
+
read_inheritable_attribute(:hidden_actions)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Hide each of the given methods from being callable as actions.
|
35
|
+
def hide_action(*names)
|
36
|
+
write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect { |n| n.to_s })
|
37
|
+
end
|
38
|
+
|
39
|
+
def build(req, env, args, resp)
|
40
|
+
cont = new
|
41
|
+
cont.parse_request(req, env, args, resp)
|
42
|
+
cont
|
43
|
+
end
|
44
|
+
end
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
def initialize(request, env, args, response)
|
40
|
-
@env = MerbHash[env.to_hash]
|
41
|
-
@status, @method, @response, @headers = 200, (env[Mongrel::Const::REQUEST_METHOD]||Mongrel::Const::GET).downcase.to_sym, response,
|
46
|
+
# Parses the http request into params, headers and cookies that you can use
|
47
|
+
# in your controller classes. Also handles file uploads by writing a
|
48
|
+
# tempfile and passing a reference in params.
|
49
|
+
def parse_request(req, env, args, resp)
|
50
|
+
env = env.to_hash
|
51
|
+
@_status, method, @_response, @_headers = 200, (env[Merb::Const::REQUEST_METHOD]||Merb::Const::GET).downcase.to_sym, resp,
|
42
52
|
{'Content-Type' =>'text/html'}
|
43
|
-
cookies = query_parse(
|
44
|
-
querystring = query_parse(
|
53
|
+
cookies = query_parse(env[Merb::Const::HTTP_COOKIE], ';,')
|
54
|
+
querystring = query_parse(env[Merb::Const::QUERY_STRING])
|
45
55
|
|
46
|
-
if MULTIPART_REGEXP =~
|
47
|
-
querystring.update(parse_multipart(
|
48
|
-
elsif
|
49
|
-
if [
|
56
|
+
if Merb::Const::MULTIPART_REGEXP =~ env[Merb::Const::UPCASE_CONTENT_TYPE] && [:put,:post].include?(method)
|
57
|
+
querystring.update(parse_multipart(req, $1, env))
|
58
|
+
elsif [:post, :put].include?(method)
|
59
|
+
if [Merb::Const::APPLICATION_JSON, Merb::Const::TEXT_JSON].include?(env[Merb::Const::UPCASE_CONTENT_TYPE])
|
50
60
|
MERB_LOGGER.info("JSON Request")
|
51
|
-
json = JSON.parse(
|
52
|
-
json = MerbHash.new(json) if json.is_a? Hash
|
61
|
+
json = JSON.parse(req.read || "") || {}
|
53
62
|
querystring.update(json)
|
54
|
-
elsif [
|
55
|
-
querystring.update(Hash.from_xml(
|
63
|
+
elsif [Merb::Const::APPLICATION_XML, Merb::Const::TEXT_XML].include?(env[Merb::Const::UPCASE_CONTENT_TYPE])
|
64
|
+
querystring.update(Hash.from_xml(req.read).with_indifferent_access)
|
56
65
|
else
|
57
|
-
querystring.update(query_parse(
|
66
|
+
querystring.update(query_parse(req.read))
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
61
|
-
@
|
62
|
-
|
70
|
+
@_cookies, @_params = cookies.symbolize_keys!, querystring.update(args).symbolize_keys!
|
71
|
+
|
72
|
+
if @_params.key?(_session_id_key) && !Merb::Server.config[:session_id_cookie_only]
|
73
|
+
@_cookies[_session_id_key] = @_params[_session_id_key]
|
74
|
+
elsif @_params.key?(_session_id_key) && Merb::Server.config[:session_id_cookie_only]
|
75
|
+
# This condition allows for certain controller/action paths to allow a
|
76
|
+
# session ID to be passed in a query string. This is needed for Flash
|
77
|
+
# Uploads to work since flash will not pass a Session Cookie Recommend
|
78
|
+
# running session.regenerate after any controller taking advantage of
|
79
|
+
# this in case someone is attempting a session fixation attack
|
80
|
+
@_cookies[_session_id_key] = @_params[_session_id_key] if Merb::Server.config[:query_string_whitelist].include?("#{params[:controller]}/#{params[:action]}")
|
81
|
+
end
|
63
82
|
|
83
|
+
# Handle alternate HTTP method passed as _method parameter. Doesn't allow
|
84
|
+
# method to be overridden for :get unless Merb is in development mode.
|
85
|
+
#
|
86
|
+
# i.e. You can pass _method=put on the querystring if you are in
|
87
|
+
# development mode.
|
64
88
|
allow = [:post, :put, :delete]
|
65
89
|
allow << :get if MERB_ENV == 'development'
|
66
|
-
if @
|
67
|
-
|
90
|
+
if @_params.key?(:_method) && allow.include?(method)
|
91
|
+
method = @_params.delete(:_method).downcase.intern
|
68
92
|
end
|
69
|
-
@
|
70
|
-
|
93
|
+
@_request = Request.new(env, method, req)
|
71
94
|
MERB_LOGGER.info("Params: #{params.inspect}\nCookies: #{cookies.inspect}")
|
72
95
|
end
|
96
|
+
|
73
97
|
|
74
|
-
|
98
|
+
|
99
|
+
def dispatch(action=:index)
|
75
100
|
start = Time.now
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
else
|
90
|
-
raise MerbControllerError, "The before filter chain is broken dude. wtf?"
|
101
|
+
begin
|
102
|
+
if !self.class.callable_actions.include?(action.to_s)
|
103
|
+
raise NotFound
|
104
|
+
MERB_LOGGER.info "Action: #{action} not in callable_actions: #{self.class.callable_actions}"
|
105
|
+
else
|
106
|
+
setup_session
|
107
|
+
super(action)
|
108
|
+
finalize_session
|
109
|
+
end
|
110
|
+
rescue ControllerExceptions::Base => e
|
111
|
+
e.set_controller(self) # for access to session, params, etc
|
112
|
+
@_body = e.call_action
|
113
|
+
set_status(e.status)
|
91
114
|
end
|
92
|
-
|
93
|
-
|
94
|
-
MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{
|
115
|
+
|
116
|
+
@_benchmarks[:action_time] = Time.now - start
|
117
|
+
MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{@_benchmarks[:action_time]} seconds")
|
118
|
+
end
|
119
|
+
|
120
|
+
# Accessor for @_body. Please use status and never @status directly.
|
121
|
+
def body
|
122
|
+
@_body
|
95
123
|
end
|
96
124
|
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
"<html><body><h1>Filter Chain Halted!</h1></body></html>"
|
125
|
+
# Accessor for @_status. Please use status and never @_status directly.
|
126
|
+
def status
|
127
|
+
@_status
|
101
128
|
end
|
102
129
|
|
103
|
-
|
104
|
-
#
|
130
|
+
|
131
|
+
# Accessor for @_request. Please use request and never @_request directly.
|
105
132
|
def request
|
106
|
-
@
|
133
|
+
@_request
|
107
134
|
end
|
108
|
-
|
109
|
-
#
|
110
|
-
# never @params directly.
|
135
|
+
|
136
|
+
# Accessor for @_params. Please use params and never @_params directly.
|
111
137
|
def params
|
112
|
-
@
|
113
|
-
end
|
138
|
+
@_params
|
139
|
+
end
|
114
140
|
|
115
|
-
#
|
116
|
-
# never @cookies directly.
|
141
|
+
# Accessor for @_cookies. Please use cookies and never @_cookies directly.
|
117
142
|
def cookies
|
118
|
-
@
|
119
|
-
end
|
120
|
-
|
121
|
-
#
|
122
|
-
# never @headers directly.
|
143
|
+
@_cookies
|
144
|
+
end
|
145
|
+
|
146
|
+
# Accessor for @_headers. Please use headers and never @_headers directly.
|
123
147
|
def headers
|
124
|
-
@
|
148
|
+
@_headers
|
125
149
|
end
|
126
150
|
|
127
|
-
#
|
128
|
-
# never @session directly.
|
151
|
+
# Accessor for @_session. Please use session and never @_session directly.
|
129
152
|
def session
|
130
|
-
@
|
153
|
+
@_session
|
131
154
|
end
|
132
155
|
|
133
|
-
#
|
134
|
-
# never @response directly.
|
156
|
+
# Accessor for @_response. Please use response and never @_response directly.
|
135
157
|
def response
|
136
|
-
@
|
158
|
+
@_response
|
137
159
|
end
|
138
160
|
|
139
|
-
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
#
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
161
|
+
# Sends a mail from a MailController
|
162
|
+
#
|
163
|
+
# send_mail FooMailer, :bar, :from => "foo@bar.com", :to => "baz@bat.com"
|
164
|
+
#
|
165
|
+
# would send an email via the FooMailer's bar method.
|
166
|
+
#
|
167
|
+
# The mail_params hash would be sent to the mailer, and includes items
|
168
|
+
# like from, to subject, and cc. See
|
169
|
+
# Merb::MailController#dispatch_and_deliver for more details.
|
170
|
+
#
|
171
|
+
# The send_params hash would be sent to the MailController, and is
|
172
|
+
# available to methods in the MailController as <tt>params</tt>. If you do
|
173
|
+
# not send any send_params, this controller's params will be available to
|
174
|
+
# the MailController as <tt>params</tt>
|
175
|
+
def send_mail(klass, method, mail_params, send_params = nil)
|
176
|
+
klass.new(send_params || params, self).dispatch_and_deliver(method, mail_params)
|
156
177
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
# shared_accessor sets up a class instance variable that can
|
161
|
-
# be unique for each class but also inherits the shared attrs
|
162
|
-
# from its superclasses. Since @@class variables are almost
|
163
|
-
# global vars within an inheritance tree, we use
|
164
|
-
# @class_instance_variables instead
|
165
|
-
class_inheritable_accessor :before_filters
|
166
|
-
class_inheritable_accessor :after_filters
|
167
|
-
|
168
|
-
# calls a filter chain according to rules.
|
169
|
-
def call_filters(filter_set)
|
170
|
-
(filter_set || []).each do |(filter, rule)|
|
171
|
-
ok = false
|
172
|
-
if rule.has_key?(:only)
|
173
|
-
if rule[:only].include?(params[:action].intern)
|
174
|
-
ok = true
|
175
|
-
end
|
176
|
-
elsif rule.has_key?(:exclude)
|
177
|
-
if !rule[:exclude].include?(params[:action].intern)
|
178
|
-
ok = true
|
179
|
-
end
|
180
|
-
else
|
181
|
-
ok = true
|
182
|
-
end
|
183
|
-
if ok
|
184
|
-
case filter
|
185
|
-
when Symbol, String
|
186
|
-
send(filter)
|
187
|
-
when Proc
|
188
|
-
filter.call(self)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
return :filter_chain_completed
|
193
|
-
end
|
194
178
|
|
195
|
-
#
|
196
|
-
# filters in your controllers. Filters can either be a symbol
|
197
|
-
# or string that corresponds to a method name to call, or a
|
198
|
-
# proc object. if it is a method name that method will be
|
199
|
-
# called and if it is a proc it will be called with an argument
|
200
|
-
# of self where self is the current controller object. When
|
201
|
-
# you use a proc as a filter it needs to take one parameter.
|
179
|
+
# Dispatches a PartController. Use like:
|
202
180
|
#
|
203
|
-
#
|
204
|
-
# before :some_filter
|
205
|
-
# before :authenticate, :exclude => [:login, :signup]
|
206
|
-
# before Proc.new {|c| c.some_method }, :only => :foo
|
181
|
+
# <%= part TodoPart => :list %>
|
207
182
|
#
|
208
|
-
#
|
209
|
-
#
|
210
|
-
# and :exclude will run for every action that is not listed.
|
183
|
+
# will instantiate a new TodoPart controller and call the :list action
|
184
|
+
# invoking the Part's before and after filters as part of the call.
|
211
185
|
#
|
212
|
-
#
|
213
|
-
# filter chain you use throw :halt . If throw is called with
|
214
|
-
# only one argument of :halt the return of the method filters_halted
|
215
|
-
# will be what is rendered to the view. You can overide filters_halted
|
216
|
-
# in your own controllers to control what it outputs. But the throw
|
217
|
-
# construct is much more powerful then just that. throw :halt can
|
218
|
-
# also take a second argument. Here is what that second arg can be
|
219
|
-
# and the behavior each type can have:
|
186
|
+
# returns a string containing the results of the Part controllers dispatch
|
220
187
|
#
|
221
|
-
#
|
222
|
-
#
|
223
|
-
# a string you can render a template or just use a plain string:
|
188
|
+
# You can compose parts easily as well, these two parts will stil be wrapped
|
189
|
+
# in the layout of the Foo controller:
|
224
190
|
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
191
|
+
# class Foo < Application
|
192
|
+
# def some_action
|
193
|
+
# wrap_layout(part(TodoPart => :new) + part(TodoPart => :list))
|
194
|
+
# end
|
195
|
+
#end
|
228
196
|
#
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
# throw :halt, :must_click_disclaimer
|
233
|
-
#
|
234
|
-
# If the second arg is a Proc, it will be called and its return
|
235
|
-
# value will be what is rendered to the browser:
|
236
|
-
# Proc:
|
237
|
-
# throw :halt, Proc.new {|c| c.access_denied }
|
238
|
-
# throw :halt, Proc.new {|c| Tidy.new(c.index) }
|
239
|
-
#
|
240
|
-
def self.before(filter, opts={})
|
241
|
-
raise(ArgumentError,
|
242
|
-
"You can specify either :only or :exclude but
|
243
|
-
not both at the same time for the same filter."
|
244
|
-
) if opts.has_key?(:only) && opts.has_key?(:exclude)
|
245
|
-
|
246
|
-
opts = shuffle_filters!(opts)
|
247
|
-
|
248
|
-
case filter
|
249
|
-
when Symbol, String, Proc
|
250
|
-
(self.before_filters ||= []) << [filter, opts]
|
251
|
-
else
|
252
|
-
raise(ArgumentError,
|
253
|
-
'filters need to be either a Symbol, String or a Proc'
|
254
|
-
)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
# #after is a class method that allows you to specify after
|
259
|
-
# filters in your controllers. Filters can either be a symbol
|
260
|
-
# or string that corresponds to a method name or a proc object.
|
261
|
-
# if it is a method name that method will be called and if it
|
262
|
-
# is a proc it will be called with an argument of self. When
|
263
|
-
# you use a proc as a filter it needs to take one parameter.
|
264
|
-
# you can gain access to the response body like so:
|
265
|
-
# after Proc.new {|c| Tidy.new(c.body) }, :only => :index
|
266
|
-
#
|
267
|
-
def self.after(filter, opts={})
|
268
|
-
raise(ArgumentError,
|
269
|
-
"You can specify either :only or :exclude but
|
270
|
-
not both at the same time for the same filter."
|
271
|
-
) if opts.has_key?(:only) && opts.has_key?(:exclude)
|
272
|
-
|
273
|
-
opts = shuffle_filters!(opts)
|
274
|
-
|
275
|
-
case filter
|
276
|
-
when Symbol, Proc, String
|
277
|
-
(self.after_filters ||= []) << [filter, opts]
|
278
|
-
else
|
279
|
-
raise(ArgumentError,
|
280
|
-
'After filters need to be either a Symbol, String or a Proc'
|
281
|
-
)
|
197
|
+
def part(opts={})
|
198
|
+
res = opts.inject([]) do |memo,(klass,action)|
|
199
|
+
memo << klass.new(self).dispatch(action)
|
282
200
|
end
|
201
|
+
res.size == 1 ? res[0] : res
|
283
202
|
end
|
284
203
|
|
285
|
-
def self.shuffle_filters!(opts={})
|
286
|
-
if opts[:only] && opts[:only].is_a?(Symbol)
|
287
|
-
opts[:only] = [opts[:only]]
|
288
|
-
end
|
289
|
-
if opts[:exclude] && opts[:exclude].is_a?(Symbol)
|
290
|
-
opts[:exclude] = [opts[:exclude]]
|
291
|
-
end
|
292
|
-
return opts
|
293
|
-
end
|
294
|
-
|
295
204
|
end
|
296
205
|
|
297
206
|
end
|