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,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