merb 0.3.4 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
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" \