raamen 0.1.0 → 0.1.1
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/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
|