ramaze 2011.07.25 → 2011.10.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. data/.gitignore +3 -0
  2. data/.mailmap +3 -2
  3. data/.travis.yml +17 -0
  4. data/.yardopts +13 -0
  5. data/README.md +95 -352
  6. data/examples/app/blog/app.rb +25 -64
  7. data/examples/app/blog/config.ru +11 -9
  8. data/examples/app/blog/controller/init.rb +29 -86
  9. data/examples/app/blog/controller/posts.rb +232 -0
  10. data/examples/app/blog/controller/users.rb +160 -0
  11. data/examples/app/blog/layout/default.xhtml +61 -0
  12. data/examples/app/blog/migrations/01_create_schema.rb +50 -0
  13. data/examples/app/blog/model/comment.rb +41 -54
  14. data/examples/app/blog/model/init.rb +41 -13
  15. data/examples/app/blog/model/post.rb +35 -0
  16. data/examples/app/blog/model/user.rb +105 -0
  17. data/examples/app/blog/public/.htaccess +24 -0
  18. data/examples/app/blog/public/css/grid.css +107 -0
  19. data/examples/app/blog/public/css/layout.css +203 -0
  20. data/examples/app/blog/public/css/reset.css +123 -0
  21. data/examples/app/blog/public/css/text.css +109 -0
  22. data/examples/app/blog/public/dispatch.fcgi +11 -0
  23. data/examples/app/blog/public/favicon.ico +0 -0
  24. data/examples/app/blog/public/images/bg.png +0 -0
  25. data/examples/app/blog/start.rb +18 -3
  26. data/examples/app/blog/view/feed.xhtml +23 -0
  27. data/examples/app/blog/view/form.xhtml +11 -0
  28. data/examples/app/blog/view/index.xhtml +44 -0
  29. data/examples/app/blog/view/users/form.xhtml +12 -0
  30. data/examples/app/blog/view/users/index.xhtml +30 -0
  31. data/examples/app/blog/view/users/login.xhtml +8 -0
  32. data/examples/app/blog/view/view.xhtml +68 -0
  33. data/{doc → guide}/AUTHORS +5 -3
  34. data/{doc → guide}/CHANGELOG +428 -0
  35. data/{doc/GPL → guide/GPL_LICENSE} +0 -0
  36. data/{doc/COPYING → guide/RUBY_LICENSE} +3 -6
  37. data/guide/_static/logo.png +0 -0
  38. data/guide/_static/logo.svg +49 -0
  39. data/guide/_static/ramaze_console.png +0 -0
  40. data/guide/css/common.css +20 -0
  41. data/guide/general/cache.md +167 -0
  42. data/guide/general/configuration.md +168 -0
  43. data/guide/general/contributing.md +108 -0
  44. data/guide/general/controllers.md +115 -0
  45. data/guide/general/helpers.md +76 -0
  46. data/guide/general/installation.md +58 -0
  47. data/guide/general/logging.md +99 -0
  48. data/guide/general/middlewares.md +100 -0
  49. data/guide/general/models.md +78 -0
  50. data/guide/general/principles.md +53 -0
  51. data/guide/general/ramaze_command.md +155 -0
  52. data/guide/general/routes.md +81 -0
  53. data/guide/general/sessions.md +140 -0
  54. data/guide/general/special_thanks.md +67 -0
  55. data/guide/general/testing.md +61 -0
  56. data/guide/general/views.md +322 -0
  57. data/guide/tutorials/introduction.md +259 -0
  58. data/lib/proto/config.ru +1 -1
  59. data/lib/proto/public/favicon.ico +0 -0
  60. data/lib/proto/view/index.xhtml +7 -7
  61. data/lib/ramaze.rb +4 -4
  62. data/lib/ramaze/app.rb +11 -11
  63. data/lib/ramaze/app_graph.rb +2 -4
  64. data/lib/ramaze/bin/console.rb +3 -3
  65. data/lib/ramaze/bin/create.rb +2 -2
  66. data/lib/ramaze/bin/restart.rb +4 -4
  67. data/lib/ramaze/bin/runner.rb +5 -5
  68. data/lib/ramaze/bin/start.rb +19 -4
  69. data/lib/ramaze/bin/status.rb +3 -3
  70. data/lib/ramaze/bin/stop.rb +3 -3
  71. data/lib/ramaze/cache.rb +1 -0
  72. data/lib/ramaze/cache/lru.rb +8 -4
  73. data/lib/ramaze/cache/memcache.rb +32 -13
  74. data/lib/ramaze/cache/redis.rb +164 -0
  75. data/lib/ramaze/cache/sequel.rb +43 -28
  76. data/lib/ramaze/controller.rb +1 -2
  77. data/lib/ramaze/dependencies.rb +40 -3
  78. data/lib/ramaze/helper/bench.rb +26 -16
  79. data/lib/ramaze/helper/blue_form.rb +46 -73
  80. data/lib/ramaze/helper/cache.rb +10 -6
  81. data/lib/ramaze/helper/csrf.rb +35 -39
  82. data/lib/ramaze/helper/disqus.rb +5 -4
  83. data/lib/ramaze/helper/email.rb +35 -24
  84. data/lib/ramaze/helper/erector.rb +9 -13
  85. data/lib/ramaze/helper/flash.rb +7 -9
  86. data/lib/ramaze/helper/formatting.rb +194 -179
  87. data/lib/ramaze/helper/gravatar.rb +4 -8
  88. data/lib/ramaze/helper/identity.rb +3 -3
  89. data/lib/ramaze/helper/layout.rb +23 -8
  90. data/lib/ramaze/helper/markaby.rb +1 -1
  91. data/lib/ramaze/helper/paginate.rb +46 -39
  92. data/lib/ramaze/helper/request_accessor.rb +3 -1
  93. data/lib/ramaze/helper/simple_captcha.rb +18 -17
  94. data/lib/ramaze/helper/stack.rb +1 -1
  95. data/lib/ramaze/helper/tagz.rb +4 -2
  96. data/lib/ramaze/helper/upload.rb +523 -0
  97. data/lib/ramaze/helper/user.rb +4 -8
  98. data/lib/ramaze/helper/xhtml.rb +11 -15
  99. data/lib/ramaze/log.rb +9 -6
  100. data/lib/ramaze/log/rotatinginformer.rb +62 -27
  101. data/lib/ramaze/log/syslog.rb +20 -15
  102. data/lib/ramaze/log/xosd.rb +2 -1
  103. data/lib/ramaze/reloader.rb +2 -0
  104. data/lib/ramaze/request.rb +11 -10
  105. data/lib/ramaze/setup.rb +23 -6
  106. data/lib/ramaze/snippets/array/put_within.rb +3 -9
  107. data/lib/ramaze/snippets/binding/locals.rb +5 -10
  108. data/lib/ramaze/snippets/fiber.rb +1 -23
  109. data/lib/ramaze/snippets/kernel/pretty_inspect.rb +3 -6
  110. data/lib/ramaze/snippets/numeric/filesize_format.rb +3 -5
  111. data/lib/ramaze/snippets/numeric/time.rb +3 -7
  112. data/lib/ramaze/snippets/object/__dir__.rb +3 -7
  113. data/lib/ramaze/snippets/object/instance_variable_defined.rb +3 -6
  114. data/lib/ramaze/snippets/object/pretty.rb +3 -7
  115. data/lib/ramaze/snippets/object/scope.rb +7 -9
  116. data/lib/ramaze/snippets/proc/locals.rb +12 -12
  117. data/lib/ramaze/snippets/ramaze/acquire.rb +15 -14
  118. data/lib/ramaze/snippets/ramaze/deprecated.rb +1 -1
  119. data/lib/ramaze/snippets/ramaze/fiber.rb +1 -1
  120. data/lib/ramaze/snippets/ramaze/lru_hash.rb +2 -3
  121. data/lib/ramaze/snippets/ramaze/struct.rb +2 -4
  122. data/lib/ramaze/snippets/string/camel_case.rb +8 -10
  123. data/lib/ramaze/snippets/string/color.rb +3 -4
  124. data/lib/ramaze/snippets/string/end_with.rb +3 -6
  125. data/lib/ramaze/snippets/string/esc.rb +3 -8
  126. data/lib/ramaze/snippets/string/ord.rb +3 -8
  127. data/lib/ramaze/snippets/string/snake_case.rb +6 -9
  128. data/lib/ramaze/snippets/string/start_with.rb +3 -8
  129. data/lib/ramaze/snippets/string/unindent.rb +3 -6
  130. data/lib/ramaze/snippets/thread/into.rb +1 -3
  131. data/lib/ramaze/spec.rb +2 -31
  132. data/lib/ramaze/spec/bacon.rb +18 -2
  133. data/lib/ramaze/version.rb +1 -1
  134. data/lib/ramaze/view.rb +1 -1
  135. data/ramaze.gemspec +1 -1
  136. data/spec/helper.rb +2 -1
  137. data/spec/ramaze/bin/start.rb +16 -20
  138. data/spec/ramaze/cache/localmemcache.rb +4 -7
  139. data/spec/ramaze/cache/memcache.rb +3 -1
  140. data/spec/ramaze/cache/redis.rb +62 -0
  141. data/spec/ramaze/helper/blue_form.rb +33 -4
  142. data/spec/ramaze/helper/layout.rb +40 -7
  143. data/spec/ramaze/helper/upload.rb +149 -0
  144. data/spec/ramaze/helper/uploads/text_1.txt +1 -0
  145. data/spec/ramaze/helper/uploads/text_2.txt +1 -0
  146. data/spec/ramaze/log/growl.rb +4 -6
  147. data/spec/ramaze/log/syslog.rb +6 -0
  148. data/spec/ramaze/view/lokar.rb +5 -0
  149. data/spec/ramaze/view/nagoro.rb +5 -0
  150. data/tasks/authors.rake +1 -1
  151. data/tasks/bacon.rake +14 -5
  152. data/tasks/changelog.rake +1 -1
  153. data/tasks/yard.rake +12 -4
  154. metadata +277 -239
  155. data/doc/LEGAL +0 -26
  156. data/examples/app/blog/README +0 -3
  157. data/examples/app/blog/controller/comment.rb +0 -45
  158. data/examples/app/blog/controller/entry.rb +0 -85
  159. data/examples/app/blog/controller/main.rb +0 -20
  160. data/examples/app/blog/controller/tag.rb +0 -9
  161. data/examples/app/blog/layout/default.nag +0 -31
  162. data/examples/app/blog/model/entry.rb +0 -89
  163. data/examples/app/blog/model/tag.rb +0 -36
  164. data/examples/app/blog/public/css/screen.css +0 -273
  165. data/examples/app/blog/spec/blog.rb +0 -87
  166. data/examples/app/blog/view/comment/form.nag +0 -10
  167. data/examples/app/blog/view/comment/show.nag +0 -16
  168. data/examples/app/blog/view/entry/edit.nag +0 -14
  169. data/examples/app/blog/view/entry/feed.atom.nag +0 -8
  170. data/examples/app/blog/view/entry/feed.rss.nag +0 -7
  171. data/examples/app/blog/view/entry/index.nag +0 -7
  172. data/examples/app/blog/view/entry/new.nag +0 -13
  173. data/examples/app/blog/view/entry/show.nag +0 -36
  174. data/examples/app/blog/view/feed.atom.nag +0 -18
  175. data/examples/app/blog/view/feed.rss.nag +0 -25
  176. data/examples/app/blog/view/index.nag +0 -6
  177. data/examples/app/blog/view/tag/index.nag +0 -5
  178. data/lib/proto/public/ramaze.png +0 -0
  179. data/lib/ramaze/rest.rb +0 -36
  180. data/spec/ramaze/rest.rb +0 -28
  181. data/tasks/rcov.rake +0 -22
@@ -1,6 +1,5 @@
1
1
  module Ramaze
2
2
  module Helper
3
-
4
3
  # Helps you building Gravatar URIs from an email address.
5
4
  #
6
5
  # For more information about gravatars, please see: http://gravatar.com
@@ -12,11 +11,9 @@ module Ramaze
12
11
  # It might not know about all the secret parameters (like 'force'), if you
13
12
  # find out more of these please consider contributing a patch.
14
13
  module Gravatar
15
-
16
14
  # API to build gravatar URIs from an email address (or any other String).
17
15
  #
18
16
  # @example Simple usage
19
- #
20
17
  # class Gravatars < Ramaze::Controller
21
18
  # helper :gravatar
22
19
  #
@@ -55,11 +52,10 @@ module Ramaze
55
52
  # identicons or the like
56
53
  #
57
54
  # @param [#to_str] email
58
- #
59
55
  # @return [URI]
60
- #
61
56
  # @see http://en.gravatar.com/site/implement/url
62
57
  # @author manveru
58
+ #
63
59
  def gravatar(email, opts = {})
64
60
  uri = URI("http://www.gravatar.com/")
65
61
  ext = opts[:ext]
@@ -74,6 +70,6 @@ module Ramaze
74
70
  uri.query = Rack::Utils.build_query(query) if query.any?
75
71
  uri
76
72
  end
77
- end
78
- end
79
- end
73
+ end # Gravatar
74
+ end # Helper
75
+ end # Ramaze
@@ -114,6 +114,6 @@ module Ramaze
114
114
  def openid_consumer
115
115
  OpenID::Consumer.new(session, Ramaze::OpenIDStore)
116
116
  end
117
- end
118
- end
119
- end
117
+ end # Identity
118
+ end # Helper
119
+ end # Ramaze
@@ -11,22 +11,22 @@ module Ramaze
11
11
  # specify a number of layouts and the methods for which these layouts should
12
12
  # be used.
13
13
  #
14
- # == Examples
14
+ # ## Examples
15
15
  #
16
16
  # The most basic example is simply setting a layout as you would do with the
17
17
  # layout() method:
18
18
  #
19
- # set_layout 'default'
19
+ # set_layout 'default'
20
20
  #
21
21
  # This of course is very boring, time to add some more spices to our code:
22
22
  #
23
- # set_layout 'default' => [:index]
23
+ # set_layout 'default' => [:index]
24
24
  #
25
25
  # Woah! What just happened? It's quite easy actually, we merely defined that
26
26
  # the layout called "default" should be used for the index method *only*.
27
27
  # Pretty sweet huh? It gets even better:
28
28
  #
29
- # set_layout 'default' => [:index, :edit], 'alternative' => [:add, :process]
29
+ # set_layout 'default' => [:index, :edit], 'alternative' => [:add, :process]
30
30
  #
31
31
  # A few things changed. First of all there are now two key/value groups.
32
32
  # Each group defines a layout (the key) and a set of methods (the value) for
@@ -49,7 +49,7 @@ module Ramaze
49
49
  # Extends the class that included this module so that the methods that
50
50
  # this helper provides can be called outside of instance of class methods.
51
51
  #
52
- # @param [Object] into The class that included this module.
52
+ # @param [Object] into The class that included this module.
53
53
  # @author Michael Fellinger
54
54
  # @author Pistos
55
55
  #
@@ -85,8 +85,10 @@ module Ramaze
85
85
  # @author Michael Fellinger
86
86
  # @author Pistos
87
87
  # @since 2011-04-07
88
+ #
88
89
  def set_layout(hash_or_layout)
89
- @_ramaze_layouts ||= {}
90
+ @_ramaze_layouts ||= {}
91
+ @_ramaze_old_layout ||= trait[:layout]
90
92
 
91
93
  # Extract the layout to use
92
94
  if hash_or_layout.respond_to?(:to_hash)
@@ -97,9 +99,22 @@ module Ramaze
97
99
  end
98
100
  end
99
101
 
100
- layout{|path| @_ramaze_layouts[path.to_s] }
102
+ layout do |path, wish|
103
+ path = path.to_s
104
+
105
+ if @_ramaze_layouts.key?(path)
106
+ use_layout = @_ramaze_layouts[path.to_s]
107
+ # Use the old layout
108
+ elsif @_ramaze_old_layout.respond_to?(:call)
109
+ use_layout = @_ramaze_old_layout.call(path, wish)
110
+ else
111
+ use_layout = @_ramaze_old_layout
112
+ end
113
+
114
+ use_layout
115
+ end
101
116
  else
102
- layout{|path| hash_or_layout }
117
+ layout { |path| hash_or_layout }
103
118
  end
104
119
  end
105
120
 
@@ -6,7 +6,7 @@ module Ramaze
6
6
  # Allows you to use some shortcuts for markaby in your Controller.
7
7
  module Markaby
8
8
  ##
9
- # use this inside your controller to directly build Markaby
9
+ # Use this inside your controller to directly build Markaby
10
10
  # Refer to the Markaby-documentation and testsuite for more examples.
11
11
  #
12
12
  # @example
@@ -4,8 +4,8 @@ module Ramaze
4
4
  module Helper
5
5
  # Helper for pagination and pagination-navigation.
6
6
  #
7
- # See detailed API docs for Paginator below.
8
- # Also have a look at the examples/helpers/paginate.rb
7
+ # See {Ramaze::Helper::Paginate#paginate} for more information.
8
+ #
9
9
  module Paginate
10
10
  include Traited
11
11
 
@@ -26,31 +26,36 @@ module Ramaze
26
26
  # The examples below are meant to be used within your controller or view.
27
27
  #
28
28
  # Usage with Array:
29
- # data = (1..100).to_a
30
- # @pager = paginate(data, :limit => 30, :page => 2)
31
- # @pager.navigation
32
- # @pager.each{|e| puts(e) }
29
+ #
30
+ # data = (1..100).to_a
31
+ # @pager = paginate(data, :limit => 30, :page => 2)
32
+ # @pager.navigation
33
+ # @pager.each{|e| puts(e) }
33
34
  #
34
35
  # Usage with Sequel:
35
- # data = Article.filter(:public => true)
36
- # @pager = paginate(data, :limit => 5)
37
- # @pager.navigation
38
- # @pager.each{|e| puts(e)
39
36
  #
40
- # Note that you must first extend Sequel with the pagination extension.
41
- # Sequel.extension :pagination
37
+ # data = Article.filter(:public => true)
38
+ # @pager = paginate(data, :limit => 5)
39
+ # @pager.navigation
40
+ # @pager.each{|e| puts(e)
41
+ #
42
+ # Note that you must first extend Sequel with the pagination extension"
43
+ #
42
44
  #
43
- # +dataset+ may be a Sequel dataset or an Array
44
- # +options+ Takes precedence to trait[:paginate] and may contain
45
- # following pairs:
46
- # :limit The number of elements used when you call #each on the
47
- # paginator
48
- # :var The variable name being used in the request, this is helpful
49
- # if you want to use two or more independent paginations on the
50
- # same page.
51
- # :page The page you are currently on, if not given it will be
52
- # retrieved from current request variables. Defaults to 1 if
53
- # neither exists.
45
+ # Sequel.extension :pagination
46
+ #
47
+ # @param [Sequel::Dataset|Array] dataset May be a Sequel dataset or an
48
+ # Array
49
+ # @param [Hash] options A hash containing custom options that takes
50
+ # precedence to ``trait[:paginate].
51
+ # @option options [Fixnum] :limit The number of elements used when you
52
+ # call #each on the paginator
53
+ # @option options [String] :var The variable name being used in the
54
+ # request, this is helpful if you want to use two or more independent
55
+ # paginations on the same page.
56
+ # @option options [Fixnum] :page The page you are currently on, if not
57
+ # given it will be retrieved from current request variables. Defaults to
58
+ # 1 if neither exists.
54
59
  #
55
60
  def paginate(dataset, options = {})
56
61
  options = ancestral_trait[:paginate].merge(options)
@@ -80,24 +85,26 @@ module Ramaze
80
85
  # with CSS.
81
86
  #
82
87
  # Output with 5 elements, page 1, limit 3:
83
- # <div class="pager">
84
- # <span class="first grey">&lt;&lt;</span>
85
- # <span class="previous grey">&lt;</span>
86
- # <a class="current" href="/index?pager=1">1</a>
87
- # <a href="/index?pager=2">2</a>
88
- # <a class="next" href="/index?pager=2">&gt;</a>
89
- # <a class="last" href="/index?pager=2">&gt;&gt;</a>
90
- # </div>
88
+ #
89
+ # <div class="pager">
90
+ # <span class="first grey">&lt;&lt;</span>
91
+ # <span class="previous grey">&lt;</span>
92
+ # <a class="current" href="/index?pager=1">1</a>
93
+ # <a href="/index?pager=2">2</a>
94
+ # <a class="next" href="/index?pager=2">&gt;</a>
95
+ # <a class="last" href="/index?pager=2">&gt;&gt;</a>
96
+ # </div>
91
97
  #
92
98
  # Output with 5 elements, page 2, limit 3:
93
- # <div class="pager" />
94
- # <a class="first" href="/index?user_page=1">&lt;&lt;</a>
95
- # <a class="previous" href="/index?user_page=1">&lt;</a>
96
- # <a href="/index?user_page=1">1</a>
97
- # <a class="current" href="/index?user_page=2">2</a>
98
- # <span class="next grey">&gt;</span>
99
- # <span class="last grey">&gt;&gt;</span>
100
- # </div>
99
+ #
100
+ # <div class="pager" />
101
+ # <a class="first" href="/index?user_page=1">&lt;&lt;</a>
102
+ # <a class="previous" href="/index?user_page=1">&lt;</a>
103
+ # <a href="/index?user_page=1">1</a>
104
+ # <a class="current" href="/index?user_page=2">2</a>
105
+ # <span class="next grey">&gt;</span>
106
+ # <span class="last grey">&gt;&gt;</span>
107
+ # </div>
101
108
  #
102
109
  def navigation(limit = 8)
103
110
  g = Ramaze::Gestalt.new
@@ -2,7 +2,9 @@ module Ramaze
2
2
  module Helper
3
3
  module RequestAccessor
4
4
  classes = [Rack::Request, Innate::Request, Ramaze::Request]
5
- methods = classes.map{|klass| klass.instance_methods(false) }.flatten.uniq
5
+ methods = classes.map { |klass|
6
+ klass.instance_methods(false)
7
+ }.flatten.uniq
6
8
 
7
9
  methods.each do |method|
8
10
  if method =~ /=/
@@ -6,25 +6,26 @@ module Ramaze
6
6
  #
7
7
  # Usage (trait is optional):
8
8
  #
9
- # class RegisterController < Ramaze::Controller
10
- # trait :captcha => lambda{
11
- # ["the answer to everything", "42"]
12
- # }
9
+ # class RegisterController < Ramaze::Controller
10
+ # trait :captcha => lambda{
11
+ # ["the answer to everything", "42"]
12
+ # }
13
13
  #
14
- # def index
15
- # %(
16
- # <form action="#{r(:answer}">
17
- # What is #{simple_captcha}?
18
- # <input type="text" name="answer" />"
19
- # <input type="submit" />
20
- # </form>
21
- # ).strip
22
- # end
14
+ # def index
15
+ # %(
16
+ # <form action="#{r(:answer}">
17
+ # What is #{simple_captcha}?
18
+ # <input type="text" name="answer" />"
19
+ # <input type="submit" />
20
+ # </form>
21
+ # ).strip
22
+ # end
23
23
  #
24
- # def answer
25
- # check_captcha(request[:answer])
24
+ # def answer
25
+ # check_captcha(request[:answer])
26
+ # end
26
27
  # end
27
- # end
28
+ #
28
29
  module SimpleCaptcha
29
30
  include Ramaze::Traited
30
31
 
@@ -56,5 +57,5 @@ module Ramaze
56
57
  answer.to_s.strip == captcha[:answer].to_s
57
58
  end
58
59
  end # SimpleCaptcha
59
- end # Helper
60
+ end # Helper
60
61
  end # Ramaze
@@ -4,7 +4,7 @@
4
4
  module Ramaze
5
5
  module Helper
6
6
  ##
7
- # provides an call/answer mechanism, this is useful for example in a
7
+ # Provides an call/answer mechanism, this is useful for example in a
8
8
  # login-system.
9
9
  #
10
10
  # It is basically good to redirect temporarly somewhere else without
@@ -9,9 +9,11 @@ module Ramaze
9
9
  # Allows you to use some shortcuts for Tagz in your Controller.
10
10
  # use this inside your controller to directly build Tagz
11
11
  # Refer to the Tagz-documentation and testsuite for more examples.
12
+ #
12
13
  # Usage:
13
- # tagz { h1_{ "Apples & Oranges" } } #=> "<h1>Apples &amp; Oranges</h1>"
14
- # tagz { h1_(:class => 'fruits&floots'){ 'Apples' } }
14
+ #
15
+ # tagz { h1_{ "Apples & Oranges" } } #=> "<h1>Apples &amp; Oranges</h1>"
16
+ # tagz { h1_(:class => 'fruits&floots'){ 'Apples' } }
15
17
  #
16
18
  Tagz = ::Tagz
17
19
  end # Helper
@@ -0,0 +1,523 @@
1
+ module Ramaze
2
+ module Helper
3
+ ##
4
+ # Helper module for handling file uploads. File uploads are mostly handled
5
+ # by Rack, but this helper adds some conveniance methods for handling
6
+ # and saving the uploaded files.
7
+ #
8
+ # @example
9
+ # class MyController < Ramaze::Controller
10
+ # # Use upload helper
11
+ # helper :upload
12
+ #
13
+ # # This action will handle *all* uploaded files
14
+ # def handleupload1
15
+ # # Iterate over uploaded files and save them in the
16
+ # # '/uploads/myapp' directory
17
+ # get_uploaded_files.each_pair do |k, v|
18
+ # v.save(
19
+ # File.join('/uploads/myapp', v.filename),
20
+ # :allow_overwrite => true
21
+ # )
22
+ #
23
+ # if v.saved?
24
+ # Ramaze::Log.info(
25
+ # "Saved uploaded file named #{k} to #{v.path}."
26
+ # )
27
+ # else
28
+ # Ramaze::Log.warn("Failed to save file named #{k}.")
29
+ # end
30
+ # end
31
+ # end
32
+ #
33
+ # # This action will handle uploaded files beginning with 'up'
34
+ # def handleupload2
35
+ # # Iterate over uploaded files and save them in the
36
+ # # '/uploads/myapp' directory
37
+ # get_uploaded_files(/^up.*/).each_pair do |k, v|
38
+ # v.save(
39
+ # File.join('/uploads/myapp', v.filename),
40
+ # :allow_overwrite => true
41
+ # )
42
+ #
43
+ # if v.saved?
44
+ # Ramaze::Log.info(
45
+ # "Saved uploaded file named #{k} to #{v.path}."
46
+ # )
47
+ # else
48
+ # Ramaze::Log.warn("Failed to save file named #{k}.")
49
+ # end
50
+ # end
51
+ # end
52
+ # end
53
+ #
54
+ # @author Lars Olsson
55
+ # @since 04-08-2011
56
+ #
57
+ module Upload
58
+ include Ramaze::Traited
59
+
60
+ ##
61
+ # This method will iterate through all request parameters and convert
62
+ # those parameters which represents uploaded files to
63
+ # Ramaze::Helper::Upload::UploadedFile objects. The matched parameters
64
+ # will then be removed from the request parameter hash.
65
+ #
66
+ # Use this method if you want to decide whether to handle file uploads
67
+ # in your action at runtime. For automatic handling, use
68
+ # Ramaze::Helper::Upload::ClassMethods#handle_all_uploads or
69
+ # Ramaze::Helper::Upload::ClassMethods#handle_uploads_for instead.
70
+ #
71
+ # @author Lars Olsson
72
+ # @since 04-08-2011
73
+ # @param [Regexp] pattern If set, only those request parameters which
74
+ # has a name matching the Regexp will be checked for file uploads.
75
+ # @return [Array] The uploaded files.
76
+ # @see Ramaze::Helper::Upload::ClassMethods#handle_all_uploads
77
+ # @see Ramaze::Helper::Upload::ClassMethods#handle_uploads_for
78
+ #
79
+ def get_uploaded_files(pattern = nil)
80
+ uploaded_files = {}
81
+
82
+ # Iterate over all request parameters
83
+ request.params.each_pair do |k, v|
84
+ # If we use a pattern, check that it matches
85
+ if pattern.nil? or pattern =~ k
86
+ # Rack supports request parameters with either a single value or
87
+ # an array of values. To support both, we need to check if the
88
+ # current parameter is an array or not.
89
+ if v.is_a?(Array)
90
+ # Got an array. Iterate through it and check for uploaded files
91
+ file_indices = []
92
+
93
+ v.each_with_index do |elem, idx|
94
+ file_indices.push(idx) if is_uploaded_file?(elem)
95
+ end
96
+
97
+ # Convert found uploaded files to
98
+ # Ramaze::Helper::Upload::UploadedFile objects
99
+ file_elems = []
100
+
101
+ file_indices.each do |fi|
102
+ file_elems << Ramaze::Helper::Upload::UploadedFile.new(
103
+ v[fi][:filename],
104
+ v[fi][:type],
105
+ v[fi][:tempfile],
106
+ ancestral_trait[:upload_options] ||
107
+ Ramaze::Helper::Upload::ClassMethods.trait[
108
+ :default_upload_options
109
+ ]
110
+ )
111
+ end
112
+
113
+ # Remove uploaded files from current request param
114
+ file_indices.reverse_each do |fi|
115
+ v.delete_at(fi)
116
+ end
117
+
118
+ # If the request parameter contained at least one file upload,
119
+ # add upload(s) to the list of uploaded files
120
+ uploaded_files[k] = file_elems unless file_elems.empty?
121
+
122
+ # Delete parameter from request parameter array if it doesn't
123
+ # contain any other elements.
124
+ request.params.delete(k) if v.empty?
125
+ else
126
+ # Got a single value. Check if it is an uploaded file
127
+ if is_uploaded_file?(v)
128
+ # The current parameter represents an uploaded file.
129
+ # Convert the parameter to a
130
+ # Ramaze::Helper::Upload::UploadedFile object
131
+ uploaded_files[k] = Ramaze::Helper::Upload::UploadedFile.new(
132
+ v[:filename],
133
+ v[:type],
134
+ v[:tempfile],
135
+ ancestral_trait[:upload_options] ||
136
+ Ramaze::Helper::Upload::ClassMethods.trait[
137
+ :default_upload_options
138
+ ]
139
+ )
140
+
141
+ # Delete parameter from request parameter array
142
+ request.params.delete(k)
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ # If at least one file upload matched, override the uploaded_files
149
+ # method with a singleton method that returns the list of uploaded
150
+ # files. Doing things this way allows us to store the list of uploaded
151
+ # files without using an instance variable.
152
+ unless uploaded_files.empty?
153
+ @_ramaze_uploaded_files = uploaded_files
154
+
155
+ # Save uploaded files if autosave is set to true
156
+ if ancestral_trait[:upload_options] and
157
+ ancestral_trait[:upload_options][:autosave]
158
+ uploaded_files().each_value do |uf|
159
+ uf.save
160
+ end
161
+ end
162
+ end
163
+
164
+ # The () is required, otherwise the name would collide with the variable
165
+ # "uploaded_files".
166
+ return uploaded_files()
167
+ end
168
+
169
+ ##
170
+ # Adds some class method to the controller whenever the helper
171
+ # is included.
172
+ #
173
+ def self.included(mod)
174
+ mod.extend(ClassMethods)
175
+ end
176
+
177
+ ##
178
+ # Returns list of currently handled file uploads.
179
+ #
180
+ # Both single and array parameters are supported. If you give
181
+ # your file upload fields the same name (for instance upload[]) Rack will
182
+ # merge them into a single parameter. The upload helper will keep this
183
+ # structure so that whenever the request parameter contains an array, the
184
+ # uploaded_files method will also return an array of
185
+ # Ramaze::Helper::Upload::UploadedFile objects for the same key.
186
+ #
187
+ # @return [Hash] Currently uploaded files. The keys in the hash
188
+ # corresponds to the names of the request parameters that contained file
189
+ # uploads and the values consist of Ramaze::Helper::Upload::UploadedFile
190
+ # objects.
191
+ #
192
+ def uploaded_files
193
+ return @_ramaze_uploaded_files || {}
194
+ end
195
+
196
+ private
197
+
198
+ # Returns whether +param+ is considered an uploaded file
199
+ # A parameter is considered to be an uploaded file if it is
200
+ # a hash and contains all parameters that Rack assigns to an
201
+ # uploaded file
202
+ #
203
+ # @param [Hash] param A request parameter
204
+ # @return [Boolean]
205
+ def is_uploaded_file?(param)
206
+ if param.respond_to?(:has_key?)
207
+ [:filename, :type, :name, :tempfile, :head].each do |k|
208
+ return false if !param.has_key?(k)
209
+ end
210
+
211
+ return true
212
+ else
213
+ return false
214
+ end
215
+ end
216
+
217
+ # Helper class methods. Methods in this module will be available
218
+ # in your controller *class* (not your controller instance).
219
+ module ClassMethods
220
+ include Ramaze::Traited
221
+
222
+ # Default options for uploaded files. You can change these options
223
+ # by using the uploads_options method
224
+ trait :default_upload_options => {
225
+ :allow_overwrite => false,
226
+ :autosave => false,
227
+ :default_upload_dir => nil,
228
+ :unlink_tempfile => false
229
+ }.freeze
230
+
231
+ ##
232
+ # This method will activate automatic handling of uploaded files
233
+ # for specified actions in the controller.
234
+ #
235
+ # @example
236
+ # class MyController < Ramaze::Controller
237
+ #
238
+ # # Use upload helper
239
+ # helper :upload
240
+ #
241
+ # # Handle all uploads for the foo and bar actions
242
+ # handle_uploads_for :foo, :bar
243
+ #
244
+ # # Handle all uploads for the baz action and uploads beginning with
245
+ # # 'up' for the qux action
246
+ # handle_uploads_for :baz, [:qux, /^up.*/]
247
+ # end
248
+ #
249
+ # @param *args An arbitrary long list of arguments with action names
250
+ # (and optionally patterns) that should handle file uploads
251
+ # automatically. Each argument can either be a symbol or a two-element
252
+ # array consisting of a symbol and a reqexp.
253
+ # @see #handle_all_uploads
254
+ # @see Ramaze::Helper::Upload#get_uploaded_files
255
+ def handle_uploads_for(*args)
256
+ args.each do |arg|
257
+ if arg.respond_to?(:first) and arg.respond_to?(:last)
258
+ before(arg.first.to_sym) do
259
+ get_uploaded_files(arg.last)
260
+ end
261
+ else
262
+ before(arg.to_sym) do
263
+ get_uploaded_files
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ ##
270
+ # Sets options for file uploads in the controller.
271
+ #
272
+ # @example
273
+ # # This controller will handle all file uploads automatically.
274
+ # # All uploaded files are saved automatically in '/uploads/myapp'
275
+ # # and old files are overwritten.
276
+ # #
277
+ # class MyController < Ramaze::Controller
278
+ #
279
+ # # Use upload helper
280
+ # helper :upload
281
+ #
282
+ # handle_all_uploads
283
+ # upload_options :allow_overwrite => true,
284
+ # :autosave => true,
285
+ # :default_upload_dir => '/uploads/myapp',
286
+ # :unlink_tempfile => true
287
+ # end
288
+ #
289
+ # # This controller will handle all file uploads automatically.
290
+ # # All uploaded files are saved automatically, but the exact location
291
+ # # is depending on a session variable. Old files are overwritten.
292
+ # #
293
+ # class MyController2 < Ramaze::Controller
294
+ #
295
+ # # Use upload helper
296
+ # helper :upload
297
+ #
298
+ # # Proc to use for save directory calculation
299
+ # calculate_dir = lambda { File.join('/uploads', session['user']) }
300
+ #
301
+ # handle_all_uploads
302
+ # upload_options :allow_overwrite => true,
303
+ # :autosave => true,
304
+ # :default_upload_dir => calculate_dir,
305
+ # :unlink_tempfile => true
306
+ # end
307
+ # @param [Hash] options Options controlling how file uploads
308
+ # are handled.
309
+ # @option options [Boolean] :allow_overwrite If set to *true*, uploaded
310
+ # files are allowed to overwrite existing ones. This option is set to
311
+ # *false* by default.
312
+ # @option options [Boolean] :autosave If set to *true*,
313
+ # Ramaze::Helper::Upload::UploadedFile#save will be called on all
314
+ # matched file uploads
315
+ # automatically. You can use this option to automatically save files
316
+ # at a preset location, but please note that you will need to set the
317
+ # :default_upload_dir (and possibly :allow_overwrite) options as well
318
+ # in order for this to work correctly. This option is set to *false*
319
+ # by default.
320
+ # @option options [String|Proc] :default_upload_dir If set to a string
321
+ # (representing a path in the file system) this option will allow you
322
+ # to save uploaded files without specifying a path. If you intend to
323
+ # call Ramaze::Helper::Upload::UploadedFile#save with a path you don't
324
+ # need to set this option at all. If you need to delay the calculation
325
+ # of the directory, you can also set this option to a proc. The proc
326
+ # should accept zero arguments and return a string. This comes in handy
327
+ # when you want to use different directory paths for different users
328
+ # etc. This option is set to *nil* by default.
329
+ # @option options [Boolean] :unlink_tempfile If set to *true*, this
330
+ # option will automatically unlink the temporary file created by Rack
331
+ # immediately after Ramaze::Helper::Upload::UploadedFile#save is done
332
+ # saving the uploaded file. This is probably not needed in most cases,
333
+ # but if you don't want to expose your uploaded files in a shared
334
+ # tempdir longer than necessary this option might be for you. This
335
+ # option is set to *false* by default.
336
+ # @see Ramaze::Helper::Upload::UploadedFile#initialize
337
+ # @see Ramaze::Helper::Upload::UploadedFile#save
338
+ #
339
+ def upload_options(options)
340
+ trait(
341
+ :upload_options => Ramaze::Helper::Upload::ClassMethods.trait[
342
+ :default_upload_options
343
+ ].merge(options)
344
+ )
345
+ end
346
+ end # ClassMethods
347
+
348
+ ##
349
+ # This class represents an uploaded file.
350
+ #
351
+ # @author Lars Olsson
352
+ # @since 18-08-2011
353
+ #
354
+ class UploadedFile
355
+ include Ramaze::Traited
356
+
357
+ # Suggested file name
358
+ # @return [String]
359
+ attr_reader :filename
360
+
361
+ # MIME-type
362
+ # @return [String]
363
+ attr_reader :type
364
+
365
+ ##
366
+ # Initializes a new Ramaze::Helper::Upload::UploadedFile object.
367
+ #
368
+ # @param [String] filename Suggested file name
369
+ # @param [String] type MIME-type
370
+ # @param [File] tempfile temporary file
371
+ # @param [Hash] options Options for uploaded files. Options supported
372
+ # match those available to
373
+ # Ramaze::Helper::Upload::ClassMethods#upload_options
374
+ # @return [Ramaze::Helper::Upload::UploadedFile] A new
375
+ # Ramaze::Helper::Upload::UploadedFile object
376
+ # @see #save
377
+ # @see Ramaze::Helper::Upload::ClassMethods#upload_options
378
+ def initialize(filename, type, tempfile, options)
379
+ @filename = File.basename(filename)
380
+ @type = type
381
+ @tempfile = tempfile
382
+ @realfile = nil
383
+
384
+ trait :options => options
385
+ end
386
+
387
+ ##
388
+ # Changes the suggested filename of this
389
+ # Ramaze::Helper::Upload::UploadedFile. +name+ should be a string
390
+ # representing the filename (only the filename, not a complete path),
391
+ # but if you provide a complete path this method it will try to identify
392
+ # the filename and use that instead.
393
+ #
394
+ # @param [String] The new suggested filename.
395
+ #
396
+ def filename=(name)
397
+ @filename = File.basename(name)
398
+ end
399
+
400
+ ##
401
+ # Returns the path of the Ramaze::Helper::Upload::UploadedFile object.
402
+ # The method will always return *nil* before *save* has been called
403
+ # on the Ramaze::Helper::Upload::UploadedFile object.
404
+ #
405
+ # @return [String|nil]
406
+ #
407
+ def path
408
+ return self.saved? ? @realfile.path : nil
409
+ end
410
+
411
+ ##
412
+ # Saves the Ramaze::Helper::Upload::UploadedFile.
413
+ #
414
+ # If +path+ is not set, the method checks whether there exists default
415
+ # options for the path and tries to use that instead.
416
+ #
417
+ # If you need to override any options set in the controller (using
418
+ # upload_options) you can set the corresponding option in +options+ to
419
+ # override the behavior for this particular
420
+ # Ramaze::Helper::Upload::UploadedFile object.
421
+ #
422
+ # @param [String] path Path where the
423
+ # Ramaze::Helper::Upload::UploadedFile will be saved
424
+ # @param [Hash] options Options for uploaded files. Options supported
425
+ # match those available to
426
+ # Ramaze::Helper::Upload::ClassMethods#upload_options
427
+ # @raise [StandardError] Will be raised if the save operation fails.
428
+ # @see #initialize
429
+ # @see Ramaze::Helper::Upload::ClassMethods#upload_options
430
+ #
431
+ def save(path = nil, options = {})
432
+ # Merge options
433
+ opts = trait[:options].merge(options)
434
+
435
+ unless path
436
+ # No path was provided, use info stored elsewhere to try to build
437
+ # the path
438
+ unless opts[:default_upload_dir]
439
+ raise StandardError.new('Unable to save file, no dirname given')
440
+ end
441
+
442
+ unless @filename
443
+ raise StandardError.new('Unable to save file, no filename given')
444
+ end
445
+
446
+ # Check to see if a proc or a string was used for the
447
+ # default_upload_dir parameter. If it was a proc, call the proc and
448
+ # use the result as the directory part of the path. If a string was
449
+ # used, use the string directly as the directory part of the path.
450
+ dn = opts[:default_upload_dir]
451
+
452
+ if dn.respond_to?(:call)
453
+ dn = dn.call
454
+ end
455
+
456
+ path = File.join(dn, @filename)
457
+ end
458
+
459
+ path = File.expand_path(path)
460
+
461
+ # Abort if file altready exists and overwrites are not allowed
462
+ if File.exists?(path) and !opts[:allow_overwrite]
463
+ raise StandardError.new('Unable to overwrite existing file')
464
+ end
465
+
466
+ # Confirm that we can read source file
467
+ unless File.readable?(@tempfile.path)
468
+ raise StandardError.new('Unable to read temporary file')
469
+ end
470
+
471
+ # Confirm that we can write to the destination file
472
+ unless (File.exists?(path) and File.writable?(path)) \
473
+ or (File.exists?(File.dirname(path)) \
474
+ and File.writable?(File.dirname(path)))
475
+ raise StandardError.new(
476
+ "Unable to save file to #{path}. Path is not writable"
477
+ )
478
+ end
479
+
480
+ # If supported, use IO,copy_stream. If not, require fileutils
481
+ # and use the same method from there
482
+ if IO.respond_to?(:copy_stream)
483
+ IO.copy_stream(@tempfile, path)
484
+ else
485
+ require 'fileutils'
486
+ File.open(@tempfile.path, 'rb') do |src|
487
+ File.open(path, 'wb') do |dest|
488
+ FileUtils.copy_stream(src, dest)
489
+ end
490
+ end
491
+ end
492
+
493
+ # Update the realfile property, indicating that the file has been
494
+ # saved
495
+ @realfile = File.new(path)
496
+
497
+ # If the unlink_tempfile option is set to true, delete the temporary
498
+ # file created by Rack
499
+ unlink_tempfile if opts[:unlink_tempfile]
500
+ end
501
+
502
+ ##
503
+ # Returns whether the Ramaze::Helper::Upload::UploadedFile has been
504
+ # saved or not.
505
+ #
506
+ # @return [Boolean]
507
+ #
508
+ def saved?
509
+ return !@realfile.nil?
510
+ end
511
+
512
+ ##
513
+ # Deletes the temporary file associated with this
514
+ # Ramaze::Helper::Upload::UploadedFile immediately.
515
+ #
516
+ def unlink_tempfile
517
+ File.unlink(@tempfile.path)
518
+ @tempfile = nil
519
+ end
520
+ end # UploadedFile
521
+ end # Upload
522
+ end # Helper
523
+ end # Ramaze