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,20 +1,72 @@
1
1
  class Numeric
2
+
3
+ # Converts a number to currency. Optionally, it allows localization of currency.
4
+ #
5
+ # 1.5.to_currency #=> "$1.50"
6
+ #
7
+ # You can also localize the symbol that appears before the number, the
8
+ # thousands delimiter, the decimal delimiter, and the symbol that appears
9
+ # after the number:
10
+ # 1.5.to_currency nil, ",", ".", "$USD" #=> "1.50$USD"
11
+ # 67_000.5.to_currency nil, ".", ",", "DM" #=> "67.000,50DM"
2
12
  def to_currency(pre_symbol='$', thousands=',', decimal='.', post_symbol=nil) #:nodoc:
3
- "#{pre_symbol}#{("%.2f" % self ).gsub(/(\d)(?=(?:\d{3})+(?:$|\.))/,"\\1#{thousands}")}#{post_symbol}"
13
+ "#{pre_symbol}#{("%.2f" % self ).gsub(".", decimal).gsub(/(\d)(?=(?:\d{3})+(?:$|[\\#{decimal}]))/,"\\1#{thousands}")}#{post_symbol}"
4
14
  end
5
15
 
16
+ # Divides the number by 1,000,000
17
+ #
18
+ # 2.microseconds #=> 2.0e-06
6
19
  def microseconds() Float(self * (10 ** -6)) end
20
+ # Divides the number by 1,000
21
+ #
22
+ # 2.milliseconds #=> 0.002
7
23
  def milliseconds() Float(self * (10 ** -3)) end
24
+ # Returns the same number as is passed in
25
+ #
26
+ # 2.seconds #=> 2
8
27
  def seconds() self end
28
+ # Multiplies the number by 60
29
+ #
30
+ # 2.minutes #=> 120
9
31
  def minutes() 60 * seconds end
32
+ # Multiplies the number by 60 minutes
33
+ #
34
+ # 2.hours #=> 7200
10
35
  def hours() 60 * minutes end
36
+ # Multiples the number by 24 hours
37
+ #
38
+ # 2.days #=> 172800
11
39
  def days() 24 * hours end
40
+ # Multiples the number by 7 days
41
+ #
42
+ # 2.weeks #=> 1209600
12
43
  def weeks() 7 * days end
44
+ # Multiples the number by 30 days
45
+ #
46
+ # 2.months #=> 5184000
13
47
  def months() 30 * days end
48
+ # Multiplies the number by 365 days
49
+ #
50
+ # 2.years #=> 63072000
14
51
  def years() 365 * days end
52
+ # Multiplies the number by 10 years
53
+ #
54
+ # 2.decades #=> 630720000
15
55
  def decades() 10 * years end
16
-
17
- %w{ microseconds milliseconds seconds minutes hours days weeks months years decades
18
- }.each{ |m| mm = m.chop; alias_method mm, m }
19
56
 
20
- end
57
+ # Each of the time extensions also works in the singular:
58
+ #
59
+ # 1.day #=> 86400
60
+ # 1.hour #=> 3600
61
+ alias_method :microsecond, :microseconds
62
+ alias_method :millisecond, :milliseconds
63
+ alias_method :second, :seconds
64
+ alias_method :minute, :minutes
65
+ alias_method :hour, :hours
66
+ alias_method :day, :days
67
+ alias_method :week, :weeks
68
+ alias_method :month, :months
69
+ alias_method :year, :years
70
+ alias_method :decade, :decades
71
+
72
+ end
@@ -1,27 +1,193 @@
1
1
  class Object
2
2
 
3
+ # Yields <tt>value</tt> to the passed block and returns the value.
4
+ # The object passed in as the value parameter is also passed to the block
5
+ # as a parameter.
6
+ #
7
+ # returning({}) do |hsh|
8
+ # hsh.merge!((:bar => :baz))
9
+ # end #=> {:bar => :baz}
3
10
  def returning(value)
4
11
  yield(value)
5
12
  value
6
13
  end
7
14
 
15
+ # Extracts the singleton class, so that metaprogramming can be done on it.
16
+ #
17
+ # Let's look at two code snippets:
18
+ #
19
+ # class MyString < String; end
20
+ #
21
+ # MyString.instance_eval do
22
+ # define_method :foo do
23
+ # puts self
24
+ # end
25
+ # end
26
+ #
27
+ # MyString.meta_class.instance_eval do
28
+ # define_method :bar do
29
+ # puts self
30
+ # end
31
+ # end
32
+ #
33
+ # def String.add_meta_var(var)
34
+ # self.meta_class.instance_eval do
35
+ # define_method var do
36
+ # puts "HELLO"
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # MyString.new("Hello").foo #=> "Hello"
42
+ # MyString.new("Hello").bar #=> NoMethodError: undefined method `bar' for "Hello":MyString
43
+ # MyString.foo #=> NoMethodError: undefined method `foo' for MyString:Class
44
+ # MyString.bar #=> MyString
45
+ # String.bar #=> NoMethodError: undefined method `bar' for String:Class
46
+ #
47
+ # MyString.add_meta_var(:x)
48
+ # MyString.x #=> HELLO
49
+ #
50
+ # As you can see, using #meta_class allows you to execute code (and here, define
51
+ # a method) on the metaclass itself. It also allows you to define class methods that can
52
+ # be run on subclasses, and then be able to execute code on the metaclass of the subclass
53
+ # (here MyString).
54
+ #
55
+ # In this case, we were able to define a class method (add_meta_var) on String that was
56
+ # executable by the MyString subclass. It was then able to define a method on the subclass
57
+ # by adding it to the MyString metaclass.
58
+ #
59
+ # For more information, you can check out _why's excellent article at:
60
+ # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
8
61
  def meta_class() class << self; self end end
9
62
 
63
+ # Runs instance_eval on the metaclass (see Object#meta_class).
64
+ #
65
+ # String.meta_eval do
66
+ # define_method :zoo do
67
+ # puts "zoo"
68
+ # end
69
+ # end
70
+ #
71
+ # String.zoo # => "zoo"
10
72
  def meta_eval(&blk) meta_class.instance_eval( &blk ) end
11
73
 
74
+ # Defines a method on the metaclass (see Object#meta_class).
75
+ #
76
+ # String.meta_def :zoo do
77
+ # puts "zoo"
78
+ # end
79
+ #
80
+ # String.zoo #=> "zoo"
81
+ #
82
+ # If the class inherits from another class, it will only be defined
83
+ # on the direct class meta_def is called on.
84
+ #
85
+ # class Foo; end
86
+ # class Bar < Foo; end
87
+ # class Baz < Foo; end
88
+ #
89
+ # Bar.meta_def :q do; "Q"; end
90
+ # Foo.q #=> undefined method `r' for Foo:Class
91
+ # Bar.q #=> "Q"
92
+ # Baz.q #=> undefined method `r' for Baz:Class
93
+ #
94
+ # See Object#class_def for a comprehensive example containing meta_def
12
95
  def meta_def(name, &blk) meta_eval { define_method name, &blk } end
13
96
 
97
+ # Defines a method on new instances of the class.
98
+ #
99
+ # String.class_def :zoo do
100
+ # puts "zoo"
101
+ # end
102
+ #
103
+ # "HELLO".zoo #=> "zoo"
104
+ #
105
+ # In combination with meta_def, you can do some pretty powerful
106
+ # things:
107
+ #
108
+ # require 'merb_object'
109
+ # class Foo
110
+ # def self.var
111
+ # @var
112
+ # end
113
+ # def self.make_var baz
114
+ # attr_accessor baz
115
+ # meta_def baz do |val|
116
+ # @var = val
117
+ # end
118
+ # class_def :initialize do
119
+ # instance_variable_set("@#{baz}", self.class.var)
120
+ # end
121
+ # end
122
+ # end
123
+ #
124
+ # It might look a bit hairy, but here are some results that
125
+ # may help:
126
+ #
127
+ # class Bar < Foo
128
+ # make_var :foo
129
+ # foo "FOO"
130
+ # end
131
+ #
132
+ # Bar.new.foo #=> "FOO"
133
+ #
134
+ # Essentially, what's happening is that Foo.make_var has the
135
+ # following effects when some symbol (:foo) is passed in:
136
+ # * Adds a new :foo accessor (returning @foo)
137
+ # * Adds a new foo method on the **class**, allowing you to set
138
+ # a default value.
139
+ # * Sets @foo to that default value when new objects are
140
+ # initialized.
141
+ #
142
+ # In the case of the Bar class, the following occurred:
143
+ # * make_var :foo created a new :foo accessor
144
+ # * foo "FOO" set the default value of @foo to "FOO"
145
+ # * Bar.new created a new Bar object containing the
146
+ # instance variable @foo containing the default value
147
+ # "FOO"
14
148
  def class_def name, &blk
15
- self.class.class_eval { define_method name, &blk }
149
+ class_eval { define_method name, &blk }
16
150
  end
17
151
 
152
+ # Returns true if:
153
+ # * it's an empty array
154
+ # * !self evaluates to true
155
+ #
156
+ # [].blank? #=> true
157
+ # nil.blank? #=> true
158
+ # false.blank? #=> true
159
+ # [nil].blank? #=> false
18
160
  def blank?
19
- if respond_to? :empty? then empty?
20
- elsif respond_to? :zero? then zero?
21
- else !self
22
- end
161
+ if respond_to?(:empty?) && respond_to?(:strip)
162
+ empty? or strip.empty?
163
+ elsif respond_to?(:empty?)
164
+ empty?
165
+ else
166
+ !self
167
+ end
23
168
  end
24
169
 
170
+ def full_const_get(name)
171
+ list = name.split("::")
172
+ obj = Object
173
+ list.each {|x| obj = obj.const_get(x) }
174
+ obj
175
+ end
176
+
177
+ # An elegant way to refactor out common options
178
+ #
179
+ # with_options :order => 'created_at', :class_name => 'Comment' do |post|
180
+ # post.has_many :comments, :conditions => ['approved = ?', true], :dependent => :delete_all
181
+ # post.has_many :unapproved_comments, :conditions => ['approved = ?', false]
182
+ # post.has_many :all_comments
183
+ # end
184
+ #
185
+ # Can also be used with an explicit reciever:
186
+ #
187
+ # map.with_options :controller => "people" do |people|
188
+ # people.connect "/people", :action => "index"
189
+ # people.connect "/people/:id", :action => "show"
190
+ # end
25
191
  def with_options(options)
26
192
  yield Merb::OptionMerger.new(self, options)
27
193
  end
@@ -51,4 +217,4 @@ module Merb
51
217
  end
52
218
  end
53
219
  end
54
- end
220
+ end
@@ -1,17 +1,10 @@
1
1
  require 'fileutils'
2
2
  require 'find'
3
3
 
4
- begin
5
- require 'archive/tar/minitar'
6
- rescue LoadError
7
- puts "You must gem install archive-tar-minitar to use the merb app generator"
8
- exit!
9
- end
10
-
11
4
  module Merb
12
5
 
13
6
  class AppGenerator
14
- SKELETON = File.expand_path File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'examples/skeleton.tar')
7
+ SKELETON_DIR = File.expand_path File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'examples/skeleton')
15
8
 
16
9
  def self.run(path)
17
10
  @path = path
@@ -23,7 +16,20 @@ module Merb
23
16
 
24
17
  puts "Copying skeleton app to '#@path'"
25
18
  # Unpacks 'test.tar' to 'x', creating 'x' if necessary.
26
- Archive::Tar::Minitar.unpack(SKELETON, @path)
19
+ FileUtils.cp_r(SKELETON_DIR, @path)
20
+ Find.find(@path) do |f|
21
+ FileUtils.rm_rf(f) if /\.svn$/ =~ f
22
+ end
23
+ public_path = File.expand_path(File.join(@path, "dist", "public"))
24
+ mailer_path = File.expand_path(File.join(@path, "dist", "app", "mailers"))
25
+ app_path = File.expand_path(File.join(@path, "dist", "app"))
26
+ FileUtils.mkdir_p(["#{mailer_path}/helpers",
27
+ "#{mailer_path}/views",
28
+ "#{@path}/log",
29
+ "#{app_path}/models",
30
+ "#{public_path}/javascripts",
31
+ "#{public_path}/stylesheets",
32
+ "#{public_path}/images"])
27
33
  puts 'Done'
28
34
  end
29
35
 
@@ -0,0 +1,193 @@
1
+ require File.dirname(__FILE__)+'/mixins/render_mixin'
2
+
3
+ module Merb
4
+
5
+ class AbstractController
6
+ include Merb::RenderMixin
7
+
8
+ class_inheritable_accessor :before_filters
9
+ class_inheritable_accessor :after_filters
10
+
11
+ # Holds internal execution times. Prefaced with an underscore to not
12
+ # conflict with user-defined controller instance variables.
13
+ attr_accessor :_benchmarks
14
+
15
+ def initialize(*args)
16
+ @_benchmarks = {}
17
+ end
18
+
19
+ def dispatch(action=:to_s)
20
+ caught = catch(:halt) do
21
+ start = Time.now
22
+ result = call_filters(before_filters)
23
+ @_benchmarks[:before_filters_time] = Time.now - start if before_filters
24
+ result
25
+ end
26
+ @_body = case caught
27
+ when :filter_chain_completed
28
+ call_action(action)
29
+ when String
30
+ caught
31
+ when nil
32
+ filters_halted
33
+ when Symbol
34
+ send(caught)
35
+ when Proc
36
+ caught.call(self)
37
+ else
38
+ raise MerbControllerError, "The before filter chain is broken dude. wtf?"
39
+ end
40
+ start = Time.now
41
+ call_filters(after_filters)
42
+ @_benchmarks[:after_filters_time] = Time.now - start if after_filters
43
+ end
44
+
45
+ protected
46
+
47
+ def call_action(action)
48
+ send(action)
49
+ end
50
+
51
+ # calls a filter chain according to rules.
52
+ def call_filters(filter_set)
53
+ (filter_set || []).each do |(filter, rule)|
54
+ ok = false
55
+ if rule.has_key?(:only)
56
+ if rule[:only].include?(params[:action].intern)
57
+ ok = true
58
+ end
59
+ elsif rule.has_key?(:exclude)
60
+ if !rule[:exclude].include?(params[:action].intern)
61
+ ok = true
62
+ end
63
+ else
64
+ ok = true
65
+ end
66
+ if ok
67
+ case filter
68
+ when Symbol, String
69
+ send(filter)
70
+ when Proc
71
+ filter.call(self)
72
+ end
73
+ end
74
+ end
75
+ return :filter_chain_completed
76
+ end
77
+
78
+ def finalize_session
79
+ # noop
80
+ end
81
+
82
+ def setup_session
83
+ # noop
84
+ end
85
+
86
+ # override this method on your controller classes to specialize
87
+ # the output when the filter chain is halted.
88
+ def filters_halted
89
+ "<html><body><h1>Filter Chain Halted!</h1></body></html>"
90
+ end
91
+
92
+
93
+ # #before is a class method that allows you to specify before filters in
94
+ # your controllers. Filters can either be a symbol or string that
95
+ # corresponds to a method name to call, or a proc object. if it is a method
96
+ # name that method will be called and if it is a proc it will be called
97
+ # with an argument of self where self is the current controller object.
98
+ # When you use a proc as a filter it needs to take one parameter.
99
+ #
100
+ # examples:
101
+ # before :some_filter
102
+ # before :authenticate, :exclude => [:login, :signup]
103
+ # before Proc.new {|c| c.some_method }, :only => :foo
104
+ #
105
+ # You can use either :only => :actionname or :exclude => [:this, :that]
106
+ # but not both at once. :only will only run before the listed actions
107
+ # and :exclude will run for every action that is not listed.
108
+ #
109
+ # Merb's before filter chain is very flexible. To halt the filter chain you
110
+ # use throw :halt. If throw is called with only one argument of :halt the
111
+ # return of the method filters_halted will be what is rendered to the view.
112
+ # You can overide filters_halted in your own controllers to control what it
113
+ # outputs. But the throw construct is much more powerful then just that.
114
+ # throw :halt can also take a second argument. Here is what that second arg
115
+ # can be and the behavior each type can have:
116
+ #
117
+ # * String:
118
+ # when the second arg is a string then that string will be what
119
+ # is rendered to the browser. Since merb's render method returns
120
+ # a string you can render a template or just use a plain string:
121
+ #
122
+ # throw :halt, "You don't have permissions to do that!"
123
+ # throw :halt, render(:action => :access_denied)
124
+ #
125
+ # * Symbol:
126
+ # If the second arg is a symbol then the method named after that
127
+ # symbol will be called
128
+ #
129
+ # throw :halt, :must_click_disclaimer
130
+ #
131
+ # * Proc:
132
+ #
133
+ # If the second arg is a Proc, it will be called and its return
134
+ # value will be what is rendered to the browser:
135
+ #
136
+ # throw :halt, Proc.new {|c| c.access_denied }
137
+ # throw :halt, Proc.new {|c| Tidy.new(c.index) }
138
+ #
139
+ def self.before(filter, opts={})
140
+ raise(ArgumentError,
141
+ "You can specify either :only or :exclude but
142
+ not both at the same time for the same filter."
143
+ ) if opts.has_key?(:only) && opts.has_key?(:exclude)
144
+
145
+ opts = shuffle_filters!(opts)
146
+
147
+ case filter
148
+ when Symbol, String, Proc
149
+ (self.before_filters ||= []) << [filter, opts]
150
+ else
151
+ raise(ArgumentError,
152
+ 'filters need to be either a Symbol, String or a Proc'
153
+ )
154
+ end
155
+ end
156
+
157
+ # #after is a class method that allows you to specify after filters in your
158
+ # controllers. Filters can either be a symbol or string that corresponds to
159
+ # a method name or a proc object. If it is a method name that method will
160
+ # be called and if it is a proc it will be called with an argument of self.
161
+ # When you use a proc as a filter it needs to take one parameter. You can
162
+ # gain access to the response body like so: after Proc.new {|c|
163
+ # Tidy.new(c.body) }, :only => :index
164
+ def self.after(filter, opts={})
165
+ raise(ArgumentError,
166
+ "You can specify either :only or :exclude but
167
+ not both at the same time for the same filter."
168
+ ) if opts.has_key?(:only) && opts.has_key?(:exclude)
169
+
170
+ opts = shuffle_filters!(opts)
171
+
172
+ case filter
173
+ when Symbol, Proc, String
174
+ (self.after_filters ||= []) << [filter, opts]
175
+ else
176
+ raise(ArgumentError,
177
+ 'After filters need to be either a Symbol, String or a Proc'
178
+ )
179
+ end
180
+ end
181
+
182
+ def self.shuffle_filters!(opts={})
183
+ if opts[:only] && opts[:only].is_a?(Symbol)
184
+ opts[:only] = [opts[:only]]
185
+ end
186
+ if opts[:exclude] && opts[:exclude].is_a?(Symbol)
187
+ opts[:exclude] = [opts[:exclude]]
188
+ end
189
+ return opts
190
+ end
191
+ end
192
+
193
+ end