padrino-core 0.10.7 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/lib/padrino-core.rb +58 -4
  3. data/lib/padrino-core/application.rb +38 -16
  4. data/lib/padrino-core/application/flash.rb +229 -0
  5. data/lib/padrino-core/application/rendering.rb +39 -11
  6. data/lib/padrino-core/application/rendering/extensions/erubis.rb +55 -0
  7. data/lib/padrino-core/application/rendering/extensions/haml.rb +26 -0
  8. data/lib/padrino-core/application/rendering/extensions/slim.rb +14 -0
  9. data/lib/padrino-core/application/routing.rb +106 -35
  10. data/lib/padrino-core/cli/base.rb +41 -38
  11. data/lib/padrino-core/cli/rake.rb +30 -9
  12. data/lib/padrino-core/cli/rake_tasks.rb +9 -14
  13. data/lib/padrino-core/loader.rb +23 -9
  14. data/lib/padrino-core/locale/fr.yml +1 -1
  15. data/lib/padrino-core/locale/ru.yml +1 -1
  16. data/lib/padrino-core/logger.rb +48 -32
  17. data/lib/padrino-core/module.rb +58 -0
  18. data/lib/padrino-core/mounter.rb +15 -5
  19. data/lib/padrino-core/reloader.rb +14 -12
  20. data/lib/padrino-core/server.rb +4 -4
  21. data/lib/padrino-core/support_lite.rb +43 -6
  22. data/lib/padrino-core/version.rb +1 -1
  23. data/padrino-core.gemspec +9 -4
  24. data/test/fixtures/app_gem/Gemfile +4 -0
  25. data/test/fixtures/app_gem/app/app.rb +3 -0
  26. data/test/fixtures/app_gem/app_gem.gemspec +17 -0
  27. data/test/fixtures/app_gem/lib/app_gem.rb +7 -0
  28. data/test/fixtures/app_gem/lib/app_gem/version.rb +3 -0
  29. data/test/mini_shoulda.rb +1 -1
  30. data/test/test_application.rb +38 -21
  31. data/test/test_csrf_protection.rb +80 -0
  32. data/test/test_filters.rb +70 -0
  33. data/test/test_flash.rb +168 -0
  34. data/test/test_logger.rb +27 -0
  35. data/test/test_mounter.rb +24 -2
  36. data/test/test_reloader_simple.rb +4 -4
  37. data/test/test_rendering.rb +75 -4
  38. data/test/test_routing.rb +164 -35
  39. data/test/test_support_lite.rb +56 -0
  40. metadata +52 -29
@@ -33,7 +33,7 @@ module Padrino
33
33
  # Specified constants can be excluded from the code unloading process.
34
34
  #
35
35
  def exclude_constants
36
- @_exclude_constants ||= []
36
+ @_exclude_constants ||= Set.new
37
37
  end
38
38
 
39
39
  ##
@@ -41,7 +41,7 @@ module Padrino
41
41
  # Default included constants are: [none]
42
42
  #
43
43
  def include_constants
44
- @_include_constants ||= []
44
+ @_include_constants ||= Set.new
45
45
  end
46
46
 
47
47
  ##
@@ -103,9 +103,12 @@ module Padrino
103
103
  # We lock dependencies sets to prevent reloading of protected constants
104
104
  #
105
105
  def lock!
106
- klasses = ObjectSpace.classes.map { |klass| klass._orig_klass_name.split('::')[0] }.uniq
106
+ klasses = ObjectSpace.classes do |klass|
107
+ klass._orig_klass_name.split('::')[0]
108
+ end
109
+
107
110
  klasses = klasses | Padrino.mounted_apps.map { |app| app.app_class }
108
- Padrino::Reloader.exclude_constants.concat(klasses)
111
+ Padrino::Reloader.exclude_constants.merge(klasses)
109
112
  end
110
113
 
111
114
  ##
@@ -130,8 +133,8 @@ module Padrino
130
133
  end
131
134
 
132
135
  # Duplicate objects and loaded features before load file
133
- klasses = ObjectSpace.classes.dup
134
- files = $LOADED_FEATURES.dup
136
+ klasses = ObjectSpace.classes
137
+ files = Set.new($LOADED_FEATURES.dup)
135
138
 
136
139
  # Now we can reload dependencies of our file
137
140
  if features = LOADED_FILES.delete(file)
@@ -142,7 +145,7 @@ module Padrino
142
145
  begin
143
146
  logger.devel :loading, began_at, file if !reload
144
147
  logger.debug :reload, began_at, file if reload
145
- $LOADED_FEATURES.delete(file)
148
+ $LOADED_FEATURES.delete(file) if files.include?(file)
146
149
  verbosity_was, $-v = $-v, nil
147
150
  loaded = false
148
151
  require(file)
@@ -152,18 +155,17 @@ module Padrino
152
155
  logger.error "Cannot require #{file} due to a syntax error: #{e.message}"
153
156
  ensure
154
157
  $-v = verbosity_was
155
- new_constants = (ObjectSpace.classes - klasses).uniq
158
+ new_constants = ObjectSpace.new_classes(klasses)
156
159
  if loaded
157
160
  # Store the file details
158
161
  LOADED_CLASSES[file] = new_constants
159
- LOADED_FILES[file] = ($LOADED_FEATURES - files - [file]).uniq
162
+ LOADED_FILES[file] = Set.new($LOADED_FEATURES) - files - [file]
160
163
  # Track only features in our Padrino.root
161
164
  LOADED_FILES[file].delete_if { |feature| !in_root?(feature) }
162
165
  else
163
166
  logger.devel "Failed to load #{file}; removing partially defined constants"
164
167
  new_constants.each { |klass| remove_constant(klass) }
165
168
  end
166
-
167
169
  end
168
170
  end
169
171
 
@@ -183,8 +185,8 @@ module Padrino
183
185
  # Removes the specified class and constant.
184
186
  #
185
187
  def remove_constant(const)
186
- return if exclude_constants.compact.uniq.any? { |c| const._orig_klass_name.index(c) == 0 } &&
187
- !include_constants.compact.uniq.any? { |c| const._orig_klass_name.index(c) == 0 }
188
+ return if exclude_constants.any? { |c| const._orig_klass_name.index(c) == 0 } &&
189
+ !include_constants.any? { |c| const._orig_klass_name.index(c) == 0 }
188
190
  begin
189
191
  parts = const.to_s.sub(/^::(Object)?/, 'Object::').split('::')
190
192
  object = parts.pop
@@ -4,8 +4,8 @@ module Padrino
4
4
  # thin, mongrel, or webrick in that order.
5
5
  #
6
6
  # @example
7
- # Padrino.run! # with these defaults => host: "localhost", port: "3000", adapter: the first found
8
- # Padrino.run!("localhost", "4000", "mongrel") # use => host: "0.0.0.0", port: "3000", adapter: "mongrel"
7
+ # Padrino.run! # with these defaults => host: "127.0.0.1", port: "3000", adapter: the first found
8
+ # Padrino.run!("0.0.0.0", "4000", "mongrel") # use => host: "0.0.0.0", port: "4000", adapter: "mongrel"
9
9
  #
10
10
  def self.run!(options={})
11
11
  Padrino.load!
@@ -17,13 +17,13 @@ module Padrino
17
17
  #
18
18
  class Server < Rack::Server
19
19
  # Server Handlers
20
- Handlers = [:thin, :mongrel, :trinidad, :webrick]
20
+ Handlers = [:thin, :puma, :mongrel, :trinidad, :webrick]
21
21
 
22
22
  # Starts the application on the available server with specified options.
23
23
  def self.start(app, opts={})
24
24
  options = {}.merge(opts) # We use a standard hash instead of Thor::CoreExt::HashWithIndifferentAccess
25
25
  options.symbolize_keys!
26
- options[:Host] = options.delete(:host) || '0.0.0.0'
26
+ options[:Host] = options.delete(:host) || '127.0.0.1'
27
27
  options[:Port] = options.delete(:port) || 3000
28
28
  options[:AccessLog] = []
29
29
  if options[:daemonize]
@@ -9,6 +9,7 @@ require 'active_support/core_ext/object/blank' # present?
9
9
  require 'active_support/core_ext/array/extract_options' # extract_options
10
10
  require 'active_support/inflector/methods' # constantize
11
11
  require 'active_support/inflector/inflections' # pluralize
12
+ require 'active_support/core_ext/string/output_safety' # SafeBuffer and html_safe
12
13
  require 'active_support/inflections' # load default inflections
13
14
  require 'yaml' unless defined?(YAML) # load yaml for i18n
14
15
  require 'win32console' if RUBY_PLATFORM =~ /(win|m)32/ # ruby color support for win
@@ -110,13 +111,49 @@ end
110
111
 
111
112
  module ObjectSpace
112
113
  class << self
114
+ ##
113
115
  # Returns all the classes in the object space.
114
- def classes
115
- ObjectSpace.each_object(Module).select do |klass|
116
- # Why? Ruby, when you remove a costant dosen't remove it from
117
- # rb_tables, this mean that here we can find classes that was
118
- # removed.
119
- klass.name rescue false
116
+ # Optionally, a block can be passed, for example the following code
117
+ # would return the classes that start with the character "A":
118
+ #
119
+ # ObjectSpace.classes do |klass|
120
+ # if klass.to_s[0] == "A"
121
+ # klass
122
+ # end
123
+ # end
124
+ #
125
+ def classes(&block)
126
+ rs = Set.new
127
+
128
+ ObjectSpace.each_object(Class).each do |klass|
129
+ if block
130
+ if r = block.call(klass)
131
+ # add the returned value if the block returns something
132
+ rs << r
133
+ end
134
+ else
135
+ rs << klass
136
+ end
137
+ end
138
+
139
+ rs
140
+ end
141
+
142
+ ##
143
+ # Returns a list of existing classes that are not included in "snapshot"
144
+ # This method is useful to get the list of new classes that were loaded
145
+ # after an event like requiring a file.
146
+ # Usage:
147
+ #
148
+ # snapshot = ObjectSpace.classes
149
+ # # require a file
150
+ # ObjectSpace.new_classes(snapshot)
151
+ #
152
+ def new_classes(snapshot)
153
+ self.classes do |klass|
154
+ if !snapshot.include?(klass)
155
+ klass
156
+ end
120
157
  end
121
158
  end
122
159
  end
@@ -6,7 +6,7 @@
6
6
  #
7
7
  module Padrino
8
8
  # The version constant for the current version of Padrino.
9
- VERSION = '0.10.7' unless defined?(Padrino::VERSION)
9
+ VERSION = '0.11.0' unless defined?(Padrino::VERSION)
10
10
 
11
11
  #
12
12
  # The current Padrino version.
data/padrino-core.gemspec CHANGED
@@ -31,8 +31,13 @@ Gem::Specification.new do |s|
31
31
  # s.post_install_message << "\n\e[32m" + ("*" * 20) + "\n\e[0m"
32
32
 
33
33
  s.add_dependency("tilt", "~> 1.3.0")
34
- s.add_dependency("sinatra", "~> 1.3.1")
35
- s.add_dependency("http_router", "~> 0.10.2")
36
- s.add_dependency("thor", "~> 0.15.2")
37
- s.add_dependency("activesupport", "~> 3.2.0")
34
+ if ENV["SINATRA_EDGE"]
35
+ s.add_dependency("sinatra")
36
+ else
37
+ s.add_dependency("sinatra", "~> 1.4.2")
38
+ end
39
+ s.add_dependency("http_router", "~> 0.11.0")
40
+ s.add_dependency("thor", "~> 0.17.0")
41
+ s.add_dependency("activesupport", ">= 3.1.0")
42
+ s.add_dependency("rack-protection", ">= 1.5.0")
38
43
  end
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in app_gem.gemspec
4
+ gemspec
@@ -0,0 +1,3 @@
1
+ class AppGem::App < Padrino::Application
2
+ set :version, AppGem::VERSION
3
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/app_gem/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Florian Gilcher"]
6
+ gem.email = ["florian.gilcher@asquera.de"]
7
+ gem.description = %q{TODO: Write a gem description}
8
+ gem.summary = %q{TODO: Write a gem summary}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "app_gem"
15
+ gem.require_paths = ["app", "lib"]
16
+ gem.version = AppGem::VERSION
17
+ end
@@ -0,0 +1,7 @@
1
+ require 'padrino'
2
+
3
+ module AppGem
4
+ extend Padrino::Module
5
+
6
+ gem! 'app_gem'
7
+ end
@@ -0,0 +1,3 @@
1
+ module AppGem
2
+ VERSION = "0.0.1"
3
+ end
data/test/mini_shoulda.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  gem 'minitest'
2
2
  require 'minitest/spec'
3
3
  require 'minitest/autorun'
4
- require 'mocha' # Load mocha after minitest
4
+ require 'mocha/setup'
5
5
 
6
6
  begin
7
7
  require 'ruby-debug'
@@ -1,17 +1,13 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/helper')
2
+ require 'haml'
2
3
 
3
4
  class PadrinoPristine < Padrino::Application; end
4
- class PadrinoTestApp < Padrino::Application; end
5
+ class PadrinoTestApp < Padrino::Application; end
5
6
  class PadrinoTestApp2 < Padrino::Application; end
6
7
 
7
8
  describe "Application" do
8
- def setup
9
- Padrino.clear!
10
- end
11
-
12
- def teardown
13
- remove_views
14
- end
9
+ before { Padrino.clear! }
10
+ after { remove_views }
15
11
 
16
12
  context 'for application functionality' do
17
13
 
@@ -19,24 +15,36 @@ describe "Application" do
19
15
  assert File.identical?(__FILE__, PadrinoPristine.app_file)
20
16
  assert_equal :padrino_pristine, PadrinoPristine.app_name
21
17
  assert_equal :test, PadrinoPristine.environment
22
- assert_equal Padrino.root("views"), PadrinoPristine.views
23
- assert PadrinoPristine.raise_errors
18
+ assert_equal Padrino.root('views'), PadrinoPristine.views
19
+ assert PadrinoPristine.raise_errors
24
20
  assert !PadrinoPristine.logging
25
21
  assert !PadrinoPristine.sessions
26
22
  assert !PadrinoPristine.dump_errors
27
23
  assert !PadrinoPristine.show_exceptions
28
- assert PadrinoPristine.raise_errors
24
+ assert PadrinoPristine.raise_errors
29
25
  assert !Padrino.configure_apps
30
26
  end
31
27
 
28
+ should 'check haml options on production' do
29
+ assert defined?(Haml), 'Haml not defined'
30
+ assert_equal :test, PadrinoPristine.environment
31
+ assert !PadrinoPristine.haml[:ugly]
32
+ Padrino.stub :env, :production do
33
+ PadrinoPristine.send :default_configuration!
34
+ assert_equal :production, Padrino.env
35
+ assert_equal :production, PadrinoPristine.environment
36
+ assert PadrinoPristine.haml[:ugly]
37
+ PadrinoPristine.environment = :test
38
+ end
39
+ end
40
+
32
41
  should 'check padrino specific options' do
33
42
  assert !PadrinoPristine.instance_variable_get(:@_configured)
34
43
  PadrinoPristine.send(:setup_application!)
35
44
  assert_equal :padrino_pristine, PadrinoPristine.app_name
36
45
  assert_equal 'StandardFormBuilder', PadrinoPristine.default_builder
37
- assert PadrinoPristine.instance_variable_get(:@_configured)
46
+ assert PadrinoPristine.instance_variable_get(:@_configured)
38
47
  assert !PadrinoPristine.reload?
39
- assert !PadrinoPristine.flash
40
48
  end
41
49
 
42
50
  should 'set global project settings' do
@@ -48,18 +56,27 @@ describe "Application" do
48
56
  assert_equal PadrinoTestApp.session_secret, PadrinoTestApp2.session_secret
49
57
  end
50
58
 
59
+ should 'be able to configure_apps multiple times' do
60
+ Padrino.configure_apps { set :foo1, "bar" }
61
+ Padrino.configure_apps { set :foo1, "bam" }
62
+ Padrino.configure_apps { set :foo2, "baz" }
63
+ PadrinoTestApp.send(:default_configuration!)
64
+ assert_equal "bam", PadrinoTestApp.settings.foo1, "should have foo1 assigned to bam"
65
+ assert_equal "baz", PadrinoTestApp.settings.foo2, "should have foo2 assigned to baz"
66
+ end
67
+
51
68
  should "have shared sessions accessible in project" do
52
69
  Padrino.configure_apps { enable :sessions; set :session_secret, 'secret' }
53
70
  Padrino.mount("PadrinoTestApp").to("/write")
54
71
  Padrino.mount("PadrinoTestApp2").to("/read")
55
- PadrinoTestApp.tap { |app| app.send(:default_configuration!)
56
- app.get("/") { session[:foo] = "shared" } }
57
- PadrinoTestApp2.tap { |app| app.send(:default_configuration!)
58
- app.get("/") { session[:foo] } }
59
- browser = Rack::Test::Session.new(Rack::MockSession.new(Padrino.application))
60
- browser.get '/write'
61
- browser.get '/read'
62
- assert_equal 'shared', browser.last_response.body
72
+ PadrinoTestApp.send :default_configuration!
73
+ PadrinoTestApp.get('/') { session[:foo] = "shared" }
74
+ PadrinoTestApp2.send(:default_configuration!)
75
+ PadrinoTestApp2.get('/') { session[:foo] }
76
+ @app = Padrino.application
77
+ get '/write'
78
+ get '/read'
79
+ assert_equal 'shared', body
63
80
  end
64
81
 
65
82
  # compare to: test_routing: allow global provides
@@ -0,0 +1,80 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/helper')
2
+
3
+ describe "Application" do
4
+ before { Padrino.clear! }
5
+ after { remove_views }
6
+
7
+ context 'CSRF protection' do
8
+ context "with CSRF protection on" do
9
+ before do
10
+ mock_app do
11
+ enable :sessions
12
+ enable :protect_from_csrf
13
+ post('/'){ 'HI' }
14
+ end
15
+ end
16
+
17
+ should "not allow requests without tokens" do
18
+ post "/"
19
+ assert_equal 403, status
20
+ end
21
+
22
+ should "allow requests with correct tokens" do
23
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}
24
+ assert_equal 200, status
25
+ end
26
+
27
+ should "not allow requests with incorrect tokens" do
28
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}
29
+ assert_equal 403, status
30
+ end
31
+ end
32
+
33
+ context "without CSRF protection on" do
34
+ before do
35
+ mock_app do
36
+ enable :sessions
37
+ disable :protect_from_csrf
38
+ post('/'){ 'HI' }
39
+ end
40
+ end
41
+
42
+ should "allows requests without tokens" do
43
+ post "/"
44
+ assert_equal 200, status
45
+ end
46
+
47
+ should "allow requests with correct tokens" do
48
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}
49
+ assert_equal 200, status
50
+ end
51
+
52
+ should "allow requests with incorrect tokens" do
53
+ post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}
54
+ assert_equal 200, status
55
+ end
56
+ end
57
+
58
+ context "with optional CSRF protection" do
59
+ before do
60
+ mock_app do
61
+ enable :sessions
62
+ enable :protect_from_csrf
63
+ set :allow_disabled_csrf, true
64
+ post('/on') { 'HI' }
65
+ post('/off', :csrf_protection => false) { 'HI' }
66
+ end
67
+ end
68
+
69
+ should "allow access to routes with csrf_protection off" do
70
+ post "/off"
71
+ assert_equal 200, status
72
+ end
73
+
74
+ should "not allow access to routes with csrf_protection on" do
75
+ post "/on"
76
+ assert_equal 403, status
77
+ end
78
+ end
79
+ end
80
+ end
data/test/test_filters.rb CHANGED
@@ -275,4 +275,74 @@ describe "Filters" do
275
275
  get '/foo'
276
276
  assert_equal 'before', test
277
277
  end
278
+
279
+ should "call before filters only once" do
280
+ once = ''
281
+ mock_app do
282
+ error 500 do
283
+ 'error 500'
284
+ end
285
+ before do
286
+ once += 'before'
287
+ end
288
+ get :index do
289
+ raise Exception, 'Oops'
290
+ end
291
+ end
292
+
293
+ get '/'
294
+ assert_equal 'before', once
295
+ end
296
+
297
+ should 'catch exceptions in before filters' do
298
+ doodle = nil
299
+ mock_app do
300
+ after do
301
+ doodle = 'Been after'
302
+ end
303
+ before do
304
+ raise StandardError, "before"
305
+ end
306
+ get :index do
307
+ doodle = 'Been now'
308
+ end
309
+ error 500 do
310
+ "We broke #{env['sinatra.error'].message}"
311
+ end
312
+ end
313
+
314
+ get '/'
315
+ assert_equal 'We broke before', body
316
+ assert_equal nil, doodle
317
+ end
318
+
319
+ should 'catch exceptions in after filters if no exceptions caught before' do
320
+ doodle = ''
321
+ mock_app do
322
+ after do
323
+ doodle += ' and after'
324
+ raise StandardError, "after"
325
+ end
326
+ get :foo do
327
+ doodle = 'Been now'
328
+ raise StandardError, "now"
329
+ end
330
+ get :index do
331
+ doodle = 'Been now'
332
+ end
333
+ error 500 do
334
+ "We broke #{env['sinatra.error'].message}"
335
+ end
336
+ end
337
+
338
+ get '/foo'
339
+ assert_equal 'We broke now', body
340
+ assert_equal 'Been now', doodle
341
+
342
+ doodle = ''
343
+ get '/'
344
+ assert_equal 'We broke after', body
345
+ assert_equal 'Been now and after', doodle
346
+ end
347
+
278
348
  end