grails-mvc 0.1.8 → 0.1.91
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.
- checksums.yaml +4 -4
- data/bin/grails +2 -2
- data/boilerplate/db/database.sql +0 -0
- data/boilerplate/db/schema.sql +0 -0
- data/boilerplate/db/seed.sql +0 -0
- data/grails-mvc.gemspec +6 -7
- data/lib/commands.rb +2 -5
- data/lib/controller_base.rb +1 -1
- data/lib/flash.rb +1 -0
- data/lib/grails.rb +29 -0
- data/lib/version.rb +1 -1
- data/spec/controller_spec.rb +84 -0
- data/spec/csrf_spec.rb +92 -0
- data/spec/exceptions_spec.rb +51 -0
- data/spec/flash_spec.rb +93 -0
- data/spec/integration_spec.rb +64 -0
- data/spec/router_spec.rb +157 -0
- data/spec/session_spec.rb +110 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/static_spec.rb +24 -0
- data/spec/template_spec.rb +55 -0
- metadata +40 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a8f23f63fdecec15c4b76fcb24a888e8bafdf6d
|
4
|
+
data.tar.gz: 5e71b2fcf017f9564896508e2f67b3876d9cd673
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c20b164478fdb30fbf637f7d056fcacb84e4246e961defb5a3bdc1065d75602ba84dbc0a7df8f2fcd1fceef42e3b02611431c343806e03f69380d03b630efc4a
|
7
|
+
data.tar.gz: 800fccd281b329b4d606adf3294953572c08aa30b279f93fb36b09fe749e8316c2cdba9f89f25a6a28f6f4900722d712325bfc2e5b5b5ad496432050bf31c69b
|
data/bin/grails
CHANGED
File without changes
|
File without changes
|
File without changes
|
data/grails-mvc.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
2
|
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require "version"
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = %q{Grails is a lightweight MVC framework based on Rails!}
|
13
13
|
spec.description = %q{Build web applications easily with Grails!}
|
14
|
-
spec.homepage = "https://github.com/victorwu3/grails
|
14
|
+
spec.homepage = "https://github.com/victorwu3/grails-mvc"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
@@ -23,18 +23,17 @@ Gem::Specification.new do |spec|
|
|
23
23
|
"public gem pushes."
|
24
24
|
end
|
25
25
|
|
26
|
-
spec.files = `git ls-files -z`.split("\x0")
|
27
|
-
f.match(%r{^(test|spec|features)/})
|
28
|
-
end
|
26
|
+
spec.files = `git ls-files -z`.split("\x0")
|
29
27
|
spec.bindir = "bin"
|
30
|
-
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
29
|
spec.require_paths = ["lib"]
|
32
30
|
|
33
31
|
spec.add_dependency "thor", "~> 0.20"
|
34
32
|
spec.add_dependency "httparty", "~> 0.13"
|
33
|
+
spec.add_dependency "rake", "~> 10.0"
|
35
34
|
spec.add_dependency "sqlite3", "~> 1.3", ">= 1.3.5"
|
36
35
|
spec.add_dependency "rack", "~> 1.6", ">= 1.6.4"
|
37
|
-
spec.add_dependency "
|
36
|
+
spec.add_dependency "activesupport", "~> 4.2", ">= 4.2.5.2"
|
38
37
|
|
39
38
|
spec.add_development_dependency "bundler", "~> 1.16"
|
40
39
|
spec.add_development_dependency "rspec", "~> 3.0"
|
data/lib/commands.rb
CHANGED
@@ -23,7 +23,7 @@ module Grails
|
|
23
23
|
raise "Model already exists" if File.exist?(path)
|
24
24
|
|
25
25
|
File.open(path, "w") do |file|
|
26
|
-
file.write("class #{name.camelcase}
|
26
|
+
file.write("class #{name.camelcase} < GrailedORM::Base\n")
|
27
27
|
file.write(" self.finalize!\n")
|
28
28
|
file.write("end\n")
|
29
29
|
end
|
@@ -34,7 +34,7 @@ module Grails
|
|
34
34
|
|
35
35
|
desc "controller NAME", "creates a controller file and view folder"
|
36
36
|
def controller(name)
|
37
|
-
controller_path = File.join(project_root, "app/controllers/#{file.pluralize.underscore}_controlller.rb"
|
37
|
+
controller_path = File.join(project_root, "app/controllers/#{file.pluralize.underscore}_controlller.rb")
|
38
38
|
controller_name = "#{name.pluralize.camelcase}Controller"
|
39
39
|
view_dir = File.join(project_root, "app/views/#{file.pluralize.underscore}")
|
40
40
|
raise "Controller already exists" if File.exist?(controller_path)
|
@@ -72,9 +72,6 @@ module Grails
|
|
72
72
|
app_dir = File.join(Dir.pwd, name)
|
73
73
|
Raise "Directory of #{app_name} already exists" if Dir.exist?(app_dir)
|
74
74
|
FileUtils.copy_enyry(TEMPLATE_PATH, app_dir)
|
75
|
-
Dir.mkdir(File.join(app_dir, "app/models"))
|
76
|
-
Dir.mkdir(File.join(app_dir, "app/controllers"))
|
77
|
-
Dir.mkdir(File.join(app_dir, "app/views"))
|
78
75
|
Dir.mkdir(File.join(app_dir, "db/migrations"))
|
79
76
|
File.new(File.join(app_dir, "config/routes.rb"))
|
80
77
|
File.new(File.join(app_dir, "Gemfile"))
|
data/lib/controller_base.rb
CHANGED
data/lib/flash.rb
CHANGED
data/lib/grails.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require_relative 'version'
|
4
|
+
|
5
|
+
|
6
|
+
module Grails
|
7
|
+
def self.project_root
|
8
|
+
current_dir = Pathname.new(Dir.pwd)
|
9
|
+
current_dir.ascend do |dir|
|
10
|
+
gemfile = File.exist?(File.join(dir, 'Gemfile'))
|
11
|
+
app_folder = Dir.exist?(File.join(dir, 'app'))
|
12
|
+
return dir if gemfile && app_folder
|
13
|
+
end
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
GRAILS_ROOT = /^(.+)\/lib/.match(File.dirname(__FILE__))[1]
|
19
|
+
TEMPLATE_PATH = File.join(GRAILS_ROOT, 'template')
|
20
|
+
PROJECT_ROOT = Grails.project_root
|
21
|
+
|
22
|
+
require_relative "#{PROJECT_ROOT}/config/database" if PROJECT_ROOT
|
23
|
+
require_relative 'controller_base'
|
24
|
+
require_relative 'flash'
|
25
|
+
require_relative 'router'
|
26
|
+
require_relative 'session'
|
27
|
+
require_relative 'show_exceptions'
|
28
|
+
require_relative 'static'
|
29
|
+
require_relative 'commands'
|
data/lib/version.rb
CHANGED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'controller_base'
|
3
|
+
|
4
|
+
describe ControllerBase do
|
5
|
+
before(:all) do
|
6
|
+
class UsersController < ControllerBase
|
7
|
+
def index
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
after(:all) { Object.send(:remove_const, 'UsersController') }
|
12
|
+
|
13
|
+
let(:req) { Rack::Request.new({'rack.input' => {}}) }
|
14
|
+
let(:res) { Rack::MockResponse.new('200',{},[]) }
|
15
|
+
let(:users_controller) { UsersController.new(req, res) }
|
16
|
+
|
17
|
+
describe '#render_content' do
|
18
|
+
before(:each) do
|
19
|
+
users_controller.render_content 'somebody', 'text/html'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets the response content type' do
|
23
|
+
expect(res['Content-Type']).to eq('text/html')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'sets the response body' do
|
27
|
+
expect(res.body).to eq('somebody')
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#already_built_response?' do
|
31
|
+
let(:users_controller2) { UsersController.new(req, res) }
|
32
|
+
|
33
|
+
it 'is false before rendering' do
|
34
|
+
expect(users_controller2.already_built_response?).to be_falsey
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'is true after rendering content' do
|
38
|
+
users_controller2.render_content 'sombody', 'text/html'
|
39
|
+
expect(users_controller2.already_built_response?).to be_truthy
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises an error when attempting to render twice' do
|
43
|
+
users_controller2.render_content 'sombody', 'text/html'
|
44
|
+
expect do
|
45
|
+
users_controller2.render_content 'sombody', 'text/html'
|
46
|
+
end.to raise_error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#redirect' do
|
52
|
+
before(:each) do
|
53
|
+
users_controller.redirect_to('http://www.google.com')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'sets the header' do
|
57
|
+
expect(users_controller.res.header['location']).to eq('http://www.google.com')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'sets the status' do
|
61
|
+
expect(users_controller.res.status).to eq(302)
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#already_built_response?' do
|
65
|
+
let(:users_controller2) { UsersController.new(req, res) }
|
66
|
+
|
67
|
+
it 'is false before rendering' do
|
68
|
+
expect(users_controller2.already_built_response?).to be_falsey
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'is true after rendering content' do
|
72
|
+
users_controller2.redirect_to('http://google.com')
|
73
|
+
expect(users_controller2.already_built_response?).to be_truthy
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'raises an error when attempting to render twice' do
|
77
|
+
users_controller2.redirect_to('http://google.com')
|
78
|
+
expect do
|
79
|
+
users_controller2.redirect_to('http://google.com')
|
80
|
+
end.to raise_error
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/spec/csrf_spec.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'controller_base'
|
3
|
+
require 'router'
|
4
|
+
|
5
|
+
describe ControllerBase do
|
6
|
+
let(:req) { Rack::Request.new({'rack.input' => {}, 'REQUEST_METHOD' => 'GET'}) }
|
7
|
+
let(:res) { Rack::Response.new([], '200', {}) }
|
8
|
+
let(:controller_base) { ControllerBase.new(req, res) }
|
9
|
+
|
10
|
+
describe '#form_authenticity_token' do
|
11
|
+
it 'adds new cookie with \'authenticity_token\' name to response' do
|
12
|
+
controller_base.form_authenticity_token
|
13
|
+
cookie_str = res.headers['Set-Cookie']
|
14
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
15
|
+
expect(cookie['authenticity_token']).not_to be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the same token set in the cookie' do
|
19
|
+
token = controller_base.form_authenticity_token
|
20
|
+
cookie_str = res.headers['Set-Cookie']
|
21
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
22
|
+
|
23
|
+
expect(cookie['authenticity_token']).to eq(token)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns the same token when called multiple times in the same response' do
|
27
|
+
expect(controller_base.form_authenticity_token).to eq(controller_base.form_authenticity_token)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#invoke_action' do
|
32
|
+
before(:all) do
|
33
|
+
class DummyController < ControllerBase
|
34
|
+
protect_from_forgery
|
35
|
+
|
36
|
+
def index
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class CatsController < ControllerBase
|
41
|
+
def index
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
after(:all) do
|
47
|
+
Object.send(:remove_const, 'DummyController')
|
48
|
+
Object.send(:remove_const, 'CatsController')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'doesn\'t check authenticity token for a GET request' do
|
52
|
+
dummy_controller = DummyController.new(req, res)
|
53
|
+
|
54
|
+
expect(dummy_controller).not_to receive(:check_authenticity_token)
|
55
|
+
dummy_controller.invoke_action(:index)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'doesn\'t check authenticity token unless ::protect_from_forgery is called' do
|
59
|
+
nonsecure_controller = CatsController.new(req, res)
|
60
|
+
|
61
|
+
expect(nonsecure_controller).not_to receive(:check_authenticity_token)
|
62
|
+
nonsecure_controller.invoke_action(:index)
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'a non-GET request' do
|
66
|
+
let(:dummy_controller) { DummyController.new(req, res) }
|
67
|
+
|
68
|
+
before(:each) do
|
69
|
+
allow(req).to receive(:request_method).and_return('POST')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'calls #check_authenticity_token' do
|
73
|
+
expect(dummy_controller).to receive(:check_authenticity_token)
|
74
|
+
dummy_controller.invoke_action(:index)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
it 'raises an error with an invalid token for any non-GET request' do
|
79
|
+
expect { dummy_controller.invoke_action(:index) }.to raise_error('Invalid authenticity token')
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'doesn\'t raise an error when a valid authenticity token is given' do
|
83
|
+
|
84
|
+
# Simulate auth token being passed in both in params from form and cookies
|
85
|
+
dummy_controller.params['authenticity_token'] = 'mocktoken'
|
86
|
+
req.env['HTTP_COOKIE'] = 'authenticity_token=mocktoken'
|
87
|
+
|
88
|
+
expect { dummy_controller.invoke_action(:index) }.not_to raise_error
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# require 'rack/show_exceptions'
|
2
|
+
require 'rack/lint'
|
3
|
+
require 'rack/mock'
|
4
|
+
require 'show_exceptions'
|
5
|
+
|
6
|
+
describe ShowExceptions do
|
7
|
+
let(:show_exceptions) { ShowExceptions.new(app) }
|
8
|
+
let(:good_dummy_app) { Proc.new {} }
|
9
|
+
let(:bad_dummy_app) { Proc.new { raise RuntimeError }}
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
it 'initializes with an app' do
|
13
|
+
mock_exception = ShowExceptions.new(good_dummy_app)
|
14
|
+
expect(mock_exception.app).to be(good_dummy_app)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#call' do
|
19
|
+
|
20
|
+
let(:env) { {} }
|
21
|
+
|
22
|
+
it 'calls #call on the app' do
|
23
|
+
mock_exception = ShowExceptions.new(good_dummy_app)
|
24
|
+
expect(good_dummy_app).to receive(:call).with(env)
|
25
|
+
mock_exception.call(env)
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when an app throws an error' do
|
29
|
+
let(:mock_exception) { ShowExceptions.new(bad_dummy_app) }
|
30
|
+
|
31
|
+
it 'catches exceptions' do
|
32
|
+
expect { mock_exception.call(env) }.not_to raise_error
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'sets the status code to 500' do
|
36
|
+
response = mock_exception.call(env)
|
37
|
+
expect(response[0]).to eq '500'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'sets the content type to text/html' do
|
41
|
+
response = mock_exception.call(env)
|
42
|
+
expect(response[1]).to eq({'Content-type' => 'text/html'})
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'the body of the response includes the error type' do
|
46
|
+
response = mock_exception.call(env)
|
47
|
+
expect(response[2]).to include 'RuntimeError'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/spec/flash_spec.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'flash'
|
3
|
+
require 'controller_base'
|
4
|
+
|
5
|
+
describe Flash do
|
6
|
+
let(:req) { Rack::Request.new({'rack.input' => {}}) }
|
7
|
+
let(:res) { Rack::Response.new([], '200', {}) }
|
8
|
+
let(:flash) { Flash.new(req) }
|
9
|
+
|
10
|
+
describe '#[]=' do
|
11
|
+
it 'sets data in flash' do
|
12
|
+
flash['golden gate park'] = 'bison'
|
13
|
+
expect(flash['golden gate park']).to eq('bison')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#store_flash' do
|
18
|
+
before(:each) do
|
19
|
+
flash['first_key'] = 'first_val'
|
20
|
+
flash.store_flash(res)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'adds new cookie with \'_rails_lite_app_flash\' name to response' do
|
24
|
+
cookie_str = res.headers['Set-Cookie']
|
25
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
26
|
+
expect(cookie['_rails_lite_app_flash']).not_to be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'stores the cookie in JSON format' do
|
30
|
+
cookie_str = res.headers['Set-Cookie']
|
31
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
32
|
+
cookie_val = cookie['_rails_lite_app_flash']
|
33
|
+
cookie_hash = JSON.parse(cookie_val)
|
34
|
+
|
35
|
+
expect(cookie_hash).to be_instance_of(Hash)
|
36
|
+
expect(cookie_hash['first_key']).to eq('first_val')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'does not persist data more than 1 request' do
|
40
|
+
second_req = Rack::Request.new({'rack.input' => {}})
|
41
|
+
second_res = Rack::Response.new([], '200', {})
|
42
|
+
|
43
|
+
cookie_str = res.headers['Set-Cookie']
|
44
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
45
|
+
|
46
|
+
second_req.cookies.merge!(cookie)
|
47
|
+
|
48
|
+
second_flash = Flash.new(second_req)
|
49
|
+
second_flash.store_flash(second_res)
|
50
|
+
|
51
|
+
second_cookie_str = second_res.headers['Set-Cookie']
|
52
|
+
second_cookie = Rack::Utils.parse_query(second_cookie_str)
|
53
|
+
second_cookie_val = second_cookie['_rails_lite_app_flash']
|
54
|
+
second_cookie_hash = JSON.parse(second_cookie_val)
|
55
|
+
|
56
|
+
expect(second_cookie_hash).not_to have_key('first_key')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#[]' do
|
61
|
+
it 'reads data from flash cookie' do
|
62
|
+
cookie = { '_rails_lite_app_flash' => { 'best_pizza' => 'Arizmendi' }.to_json }
|
63
|
+
req.cookies.merge!(cookie)
|
64
|
+
updated_flash = Flash.new(req)
|
65
|
+
expect(updated_flash['best_pizza']).to eq('Arizmendi')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'can be accessed using either strings or symbols' do
|
69
|
+
flash = Flash.new(req)
|
70
|
+
flash['notice'] = 'test'
|
71
|
+
expect(flash[:notice]).to eq('test')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#now' do
|
76
|
+
before(:each) do
|
77
|
+
flash.now['abc'] = 'xyz'
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'reads data from flash.now' do
|
81
|
+
expect(flash['abc']).to eq('xyz')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'does not persist flash.now data' do
|
85
|
+
flash.store_flash(res)
|
86
|
+
cookie_str = res.headers['Set-Cookie']
|
87
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
88
|
+
cookie_val = cookie['_rails_lite_app_flash']
|
89
|
+
cookie_hash = JSON.parse(cookie_val)
|
90
|
+
expect(cookie_hash['abc']).to be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'controller_base'
|
3
|
+
require 'router'
|
4
|
+
|
5
|
+
describe 'the symphony of things' do
|
6
|
+
let(:req) { Rack::Request.new({'rack.input' => ''}) }
|
7
|
+
let(:res) { Rack::MockResponse.new('200', [], {}) }
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
class Ctrlr < ControllerBase
|
11
|
+
def route_render
|
12
|
+
render_content('testing', 'text/html')
|
13
|
+
end
|
14
|
+
|
15
|
+
def route_does_params
|
16
|
+
render_content("got ##{params['id']}", 'text/text')
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_session
|
20
|
+
session['token'] = 'testing'
|
21
|
+
render_content('hi', 'text/html')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
after(:all) { Object.send(:remove_const, 'Ctrlr') }
|
26
|
+
|
27
|
+
describe 'routes and params' do
|
28
|
+
it 'route instantiates controller and calls invoke action' do
|
29
|
+
route = Route.new(Regexp.new('^/statuses/(?<id>\\d+)$'), :get, Ctrlr, :route_render)
|
30
|
+
allow(req).to receive(:path) { '/statuses/1' }
|
31
|
+
allow(req).to receive(:request_method) { 'GET' }
|
32
|
+
route.run(req, res)
|
33
|
+
expect(res.body).to eq('testing')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'route adds to params' do
|
37
|
+
route = Route.new(Regexp.new('^/statuses/(?<id>\\d+)$'), :get, Ctrlr, :route_does_params)
|
38
|
+
allow(req).to receive(:path) { '/statuses/1' }
|
39
|
+
allow(req).to receive(:request_method) { 'GET' }
|
40
|
+
route.run(req, res)
|
41
|
+
expect(res.body).to eq('got #1')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'controller sessions' do
|
46
|
+
let(:ctrlr) { Ctrlr.new(req, res) }
|
47
|
+
|
48
|
+
it 'exposes a session via the session method' do
|
49
|
+
expect(ctrlr.session).to be_instance_of(Session)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'saves the session after rendering content' do
|
53
|
+
ctrlr.update_session
|
54
|
+
# Currently broken when flash is used. Need to store flash in the cookie
|
55
|
+
# or change this spec.
|
56
|
+
expect(res.headers['Set-Cookie']).to_not be_empty
|
57
|
+
cookie_str = res.headers['Set-Cookie']
|
58
|
+
cookie_val = Rack::Utils.parse_query(cookie_str)
|
59
|
+
cookie_str = cookie_val['_rails_lite_app']
|
60
|
+
cookie_hash = JSON.parse(cookie_str)
|
61
|
+
expect(cookie_hash['token']).to eq('testing')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/router_spec.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'router'
|
3
|
+
require 'controller_base'
|
4
|
+
|
5
|
+
describe Route do
|
6
|
+
let(:req) { Rack::Request.new({'rack.input' => {}}) }
|
7
|
+
let(:res) { Rack::MockResponse.new('200', {}, []) }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
allow(req).to receive(:request_method).and_return('GET')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#matches?' do
|
14
|
+
it 'matches simple regular expression' do
|
15
|
+
index_route = Route.new(Regexp.new('^/users$'), :get, 'x', :x)
|
16
|
+
allow(req).to receive(:path) { '/users' }
|
17
|
+
allow(req).to receive(:request_method) { 'GET' }
|
18
|
+
expect(index_route.matches?(req)).to be_truthy
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'matches regular expression with capture' do
|
22
|
+
index_route = Route.new(Regexp.new('^/users/(?<id>\\d+)$'), :get, 'x', :x)
|
23
|
+
allow(req).to receive(:path) { '/users/1' }
|
24
|
+
allow(req).to receive(:request_method) { 'GET' }
|
25
|
+
expect(index_route.matches?(req)).to be_truthy
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'correctly doesn\'t match regular expression with capture' do
|
29
|
+
index_route = Route.new(Regexp.new('^/users/(?<id>\\d+)$'), :get, 'UsersController', :index)
|
30
|
+
allow(req).to receive(:path) { '/statuses/1' }
|
31
|
+
allow(req).to receive(:request_method) { 'GET' }
|
32
|
+
expect(index_route.matches?(req)).to be_falsey
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#run' do
|
37
|
+
before(:all) { class DummyController; end }
|
38
|
+
after(:all) { Object.send(:remove_const, 'DummyController') }
|
39
|
+
|
40
|
+
it 'instantiates controller and invokes action' do
|
41
|
+
# reader beware. hairy adventures ahead.
|
42
|
+
# this is really checking way too much implementation,
|
43
|
+
# but tests the approach recommended in the project
|
44
|
+
allow(req).to receive(:path) { '/users' }
|
45
|
+
|
46
|
+
dummy_controller_class = DummyController
|
47
|
+
dummy_controller_instance = DummyController.new
|
48
|
+
allow(dummy_controller_instance).to receive(:invoke_action)
|
49
|
+
allow(dummy_controller_class).to receive(:new).with(req, res, {}) do
|
50
|
+
dummy_controller_instance
|
51
|
+
end
|
52
|
+
expect(dummy_controller_instance).to receive(:invoke_action)
|
53
|
+
index_route = Route.new(Regexp.new('^/users$'), :get, dummy_controller_class, :index)
|
54
|
+
index_route.run(req, res)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe Router do
|
60
|
+
let(:req) { Rack::Request.new({'rack-input' => {}}) }
|
61
|
+
let(:res) { Rack::MockResponse.new('200', {}, []) }
|
62
|
+
|
63
|
+
describe '#add_route' do
|
64
|
+
it 'adds a route' do
|
65
|
+
subject.add_route(1, 2, 3, 4)
|
66
|
+
expect(subject.routes.count).to eq(1)
|
67
|
+
subject.add_route(1, 2, 3, 4)
|
68
|
+
subject.add_route(1, 2, 3, 4)
|
69
|
+
expect(subject.routes.count).to eq(3)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#match' do
|
74
|
+
it 'matches a correct route' do
|
75
|
+
subject.add_route(Regexp.new('^/users$'), :get, :x, :x)
|
76
|
+
allow(req).to receive(:path) { '/users' }
|
77
|
+
allow(req).to receive(:request_method) { 'GET' }
|
78
|
+
matched = subject.match(req)
|
79
|
+
expect(matched).not_to be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'doesn\'t match an incorrect route' do
|
83
|
+
subject.add_route(Regexp.new('^/users$'), :get, :x, :x)
|
84
|
+
allow(req).to receive(:path) { '/incorrect_path' }
|
85
|
+
allow(req).to receive(:request_method) { 'GET' }
|
86
|
+
matched = subject.match(req)
|
87
|
+
expect(matched).to be_nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#run' do
|
92
|
+
it 'sets status to 404 if no route is found' do
|
93
|
+
subject.add_route(Regexp.new('^/users$'), :get, :x, :x)
|
94
|
+
allow(req).to receive(:path).and_return('/incorrect_path')
|
95
|
+
allow(req).to receive(:request_method).and_return('GET')
|
96
|
+
subject.run(req, res)
|
97
|
+
expect(res.status).to eq(404)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'http method (get, put, post, delete)' do
|
102
|
+
it 'adds methods get, put, post and delete' do
|
103
|
+
router = Router.new
|
104
|
+
expect((router.methods - Class.new.methods)).to include(:get)
|
105
|
+
expect((router.methods - Class.new.methods)).to include(:put)
|
106
|
+
expect((router.methods - Class.new.methods)).to include(:post)
|
107
|
+
expect((router.methods - Class.new.methods)).to include(:delete)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'adds a route when an http method method is called' do
|
111
|
+
router = Router.new
|
112
|
+
router.get Regexp.new('^/users$'), ControllerBase, :index
|
113
|
+
expect(router.routes.count).to eq(1)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#draw' do
|
118
|
+
it 'calls http method methods with the route information to add the route' do
|
119
|
+
index_route = double('route')
|
120
|
+
post_route = double('route')
|
121
|
+
|
122
|
+
routes = Proc.new do
|
123
|
+
get index_route
|
124
|
+
post post_route
|
125
|
+
end
|
126
|
+
|
127
|
+
router = Router.new
|
128
|
+
|
129
|
+
expect(router).to receive(:get).with(index_route)
|
130
|
+
expect(router).to receive(:post).with(post_route)
|
131
|
+
|
132
|
+
router.draw(&routes)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe 'ControllerBase#initialize' do
|
138
|
+
before(:all) do
|
139
|
+
class CatsController < ControllerBase
|
140
|
+
def index
|
141
|
+
@cats = ['Gizmo']
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
after(:all) { Object.send(:remove_const, 'CatsController') }
|
147
|
+
|
148
|
+
let(:req) { Rack::Request.new({'rack.input' => {}}) }
|
149
|
+
let(:res) { Rack::MockResponse.new('200', {}, []) }
|
150
|
+
let(:cats_controller) { CatsController.new(req, res, { 'key' => 'val' } ) }
|
151
|
+
|
152
|
+
context '#initialize' do
|
153
|
+
it 'includes route params in the params object' do
|
154
|
+
expect(cats_controller.params['key']).to eq('val')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'session'
|
3
|
+
require 'controller_base'
|
4
|
+
|
5
|
+
describe Session do
|
6
|
+
let(:req) { Rack::Request.new({ 'rack.input' => {} }) }
|
7
|
+
let(:res) { Rack::Response.new([], '200', {}) }
|
8
|
+
let(:cook) { {'_rails_lite_app' => { 'xyz' => 'abc' }.to_json} }
|
9
|
+
|
10
|
+
it 'deserializes json cookie if one exists' do
|
11
|
+
req.cookies.merge!(cook)
|
12
|
+
session = Session.new(req)
|
13
|
+
expect(session['xyz']).to eq('abc')
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#store_session' do
|
17
|
+
context 'without cookies in request' do
|
18
|
+
before(:each) do
|
19
|
+
session = Session.new(req)
|
20
|
+
session['first_key'] = 'first_val'
|
21
|
+
session.store_session(res)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'adds new cookie with \'_rails_lite_app\' name to response' do
|
25
|
+
cookie_str = res.headers['Set-Cookie']
|
26
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
27
|
+
expect(cookie['_rails_lite_app']).not_to be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'stores the cookie in json format' do
|
31
|
+
cookie_str = res.headers['Set-Cookie']
|
32
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
33
|
+
cookie_val = cookie['_rails_lite_app']
|
34
|
+
cookie_hash = JSON.parse(cookie_val)
|
35
|
+
expect(cookie_hash).to be_instance_of(Hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with cookies in request' do
|
40
|
+
before(:each) do
|
41
|
+
cook = {'_rails_lite_app' => { 'pho' => 'soup' }.to_json }
|
42
|
+
req.cookies.merge!(cook)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'reads the pre-existing cookie data into hash' do
|
46
|
+
session = Session.new(req)
|
47
|
+
expect(session['pho']).to eq('soup')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'saves new and old data to the cookie' do
|
51
|
+
session = Session.new(req)
|
52
|
+
session['machine'] = 'mocha'
|
53
|
+
session.store_session(res)
|
54
|
+
cookie_str = res['Set-Cookie']
|
55
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
56
|
+
cookie_val = cookie['_rails_lite_app']
|
57
|
+
cookie_hash = JSON.parse(cookie_val)
|
58
|
+
expect(cookie_hash['pho']).to eq('soup')
|
59
|
+
expect(cookie_hash['machine']).to eq('mocha')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ControllerBase do
|
66
|
+
before(:all) do
|
67
|
+
class CatsController < ControllerBase
|
68
|
+
end
|
69
|
+
end
|
70
|
+
after(:all) { Object.send(:remove_const, 'CatsController') }
|
71
|
+
|
72
|
+
let(:req) { Rack::Request.new({'rack.input' => {}}) }
|
73
|
+
let(:res) { Rack::Response.new([], '200', {}) }
|
74
|
+
let(:cats_controller) { CatsController.new(req, res) }
|
75
|
+
|
76
|
+
describe '#session' do
|
77
|
+
it 'returns a session instance' do
|
78
|
+
expect(cats_controller.session).to be_a(Session)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns the same instance on successive invocations' do
|
82
|
+
first_result = cats_controller.session
|
83
|
+
expect(cats_controller.session).to be(first_result)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
shared_examples_for 'storing session data' do
|
88
|
+
it 'should store the session data' do
|
89
|
+
cats_controller.session['test_key'] = 'test_value'
|
90
|
+
cats_controller.send(method, *args)
|
91
|
+
cookie_str = res['Set-Cookie']
|
92
|
+
cookie = Rack::Utils.parse_query(cookie_str)
|
93
|
+
cookie_val = cookie['_rails_lite_app']
|
94
|
+
cookie_hash = JSON.parse(cookie_val)
|
95
|
+
expect(cookie_hash['test_key']).to eq('test_value')
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#render_content' do
|
100
|
+
let(:method) { :render_content }
|
101
|
+
let(:args) { ['test', 'text/plain'] }
|
102
|
+
include_examples 'storing session data'
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#redirect_to' do
|
106
|
+
let(:method) { :redirect_to }
|
107
|
+
let(:args) { ['http://appacademy.io'] }
|
108
|
+
include_examples 'storing session data'
|
109
|
+
end
|
110
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
# Enable flags like --only-failures and --next-failure
|
5
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
6
|
+
|
7
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
8
|
+
config.disable_monkey_patching!
|
9
|
+
|
10
|
+
config.expect_with :rspec do |c|
|
11
|
+
c.syntax = :expect
|
12
|
+
end
|
13
|
+
end
|
data/spec/static_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require 'static'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
class DummyApp
|
6
|
+
def call(env)
|
7
|
+
[200, {'Content-Type' => 'text/plain'}, ['Hello World']]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Static do
|
12
|
+
let(:static) { Static.new(DummyApp.new) }
|
13
|
+
let(:request) { Rack::MockRequest.new(static) }
|
14
|
+
|
15
|
+
it 'serves files' do
|
16
|
+
res = request.get('/public/hello.txt')
|
17
|
+
expect(res.body).to match(/Hello there friend/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it '404s if url root is known but it can\'t find the file' do
|
21
|
+
res = request.get('/public/nicholas.jpg')
|
22
|
+
expect(res.status).to be(404)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'controller_base'
|
3
|
+
|
4
|
+
describe ControllerBase do
|
5
|
+
before(:all) do
|
6
|
+
class CatsController < ControllerBase
|
7
|
+
def index
|
8
|
+
@cats = ['GIZMO']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
after(:all) { Object.send(:remove_const, 'CatsController') }
|
13
|
+
|
14
|
+
let(:req) { Rack::Request.new({'rack.input' => {}}) }
|
15
|
+
let(:res) { Rack::MockResponse.new('200', {}, []) }
|
16
|
+
let(:cats_controller) { CatsController.new(req, res) }
|
17
|
+
|
18
|
+
describe '#render' do
|
19
|
+
before(:each) do
|
20
|
+
cats_controller.index
|
21
|
+
cats_controller.render(:index)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'renders the html of the index view' do
|
25
|
+
expect(cats_controller.res.body).to include('ALL THE CATS')
|
26
|
+
expect(cats_controller.res.body).to include('<h1>')
|
27
|
+
expect(cats_controller.res['Content-Type']).to eq('text/html')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'shows the proper instance variables in the index view' do
|
31
|
+
expect(cats_controller.res.body).to include('GIZMO')
|
32
|
+
expect(cats_controller.res['Content-Type']).to eq('text/html')
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#already_built_response?' do
|
36
|
+
let(:cats_controller2) { CatsController.new(req, res) }
|
37
|
+
|
38
|
+
it 'is false before rendering' do
|
39
|
+
expect(cats_controller2.already_built_response?).to be_falsey
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'is true after rendering content' do
|
43
|
+
cats_controller2.render(:index)
|
44
|
+
expect(cats_controller2.already_built_response?).to be_truthy
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises an error when attempting to render twice' do
|
48
|
+
cats_controller2.render(:index)
|
49
|
+
expect do
|
50
|
+
cats_controller2.render(:index)
|
51
|
+
end.to raise_error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grails-mvc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.91
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor Wu
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: sqlite3
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -79,19 +93,25 @@ dependencies:
|
|
79
93
|
- !ruby/object:Gem::Version
|
80
94
|
version: 1.6.4
|
81
95
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
96
|
+
name: activesupport
|
83
97
|
requirement: !ruby/object:Gem::Requirement
|
84
98
|
requirements:
|
85
99
|
- - "~>"
|
86
100
|
- !ruby/object:Gem::Version
|
87
|
-
version: '
|
101
|
+
version: '4.2'
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 4.2.5.2
|
88
105
|
type: :runtime
|
89
106
|
prerelease: false
|
90
107
|
version_requirements: !ruby/object:Gem::Requirement
|
91
108
|
requirements:
|
92
109
|
- - "~>"
|
93
110
|
- !ruby/object:Gem::Version
|
94
|
-
version: '
|
111
|
+
version: '4.2'
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: 4.2.5.2
|
95
115
|
- !ruby/object:Gem::Dependency
|
96
116
|
name: bundler
|
97
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -157,8 +177,7 @@ dependencies:
|
|
157
177
|
description: Build web applications easily with Grails!
|
158
178
|
email:
|
159
179
|
- victor.wu.1@vanderbilt.edu
|
160
|
-
executables:
|
161
|
-
- grails
|
180
|
+
executables: []
|
162
181
|
extensions: []
|
163
182
|
extra_rdoc_files: []
|
164
183
|
files:
|
@@ -175,6 +194,9 @@ files:
|
|
175
194
|
- bin/grails
|
176
195
|
- bin/server.rb
|
177
196
|
- bin/setup
|
197
|
+
- boilerplate/db/database.sql
|
198
|
+
- boilerplate/db/schema.sql
|
199
|
+
- boilerplate/db/seed.sql
|
178
200
|
- grailedorm/.rspec
|
179
201
|
- grailedorm/Gemfile
|
180
202
|
- grailedorm/Gemfile.lock
|
@@ -192,12 +214,23 @@ files:
|
|
192
214
|
- lib/commands.rb
|
193
215
|
- lib/controller_base.rb
|
194
216
|
- lib/flash.rb
|
217
|
+
- lib/grails.rb
|
195
218
|
- lib/router.rb
|
196
219
|
- lib/session.rb
|
197
220
|
- lib/show_exceptions.rb
|
198
221
|
- lib/static.rb
|
199
222
|
- lib/version.rb
|
200
|
-
|
223
|
+
- spec/controller_spec.rb
|
224
|
+
- spec/csrf_spec.rb
|
225
|
+
- spec/exceptions_spec.rb
|
226
|
+
- spec/flash_spec.rb
|
227
|
+
- spec/integration_spec.rb
|
228
|
+
- spec/router_spec.rb
|
229
|
+
- spec/session_spec.rb
|
230
|
+
- spec/spec_helper.rb
|
231
|
+
- spec/static_spec.rb
|
232
|
+
- spec/template_spec.rb
|
233
|
+
homepage: https://github.com/victorwu3/grails-mvc
|
201
234
|
licenses:
|
202
235
|
- MIT
|
203
236
|
metadata:
|