raamen 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/raamen.rb +6 -1
- data/lib/raamen/controller_base.rb +90 -0
- data/lib/raamen/db/sqlite3.db +0 -0
- data/lib/raamen/db/sqlite3.sql +43 -0
- data/lib/raamen/db_connection.rb +62 -0
- data/lib/raamen/flash.rb +41 -0
- data/lib/raamen/router.rb +62 -0
- data/lib/raamen/session.rb +25 -0
- data/lib/raamen/show_exceptions.rb +66 -0
- data/lib/raamen/sql_object.rb +120 -0
- data/lib/raamen/sql_object_modules/associatable.rb +90 -0
- data/lib/raamen/sql_object_modules/searchable.rb +18 -0
- data/lib/raamen/static.rb +59 -0
- data/lib/raamen/templates/rescue.html.erb +52 -0
- data/lib/raamen/version.rb +1 -1
- metadata +14 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31023fc5e442be1d7d1f7346a40bb53355847fcb
|
4
|
+
data.tar.gz: f4fe204f88692a72324501ab003627b4c37b43af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b37d65b71beafcab9e9ce8e25c8b10a0f1523c7ba2441c44d2a0354a105866ba20627ec5adf918c1a592dd04c3b62bbe8a40df8d3daa85e90747dc1c685356f
|
7
|
+
data.tar.gz: 03b2e0b4b29254475fd02eeceee34237296fc71cad2a6af1cbc0bfaa7252d7af1aabc68b624d7d3bae7577b87d59eebca337295d6d9ef536e9c6075823a1a5d7
|
data/lib/raamen.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
|
1
|
+
require_relative "raamen/version.rb"
|
2
|
+
require_relative "raamen/sql_object"
|
3
|
+
require_relative "raamen/controller_base"
|
4
|
+
require_relative "raamen/router"
|
5
|
+
require_relative "raamen/static"
|
6
|
+
require_relative "raamen/show_exceptions"
|
2
7
|
|
3
8
|
module Raamen
|
4
9
|
# Your code goes here...
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
require 'erb'
|
4
|
+
require_relative 'session'
|
5
|
+
require_relative 'flash'
|
6
|
+
|
7
|
+
module Raamen
|
8
|
+
class ControllerBase
|
9
|
+
attr_reader :req, :res, :params, :session, :flash
|
10
|
+
attr_accessor :already_built_response, :authenticity_token
|
11
|
+
|
12
|
+
def initialize(req, res, route_params = {})
|
13
|
+
@req = req
|
14
|
+
@res = res
|
15
|
+
@params = route_params.merge(req.params)
|
16
|
+
@session = Session.new(req)
|
17
|
+
@flash = Flash.new(req)
|
18
|
+
@already_built_response = false
|
19
|
+
@authenticity_token = generate_authenticity_token
|
20
|
+
@@protect_from_forgery ||= false
|
21
|
+
end
|
22
|
+
|
23
|
+
def already_built_response?
|
24
|
+
self.already_built_response
|
25
|
+
end
|
26
|
+
|
27
|
+
def redirect_to(url)
|
28
|
+
raise "double render" if already_built_response?
|
29
|
+
self.res["location"] = url
|
30
|
+
self.res.status = 302
|
31
|
+
self.session.store_session(res)
|
32
|
+
self.flash.store_flash(res)
|
33
|
+
self.already_built_response = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def render_content(content, content_type)
|
37
|
+
raise "double render" if already_built_response?
|
38
|
+
self.res["Content-Type"] = content_type
|
39
|
+
self.res.write(content)
|
40
|
+
self.session.store_session(res)
|
41
|
+
self.flash.store_flash(res)
|
42
|
+
self.already_built_response = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def render(template_name)
|
46
|
+
template_path = File.join(
|
47
|
+
File.dirname(__FILE__),
|
48
|
+
"..",
|
49
|
+
"views",
|
50
|
+
"#{self.class.name.underscore}",
|
51
|
+
"#{template_name}.html.erb"
|
52
|
+
)
|
53
|
+
template_content = File.read(template_path)
|
54
|
+
render_content(ERB.new(template_content).result(binding), "text/html")
|
55
|
+
end
|
56
|
+
|
57
|
+
def invoke_action(name)
|
58
|
+
if @@protect_from_forgery && self.req.request_method != "GET"
|
59
|
+
check_authenticity_token
|
60
|
+
end
|
61
|
+
self.send(name)
|
62
|
+
render(name) unless already_built_response?
|
63
|
+
end
|
64
|
+
|
65
|
+
def form_authenticity_token
|
66
|
+
self.res.set_cookie(
|
67
|
+
"authenticity_token",
|
68
|
+
{path: "/", value: self.authenticity_token}
|
69
|
+
)
|
70
|
+
self.authenticity_token
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.protect_from_forgery
|
74
|
+
@@protect_from_forgery = true
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def generate_authenticity_token
|
80
|
+
SecureRandom.urlsafe_base64(16)
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_authenticity_token
|
84
|
+
cookie = self.req.cookies["authenticity_token"]
|
85
|
+
unless cookie && cookie == params["authenticity_token"]
|
86
|
+
raise "Invalid authenticity token"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
Binary file
|
@@ -0,0 +1,43 @@
|
|
1
|
+
CREATE TABLE cats (
|
2
|
+
id INTEGER PRIMARY KEY,
|
3
|
+
name VARCHAR(255) NOT NULL,
|
4
|
+
owner_id INTEGER,
|
5
|
+
|
6
|
+
FOREIGN KEY(owner_id) REFERENCES human(id)
|
7
|
+
);
|
8
|
+
|
9
|
+
CREATE TABLE humans (
|
10
|
+
id INTEGER PRIMARY KEY,
|
11
|
+
fname VARCHAR(255) NOT NULL,
|
12
|
+
lname VARCHAR(255) NOT NULL,
|
13
|
+
house_id INTEGER,
|
14
|
+
|
15
|
+
FOREIGN KEY(house_id) REFERENCES human(id)
|
16
|
+
);
|
17
|
+
|
18
|
+
CREATE TABLE houses (
|
19
|
+
id INTEGER PRIMARY KEY,
|
20
|
+
address VARCHAR(255) NOT NULL
|
21
|
+
);
|
22
|
+
|
23
|
+
INSERT INTO
|
24
|
+
houses (id, address)
|
25
|
+
VALUES
|
26
|
+
(1, "26th and Guerrero"), (2, "Dolores and Market");
|
27
|
+
|
28
|
+
INSERT INTO
|
29
|
+
humans (id, fname, lname, house_id)
|
30
|
+
VALUES
|
31
|
+
(1, "Devon", "Watts", 1),
|
32
|
+
(2, "Matt", "Rubens", 1),
|
33
|
+
(3, "Ned", "Ruggeri", 2),
|
34
|
+
(4, "Catless", "Human", NULL);
|
35
|
+
|
36
|
+
INSERT INTO
|
37
|
+
cats (id, name, owner_id)
|
38
|
+
VALUES
|
39
|
+
(1, "Breakfast", 1),
|
40
|
+
(2, "Earl", 2),
|
41
|
+
(3, "Haskell", 3),
|
42
|
+
(4, "Markov", 3),
|
43
|
+
(5, "Stray Cat", NULL);
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
|
3
|
+
module Raamen
|
4
|
+
PRINT_QUERIES = ENV['PRINT_QUERIES'] == 'true'
|
5
|
+
# https://tomafro.net/2010/01/tip-relative-paths-with-file-expand-path
|
6
|
+
ROOT_FOLDER = File.join(File.dirname(__FILE__), '..')
|
7
|
+
SQL_FILE = File.join(ROOT_FOLDER, 'db', 'sqlite3.sql')
|
8
|
+
DB_FILE = File.join(ROOT_FOLDER, 'db', 'sqlite3.db')
|
9
|
+
|
10
|
+
class DBConnection
|
11
|
+
def self.open(db_file_name)
|
12
|
+
@db = SQLite3::Database.new(db_file_name)
|
13
|
+
@db.results_as_hash = true
|
14
|
+
@db.type_translation = true
|
15
|
+
|
16
|
+
@db
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reset
|
20
|
+
commands = [
|
21
|
+
"rm '#{DB_FILE}'",
|
22
|
+
"cat '#{SQL_FILE}' | sqlite3 '#{DB_FILE}'"
|
23
|
+
]
|
24
|
+
|
25
|
+
commands.each { |command| `#{command}` }
|
26
|
+
DBConnection.open(DB_FILE)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.instance
|
30
|
+
reset if @db.nil?
|
31
|
+
|
32
|
+
@db
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.execute(*args)
|
36
|
+
print_query(*args)
|
37
|
+
instance.execute(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.execute2(*args)
|
41
|
+
print_query(*args)
|
42
|
+
instance.execute2(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.last_insert_row_id
|
46
|
+
instance.last_insert_row_id
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def self.print_query(query, *interpolation_args)
|
52
|
+
return unless PRINT_QUERIES
|
53
|
+
|
54
|
+
puts '--------------------'
|
55
|
+
puts query
|
56
|
+
unless interpolation_args.empty?
|
57
|
+
puts "interpolate: #{interpolation_args.inspect}"
|
58
|
+
end
|
59
|
+
puts '--------------------'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/raamen/flash.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Raamen
|
4
|
+
class Flash
|
5
|
+
attr_reader :flash, :now
|
6
|
+
|
7
|
+
def initialize(req)
|
8
|
+
flash = req.cookies["_rails_lite_app_flash"]
|
9
|
+
@now = flash ? Now.new(JSON.parse(flash)) : Now.new({})
|
10
|
+
@flash = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
self.now[key.to_sym] || self.flash[key.to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, val)
|
18
|
+
self.flash[key.to_sym] = val
|
19
|
+
end
|
20
|
+
|
21
|
+
def store_flash(res)
|
22
|
+
res.set_cookie("_rails_lite_app_flash", {path: "/", value: self.flash.to_json})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Now
|
27
|
+
attr_reader :now
|
28
|
+
|
29
|
+
def initialize(now)
|
30
|
+
@now = Hash[now.map{ |k,v| [k.to_sym, v] }]
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](key)
|
34
|
+
self.now[key.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
def []=(key, val)
|
38
|
+
self.now[key.to_sym] = val
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Raamen
|
2
|
+
class Route
|
3
|
+
attr_reader :pattern, :http_method, :controller_class, :action_name
|
4
|
+
|
5
|
+
def initialize(pattern, http_method, controller_class, action_name)
|
6
|
+
@pattern = pattern
|
7
|
+
@http_method = http_method
|
8
|
+
@controller_class = controller_class
|
9
|
+
@action_name = action_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(req)
|
13
|
+
req.path =~ self.pattern &&
|
14
|
+
req.request_method == self.http_method.to_s.upcase
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(req, res)
|
18
|
+
match_data = self.pattern.match(req.path)
|
19
|
+
route_params = Hash[match_data.names.zip(match_data.captures)]
|
20
|
+
self.controller_class.new(req, res, route_params)
|
21
|
+
.invoke_action(self.action_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Router
|
26
|
+
attr_reader :routes
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@routes = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_route(pattern, method, controller_class, action_name)
|
33
|
+
self.routes.push(Route.new(pattern, method, controller_class, action_name))
|
34
|
+
end
|
35
|
+
|
36
|
+
def draw(&proc)
|
37
|
+
self.instance_eval(&proc)
|
38
|
+
end
|
39
|
+
|
40
|
+
[:get, :post, :put, :delete].each do |http_method|
|
41
|
+
define_method(http_method) do |pattern, controller_class, action_name|
|
42
|
+
add_route(pattern, http_method, controller_class, action_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def match(req)
|
47
|
+
self.routes.each do |route|
|
48
|
+
return route if route.matches?(req)
|
49
|
+
end
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def run(req, res)
|
54
|
+
matching_route = match(req)
|
55
|
+
unless matching_route
|
56
|
+
res.status = 404
|
57
|
+
return
|
58
|
+
end
|
59
|
+
matching_route.run(req, res)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Raamen
|
4
|
+
class Session
|
5
|
+
attr_reader :cookies
|
6
|
+
|
7
|
+
def initialize(req)
|
8
|
+
cookies = req.cookies["_rails_lite_app"]
|
9
|
+
cookies = Hash[JSON.parse(cookies).map{ |k,v| [k.to_sym, v] }] if cookies
|
10
|
+
@cookies = cookies || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
self.cookies[key.to_sym]
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, val)
|
18
|
+
self.cookies[key.to_sym] = val
|
19
|
+
end
|
20
|
+
|
21
|
+
def store_session(res)
|
22
|
+
res.set_cookie("_rails_lite_app", {path: "/", value: self.cookies.to_json})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Raamen
|
4
|
+
class ShowExceptions
|
5
|
+
attr_reader :app
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
begin
|
13
|
+
self.app.call(env)
|
14
|
+
rescue Exception => e
|
15
|
+
render_exception(e)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def render_exception(e)
|
22
|
+
template_path = File.join(
|
23
|
+
File.dirname(__FILE__),
|
24
|
+
"templates",
|
25
|
+
"rescue.html.erb")
|
26
|
+
template_content = File.read(template_path)
|
27
|
+
content = ERB.new(template_content).result(binding)
|
28
|
+
|
29
|
+
res = Rack::Response.new
|
30
|
+
res.status = 500
|
31
|
+
res["Content-Type"] = "text/html"
|
32
|
+
res.write(content)
|
33
|
+
res.finish
|
34
|
+
end
|
35
|
+
|
36
|
+
def stack_trace_top(e)
|
37
|
+
e.backtrace[0].split(':')
|
38
|
+
end
|
39
|
+
|
40
|
+
def source_line_num(e)
|
41
|
+
stack_trace_top(e)[1].to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def error_source_file(e)
|
45
|
+
stack_trace_top(e)[0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_source(file)
|
49
|
+
source_file = File.open(file, 'r')
|
50
|
+
source_file.readlines
|
51
|
+
end
|
52
|
+
|
53
|
+
def format_source(source_lines, source_line_num)
|
54
|
+
start = [0, source_line_num - 3].max
|
55
|
+
lines = source_lines[start..(start + 5)]
|
56
|
+
Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
|
57
|
+
end
|
58
|
+
|
59
|
+
def extract_formatted_source(e)
|
60
|
+
source_file_name = error_source_file(e)
|
61
|
+
source_line_num = source_line_num(e)
|
62
|
+
source_lines = extract_source(source_file_name)
|
63
|
+
format_source(source_lines, source_line_num)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require_relative 'db_connection'
|
2
|
+
require_relative 'sql_object_modules/searchable'
|
3
|
+
require_relative 'sql_object_modules/associatable'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
module Raamen
|
7
|
+
class SQLObject
|
8
|
+
extend Searchable
|
9
|
+
extend Associatable
|
10
|
+
|
11
|
+
def self.columns
|
12
|
+
@columns ||= DBConnection.execute2(<<-SQL).first.map(&:to_sym)
|
13
|
+
SELECT
|
14
|
+
*
|
15
|
+
FROM
|
16
|
+
#{self.table_name}
|
17
|
+
LIMIT
|
18
|
+
0
|
19
|
+
SQL
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.finalize!
|
23
|
+
self.columns.each do |col|
|
24
|
+
define_method(col) do
|
25
|
+
attributes[col]
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method("#{col}=") do |value|
|
29
|
+
attributes[col] = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.table_name=(table_name)
|
35
|
+
@table_name = table_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.table_name
|
39
|
+
@table_name || self.name.tableize
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.all
|
43
|
+
parse_all(results = DBConnection.execute(<<-SQL))
|
44
|
+
SELECT
|
45
|
+
*
|
46
|
+
FROM
|
47
|
+
#{self.table_name}
|
48
|
+
SQL
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.parse_all(results)
|
52
|
+
results.map do |result|
|
53
|
+
self.new(result)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.find(id)
|
58
|
+
parse_all(DBConnection.execute(<<-SQL, id)).first
|
59
|
+
SELECT
|
60
|
+
*
|
61
|
+
FROM
|
62
|
+
#{self.table_name}
|
63
|
+
WHERE
|
64
|
+
id = ?
|
65
|
+
SQL
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize(params = {})
|
69
|
+
params.each do |col, value|
|
70
|
+
col = col.to_sym
|
71
|
+
raise "unknown attribute '#{col}'" unless self.class.columns.include?(col)
|
72
|
+
self.send("#{col}=", value)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def attributes
|
77
|
+
@attributes ||= {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def attribute_values
|
81
|
+
self.class.columns.map do |col|
|
82
|
+
self.send(col)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def insert
|
87
|
+
columns = self.class.columns.drop(1)
|
88
|
+
col_names = columns.map(&:to_sym).join(", ")
|
89
|
+
question_marks = (["?"] * columns.count).join(", ")
|
90
|
+
|
91
|
+
DBConnection.execute(<<-SQL, attribute_values.drop(1))
|
92
|
+
INSERT INTO
|
93
|
+
#{self.class.table_name} (#{col_names})
|
94
|
+
VALUES
|
95
|
+
(#{question_marks})
|
96
|
+
SQL
|
97
|
+
|
98
|
+
self.id = DBConnection.last_insert_row_id
|
99
|
+
end
|
100
|
+
|
101
|
+
def update
|
102
|
+
set_line = self.class.columns.map do |col|
|
103
|
+
"#{col}= ?"
|
104
|
+
end.join(", ")
|
105
|
+
|
106
|
+
DBConnection.execute(<<-SQL, *attribute_values, id)
|
107
|
+
UPDATE
|
108
|
+
#{self.class.table_name}
|
109
|
+
SET
|
110
|
+
#{set_line}
|
111
|
+
WHERE
|
112
|
+
id = ?
|
113
|
+
SQL
|
114
|
+
end
|
115
|
+
|
116
|
+
def save
|
117
|
+
id.nil? ? insert : update
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Raamen
|
2
|
+
class AssocOptions
|
3
|
+
attr_accessor(
|
4
|
+
:foreign_key,
|
5
|
+
:class_name,
|
6
|
+
:primary_key
|
7
|
+
)
|
8
|
+
|
9
|
+
def model_class
|
10
|
+
self.class_name.constantize
|
11
|
+
end
|
12
|
+
|
13
|
+
def table_name
|
14
|
+
model_class.table_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class BelongsToOptions < AssocOptions
|
19
|
+
def initialize(name, options = {})
|
20
|
+
self.class_name = options[:class_name] || name.to_s.camelcase
|
21
|
+
self.foreign_key = options[:foreign_key] || (name.to_s + "_id").to_sym
|
22
|
+
self.primary_key = options[:primary_key] || :id
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class HasManyOptions < AssocOptions
|
27
|
+
def initialize(name, self_class_name, options = {})
|
28
|
+
self.class_name = options[:class_name] || name.to_s.singularize.camelcase
|
29
|
+
self.foreign_key = options[:foreign_key] || (self_class_name.downcase + "_id").to_sym
|
30
|
+
self.primary_key = options[:primary_key] || :id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Associatable
|
35
|
+
def belongs_to(name, options = {})
|
36
|
+
self.assoc_options[name] = BelongsToOptions.new(name, options)
|
37
|
+
|
38
|
+
define_method(name) do
|
39
|
+
options = self.class.assoc_options[name]
|
40
|
+
key_val = self.send(options.foreign_key)
|
41
|
+
options.model_class.where(options.primary_key => key_val).first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_many(name, options = {})
|
46
|
+
self.assoc_options[name] = HasManyOptions.new(name, self.name, options)
|
47
|
+
|
48
|
+
define_method(name) do
|
49
|
+
options = self.class.assoc_options[name]
|
50
|
+
key_val = self.send(options.primary_key)
|
51
|
+
options.model_class.where(options.foreign_key => key_val)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_one_through(name, through_name, source_name)
|
56
|
+
define_method(name) do
|
57
|
+
through_options = self.class.assoc_options[through_name]
|
58
|
+
source_options = through_options.model_class.assoc_options[source_name]
|
59
|
+
|
60
|
+
through_table = through_options.table_name
|
61
|
+
through_pk = through_options.primary_key
|
62
|
+
through_fk = through_options.foreign_key
|
63
|
+
|
64
|
+
source_table = source_options.table_name
|
65
|
+
source_pk = source_options.primary_key
|
66
|
+
source_fk = source_options.foreign_key
|
67
|
+
|
68
|
+
key_val = self.send(through_fk)
|
69
|
+
results = DBConnection.execute(<<-SQL, key_val)
|
70
|
+
SELECT
|
71
|
+
#{source_table}.*
|
72
|
+
FROM
|
73
|
+
#{through_table}
|
74
|
+
JOIN
|
75
|
+
#{source_table}
|
76
|
+
ON
|
77
|
+
#{through_table}.#{source_fk} = #{source_table}.#{source_pk}
|
78
|
+
WHERE
|
79
|
+
#{through_table}.#{through_pk} = ?
|
80
|
+
SQL
|
81
|
+
|
82
|
+
source_options.model_class.parse_all(results).first
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def assoc_options
|
87
|
+
@assoc_options ||= {}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Raamen
|
2
|
+
module Searchable
|
3
|
+
def where(params)
|
4
|
+
where_line = params.keys.map do |col|
|
5
|
+
"#{col}= ?"
|
6
|
+
end.join(" AND ")
|
7
|
+
|
8
|
+
parse_all(DBConnection.execute(<<-SQL, *params.values))
|
9
|
+
SELECT
|
10
|
+
*
|
11
|
+
FROM
|
12
|
+
#{self.table_name}
|
13
|
+
where
|
14
|
+
#{where_line}
|
15
|
+
SQL
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Raamen
|
2
|
+
class Static
|
3
|
+
attr_reader :app, :root, :file_server
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
@root = :public
|
8
|
+
@file_server = FileServer.new(self.root)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
req = Rack::Request.new(env)
|
13
|
+
path = req.path
|
14
|
+
|
15
|
+
if path.include?("/#{self.root}")
|
16
|
+
res = self.file_server.call(env)
|
17
|
+
else
|
18
|
+
res = self.app.call(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
res
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FileServer
|
26
|
+
MIME_TYPES = {
|
27
|
+
".txt" => "text/plain",
|
28
|
+
".jpg" => "image/jpeg",
|
29
|
+
".zip" => "application/zip"
|
30
|
+
}
|
31
|
+
|
32
|
+
def initialize(root)
|
33
|
+
@root = root
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
req = Rack::Request.new(env)
|
38
|
+
res = Rack::Response.new
|
39
|
+
file_path = File.join(
|
40
|
+
File.dirname(__FILE__),
|
41
|
+
"..",
|
42
|
+
req.path
|
43
|
+
)
|
44
|
+
|
45
|
+
if File.exist?(file_path)
|
46
|
+
extension = File.extname(file_path)
|
47
|
+
content_type = MIME_TYPES[extension]
|
48
|
+
file_content = File.read(file_path)
|
49
|
+
res["Content-Type"] = content_type
|
50
|
+
res.write(file_content)
|
51
|
+
else
|
52
|
+
res.status = 404
|
53
|
+
res.write("File not found")
|
54
|
+
end
|
55
|
+
|
56
|
+
res
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<head>
|
2
|
+
<style>
|
3
|
+
.header {
|
4
|
+
color: red;
|
5
|
+
}
|
6
|
+
|
7
|
+
.source-view {
|
8
|
+
border: 10px solid lightgray;
|
9
|
+
width: 800px;
|
10
|
+
}
|
11
|
+
|
12
|
+
.line {
|
13
|
+
white-space: pre-wrap;
|
14
|
+
}
|
15
|
+
|
16
|
+
.line-num {
|
17
|
+
}
|
18
|
+
|
19
|
+
.line.error {
|
20
|
+
color: red;
|
21
|
+
}
|
22
|
+
</style>
|
23
|
+
</head>
|
24
|
+
|
25
|
+
<body>
|
26
|
+
<h2 class='header'><%=e.class%>: <%=e.message%></h2>
|
27
|
+
|
28
|
+
<h4>Extracted source (around line <b><%=source_line_num(e)%></b>):</h4>
|
29
|
+
|
30
|
+
<div class='source-view'>
|
31
|
+
<table cellpadding="0" cellspacing="0">
|
32
|
+
<% extract_formatted_source(e).each do |line_num, line| %>
|
33
|
+
<tr>
|
34
|
+
<td>
|
35
|
+
<pre class='line-num'> <%=line_num %> </pre>
|
36
|
+
</td>
|
37
|
+
<td>
|
38
|
+
<pre class='line
|
39
|
+
<%= 'error' if line_num == source_line_num(e)%>'><%= line %></pre>
|
40
|
+
</td>
|
41
|
+
</tr>
|
42
|
+
<% end %>
|
43
|
+
</table>
|
44
|
+
</div>
|
45
|
+
<h5><%= File.expand_path(error_source_file(e)) %></h5>
|
46
|
+
|
47
|
+
<h3>Stack trace</h3>
|
48
|
+
<% e.backtrace.each do |stack_line| %>
|
49
|
+
<%= stack_line %>
|
50
|
+
<br>
|
51
|
+
<% end %>
|
52
|
+
</body>
|
data/lib/raamen/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raamen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuan Gao
|
@@ -70,6 +70,19 @@ files:
|
|
70
70
|
- bin/console
|
71
71
|
- bin/setup
|
72
72
|
- lib/raamen.rb
|
73
|
+
- lib/raamen/controller_base.rb
|
74
|
+
- lib/raamen/db/sqlite3.db
|
75
|
+
- lib/raamen/db/sqlite3.sql
|
76
|
+
- lib/raamen/db_connection.rb
|
77
|
+
- lib/raamen/flash.rb
|
78
|
+
- lib/raamen/router.rb
|
79
|
+
- lib/raamen/session.rb
|
80
|
+
- lib/raamen/show_exceptions.rb
|
81
|
+
- lib/raamen/sql_object.rb
|
82
|
+
- lib/raamen/sql_object_modules/associatable.rb
|
83
|
+
- lib/raamen/sql_object_modules/searchable.rb
|
84
|
+
- lib/raamen/static.rb
|
85
|
+
- lib/raamen/templates/rescue.html.erb
|
73
86
|
- lib/raamen/version.rb
|
74
87
|
- raamen.gemspec
|
75
88
|
homepage: https://github.com/yuangaonyc/raamen
|