merb 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +25 -26
- data/Rakefile +48 -36
- data/app_generators/merb/USAGE +5 -0
- data/app_generators/merb/merb_generator.rb +107 -0
- data/app_generators/merb/templates/Rakefile +99 -0
- data/{examples/skeleton/dist → app_generators/merb/templates}/app/controllers/application.rb +1 -1
- data/app_generators/merb/templates/app/controllers/exceptions.rb +13 -0
- data/{examples/skeleton/dist → app_generators/merb/templates}/app/helpers/global_helper.rb +0 -0
- data/{examples/skeleton/dist/app/mailers → app_generators/merb/templates/app/mailers/views}/layout/application.erb +0 -0
- data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +207 -0
- data/app_generators/merb/templates/app/views/exceptions/not_acceptable.html.erb +38 -0
- data/app_generators/merb/templates/app/views/exceptions/not_found.html.erb +40 -0
- data/app_generators/merb/templates/app/views/layout/application.html.erb +11 -0
- data/app_generators/merb/templates/config/boot.rb +11 -0
- data/app_generators/merb/templates/config/dependencies.rb +41 -0
- data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/development.rb +0 -0
- data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/production.rb +0 -0
- data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/test.rb +0 -0
- data/app_generators/merb/templates/config/merb.yml +64 -0
- data/app_generators/merb/templates/config/merb_init.rb +16 -0
- data/app_generators/merb/templates/config/plugins.yml +1 -0
- data/app_generators/merb/templates/config/router.rb +32 -0
- data/{lib/merb/core_ext/merb_array.rb → app_generators/merb/templates/config/upload.conf} +0 -0
- data/app_generators/merb/templates/public/images/merb.jpg +0 -0
- data/app_generators/merb/templates/public/merb.fcgi +6 -0
- data/app_generators/merb/templates/public/stylesheets/master.css +119 -0
- data/app_generators/merb/templates/script/destroy +28 -0
- data/app_generators/merb/templates/script/generate +28 -0
- data/{examples/skeleton → app_generators/merb/templates}/script/stop_merb +0 -0
- data/app_generators/merb/templates/script/win_script.cmd +1 -0
- data/app_generators/merb/templates/spec/spec.opts +6 -0
- data/app_generators/merb/templates/spec/spec_helper.rb +10 -0
- data/app_generators/merb/templates/test/test_helper.rb +13 -0
- data/app_generators/merb_plugin/USAGE +5 -0
- data/app_generators/merb_plugin/merb_plugin_generator.rb +64 -0
- data/app_generators/merb_plugin/templates/LICENSE +20 -0
- data/app_generators/merb_plugin/templates/README +4 -0
- data/app_generators/merb_plugin/templates/Rakefile +35 -0
- data/app_generators/merb_plugin/templates/TODO +5 -0
- data/app_generators/merb_plugin/templates/merbtasks.rb +6 -0
- data/app_generators/merb_plugin/templates/sampleplugin.rb +10 -0
- data/app_generators/merb_plugin/templates/sampleplugin_spec.rb +7 -0
- data/app_generators/merb_plugin/templates/spec_helper.rb +2 -0
- data/bin/merb +1 -1
- data/lib/autotest/discover.rb +3 -0
- data/lib/autotest/merb_rspec.rb +79 -0
- data/lib/merb.rb +72 -93
- data/lib/merb/{merb_abstract_controller.rb → abstract_controller.rb} +28 -5
- data/lib/merb/caching/action_cache.rb +65 -29
- data/lib/merb/caching/fragment_cache.rb +9 -4
- data/lib/merb/caching/store/file_cache.rb +22 -14
- data/lib/merb/caching/store/memory_cache.rb +26 -8
- data/lib/merb/{merb_constants.rb → constants.rb} +9 -7
- data/lib/merb/controller.rb +178 -0
- data/lib/merb/core_ext.rb +13 -11
- data/lib/merb/core_ext/array.rb +0 -0
- data/lib/merb/core_ext/{merb_class.rb → class.rb} +0 -0
- data/lib/merb/core_ext/{merb_enumerable.rb → enumerable.rb} +0 -0
- data/lib/merb/core_ext/get_args.rb +52 -0
- data/lib/merb/core_ext/{merb_hash.rb → hash.rb} +40 -11
- data/lib/merb/core_ext/{merb_inflections.rb → inflections.rb} +0 -0
- data/lib/merb/core_ext/{merb_inflector.rb → inflector.rb} +1 -1
- data/lib/merb/core_ext/{merb_kernel.rb → kernel.rb} +56 -3
- data/lib/merb/core_ext/mash.rb +88 -0
- data/lib/merb/core_ext/{merb_module.rb → module.rb} +0 -0
- data/lib/merb/core_ext/{merb_numeric.rb → numeric.rb} +0 -0
- data/lib/merb/core_ext/{merb_object.rb → object.rb} +10 -47
- data/lib/merb/core_ext/string.rb +56 -0
- data/lib/merb/core_ext/{merb_symbol.rb → symbol.rb} +0 -0
- data/lib/merb/dispatcher.rb +109 -0
- data/lib/merb/{merb_drb_server.rb → drb_server.rb} +0 -0
- data/lib/merb/erubis_ext.rb +10 -0
- data/lib/merb/exceptions.rb +173 -0
- data/lib/merb/generators/merb_app/merb_app.rb +5 -25
- data/lib/merb/generators/merb_generator_helpers.rb +317 -0
- data/lib/merb/generators/merb_plugin.rb +19 -0
- data/lib/merb/logger.rb +65 -0
- data/lib/merb/{merb_mail_controller.rb → mail_controller.rb} +102 -49
- data/lib/merb/{merb_mailer.rb → mailer.rb} +31 -27
- data/lib/merb/mixins/{basic_authentication_mixin.rb → basic_authentication.rb} +3 -3
- data/lib/merb/mixins/{controller_mixin.rb → controller.rb} +131 -112
- data/lib/merb/mixins/{erubis_capture_mixin.rb → erubis_capture.rb} +12 -21
- data/lib/merb/mixins/{form_control_mixin.rb → form_control.rb} +6 -12
- data/lib/merb/mixins/render.rb +401 -0
- data/lib/merb/mixins/responder.rb +378 -0
- data/lib/merb/mixins/{view_context_mixin.rb → view_context.rb} +65 -10
- data/lib/merb/mixins/web_controller.rb +29 -0
- data/lib/merb/{merb_handler.rb → mongrel_handler.rb} +59 -38
- data/lib/merb/part_controller.rb +19 -0
- data/lib/merb/plugins.rb +16 -0
- data/lib/merb/rack_adapter.rb +37 -0
- data/lib/merb/request.rb +421 -0
- data/lib/merb/router.rb +576 -0
- data/lib/merb/{merb_server.rb → server.rb} +275 -71
- data/lib/merb/session.rb +10 -10
- data/lib/merb/session/cookie_store.rb +125 -0
- data/lib/merb/session/{merb_mem_cache_session.rb → mem_cache_session.rb} +22 -9
- data/lib/merb/session/{merb_memory_session.rb → memory_session.rb} +15 -11
- data/lib/merb/template.rb +35 -8
- data/lib/merb/template/erubis.rb +16 -10
- data/lib/merb/template/haml.rb +33 -20
- data/lib/merb/template/markaby.rb +16 -14
- data/lib/merb/template/xml_builder.rb +8 -4
- data/lib/merb/test/{merb_fake_request.rb → fake_request.rb} +11 -5
- data/lib/merb/test/helper.rb +31 -0
- data/lib/merb/test/hpricot.rb +136 -0
- data/lib/merb/test/{merb_multipart.rb → multipart.rb} +1 -1
- data/lib/merb/test/rspec.rb +93 -0
- data/lib/merb/{merb_upload_handler.rb → upload_handler.rb} +5 -6
- data/lib/merb/{merb_upload_progress.rb → upload_progress.rb} +1 -1
- data/lib/merb/{merb_view_context.rb → view_context.rb} +27 -42
- data/lib/{merb_tasks.rb → tasks.rb} +0 -0
- data/lib/tasks/merb.rake +21 -11
- data/merb_default_generators/model/USAGE +0 -0
- data/merb_default_generators/model/model_generator.rb +16 -0
- data/merb_default_generators/model/templates/new_model_template.erb +5 -0
- data/merb_default_generators/resource_controller/USAGE +0 -0
- data/merb_default_generators/resource_controller/resource_controller_generator.rb +26 -0
- data/merb_default_generators/resource_controller/templates/controller.rb +30 -0
- data/merb_default_generators/resource_controller/templates/edit.html.erb +1 -0
- data/merb_default_generators/resource_controller/templates/helper.rb +5 -0
- data/merb_default_generators/resource_controller/templates/index.html.erb +1 -0
- data/merb_default_generators/resource_controller/templates/new.html.erb +1 -0
- data/merb_default_generators/resource_controller/templates/show.html.erb +1 -0
- data/merb_generators/controller/USAGE +5 -0
- data/merb_generators/controller/controller_generator.rb +16 -0
- data/merb_generators/controller/templates/controller.rb +8 -0
- data/merb_generators/controller/templates/helper.rb +5 -0
- data/merb_generators/controller/templates/index.html.erb +3 -0
- data/merb_generators/resource/USAGE +0 -0
- data/merb_generators/resource/resource_generator.rb +60 -0
- data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +67 -0
- data/rspec_generators/merb_controller_test/templates/controller_spec.rb +8 -0
- data/rspec_generators/merb_controller_test/templates/edit_spec.rb +12 -0
- data/rspec_generators/merb_controller_test/templates/helper_spec.rb +5 -0
- data/rspec_generators/merb_controller_test/templates/index_spec.rb +12 -0
- data/rspec_generators/merb_controller_test/templates/new_spec.rb +12 -0
- data/rspec_generators/merb_controller_test/templates/show_spec.rb +5 -0
- data/rspec_generators/merb_model_test/merb_model_test_generator.rb +26 -0
- data/rspec_generators/merb_model_test/templates/model_spec_template.erb +7 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test_unit_generators/merb_controller_test/merb_controller_test_generator.rb +53 -0
- data/test_unit_generators/merb_controller_test/templates/functional_test.rb +17 -0
- data/test_unit_generators/merb_controller_test/templates/helper_test.rb +9 -0
- data/test_unit_generators/merb_model_test/merb_model_test_generator.rb +29 -0
- data/test_unit_generators/merb_model_test/templates/model_test_unit_template.erb +9 -0
- metadata +172 -94
- data/examples/README_EXAMPLES +0 -10
- data/examples/skeleton/Rakefile +0 -68
- data/examples/skeleton/dist/app/views/layout/application.herb +0 -12
- data/examples/skeleton/dist/conf/database.yml +0 -23
- data/examples/skeleton/dist/conf/merb.yml +0 -57
- data/examples/skeleton/dist/conf/merb_init.rb +0 -24
- data/examples/skeleton/dist/conf/router.rb +0 -22
- data/examples/skeleton/dist/conf/upload.conf +0 -5
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -14
- data/examples/skeleton/script/new_migration +0 -21
- data/lib/merb/core_ext/merb_string.rb +0 -18
- data/lib/merb/merb_controller.rb +0 -206
- data/lib/merb/merb_dispatcher.rb +0 -87
- data/lib/merb/merb_exceptions.rb +0 -319
- data/lib/merb/merb_part_controller.rb +0 -42
- data/lib/merb/merb_plugins.rb +0 -293
- data/lib/merb/merb_request.rb +0 -165
- data/lib/merb/merb_router.rb +0 -309
- data/lib/merb/merb_yaml_store.rb +0 -31
- data/lib/merb/mixins/render_mixin.rb +0 -283
- data/lib/merb/mixins/responder_mixin.rb +0 -159
- data/lib/merb/session/merb_ar_session.rb +0 -131
- data/lib/merb/vendor/paginator/README.txt +0 -84
- data/lib/merb/vendor/paginator/paginator.rb +0 -124
- data/lib/tasks/db.rake +0 -55
data/lib/merb/router.rb
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
module Merb
|
|
2
|
+
|
|
3
|
+
class Router
|
|
4
|
+
SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
|
|
5
|
+
SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
|
|
6
|
+
JUST_BRACKETS = /\[(\d+)\]/
|
|
7
|
+
PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
|
|
8
|
+
|
|
9
|
+
@@named_routes = {}
|
|
10
|
+
@@routes = []
|
|
11
|
+
cattr_accessor :routes, :named_routes
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def append(&block)
|
|
15
|
+
prepare(@@routes, [], &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def prepend(&block)
|
|
19
|
+
prepare([], @@routes, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def prepare(first = [], last = [], &block)
|
|
23
|
+
@@routes = []
|
|
24
|
+
yield Behavior.new({}, {:action => 'index'}) # defaults
|
|
25
|
+
@@routes = first + @@routes + last
|
|
26
|
+
compile
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def compiled_statement
|
|
30
|
+
@@compiled_statement = "lambda { |request, params|\n"
|
|
31
|
+
@@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
|
|
32
|
+
# @@compiled_statement << " puts cached_path.inspect; puts cached_method.inspect\n"
|
|
33
|
+
@@routes.each_with_index { |route, i| @@compiled_statement << route.compile(i == 0) }
|
|
34
|
+
@@compiled_statement << " else\n [nil, {}]\n"
|
|
35
|
+
@@compiled_statement << " end\n"
|
|
36
|
+
@@compiled_statement << "}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def compile
|
|
40
|
+
meta_def(:match, &eval(compiled_statement))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def generate(name, params = {}, fallback = {})
|
|
44
|
+
raise "Named route not found: #{name}" unless @@named_routes.has_key? name
|
|
45
|
+
@@named_routes[name].generate(params, fallback)
|
|
46
|
+
end
|
|
47
|
+
end # self
|
|
48
|
+
|
|
49
|
+
# Cache procs for future reference in eval statement
|
|
50
|
+
class CachedProc
|
|
51
|
+
@@index = 0
|
|
52
|
+
@@list = []
|
|
53
|
+
|
|
54
|
+
attr_accessor :cache, :index
|
|
55
|
+
|
|
56
|
+
def initialize(cache)
|
|
57
|
+
@cache, @index = cache, CachedProc.register(self)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Make each CachedProc object embeddable within a string
|
|
61
|
+
def to_s() "CachedProc[#{@index}].cache" end
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
def register(cached_code)
|
|
65
|
+
CachedProc[@@index] = cached_code
|
|
66
|
+
@@index += 1
|
|
67
|
+
@@index - 1
|
|
68
|
+
end
|
|
69
|
+
def []=(index, code) @@list[index] = code end
|
|
70
|
+
def [](index) @@list[index] end
|
|
71
|
+
end
|
|
72
|
+
end # CachedProc
|
|
73
|
+
|
|
74
|
+
class Route
|
|
75
|
+
attr_reader :conditions, :conditional_block
|
|
76
|
+
attr_reader :params, :behavior, :segments, :index, :symbol
|
|
77
|
+
|
|
78
|
+
def initialize(conditions, params, behavior = nil, &conditional_block)
|
|
79
|
+
@conditions, @params, @behavior = conditions, params, behavior
|
|
80
|
+
@conditional_block = conditional_block
|
|
81
|
+
if @behavior && (path = @behavior.merged_original_conditions[:path])
|
|
82
|
+
@segments = segments_from_path(path)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Registers itself in the Router.routes array
|
|
87
|
+
def register
|
|
88
|
+
@index = Router.routes.size
|
|
89
|
+
Router.routes << self
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Get the symbols out of the segments array
|
|
94
|
+
def symbol_segments
|
|
95
|
+
segments.select{ |s| s.is_a?(Symbol) }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Turn a path into string and symbol segments so it can be reconstructed, as in the
|
|
99
|
+
# case of a named route.
|
|
100
|
+
def segments_from_path(path)
|
|
101
|
+
# Remove leading ^ and trailing $ from each segment (left-overs from regexp joining)
|
|
102
|
+
strip = proc { |str| str.gsub(/^\^/, '').gsub(/\$$/, '') }
|
|
103
|
+
segments = []
|
|
104
|
+
while match = (path.match(SEGMENT_REGEXP))
|
|
105
|
+
segments << strip[match.pre_match] unless match.pre_match.empty?
|
|
106
|
+
segments << match[2].intern
|
|
107
|
+
path = strip[match.post_match]
|
|
108
|
+
end
|
|
109
|
+
segments << strip[path] unless path.empty?
|
|
110
|
+
segments
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Name this route
|
|
114
|
+
def name(symbol = nil)
|
|
115
|
+
@symbol = symbol
|
|
116
|
+
Router.named_routes[@symbol] = self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def regexp?
|
|
120
|
+
behavior.regexp? || behavior.send(:ancestors).any?{ |a| a.regexp? }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Given a hash of +params+, returns a string using the stored route segments
|
|
124
|
+
# for reconstruction of the URL.
|
|
125
|
+
def generate(params = {}, fallback = {})
|
|
126
|
+
url = @segments.map do |segment|
|
|
127
|
+
value =
|
|
128
|
+
if segment.is_a? Symbol
|
|
129
|
+
if params.is_a? Hash
|
|
130
|
+
params[segment] || fallback[segment]
|
|
131
|
+
else
|
|
132
|
+
if params.respond_to?(segment)
|
|
133
|
+
params.send(segment)
|
|
134
|
+
else
|
|
135
|
+
fallback[segment]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
elsif segment.respond_to? :to_s
|
|
139
|
+
segment
|
|
140
|
+
else
|
|
141
|
+
raise "Segment type '#{segment.class}' can't be converted to a string"
|
|
142
|
+
end
|
|
143
|
+
(value.respond_to?(:to_param) ? value.to_param : value).to_s
|
|
144
|
+
end.join
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def if_conditions(params_as_string)
|
|
148
|
+
cond = []
|
|
149
|
+
condition_string = proc do |key, value, regexp_string|
|
|
150
|
+
max = Behavior.count_parens_up_to(value.source, value.source.size)
|
|
151
|
+
captures = if max == 0 then "" else
|
|
152
|
+
" && (" +
|
|
153
|
+
(1..max).to_a.map{ |n| "#{key}#{n}" }.join(", ") + " = " +
|
|
154
|
+
(1..max).to_a.map{ |n| "$#{n}"}.join(", ") +
|
|
155
|
+
")"
|
|
156
|
+
end
|
|
157
|
+
" (#{value.inspect} =~ #{regexp_string}" + captures + ")"
|
|
158
|
+
end
|
|
159
|
+
@conditions.each_pair do |key, value|
|
|
160
|
+
|
|
161
|
+
# Note: =~ is slightly faster than .match
|
|
162
|
+
cond << case key
|
|
163
|
+
when :path then condition_string[key, value, "cached_path"]
|
|
164
|
+
when :method then condition_string[key, value, "cached_method"]
|
|
165
|
+
else condition_string[key, value, "request.#{key}.to_s"]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
if @conditional_block
|
|
169
|
+
str = " # #{@conditional_block.inspect.scan(/@([^>]+)/).flatten.first}\n"
|
|
170
|
+
str << " (block_result = #{CachedProc.new(@conditional_block)}.call(request, params.merge({#{params_as_string}})))" if @conditional_block
|
|
171
|
+
cond << str
|
|
172
|
+
end
|
|
173
|
+
cond
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def compile(first = false)
|
|
177
|
+
code = ""
|
|
178
|
+
default_params = {:action => "index"}
|
|
179
|
+
get_value = proc do |key|
|
|
180
|
+
if default_params.has_key?(key) && params[key][0] != ?"
|
|
181
|
+
"#{params[key]} || \"#{default_params[key]}\""
|
|
182
|
+
else
|
|
183
|
+
"#{params[key]}"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
params_as_string = params.keys.map{|k| "#{k.inspect} => #{get_value[k]}"}.join(", ")
|
|
187
|
+
code << " els" unless first
|
|
188
|
+
code << "if # #{@behavior.merged_original_conditions.inspect}\n "
|
|
189
|
+
code << if_conditions(params_as_string).join(" &&\n ") << "\n"
|
|
190
|
+
code << " # then\n"
|
|
191
|
+
if @conditional_block
|
|
192
|
+
code << " [#{@index.inspect}, block_result]\n"
|
|
193
|
+
else
|
|
194
|
+
code << " [#{@index.inspect}, {#{params_as_string}}]\n"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def behavior_trace
|
|
199
|
+
if @behavior
|
|
200
|
+
puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts
|
|
201
|
+
else
|
|
202
|
+
puts "No behavior to trace #{self}"
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end # Route
|
|
206
|
+
|
|
207
|
+
# The Behavior class is an interim route-building class that ties pattern-matching +conditions+ to
|
|
208
|
+
# output parameters, +params+.
|
|
209
|
+
class Behavior
|
|
210
|
+
attr_reader :placeholders, :conditions, :params
|
|
211
|
+
attr_accessor :parent
|
|
212
|
+
|
|
213
|
+
def initialize(conditions = {}, params = {}, parent = nil)
|
|
214
|
+
@conditions, @params, @parent = conditions, {}, parent
|
|
215
|
+
@placeholders = {}
|
|
216
|
+
stringify_conditions
|
|
217
|
+
copy_original_conditions
|
|
218
|
+
deduce_placeholders
|
|
219
|
+
# Must wait until after deducing placeholders to set @params
|
|
220
|
+
@params.merge!(params)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def add(path, params = {})
|
|
224
|
+
match(path).to(params)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Matches a +path+ and any number of optional request methods as conditions of a route.
|
|
228
|
+
# Alternatively, +path+ can be a hash of conditions, in which case +conditions+ is ignored.
|
|
229
|
+
# Yields 'self' so that sub-matching may occur.
|
|
230
|
+
def match(path = '', conditions = {}, &block)
|
|
231
|
+
if path.is_a? Hash
|
|
232
|
+
conditions = path
|
|
233
|
+
else
|
|
234
|
+
conditions[:path] = path
|
|
235
|
+
end
|
|
236
|
+
match_without_path(conditions, &block)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def match_without_path(conditions = {})
|
|
240
|
+
new_behavior = self.class.new(conditions, {}, self)
|
|
241
|
+
conditions.delete :path if ['', '^$'].include?(conditions[:path])
|
|
242
|
+
yield new_behavior if block_given?
|
|
243
|
+
new_behavior
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def to_route(params = {}, &conditional_block)
|
|
247
|
+
@params.merge!(params)
|
|
248
|
+
Route.new(compiled_conditions, compiled_params, self, &conditional_block)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Creates a Route from one or more Behavior objects, unless a +block+ is passed in.
|
|
252
|
+
# If a block is passed in, a Behavior object is yielded and further .to operations
|
|
253
|
+
# may be called in the block. For example:
|
|
254
|
+
# r.match('/:controller/:id).to(:action => 'show')
|
|
255
|
+
# vs.
|
|
256
|
+
# r.to(:controller => "simple") do |simple|
|
|
257
|
+
# simple.match('/test').to(:action => 'index')
|
|
258
|
+
# simple.match('/other').to(:action => 'other')
|
|
259
|
+
# end
|
|
260
|
+
def to(params = {}, &block)
|
|
261
|
+
if block_given?
|
|
262
|
+
new_behavior = self.class.new({}, params, self)
|
|
263
|
+
yield new_behavior if block_given?
|
|
264
|
+
new_behavior
|
|
265
|
+
else
|
|
266
|
+
to_route(params).register
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Takes a block and stores it for defered conditional routes. The block takes the
|
|
271
|
+
# +request+ object and the +params+ hash as parameters and should return a hash of params.
|
|
272
|
+
# For example:
|
|
273
|
+
# r.defer_to do |request, params|
|
|
274
|
+
# params.merge(:controller => 'here', :action => 'there') if External.says_so?
|
|
275
|
+
# end
|
|
276
|
+
def defer_to(params = {}, &conditional_block)
|
|
277
|
+
Router.routes << (route = to_route(params, &conditional_block))
|
|
278
|
+
route
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def default_routes(params = {}, &block)
|
|
282
|
+
match(%r[/:controller(/:action(/:id)?)?(\.:format)?]).to(params, &block)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def to_resources(params = {}, &block)
|
|
286
|
+
many_behaviors_to(resources_behaviors, params, &block)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def resources(name, options = {})
|
|
290
|
+
# singular = name.singularize
|
|
291
|
+
next_level = match("/#{name}")
|
|
292
|
+
behaviors = []
|
|
293
|
+
|
|
294
|
+
singular = name.to_s.singularize
|
|
295
|
+
|
|
296
|
+
route_plural_name = (options[:name_prefix] || "") + name.to_s
|
|
297
|
+
route_singular_name = (options[:name_prefix] || "") + singular
|
|
298
|
+
|
|
299
|
+
# Add optional member actions
|
|
300
|
+
options[:member].each_pair do |action, methods|
|
|
301
|
+
conditions = {:path => %r[^/:id[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/}
|
|
302
|
+
behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level)
|
|
303
|
+
next_level.match("/:id/#{action}").
|
|
304
|
+
to_route.name(:"#{action}_#{route_singular_name}")
|
|
305
|
+
end if options[:member]
|
|
306
|
+
|
|
307
|
+
# Add optional collection actions
|
|
308
|
+
options[:collection].each_pair do |action, methods|
|
|
309
|
+
conditions = {:path => %r[^[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/}
|
|
310
|
+
behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level)
|
|
311
|
+
next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
|
|
312
|
+
end if options[:collection]
|
|
313
|
+
|
|
314
|
+
controller = options[:controller] || merged_params[:controller] || name.to_s
|
|
315
|
+
routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors), :controller => controller)
|
|
316
|
+
|
|
317
|
+
# Add names to some routes
|
|
318
|
+
next_level.match("").to_route.name(:"#{route_plural_name}")
|
|
319
|
+
next_level.match("/:id").to_route.name(:"#{route_singular_name}")
|
|
320
|
+
next_level.match("/new").to_route.name(:"new_#{route_singular_name}")
|
|
321
|
+
next_level.match("/:id/edit").to_route.name(:"edit_#{route_singular_name}")
|
|
322
|
+
next_level.match("/:action/:id").to_route.name(:"custom_#{route_singular_name}")
|
|
323
|
+
|
|
324
|
+
yield next_level.match("/:#{singular}_id") if block_given?
|
|
325
|
+
routes
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def to_resource(params = {}, &block)
|
|
329
|
+
many_behaviors_to(resource_behaviors, params, &block)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def resource(name, options = {})
|
|
333
|
+
next_level = match("/#{name}")
|
|
334
|
+
|
|
335
|
+
controller = options[:controller] || merged_params[:controller] || name.to_s
|
|
336
|
+
routes = next_level.to_resource(:controller => controller)
|
|
337
|
+
|
|
338
|
+
route_name = (options[:name_prefix] || "") + name.to_s
|
|
339
|
+
|
|
340
|
+
next_level.match("").to_route.name(:"#{route_name}")
|
|
341
|
+
next_level.match("/new").to_route.name(:"new_#{route_name}")
|
|
342
|
+
next_level.match("/edit").to_route.name(:"edit_#{route_name}")
|
|
343
|
+
|
|
344
|
+
yield next_level if block_given?
|
|
345
|
+
routes
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def merged_original_conditions
|
|
349
|
+
if parent.nil?
|
|
350
|
+
@original_conditions
|
|
351
|
+
else
|
|
352
|
+
merged_so_far = parent.merged_original_conditions
|
|
353
|
+
path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
|
|
354
|
+
path ?
|
|
355
|
+
merged_so_far.merge(@original_conditions).merge(:path => path) :
|
|
356
|
+
merged_so_far.merge(@original_conditions)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def merged_conditions
|
|
361
|
+
if parent.nil?
|
|
362
|
+
@conditions
|
|
363
|
+
else
|
|
364
|
+
merged_so_far = parent.merged_conditions
|
|
365
|
+
path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
|
|
366
|
+
path ?
|
|
367
|
+
merged_so_far.merge(@conditions).merge(:path => path) :
|
|
368
|
+
merged_so_far.merge(@conditions)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def merged_params
|
|
373
|
+
if parent.nil?
|
|
374
|
+
@params
|
|
375
|
+
else
|
|
376
|
+
parent.merged_params.merge(@params)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def merged_placeholders
|
|
381
|
+
placeholders = {}
|
|
382
|
+
(ancestors.reverse + [self]).each do |a|
|
|
383
|
+
a.placeholders.each_pair do |key, pair|
|
|
384
|
+
param, place = pair
|
|
385
|
+
placeholders[key] = [param, place + (param == :path ? a.total_previous_captures : 0)]
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
placeholders
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def inspect
|
|
392
|
+
"[captures: #{path_captures}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def regexp?
|
|
396
|
+
@conditions_have_regexp
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
protected
|
|
400
|
+
def resources_behaviors(parent = self)
|
|
401
|
+
[
|
|
402
|
+
Behavior.new({:path => %r[^/?(\.:format)?$], :method => :get}, {:action => "index"}, parent),
|
|
403
|
+
Behavior.new({:path => %r[^/index(\.:format)?$], :method => :get}, {:action => "index"}, parent),
|
|
404
|
+
Behavior.new({:path => %r[^/new$], :method => :get}, {:action => "new"}, parent),
|
|
405
|
+
Behavior.new({:path => %r[^/?(\.:format)?$], :method => :post}, {:action => "create"}, parent),
|
|
406
|
+
Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :get}, {:action => "show"}, parent),
|
|
407
|
+
Behavior.new({:path => %r[^/:id[;/]edit$], :method => :get}, {:action => "edit"}, parent),
|
|
408
|
+
Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :put}, {:action => "update"}, parent),
|
|
409
|
+
Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :delete}, {:action => "destroy"}, parent)
|
|
410
|
+
]
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def resource_behaviors(parent = self)
|
|
414
|
+
[
|
|
415
|
+
Behavior.new({:path => %r{^[;/]new$}, :method => :get}, {:action => "new"}, parent),
|
|
416
|
+
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :post}, {:action => "create"}, parent),
|
|
417
|
+
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :get}, {:action => "show"}, parent),
|
|
418
|
+
Behavior.new({:path => %r{^[;/]edit$}, :method => :get}, {:action => "edit"}, parent),
|
|
419
|
+
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :put}, {:action => "update"}, parent),
|
|
420
|
+
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :delete}, {:action => "destroy"}, parent)
|
|
421
|
+
]
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Creates a series of routes from an array of Behavior objects.
|
|
425
|
+
# You can pass in optional +params+, and an optional block that will be
|
|
426
|
+
# passed along to the #to method.
|
|
427
|
+
def many_behaviors_to(behaviors, params = {}, &conditional_block)
|
|
428
|
+
routes = []
|
|
429
|
+
behaviors.each { |b| routes << b.to(params, &conditional_block) }
|
|
430
|
+
routes
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Convert conditions to regular expression string sources for consistency
|
|
434
|
+
def stringify_conditions
|
|
435
|
+
@conditions_have_regexp = false
|
|
436
|
+
@conditions.each_pair do |key, value|
|
|
437
|
+
# TODO: Other Regexp special chars
|
|
438
|
+
@conditions[key] = case value
|
|
439
|
+
when String then "^" + value.escape_regexp + "$"
|
|
440
|
+
when Symbol then "^" + value.to_s + "$"
|
|
441
|
+
when Regexp:
|
|
442
|
+
@conditions_have_regexp = true
|
|
443
|
+
value.source
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def copy_original_conditions
|
|
449
|
+
@original_conditions = {}
|
|
450
|
+
@conditions.each_pair do |key, value|
|
|
451
|
+
@original_conditions[key] = value.dup
|
|
452
|
+
end
|
|
453
|
+
@original_conditions
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def deduce_placeholders
|
|
457
|
+
@conditions.each_pair do |match_key, source|
|
|
458
|
+
while match = SEGMENT_REGEXP.match(source)
|
|
459
|
+
source.sub!(SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING)
|
|
460
|
+
unless match[2] == ":" # No need to store anonymous place holders
|
|
461
|
+
placeholder_key = match[2].intern
|
|
462
|
+
@params[placeholder_key] = "#{match[1]}"
|
|
463
|
+
@placeholders[placeholder_key] = [match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])]
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def ancestors(list = [])
|
|
470
|
+
if parent.nil?
|
|
471
|
+
list
|
|
472
|
+
else
|
|
473
|
+
list.push parent
|
|
474
|
+
parent.ancestors(list)
|
|
475
|
+
list
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Count the number of regexp captures in the :path condition
|
|
480
|
+
def path_captures
|
|
481
|
+
return 0 unless conditions[:path]
|
|
482
|
+
Behavior.count_parens_up_to(conditions[:path], conditions[:path].size)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def total_previous_captures
|
|
486
|
+
ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n}
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# def merge_with_ancestors
|
|
490
|
+
# self.class.new(merged_conditions, merged_params)
|
|
491
|
+
# end
|
|
492
|
+
|
|
493
|
+
def compiled_conditions(conditions = merged_conditions)
|
|
494
|
+
compiled = {}
|
|
495
|
+
conditions.each { |key, value| compiled[key] = Regexp.new(value) }
|
|
496
|
+
compiled
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Compiles the params hash into 'eval'-able form.
|
|
500
|
+
# For example:
|
|
501
|
+
# @params = {:controller => "admin/:controller"}
|
|
502
|
+
# Could become:
|
|
503
|
+
# {:controller => "'admin/' + matches[:path][1]"}
|
|
504
|
+
#
|
|
505
|
+
def compiled_params(params = merged_params, placeholders = merged_placeholders)
|
|
506
|
+
compiled = {}
|
|
507
|
+
params.each_pair do |key, value|
|
|
508
|
+
raise ArgumentError, "param value must be string (#{value.inspect})" unless value.is_a? String
|
|
509
|
+
result = []
|
|
510
|
+
value = value.dup
|
|
511
|
+
match = true
|
|
512
|
+
while match
|
|
513
|
+
if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
|
|
514
|
+
result << match.pre_match unless match.pre_match.empty?
|
|
515
|
+
ph_key = match[1][1..-1].intern
|
|
516
|
+
if match[2] # has brackets, e.g. :path[2]
|
|
517
|
+
result << :"#{ph_key}#{match[3]}"
|
|
518
|
+
else # no brackets, e.g. a named placeholder such as :controller
|
|
519
|
+
if place = placeholders[ph_key]
|
|
520
|
+
result << :"#{place[0]}#{place[1]}"
|
|
521
|
+
else
|
|
522
|
+
raise "Placeholder not found while compiling routes: :#{ph_key}"
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
value = match.post_match
|
|
526
|
+
elsif match = JUST_BRACKETS.match(value)
|
|
527
|
+
# This is a reference to :path
|
|
528
|
+
result << match.pre_match unless match.pre_match.empty?
|
|
529
|
+
result << :"path#{match[1]}"
|
|
530
|
+
value = match.post_match
|
|
531
|
+
else
|
|
532
|
+
result << value unless value.empty?
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
compiled[key] = Behavior.array_to_code(result).gsub("\\_", "_")
|
|
536
|
+
end
|
|
537
|
+
compiled
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
public
|
|
541
|
+
# Count the number of open parentheses in +string+, up to and including +pos+
|
|
542
|
+
def self.count_parens_up_to(string, pos)
|
|
543
|
+
string[0..pos].gsub(/[^\(]/, "").size
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Concatenate strings and remove regexp end caps
|
|
547
|
+
def self.concat_without_endcaps(string1, string2)
|
|
548
|
+
return nil if !string1 and !string2
|
|
549
|
+
return string1 if string2.nil?
|
|
550
|
+
return string2 if string1.nil?
|
|
551
|
+
s1 = string1[-1] == ?$ ? string1[0..-2] : string1
|
|
552
|
+
s2 = string2[0] == ?^ ? string2[1..-1] : string2
|
|
553
|
+
s1 + s2
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Join an array's elements into a string using " + " as a joiner, and
|
|
557
|
+
# surround string elements in quotes.
|
|
558
|
+
def self.array_to_code(arr)
|
|
559
|
+
code = ""
|
|
560
|
+
arr.each_with_index do |part, i|
|
|
561
|
+
code << " + " if i > 0
|
|
562
|
+
case part
|
|
563
|
+
when Symbol
|
|
564
|
+
code << "#{part}"
|
|
565
|
+
when String
|
|
566
|
+
code << "\"" + part + "\""
|
|
567
|
+
else
|
|
568
|
+
raise "Don't know how to compile array part: #{part.class} [#{i}]"
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
code
|
|
572
|
+
end
|
|
573
|
+
end # Behavior
|
|
574
|
+
|
|
575
|
+
end
|
|
576
|
+
end
|