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
@@ -1,7 +1,49 @@
|
|
1
1
|
module Enumerable
|
2
|
+
|
3
|
+
# Abstract the common pattern of injecting a hash into a block to accumulate
|
4
|
+
# and return the injected hash.
|
5
|
+
#
|
6
|
+
# Both of these are equivalent
|
7
|
+
# [1,2,3].inject({}){|m,i| m[i] = i; m }
|
8
|
+
# [1,2,3].injecting({}){|m,i| m[i] = i }
|
9
|
+
# => {1=>1, 2=>2, 3=>3}
|
10
|
+
#
|
11
|
+
# The main difference is with injecting you do not have to end the block
|
12
|
+
# with ;m to return the accumulated hash m. In this sense it is very much
|
13
|
+
# like Object#returning
|
2
14
|
def injecting(s)
|
3
15
|
inject(s) do |k, i|
|
4
16
|
yield(k, i); k
|
5
17
|
end
|
6
18
|
end
|
7
|
-
|
19
|
+
|
20
|
+
# Look for any of an array of things inside another array (or any Enumerable).
|
21
|
+
#
|
22
|
+
# ['louie', 'bert'].include_any?('louie', 'chicken')
|
23
|
+
# => true
|
24
|
+
def include_any?(*args)
|
25
|
+
args.any? {|arg| self.include?(arg) }
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
#
|
30
|
+
# Returns a hash, which keys are evaluated result from the
|
31
|
+
# block, and values are arrays of elements in <i>enum</i>
|
32
|
+
# corresponding to the key.
|
33
|
+
#
|
34
|
+
# (1..6).group_by {|i| i%3} #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
|
35
|
+
#
|
36
|
+
# This is included in Ruby 1.9
|
37
|
+
# http://www.ruby-doc.org/core-1.9/classes/Enumerable.html#M002672
|
38
|
+
#
|
39
|
+
# Implementation from Ruby on Rails:
|
40
|
+
# trunk/activesupport/lib/active_support/core_ext/enumerable.rb
|
41
|
+
# [rev 5334]
|
42
|
+
#
|
43
|
+
def group_by
|
44
|
+
inject({}) do |groups, element|
|
45
|
+
(groups[yield(element)] ||= []) << element
|
46
|
+
groups
|
47
|
+
end
|
48
|
+
end if RUBY_VERSION < '1.9'
|
49
|
+
end
|
@@ -1,76 +1,73 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
|
1
3
|
class Hash
|
2
4
|
|
3
5
|
class << self
|
4
|
-
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
6
|
+
# Converts valid XML into a Ruby Hash structure.
|
7
|
+
# <tt>xml</tt>:: A string representation of valid XML
|
8
|
+
#
|
9
|
+
# == Typecasting
|
10
|
+
# Typecasting is performed on elements that have a "<tt>type</tt>" attribute of
|
11
|
+
# <tt>integer</tt>::
|
12
|
+
# <tt>boolean</tt>:: anything other than "true" evaluates to false
|
13
|
+
# <tt>datetime</tt>:: Returns a Time object. See +Time+ documentation for valid Time strings
|
14
|
+
# <tt>date</tt>:: Returns a Date object. See +Date+ documentation for valid Date strings
|
15
|
+
#
|
16
|
+
# Keys are automatically converted to +snake_case+
|
17
|
+
#
|
18
|
+
# == Caveats
|
19
|
+
# * Mixed content tags are assumed to be text and any xml tags are kept as a String
|
20
|
+
# * Any attributes other than type on a node containing a text node will be discarded
|
21
|
+
#
|
22
|
+
# == Examples
|
23
|
+
#
|
24
|
+
# ===Standard
|
25
|
+
# <user gender='m'>
|
26
|
+
# <age type='integer'>35</age>
|
27
|
+
# <name>Home Simpson</name>
|
28
|
+
# <dob type='date'>1988-01-01</dob>
|
29
|
+
# <joined-at type='datetime'>2000-04-28 23:01</joined-at>
|
30
|
+
# <is-cool type='boolean'>true</is-cool>
|
31
|
+
# </user>
|
32
|
+
#
|
33
|
+
# evaluates to
|
34
|
+
#
|
35
|
+
# { "user" =>
|
36
|
+
# { "gender" => "m",
|
37
|
+
# "age" => 35,
|
38
|
+
# "name" => "Home Simpson",
|
39
|
+
# "dob" => DateObject( 1998-01-01 ),
|
40
|
+
# "joined_at" => TimeObject( 2000-04-28 23:01),
|
41
|
+
# "is_cool" => true
|
42
|
+
# }
|
43
|
+
# }
|
44
|
+
#
|
45
|
+
# === Mixed Content
|
46
|
+
# <story>
|
47
|
+
# A Quick <em>brown</em> Fox
|
48
|
+
# </story>
|
49
|
+
#
|
50
|
+
# evaluates to
|
51
|
+
# { "story" => "A Quick <em>brown</em> Fox" }
|
52
|
+
#
|
53
|
+
# === Attributes other than type on a node containing text
|
54
|
+
# <story is-good='fasle'>
|
55
|
+
# A Quick <em>brown</em> Fox
|
56
|
+
# </story>
|
57
|
+
#
|
58
|
+
# evaluates to
|
59
|
+
# { "story" => "A Quick <em>brown</em> Fox" }
|
60
|
+
#
|
61
|
+
# <bicep unit='inches' type='integer'>60</bicep>
|
62
|
+
#
|
63
|
+
# evaluates with a typecast to an integer. But ignores the unit attribute
|
64
|
+
# { "bicep" => 60 }
|
65
|
+
|
66
|
+
def from_xml( xml )
|
67
|
+
ToHashParser.from_xml(xml)
|
45
68
|
end
|
46
|
-
|
47
|
-
def translate_xml_entities(value)
|
48
|
-
value.gsub(/</, "<").
|
49
|
-
gsub(/>/, ">").
|
50
|
-
gsub(/"/, '"').
|
51
|
-
gsub(/'/, "'").
|
52
|
-
gsub(/&/, "&")
|
53
|
-
end
|
54
|
-
|
55
|
-
def undasherize_keys(params)
|
56
|
-
case params.class.to_s
|
57
|
-
when "Hash"
|
58
|
-
params.inject({}) do |h,(k,v)|
|
59
|
-
h[k.to_s.tr("-", "_")] = undasherize_keys(v)
|
60
|
-
h
|
61
|
-
end
|
62
|
-
when "Array"
|
63
|
-
params.map { |v| undasherize_keys(v) }
|
64
|
-
else
|
65
|
-
params
|
66
|
-
end
|
67
|
-
end
|
68
69
|
end
|
69
70
|
|
70
|
-
|
71
|
-
def with_indifferent_access
|
72
|
-
MerbHash.new(self)
|
73
|
-
end
|
74
71
|
def to_params
|
75
72
|
result = ''
|
76
73
|
stack = []
|
@@ -94,88 +91,169 @@ class Hash
|
|
94
91
|
# lets through the keys in the argument
|
95
92
|
# >> {:one => 1, :two => 2, :three => 3}.pass(:one)
|
96
93
|
# => {:one=>1}
|
97
|
-
def pass(*
|
98
|
-
self.reject { |k,v| !
|
94
|
+
def pass(*allowed)
|
95
|
+
self.reject { |k,v| ! allowed.include?(k) }
|
99
96
|
end
|
100
97
|
alias only pass
|
101
98
|
|
102
99
|
# blocks the keys in the arguments
|
103
100
|
# >> {:one => 1, :two => 2, :three => 3}.block(:one)
|
104
101
|
# => {:two=>2, :three=>3}
|
105
|
-
def block(*
|
106
|
-
self.reject { |k,v|
|
102
|
+
def block(*rejected)
|
103
|
+
self.reject { |k,v| rejected.include?(k) }
|
107
104
|
end
|
108
105
|
alias except block
|
109
|
-
end
|
110
106
|
|
111
|
-
|
112
|
-
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
107
|
+
|
108
|
+
# Destructively convert all keys to symbols recursively.
|
109
|
+
def symbolize_keys!
|
110
|
+
keys.each do |key|
|
111
|
+
unless key.is_a?(Symbol)
|
112
|
+
self[key.to_sym] = self[key]
|
113
|
+
delete(key)
|
114
|
+
end
|
115
|
+
if Hash === (sub = self[key.to_sym])
|
116
|
+
sub.symbolize_keys!
|
117
|
+
end
|
119
118
|
end
|
119
|
+
self
|
120
120
|
end
|
121
|
-
|
122
|
-
def default(key)
|
123
|
-
self[key.to_s] if key.is_a?(Symbol)
|
124
|
-
end
|
125
|
-
|
126
|
-
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
127
|
-
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
128
|
-
alias_method :u, :regular_update
|
129
121
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
def update(other_hash)
|
135
|
-
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
122
|
+
def environmentize_keys!
|
123
|
+
self.each do |key, value|
|
124
|
+
self[key.to_s.upcase] = delete(key)
|
125
|
+
end
|
136
126
|
self
|
137
127
|
end
|
128
|
+
|
129
|
+
def method_missing(m,*a)
|
130
|
+
m.to_s =~ /=$/ ? self[$`]=a[0] : a==[] ? self[m] : raise(NoMethodError,"#{m}")
|
131
|
+
end
|
132
|
+
|
133
|
+
def respond_to?(method, include_private=false)
|
134
|
+
return true if keys.include?(method)
|
135
|
+
super(method, include_private)
|
136
|
+
end
|
138
137
|
|
139
|
-
|
138
|
+
end
|
139
|
+
|
140
|
+
require 'rexml/parsers/streamparser'
|
141
|
+
require 'rexml/parsers/baseparser'
|
142
|
+
require 'rexml/light/node'
|
140
143
|
|
141
|
-
|
142
|
-
|
144
|
+
# This is a slighly modified version of the XMLUtilityNode from
|
145
|
+
# http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
|
146
|
+
# It's mainly just adding vowels, as I ht cd wth n vwls :)
|
147
|
+
# This represents the hard part of the work, all I did was change the underlying
|
148
|
+
# parser
|
149
|
+
class REXMLUtilityNode # :nodoc:
|
150
|
+
attr_accessor :name, :attributes, :children
|
151
|
+
|
152
|
+
def initialize(name, attributes = {})
|
153
|
+
@name = name.tr("-", "_")
|
154
|
+
@attributes = undasherize_keys(attributes)
|
155
|
+
@children = []
|
156
|
+
@text = false
|
143
157
|
end
|
158
|
+
|
159
|
+
def add_node(node)
|
160
|
+
@text = true if node.is_a? String
|
161
|
+
@children << node
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_hash
|
165
|
+
if @text
|
166
|
+
return { name => typecast_value( translate_xml_entities( inner_html ) ) }
|
167
|
+
else
|
168
|
+
#change repeating groups into an array
|
169
|
+
# group by the first key of each element of the array to find repeating groups
|
170
|
+
groups = @children.group_by{ |c| c.name }
|
144
171
|
|
145
|
-
|
146
|
-
|
147
|
-
|
172
|
+
hash = {}
|
173
|
+
groups.each do |key, values|
|
174
|
+
if values.size == 1
|
175
|
+
hash.merge!( values.first )
|
176
|
+
else
|
177
|
+
hash.merge!( key => values.map{ |element| element.to_hash[key] } )
|
178
|
+
end
|
179
|
+
end
|
148
180
|
|
149
|
-
|
150
|
-
|
181
|
+
# merge the arrays, including attributes
|
182
|
+
hash.merge!( attributes ) unless attributes.empty?
|
183
|
+
return { name => hash }
|
184
|
+
end
|
151
185
|
end
|
152
186
|
|
153
|
-
def
|
154
|
-
|
187
|
+
def to_s
|
188
|
+
self.to_html
|
155
189
|
end
|
156
190
|
|
157
|
-
|
158
|
-
|
191
|
+
|
192
|
+
def typecast_value(value)
|
193
|
+
return value unless attributes["type"]
|
194
|
+
|
195
|
+
case attributes["type"]
|
196
|
+
when "integer" then value.to_i
|
197
|
+
when "boolean" then value.strip == "true"
|
198
|
+
when "datetime" then ::Time.parse(value).utc
|
199
|
+
when "date" then ::Date.parse(value)
|
200
|
+
else value
|
201
|
+
end
|
159
202
|
end
|
160
|
-
|
161
|
-
def
|
162
|
-
|
203
|
+
|
204
|
+
def translate_xml_entities(value)
|
205
|
+
value.gsub(/</, "<").
|
206
|
+
gsub(/>/, ">").
|
207
|
+
gsub(/"/, '"').
|
208
|
+
gsub(/'/, "'").
|
209
|
+
gsub(/&/, "&")
|
163
210
|
end
|
164
211
|
|
165
|
-
|
166
|
-
|
212
|
+
def undasherize_keys(params)
|
213
|
+
params.keys.each do |key, vvalue|
|
214
|
+
params[key.tr("-", "_")] = params.delete(key)
|
215
|
+
end
|
216
|
+
params
|
167
217
|
end
|
168
218
|
|
169
|
-
|
170
|
-
|
171
|
-
m.to_s =~ /=$/?self[$`]=a[0]:a==[]?self[m]:raise(NoMethodError,"#{m}")
|
219
|
+
def inner_html
|
220
|
+
@children.join
|
172
221
|
end
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
222
|
+
|
223
|
+
def to_html
|
224
|
+
"<#{name}#{attributes_to_xml}>#{inner_html}</#{name}>"
|
225
|
+
end
|
226
|
+
|
227
|
+
def attributes_to_xml
|
228
|
+
attributes.keys.map do |key|
|
229
|
+
"#{key}='#{attributes[key]}'"
|
230
|
+
end.join
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class ToHashParser # :nodoc:
|
235
|
+
def self.from_xml(xml)
|
236
|
+
stack = []
|
237
|
+
parser = REXML::Parsers::BaseParser.new(xml)
|
238
|
+
|
239
|
+
while true
|
240
|
+
event = parser.pull
|
241
|
+
case event[0]
|
242
|
+
when :end_document
|
243
|
+
break
|
244
|
+
when :end_doctype, :start_doctype
|
245
|
+
# do nothing
|
246
|
+
when :start_element
|
247
|
+
stack.push REXMLUtilityNode.new(event[1], event[2])
|
248
|
+
when :end_element
|
249
|
+
if stack.size > 1
|
250
|
+
temp = stack.pop
|
251
|
+
stack.last.add_node(temp)
|
252
|
+
end
|
253
|
+
when :text
|
254
|
+
stack.last.add_node(event[1]) unless event[1].strip.length == 0
|
255
|
+
end
|
256
|
+
end
|
257
|
+
stack.pop.to_hash
|
258
|
+
end
|
181
259
|
end
|
@@ -1,5 +1,26 @@
|
|
1
1
|
class Module
|
2
2
|
|
3
|
+
# alias_method_chain :foo, :bar produces the following pattern:
|
4
|
+
#
|
5
|
+
# alias_method :foo_without_bar, :foo
|
6
|
+
# alias_method :foo, :foo_with_bar
|
7
|
+
#
|
8
|
+
# You will then need to write the foo_with_bar method, which will
|
9
|
+
# be able to reference foo_without_bar:
|
10
|
+
#
|
11
|
+
# def foo_with_bar
|
12
|
+
# foo_without_bar.map {|x| x + 1 }
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Method punctuation (foo? and foo!) will be retained:
|
16
|
+
#
|
17
|
+
# alias_method_chain :foo?, :bar will produce:
|
18
|
+
# foo_without_bar?
|
19
|
+
# foo_with_bar?
|
20
|
+
#
|
21
|
+
# alias_method_chain :foo!, :bar will produce:
|
22
|
+
# foo_without_bar!
|
23
|
+
# foo_with_bar!
|
3
24
|
def alias_method_chain(target, feature)
|
4
25
|
# Strip out punctuation on predicates or bang methods since
|
5
26
|
# e.g. target?_without_feature is not a valid method name.
|
@@ -9,6 +30,26 @@ class Module
|
|
9
30
|
alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
|
10
31
|
end
|
11
32
|
|
33
|
+
# defines a series of instance variables to be initialized when
|
34
|
+
# the class is initialized.
|
35
|
+
#
|
36
|
+
# For instance, you could create the class Foo:
|
37
|
+
#
|
38
|
+
# class Foo
|
39
|
+
# attr_initialize :bar, :baz
|
40
|
+
# attr_accessor :bar, :baz
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# and initialize an instance of the class as follows:
|
44
|
+
#
|
45
|
+
# Foo.new(1, 2)
|
46
|
+
#
|
47
|
+
# which will create a new Foo object with #bar equal to 1 and #baz
|
48
|
+
# equal to 2.
|
49
|
+
#
|
50
|
+
# Passing a different number of arguments to <tt>new</tt> than you
|
51
|
+
# passed to attr_initialize will result in an ArgumentError being
|
52
|
+
# thrown.
|
12
53
|
def attr_initialize(*attrs)
|
13
54
|
define_method(:initialize) do |*passed|
|
14
55
|
raise ArgumentError, "Wrong number of arguments" \
|