merb 0.3.4 → 0.3.7

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.
Files changed (62) hide show
  1. data/README +206 -197
  2. data/Rakefile +12 -21
  3. data/bin/merb +1 -1
  4. data/examples/skeleton/Rakefile +6 -20
  5. data/examples/skeleton/dist/app/mailers/layout/application.erb +1 -0
  6. data/examples/skeleton/dist/conf/database.yml +23 -0
  7. data/examples/skeleton/dist/conf/environments/development.rb +1 -0
  8. data/examples/skeleton/dist/conf/environments/production.rb +1 -0
  9. data/examples/skeleton/dist/conf/environments/test.rb +1 -0
  10. data/examples/skeleton/dist/conf/merb.yml +32 -28
  11. data/examples/skeleton/dist/conf/merb_init.rb +16 -13
  12. data/examples/skeleton/dist/conf/router.rb +9 -9
  13. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +2 -2
  14. data/lib/merb.rb +23 -18
  15. data/lib/merb/caching/fragment_cache.rb +3 -7
  16. data/lib/merb/caching/store/memcache.rb +20 -0
  17. data/lib/merb/core_ext/merb_array.rb +0 -0
  18. data/lib/merb/core_ext/merb_class.rb +44 -4
  19. data/lib/merb/core_ext/merb_enumerable.rb +43 -1
  20. data/lib/merb/core_ext/merb_hash.rb +200 -122
  21. data/lib/merb/core_ext/merb_kernel.rb +2 -0
  22. data/lib/merb/core_ext/merb_module.rb +41 -0
  23. data/lib/merb/core_ext/merb_numeric.rb +57 -5
  24. data/lib/merb/core_ext/merb_object.rb +172 -6
  25. data/lib/merb/generators/merb_app/merb_app.rb +15 -9
  26. data/lib/merb/merb_abstract_controller.rb +193 -0
  27. data/lib/merb/merb_constants.rb +26 -1
  28. data/lib/merb/merb_controller.rb +143 -234
  29. data/lib/merb/merb_dispatcher.rb +28 -20
  30. data/lib/merb/merb_drb_server.rb +2 -3
  31. data/lib/merb/merb_exceptions.rb +194 -49
  32. data/lib/merb/merb_handler.rb +34 -26
  33. data/lib/merb/merb_mail_controller.rb +200 -0
  34. data/lib/merb/merb_mailer.rb +33 -13
  35. data/lib/merb/merb_part_controller.rb +42 -0
  36. data/lib/merb/merb_plugins.rb +293 -0
  37. data/lib/merb/merb_request.rb +6 -4
  38. data/lib/merb/merb_router.rb +99 -65
  39. data/lib/merb/merb_server.rb +65 -21
  40. data/lib/merb/merb_upload_handler.rb +2 -1
  41. data/lib/merb/merb_view_context.rb +36 -15
  42. data/lib/merb/mixins/basic_authentication_mixin.rb +5 -5
  43. data/lib/merb/mixins/controller_mixin.rb +67 -28
  44. data/lib/merb/mixins/erubis_capture_mixin.rb +1 -8
  45. data/lib/merb/mixins/form_control_mixin.rb +280 -42
  46. data/lib/merb/mixins/render_mixin.rb +127 -45
  47. data/lib/merb/mixins/responder_mixin.rb +5 -7
  48. data/lib/merb/mixins/view_context_mixin.rb +260 -94
  49. data/lib/merb/session.rb +23 -0
  50. data/lib/merb/session/merb_ar_session.rb +28 -16
  51. data/lib/merb/session/merb_mem_cache_session.rb +108 -0
  52. data/lib/merb/session/merb_memory_session.rb +65 -20
  53. data/lib/merb/template/erubis.rb +22 -13
  54. data/lib/merb/template/haml.rb +5 -16
  55. data/lib/merb/template/markaby.rb +5 -3
  56. data/lib/merb/template/xml_builder.rb +17 -5
  57. data/lib/merb/test/merb_fake_request.rb +63 -0
  58. data/lib/merb/test/merb_multipart.rb +58 -0
  59. data/lib/tasks/db.rake +2 -0
  60. data/lib/tasks/merb.rake +20 -8
  61. metadata +24 -25
  62. 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
- end
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
- def from_xml(xml)
5
- # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
6
- undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml,
7
- 'forcearray' => false,
8
- 'forcecontent' => true,
9
- 'keeproot' => true,
10
- 'contentkey' => '__content__')
11
- ))
12
- end
13
-
14
- private
15
- def typecast_xml_value(value)
16
- case value.class.to_s
17
- when "Hash"
18
- if value.has_key?("__content__")
19
- content = translate_xml_entities(value["__content__"])
20
- case value["type"]
21
- when "integer" then content.to_i
22
- when "boolean" then content.strip == "true"
23
- when "datetime" then ::Time.parse(content).utc
24
- when "date" then ::Date.parse(content)
25
- else content
26
- end
27
- else
28
- (value.blank? || value['type'] || value['nil'] == 'true') ? nil : value.inject({}) do |h,(k,v)|
29
- h[k] = typecast_xml_value(v)
30
- h
31
- end
32
- end
33
- when "Array"
34
- value.map! { |i| typecast_xml_value(i) }
35
- case value.length
36
- when 0 then nil
37
- when 1 then value.first
38
- else value
39
- end
40
- when "String"
41
- value
42
- else
43
- raise "can't typecast #{value.inspect}"
44
- end
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(/&lt;/, "<").
49
- gsub(/&gt;/, ">").
50
- gsub(/&quot;/, '"').
51
- gsub(/&apos;/, "'").
52
- gsub(/&amp;/, "&")
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(*keys)
98
- self.reject { |k,v| ! keys.include?(k) }
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(*keys)
106
- self.reject { |k,v| keys.include?(k) }
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
- # like HashWithIndifferentAccess from ActiveSupport.
112
- class MerbHash < Hash
113
- def initialize(constructor = {})
114
- if constructor.is_a?(Hash)
115
- super()
116
- update(constructor)
117
- else
118
- super(constructor)
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 []=(key, value)
131
- regular_writer(convert_key(key), convert_value(value))
132
- end
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
- alias_method :merge!, :update
138
+ end
139
+
140
+ require 'rexml/parsers/streamparser'
141
+ require 'rexml/parsers/baseparser'
142
+ require 'rexml/light/node'
140
143
 
141
- def key?(key)
142
- super(convert_key(key))
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
- alias_method :include?, :key?
146
- alias_method :has_key?, :key?
147
- alias_method :member?, :key?
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
- def fetch(key, *extras)
150
- super(convert_key(key), *extras)
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 values_at(*indices)
154
- indices.collect {|key| self[convert_key(key)]}
187
+ def to_s
188
+ self.to_html
155
189
  end
156
190
 
157
- def dup
158
- MerbHash.new(self)
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 merge(hash)
162
- self.dup.update(hash)
203
+
204
+ def translate_xml_entities(value)
205
+ value.gsub(/&lt;/, "<").
206
+ gsub(/&gt;/, ">").
207
+ gsub(/&quot;/, '"').
208
+ gsub(/&apos;/, "'").
209
+ gsub(/&amp;/, "&")
163
210
  end
164
211
 
165
- def delete(key)
166
- super(convert_key(key))
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
- # allow merbhash.key to work the same as merbhash[key]
170
- def method_missing(m,*a)
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
- protected
175
- def convert_key(key)
176
- key.kind_of?(Symbol) ? key.to_s : key
177
- end
178
- def convert_value(value)
179
- value.is_a?(Hash) ? value.with_indifferent_access : value
180
- end
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
@@ -14,6 +14,8 @@ module Kernel
14
14
  end
15
15
  end
16
16
 
17
+ # does a basic require, and prints the message passed as an optional
18
+ # second parameter if an error occurs.
17
19
  def rescue_require(sym, message = nil)
18
20
  require sym
19
21
  rescue LoadError, RuntimeError
@@ -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" \