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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8a824183af8e3398198698e5005affd11902c660
4
- data.tar.gz: a6fa9471d103bdfff64c07b2ba44f655fbf40f40
3
+ metadata.gz: 31023fc5e442be1d7d1f7346a40bb53355847fcb
4
+ data.tar.gz: f4fe204f88692a72324501ab003627b4c37b43af
5
5
  SHA512:
6
- metadata.gz: 26905102435808cb9c096506578f6ce226ae56f891154d90ebd007f62870a0a88460973e5f9edb9d5287511fea3a8538ab5297642299254871d840a14dc65703
7
- data.tar.gz: abfaaa5d58acb5524711a44794bdb403acfbb15485670e74ad94d3ed1e223df05aee054881e5cdbb979c8569ec63e1b8efafdcc915623494694b138d28e1f779
6
+ metadata.gz: 3b37d65b71beafcab9e9ce8e25c8b10a0f1523c7ba2441c44d2a0354a105866ba20627ec5adf918c1a592dd04c3b62bbe8a40df8d3daa85e90747dc1c685356f
7
+ data.tar.gz: 03b2e0b4b29254475fd02eeceee34237296fc71cad2a6af1cbc0bfaa7252d7af1aabc68b624d7d3bae7577b87d59eebca337295d6d9ef536e9c6075823a1a5d7
@@ -1,4 +1,9 @@
1
- require "raamen/version"
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
@@ -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>
@@ -1,3 +1,3 @@
1
1
  module Raamen
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
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.0
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