ramaze 2011.07.25 → 2011.10.23

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