gazebo 0.1.2
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 +7 -0
- data/README.md +9 -0
- data/bin/gazebo +6 -0
- data/lib/actioncondor/actioncondor.rb +17 -0
- data/lib/actioncondor/controller_base.rb +111 -0
- data/lib/actioncondor/flash.rb +31 -0
- data/lib/actioncondor/session.rb +29 -0
- data/lib/activeleopard/activeleopard.rb +21 -0
- data/lib/activeleopard/assoc_options.rb +64 -0
- data/lib/activeleopard/db_connection.rb +136 -0
- data/lib/activeleopard/errors.rb +5 -0
- data/lib/activeleopard/modules/associatable.rb +86 -0
- data/lib/activeleopard/modules/searchable.rb +65 -0
- data/lib/activeleopard/modules/validatable.rb +25 -0
- data/lib/activeleopard/query_clauses/all_clauses.rb +7 -0
- data/lib/activeleopard/query_clauses/from_clause.rb +11 -0
- data/lib/activeleopard/query_clauses/group_clause.rb +12 -0
- data/lib/activeleopard/query_clauses/join_clause.rb +46 -0
- data/lib/activeleopard/query_clauses/limit_clause.rb +15 -0
- data/lib/activeleopard/query_clauses/order_clause.rb +12 -0
- data/lib/activeleopard/query_clauses/select_clause.rb +18 -0
- data/lib/activeleopard/query_clauses/where_clause.rb +61 -0
- data/lib/activeleopard/relation.rb +106 -0
- data/lib/activeleopard/sql_object.rb +213 -0
- data/lib/auto_loader.rb +31 -0
- data/lib/cli.rb +16 -0
- data/lib/gazebo.rb +53 -0
- data/lib/router.rb +78 -0
- data/lib/show_exceptions.rb +28 -0
- data/lib/static_asset_server.rb +57 -0
- data/lib/templates/rescue.html.erb +18 -0
- metadata +184 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
class SelectClause
|
2
|
+
attr_accessor :distinct, :params
|
3
|
+
|
4
|
+
def initialize(params = [])
|
5
|
+
@distinct = false
|
6
|
+
@params = params
|
7
|
+
end
|
8
|
+
|
9
|
+
def as_sql
|
10
|
+
"SELECT " +
|
11
|
+
"#{distinct ? 'DISTINCT ' : ''}" +
|
12
|
+
"#{params_as_sql}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def params_as_sql
|
16
|
+
params.map(&:to_s).join(', ')
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative '../errors'
|
2
|
+
|
3
|
+
class WhereClause
|
4
|
+
def initialize(options = [])
|
5
|
+
unless options.is_a?(Array) && options.length <= 2
|
6
|
+
raise InvalidInput, "Where takes 1 or 2 arguments"
|
7
|
+
end
|
8
|
+
|
9
|
+
@conditions = parse_conditions(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def values
|
13
|
+
conditions.values.flatten
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_sql
|
17
|
+
return "" if conditions.empty?
|
18
|
+
" WHERE " + conditions_as_sql
|
19
|
+
end
|
20
|
+
|
21
|
+
def append(options)
|
22
|
+
conditions.merge!(parse_conditions(options))
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :<<, :append
|
26
|
+
|
27
|
+
private
|
28
|
+
attr_reader :bind_params, :conditions
|
29
|
+
|
30
|
+
def conditions_as_sql
|
31
|
+
conditions.map do |condition, values|
|
32
|
+
if condition.is_a?(String)
|
33
|
+
format_condition(condition, values)
|
34
|
+
elsif condition.is_a?(Symbol)
|
35
|
+
"#{condition} = ?"
|
36
|
+
end
|
37
|
+
end.flatten.join(' AND ').gsub("?").with_index { |_, i| "$#{i + 1}" }
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_params(params_input)
|
41
|
+
return [] if params_input.nil?
|
42
|
+
params_input.is_a?(Array) ? params_input : [params_input]
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_conditions(input)
|
46
|
+
if input.first.is_a?(String)
|
47
|
+
{ input.first => parse_params(input[1]) }
|
48
|
+
elsif input.first.is_a?(Hash)
|
49
|
+
input.first
|
50
|
+
else
|
51
|
+
{}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_condition(condition, values)
|
56
|
+
condition.gsub('(?)') do
|
57
|
+
"(#{values.map.with_index { |_, i| "$#{i + 1}" }})"
|
58
|
+
# "(#{(['?'] * values.count).join(', ')})"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Relation
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
attr_reader :query, :cache, :source_class, :data
|
5
|
+
|
6
|
+
def defaults
|
7
|
+
{
|
8
|
+
select: SelectClause.new(["#{source_class.table_name}.*"]),
|
9
|
+
from: FromClause.new(source_class.table_name),
|
10
|
+
join: JoinOptions.new,
|
11
|
+
where: WhereClause.new,
|
12
|
+
limit: LimitClause.new,
|
13
|
+
group: GroupClause.new,
|
14
|
+
order: OrderClause.new
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.ordered_clauses
|
19
|
+
[:select, :from, :join, :where, :group, :order, :limit]
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(query, source_class)
|
23
|
+
@source_class = source_class
|
24
|
+
@query = defaults.merge(query)
|
25
|
+
@cache = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def where(*where_params)
|
29
|
+
query[:where] << where_params
|
30
|
+
empty_cache!
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def joins(association, join_class = source_class)
|
35
|
+
options = join_class.assoc_options[association]
|
36
|
+
query[:join].append(options, join_class.table_name)
|
37
|
+
empty_cache!
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def limit(n)
|
42
|
+
query[:limit].set(n)
|
43
|
+
empty_cache!
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def select(*params)
|
48
|
+
query[:select].params = params
|
49
|
+
empty_cache!
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def distinct
|
54
|
+
query[:select].distinct = true
|
55
|
+
empty_cache!
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def group(grouping_attr)
|
60
|
+
query[:group].grouping_attr = grouping_attr
|
61
|
+
empty_cache!
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def order(ordering_attr)
|
66
|
+
query[:order].ordering_attr = ordering_attr
|
67
|
+
empty_cache!
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def as_sql
|
72
|
+
Relation.ordered_clauses.map do |clause|
|
73
|
+
query[clause].as_sql
|
74
|
+
end.join(" \n ")
|
75
|
+
end
|
76
|
+
|
77
|
+
def data
|
78
|
+
execute! if cache.nil?
|
79
|
+
cache
|
80
|
+
end
|
81
|
+
|
82
|
+
def each(&prc)
|
83
|
+
to_a.each { |el| prc.call(el) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def bind_params
|
87
|
+
query[:where].values
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_a
|
91
|
+
execute! if cache.nil?
|
92
|
+
cache.map { |datum| source_class.new(datum) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def execute!
|
96
|
+
@cache = DBConnection.execute(as_sql, bind_params)
|
97
|
+
end
|
98
|
+
|
99
|
+
def inspect
|
100
|
+
p to_a
|
101
|
+
end
|
102
|
+
|
103
|
+
def empty_cache!
|
104
|
+
@cache = nil
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
class SQLObject
|
2
|
+
def self.after_initialize(method_name)
|
3
|
+
callbacks[:after_initialize] = method_name
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.all
|
7
|
+
Relation.new({}, self)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.callbacks
|
11
|
+
@callbacks ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.columns
|
15
|
+
return @columns if @columns
|
16
|
+
|
17
|
+
cols = DBConnection.execute(<<-SQL, [self.table_name])
|
18
|
+
SELECT
|
19
|
+
column_name
|
20
|
+
FROM
|
21
|
+
information_schema.columns
|
22
|
+
WHERE
|
23
|
+
table_name = $1
|
24
|
+
SQL
|
25
|
+
|
26
|
+
@columns = cols.map { |c| c['column_name'].to_sym }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.destroy_all
|
30
|
+
DBConnection.execute(<<-SQL)
|
31
|
+
DELETE FROM #{self.table_name}
|
32
|
+
SQL
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.finalize!
|
36
|
+
self.columns.each do |col|
|
37
|
+
define_method(col) do #setter method
|
38
|
+
attributes[col]
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method("#{col}=") do |val| #getter method
|
42
|
+
attributes[col] = val
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.first
|
48
|
+
first_data = DBConnection.get_first_row(<<-SQL)
|
49
|
+
SELECT
|
50
|
+
*
|
51
|
+
FROM
|
52
|
+
#{self.table_name}
|
53
|
+
ORDER BY
|
54
|
+
id
|
55
|
+
LIMIT
|
56
|
+
1
|
57
|
+
SQL
|
58
|
+
|
59
|
+
self.new(first_data)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.last
|
63
|
+
last_data = DBConnection.get_first_row(<<-SQL)
|
64
|
+
SELECT
|
65
|
+
*
|
66
|
+
FROM
|
67
|
+
#{self.table_name}
|
68
|
+
ORDER BY
|
69
|
+
id DESC
|
70
|
+
LIMIT
|
71
|
+
1
|
72
|
+
SQL
|
73
|
+
|
74
|
+
self.new(last_data)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.parse_all(all_options)
|
78
|
+
all_options.map { |options| self.new(options) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.table_name=(table_name)
|
82
|
+
@table_name = table_name
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.table_name
|
86
|
+
@table_name ||= self.to_s.tableize.gsub('humen') { 'humans' }
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.validations
|
90
|
+
@validations ||= []
|
91
|
+
end
|
92
|
+
|
93
|
+
def attributes
|
94
|
+
@attributes ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def attr_count
|
98
|
+
@attributes.count
|
99
|
+
end
|
100
|
+
|
101
|
+
def attribute_values
|
102
|
+
attributes.values
|
103
|
+
end
|
104
|
+
|
105
|
+
def attr_values_to_update
|
106
|
+
attributes.reject { |attr_name, _| attr_name == :id }.values
|
107
|
+
end
|
108
|
+
|
109
|
+
def col_names
|
110
|
+
self.class.columns
|
111
|
+
.map(&:to_s)
|
112
|
+
.drop(1).join(', ')
|
113
|
+
end
|
114
|
+
|
115
|
+
def destroy
|
116
|
+
DBConnection.execute(<<-SQL, [self.id])
|
117
|
+
DELETE FROM #{self.class.table_name}
|
118
|
+
WHERE id = $1
|
119
|
+
SQL
|
120
|
+
end
|
121
|
+
|
122
|
+
def errors
|
123
|
+
@errors ||= Hash.new { |h, k| h[k] = [] }
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize(params = {})
|
127
|
+
self.class.columns.each do |attr_name|
|
128
|
+
params_val = params[attr_name] || params[attr_name.to_s]
|
129
|
+
params_val.strip! if params_val
|
130
|
+
send("#{attr_name}=", params_val)
|
131
|
+
end
|
132
|
+
|
133
|
+
params.each do |attr_name, val|
|
134
|
+
attr_method = "#{attr_name}="
|
135
|
+
next if self.class.columns.include?(attr_name.to_sym)
|
136
|
+
next unless self.respond_to?(attr_method)
|
137
|
+
|
138
|
+
val.strip! if val
|
139
|
+
send(attr_method, val)
|
140
|
+
end
|
141
|
+
|
142
|
+
if self.class.callbacks[:after_initialize]
|
143
|
+
send(self.class.callbacks[:after_initialize])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def insert
|
148
|
+
result = DBConnection.execute(<<-SQL, attr_values_to_update)
|
149
|
+
INSERT INTO
|
150
|
+
#{self.class.table_name} (#{col_names})
|
151
|
+
VALUES
|
152
|
+
(#{question_marks})
|
153
|
+
RETURNING
|
154
|
+
id
|
155
|
+
SQL
|
156
|
+
|
157
|
+
self.id = result.getvalue(0,0)
|
158
|
+
end
|
159
|
+
|
160
|
+
def question_marks
|
161
|
+
(1...attributes.count).map { |n| "$#{n}"}.join(', ')
|
162
|
+
end
|
163
|
+
|
164
|
+
def save
|
165
|
+
validate!
|
166
|
+
if valid?
|
167
|
+
id ? update : insert
|
168
|
+
true
|
169
|
+
else
|
170
|
+
show_errors
|
171
|
+
false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def show_errors
|
176
|
+
errors.each do |key, messages|
|
177
|
+
messages.each { |m| puts "#{key} #{m}" }
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_s
|
182
|
+
"#{self.class}:#{self.object_id}"
|
183
|
+
end
|
184
|
+
|
185
|
+
def update_set_line
|
186
|
+
col_names.split(', ').map.with_index { |c, i| "#{c} = $#{i + 1}" }.join(', ')
|
187
|
+
end
|
188
|
+
|
189
|
+
def update
|
190
|
+
vals = attr_values_to_update << id
|
191
|
+
DBConnection.execute(<<-SQL, vals)
|
192
|
+
UPDATE
|
193
|
+
#{self.class.table_name}
|
194
|
+
SET
|
195
|
+
#{update_set_line}
|
196
|
+
WHERE
|
197
|
+
id = $#{vals.length}
|
198
|
+
SQL
|
199
|
+
end
|
200
|
+
|
201
|
+
def validate!
|
202
|
+
@errors = Hash.new { |h, k| h[k] = [] }
|
203
|
+
|
204
|
+
self.class.validations.each do |validation|
|
205
|
+
self.send(validation)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def valid?
|
210
|
+
validate!
|
211
|
+
errors.all? { |_, v| v.empty? }
|
212
|
+
end
|
213
|
+
end
|
data/lib/auto_loader.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Gazebo
|
2
|
+
LOAD_PATHS = [
|
3
|
+
"app/models",
|
4
|
+
"app/controllers"
|
5
|
+
]
|
6
|
+
end
|
7
|
+
|
8
|
+
class Object
|
9
|
+
def self.const_missing(const)
|
10
|
+
auto_load(const)
|
11
|
+
Kernel.const_get(const)
|
12
|
+
end
|
13
|
+
|
14
|
+
def auto_load(const)
|
15
|
+
Gazebo::LOAD_PATHS.each do |dir|
|
16
|
+
file = File.join(Gazebo::ROOT, dir, const.to_s.underscore)
|
17
|
+
|
18
|
+
begin
|
19
|
+
require_relative(file)
|
20
|
+
return
|
21
|
+
rescue LoadError => e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Module
|
28
|
+
def const_missing(const)
|
29
|
+
Object.const_missing(const)
|
30
|
+
end
|
31
|
+
end
|
data/lib/cli.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Gazebo
|
4
|
+
class DatabaseTasks < Thor
|
5
|
+
desc "migrate", "run any previously unexecuted migrations"
|
6
|
+
def migrate
|
7
|
+
DBConnection.run_migrations
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "seed", "seed database"
|
11
|
+
def seed
|
12
|
+
Gazebo.seed
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/gazebo.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
require 'rack'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require_relative 'activeleopard/activeleopard'
|
6
|
+
require_relative 'actioncondor/actioncondor'
|
7
|
+
require_relative 'static_asset_server'
|
8
|
+
require_relative 'show_exceptions'
|
9
|
+
require_relative 'auto_loader'
|
10
|
+
require_relative 'router'
|
11
|
+
require_relative 'cli'
|
12
|
+
|
13
|
+
module Gazebo
|
14
|
+
Router = Router.new
|
15
|
+
VERSION = "0.1.2"
|
16
|
+
|
17
|
+
def self.app
|
18
|
+
fetch_routes
|
19
|
+
|
20
|
+
app = Proc.new do |env|
|
21
|
+
req = Rack::Request.new(env)
|
22
|
+
res = Rack::Response.new
|
23
|
+
Gazebo::Router.run(req, res)
|
24
|
+
res
|
25
|
+
end
|
26
|
+
|
27
|
+
app = Rack::Builder.new do
|
28
|
+
use ShowExceptions
|
29
|
+
use StaticAssetServer
|
30
|
+
run app
|
31
|
+
end.to_app
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.root=(root)
|
35
|
+
const_set("ROOT", root)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.fetch_routes
|
39
|
+
file = File.join(ROOT, "config/routes.rb")
|
40
|
+
|
41
|
+
File.open(file) do |f|
|
42
|
+
self.class_eval(f.read)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.seed
|
47
|
+
file = File.join(ROOT, "db/seeds.rb")
|
48
|
+
|
49
|
+
File.open(file) do |f|
|
50
|
+
self.class_eval(f.read)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/router.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
class Route
|
2
|
+
attr_reader :pattern, :http_method, :controller_class, :action_name
|
3
|
+
|
4
|
+
def initialize(pattern, http_method, controller_class, action_name)
|
5
|
+
@pattern = pattern
|
6
|
+
@http_method = http_method
|
7
|
+
@controller_class = controller_class
|
8
|
+
@action_name = action_name
|
9
|
+
end
|
10
|
+
|
11
|
+
# checks if pattern matches path and method matches request method
|
12
|
+
def matches?(req)
|
13
|
+
return false if (pattern =~ req.path).nil?
|
14
|
+
|
15
|
+
# check if form is providing non-get/post method
|
16
|
+
req_method = req.params["_method"] || req.request_method
|
17
|
+
|
18
|
+
req_method.upcase == http_method.to_s.upcase
|
19
|
+
end
|
20
|
+
|
21
|
+
# use pattern to pull out route params (save for later?)
|
22
|
+
# instantiate controller and call controller action
|
23
|
+
def run(req, res)
|
24
|
+
matched_params = pattern.match(req.path)
|
25
|
+
|
26
|
+
params = {}
|
27
|
+
matched_params.names.each do |param_key|
|
28
|
+
params[param_key] = matched_params[param_key]
|
29
|
+
end
|
30
|
+
|
31
|
+
controller_class.new(req, res, params).invoke_action(action_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Router
|
36
|
+
attr_reader :routes
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@routes = []
|
40
|
+
end
|
41
|
+
|
42
|
+
# simply adds a new route to the list of routes
|
43
|
+
def add_route(pattern, method, controller_class, action_name)
|
44
|
+
route = Route.new(pattern, method, controller_class, action_name)
|
45
|
+
@routes << route
|
46
|
+
end
|
47
|
+
|
48
|
+
# evaluate the proc in the context of the instance
|
49
|
+
# for syntactic sugar :)
|
50
|
+
def draw(&proc)
|
51
|
+
self.instance_eval(&proc)
|
52
|
+
end
|
53
|
+
|
54
|
+
# make each of these methods that
|
55
|
+
# when called add route
|
56
|
+
[:get, :post, :put, :delete].each do |http_method|
|
57
|
+
define_method(http_method) do |pattern, controller_class, action_name|
|
58
|
+
add_route(pattern, http_method, controller_class, action_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# should return the route that matches this request
|
63
|
+
def match(req)
|
64
|
+
routes.find { |route| route.matches?(req) }
|
65
|
+
end
|
66
|
+
|
67
|
+
# either throw 404 or call run on a matched route
|
68
|
+
def run(req, res)
|
69
|
+
matched_route = match(req)
|
70
|
+
|
71
|
+
if matched_route
|
72
|
+
res.status = 200
|
73
|
+
matched_route.run(req, res)
|
74
|
+
else
|
75
|
+
res.status = 404
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class ShowExceptions
|
2
|
+
attr_reader :app, :error
|
3
|
+
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
begin
|
10
|
+
@app.call(env)
|
11
|
+
rescue StandardError => e
|
12
|
+
render_exception(e)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def render_exception(e)
|
19
|
+
res = Rack::Response.new
|
20
|
+
file_content = File.read("#{File.dirname(__FILE__)}/templates/rescue.html.erb")
|
21
|
+
content = ERB.new(file_content).result(binding)
|
22
|
+
|
23
|
+
res['Content-Type'] = 'text/html'
|
24
|
+
res.status = 500
|
25
|
+
res.write(content)
|
26
|
+
res.finish
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class StaticAssetServer
|
2
|
+
attr_reader :file_server, :app, :root
|
3
|
+
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
@root = 'app/assets/'
|
7
|
+
@file_server = FileServer.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
req = Rack::Request.new(env)
|
12
|
+
path = req.path
|
13
|
+
|
14
|
+
if servable?(path)
|
15
|
+
res = file_server.call(env)
|
16
|
+
else
|
17
|
+
res = app.call(env)
|
18
|
+
end
|
19
|
+
res.finish
|
20
|
+
end
|
21
|
+
|
22
|
+
def servable?(path)
|
23
|
+
path.match("#{root}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class FileServer
|
28
|
+
def call(env)
|
29
|
+
res = Rack::Response.new
|
30
|
+
file_name = requested_file_name(env)
|
31
|
+
|
32
|
+
if File.exists?(file_name)
|
33
|
+
serve(file_name, res)
|
34
|
+
else
|
35
|
+
res.status = 404
|
36
|
+
res.write("File not found")
|
37
|
+
end
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
def serve(file_name, res)
|
42
|
+
extension = File.extname(file_name)
|
43
|
+
extension = '.json' if extension == '.map'
|
44
|
+
content_type = Rack::Mime::MIME_TYPES[extension]
|
45
|
+
|
46
|
+
res['Content-Type'] = content_type
|
47
|
+
file = File.read(file_name)
|
48
|
+
res.write(file)
|
49
|
+
end
|
50
|
+
|
51
|
+
def requested_file_name(env)
|
52
|
+
req = Rack::Request.new(env)
|
53
|
+
path = req.path
|
54
|
+
dir = File.dirname(__FILE__)
|
55
|
+
File.join(dir, '..', path)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<title></title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1><%= e.class %></h1>
|
9
|
+
<h2>Message: <%= e.message %></h2>
|
10
|
+
|
11
|
+
<h3>Backtrace:</h3>
|
12
|
+
<ul>
|
13
|
+
<% e.backtrace.take(10).each do |trace| %>
|
14
|
+
<li><%= trace %></li>
|
15
|
+
<% end %>
|
16
|
+
</ul>
|
17
|
+
</body>
|
18
|
+
</html>
|