waxx 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
- checksums.yaml.gz.sig +0 -0
- data/LICENSE +201 -0
- data/README.md +879 -0
- data/bin/waxx +120 -0
- data/lib/waxx/app.rb +173 -0
- data/lib/waxx/conf.rb +54 -0
- data/lib/waxx/console.rb +204 -0
- data/lib/waxx/csrf.rb +14 -0
- data/lib/waxx/database.rb +80 -0
- data/lib/waxx/encrypt.rb +38 -0
- data/lib/waxx/error.rb +60 -0
- data/lib/waxx/html.rb +33 -0
- data/lib/waxx/http.rb +268 -0
- data/lib/waxx/init.rb +273 -0
- data/lib/waxx/irb.rb +44 -0
- data/lib/waxx/irb_env.rb +18 -0
- data/lib/waxx/json.rb +23 -0
- data/lib/waxx/mongodb.rb +221 -0
- data/lib/waxx/mysql2.rb +234 -0
- data/lib/waxx/object.rb +115 -0
- data/lib/waxx/patch.rb +138 -0
- data/lib/waxx/pdf.rb +69 -0
- data/lib/waxx/pg.rb +246 -0
- data/lib/waxx/process.rb +270 -0
- data/lib/waxx/req.rb +116 -0
- data/lib/waxx/res.rb +98 -0
- data/lib/waxx/server.rb +304 -0
- data/lib/waxx/sqlite3.rb +237 -0
- data/lib/waxx/supervisor.rb +47 -0
- data/lib/waxx/test.rb +162 -0
- data/lib/waxx/util.rb +57 -0
- data/lib/waxx/version.rb +3 -0
- data/lib/waxx/view.rb +389 -0
- data/lib/waxx/waxx.rb +73 -0
- data/lib/waxx/x.rb +103 -0
- data/lib/waxx.rb +50 -0
- data/skel/README.md +11 -0
- data/skel/app/app/app.rb +39 -0
- data/skel/app/app/error/app_error.rb +16 -0
- data/skel/app/app/error/dhtml.rb +9 -0
- data/skel/app/app/error/html.rb +8 -0
- data/skel/app/app/error/json.rb +8 -0
- data/skel/app/app/error/pdf.rb +13 -0
- data/skel/app/app/log/app_log.rb +13 -0
- data/skel/app/app.rb +20 -0
- data/skel/app/home/home.rb +16 -0
- data/skel/app/home/html.rb +145 -0
- data/skel/app/html.rb +192 -0
- data/skel/app/usr/email.rb +66 -0
- data/skel/app/usr/html.rb +115 -0
- data/skel/app/usr/list.rb +51 -0
- data/skel/app/usr/password.rb +54 -0
- data/skel/app/usr/record.rb +98 -0
- data/skel/app/usr/usr.js +67 -0
- data/skel/app/usr/usr.rb +277 -0
- data/skel/app/waxx/waxx.rb +109 -0
- data/skel/bin/README.md +1 -0
- data/skel/db/README.md +11 -0
- data/skel/db/app/0-init.sql +88 -0
- data/skel/lib/README.md +1 -0
- data/skel/log/README.md +1 -0
- data/skel/opt/dev/config.yaml +1 -0
- data/skel/opt/prod/config.yaml +1 -0
- data/skel/opt/stage/config.yaml +1 -0
- data/skel/opt/test/config.yaml +1 -0
- data/skel/private/README.md +1 -0
- data/skel/public/lib/site.css +202 -0
- data/skel/public/lib/waxx/w.ico +0 -0
- data/skel/public/lib/waxx/w.png +0 -0
- data/skel/public/lib/waxx/waxx.js +111 -0
- data/skel/tmp/pids/README.md +1 -0
- data.tar.gz.sig +0 -0
- metadata +140 -0
- metadata.gz.sig +3 -0
data/lib/waxx/test.rb
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
|
|
2
|
+
# Released under the Apache Version 2 License. See LICENSE.txt.
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# The test framework for waxx
|
|
6
|
+
#
|
|
7
|
+
# ## Usage:
|
|
8
|
+
#
|
|
9
|
+
#
|
|
10
|
+
# ```
|
|
11
|
+
# module Waxx
|
|
12
|
+
# def test_waxx_app
|
|
13
|
+
# # Setup x vars for different situations
|
|
14
|
+
# Waxx::App.init
|
|
15
|
+
# x_guest = Waxx::Test.x_nonuser
|
|
16
|
+
# x_admin = Waxx::Test.x_user
|
|
17
|
+
# x_admin.usr['grp'] = ["admin"]
|
|
18
|
+
# handler = {
|
|
19
|
+
# test: {
|
|
20
|
+
# desc: "A test handler",
|
|
21
|
+
# get: -> (x, n){
|
|
22
|
+
# "get #{n}"
|
|
23
|
+
# }
|
|
24
|
+
# }
|
|
25
|
+
# }
|
|
26
|
+
#
|
|
27
|
+
# Waxx::Test.test(Waxx::App, # Module to test
|
|
28
|
+
#
|
|
29
|
+
# "access?" => { # Method to test.
|
|
30
|
+
# # Each item below is a "test-name": [result value,
|
|
31
|
+
# # expect: extepected-value,
|
|
32
|
+
# # run: Proc to run that returns true on success,
|
|
33
|
+
# # args: args to pass to the module method
|
|
34
|
+
# # ]
|
|
35
|
+
# "nil" => [Waxx::App.access?(x_guest), expect: true],
|
|
36
|
+
# "asterisk" => [Waxx::App.access?(x_guest, acl:"*"), expect: true],
|
|
37
|
+
# "all" => [Waxx::App.access?(x_guest, acl:"all"), expect: true],
|
|
38
|
+
# "any" => [Waxx::App.access?(x_guest, acl:"any"), expect: true],
|
|
39
|
+
# "user" => [Waxx::App.access?(x_guest, acl:"user"), expect: false],
|
|
40
|
+
# "non-user-admin" => [Waxx::App.access?(x_guest, acl:"admin"), expect: false],
|
|
41
|
+
# "non-user-array-of-admin-other" => [Waxx::App.access?(x_guest, acl:%w(admin other)), expect: false],
|
|
42
|
+
# "proc-true" => [Waxx::App.access?(x_guest, acl: ->(x){true}), expect: true],
|
|
43
|
+
# "proc-false" => [Waxx::App.access?(x_guest, acl: ->(x){false}), expect: false],
|
|
44
|
+
# "admin-admin" => [Waxx::App.access?(x_admin, acl:"admin"), expect: true],
|
|
45
|
+
# "admin-array-of-admin-other" => [Waxx::App.access?(x_admin, acl:%w(admin other)), expect: true],
|
|
46
|
+
# },
|
|
47
|
+
#
|
|
48
|
+
# "not_found" => {
|
|
49
|
+
# "message" => [Waxx::App.not_found(x_guest, message:"not found"), run: -> (x) {x.res == "not found"}, args: [x_guest]]
|
|
50
|
+
# },
|
|
51
|
+
#
|
|
52
|
+
# "runs" => {
|
|
53
|
+
# "with-opts" => [(Waxx::App.init; Waxx::App[:test_app_handler] = handler), expect: handler],
|
|
54
|
+
# "with-name" => [Waxx::App[:test_app_handler], expect: handler],
|
|
55
|
+
# "run" => [Waxx::App.run(x_guest, :test_app_handler, :test, :get, [1]), expect: "get 1"],
|
|
56
|
+
# }
|
|
57
|
+
#
|
|
58
|
+
# )
|
|
59
|
+
# end
|
|
60
|
+
# ```
|
|
61
|
+
module Waxx::Test
|
|
62
|
+
extend self
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Setup and process a test on a module method
|
|
66
|
+
def test(module_name, methods={})
|
|
67
|
+
mod = module_name.to_s
|
|
68
|
+
re = {mod => {}}
|
|
69
|
+
methods.each{|meth, tests|
|
|
70
|
+
re[mod][meth] = {}
|
|
71
|
+
passed = 0
|
|
72
|
+
tests.each{|test_name, args|
|
|
73
|
+
begin
|
|
74
|
+
re[mod][meth][test_name] = run_test(*args)
|
|
75
|
+
passed += 1 if re[mod][meth][test_name]['status'] == 'pass'
|
|
76
|
+
rescue => test_error
|
|
77
|
+
re[mod][meth][test_name] = {
|
|
78
|
+
"status" => "fail",
|
|
79
|
+
"error" => {
|
|
80
|
+
"got" => "ERROR",
|
|
81
|
+
"message" => test_error.to_s,
|
|
82
|
+
"backtrace" => test_error.backtrace
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
}
|
|
87
|
+
re[mod][meth]['tests'] = tests.size
|
|
88
|
+
re[mod][meth]['tests_passed'] = passed
|
|
89
|
+
re[mod][meth]['test_performance'] = "#{((passed.to_f / tests.size) * 100).to_i}%"
|
|
90
|
+
}
|
|
91
|
+
re
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# Run the test
|
|
96
|
+
def run_test(got, expect:nil, run: nil, args:[])
|
|
97
|
+
if run
|
|
98
|
+
expect = true
|
|
99
|
+
got = run.call(*args)
|
|
100
|
+
end
|
|
101
|
+
if got == expect
|
|
102
|
+
{"status" => "pass"}
|
|
103
|
+
else
|
|
104
|
+
{"status" => "fail",
|
|
105
|
+
"error" => {
|
|
106
|
+
"got" => got,
|
|
107
|
+
"expect" => expect
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A mock request skeleton. Create and then edit with test-specific attrs
|
|
114
|
+
def mock_req
|
|
115
|
+
Waxx::Req.new(ENV, {}, 'GET', "/test/test/1", {}, {}, {}, Time.new).freeze
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# A mock request with default values
|
|
119
|
+
def mock_res(req)
|
|
120
|
+
Waxx::Res.new("", 200, Waxx::Server.default_response_headers(req, "txt"), [], [], [])
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A logged-in user
|
|
124
|
+
def x_user(req=mock_req, res=nil, db=nil)
|
|
125
|
+
res ||= mock_res(req)
|
|
126
|
+
#x = Waxx::X.new(req, res, usr, ua, db, meth.downcase.to_sym, app, act, oid, args, ext, jobs).freeze
|
|
127
|
+
Waxx::X.new(
|
|
128
|
+
req,
|
|
129
|
+
res,
|
|
130
|
+
{'id' => 1, 'grp'=>['user']},
|
|
131
|
+
{'id' => 1},
|
|
132
|
+
db,
|
|
133
|
+
:get,
|
|
134
|
+
:test,
|
|
135
|
+
:test,
|
|
136
|
+
1,
|
|
137
|
+
[1],
|
|
138
|
+
"json",
|
|
139
|
+
[]
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# A non-user/public/guest
|
|
144
|
+
def x_nonuser(req=mock_req, res=nil, db=nil)
|
|
145
|
+
res ||= mock_res(req)
|
|
146
|
+
Waxx::X.new(
|
|
147
|
+
req,
|
|
148
|
+
res,
|
|
149
|
+
{'grp'=>[]},
|
|
150
|
+
{},
|
|
151
|
+
db,
|
|
152
|
+
:get,
|
|
153
|
+
:test,
|
|
154
|
+
:test,
|
|
155
|
+
1,
|
|
156
|
+
[1],
|
|
157
|
+
"json",
|
|
158
|
+
[]
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
data/lib/waxx/util.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
|
|
2
|
+
# Released under the Apache Version 2 License. See LICENSE.txt.
|
|
3
|
+
|
|
4
|
+
module Waxx::Util
|
|
5
|
+
extend self
|
|
6
|
+
|
|
7
|
+
def camel_case(str)
|
|
8
|
+
title_case(str.to_s.gsub(/[\._-]/,' ')).gsub(' ','')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
alias camelize camel_case
|
|
12
|
+
|
|
13
|
+
def label(str)
|
|
14
|
+
title_case(humanize(str.to_s))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def humanize(str)
|
|
18
|
+
underscore(str.to_s).gsub('_',' ').capitalize
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def title_case(str)
|
|
22
|
+
str.to_s.split(' ').map{|t| t.capitalize}.join(' ')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias titleize title_case
|
|
26
|
+
|
|
27
|
+
def underscore(str)
|
|
28
|
+
str.to_s.gsub(/::/, '_')
|
|
29
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
|
30
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
|
31
|
+
.tr("-", "_").tr(" ","_")
|
|
32
|
+
.downcase
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def app_from_class(cls)
|
|
36
|
+
cls.to_s.split("::")[1]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def table_from_class(cls)
|
|
40
|
+
underscore(app_from_class(cls))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def class_from_table(tbl)
|
|
44
|
+
tbl.to_s.split("_").map{|i| i.capitalize}.join
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_const(base_class, *names)
|
|
48
|
+
names.inject(base_class){|c, n|
|
|
49
|
+
c.const_get(n.to_s.split("_").map{|i| i.capitalize}.join)
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def qs(str)
|
|
54
|
+
Waxx::Http.qs(str)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
data/lib/waxx/version.rb
ADDED
data/lib/waxx/view.rb
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com> All rights reserved.
|
|
2
|
+
# Released under the Apache Version 2 License. See LICENSE.txt.
|
|
3
|
+
|
|
4
|
+
##
|
|
5
|
+
# A Waxx::View is like a database view.
|
|
6
|
+
# You define the primary object and related tables and fields on the view and it handles some routine processes for you.
|
|
7
|
+
# You can also just use it as a container for specialized business logic or complicated queries.
|
|
8
|
+
#
|
|
9
|
+
# Example usage:
|
|
10
|
+
#
|
|
11
|
+
# ```
|
|
12
|
+
# module App::Usr::List
|
|
13
|
+
# extend Waxx::View
|
|
14
|
+
# extend self
|
|
15
|
+
#
|
|
16
|
+
# # Define what layouts to allow
|
|
17
|
+
# as :json
|
|
18
|
+
#
|
|
19
|
+
# # Define what fields are in the view
|
|
20
|
+
# has(
|
|
21
|
+
# # Fields that are in the 'usr' table
|
|
22
|
+
# :id,
|
|
23
|
+
# :usr_name,
|
|
24
|
+
# :last_login_date,
|
|
25
|
+
# :last_login_host,
|
|
26
|
+
# :failed_login_count,
|
|
27
|
+
# # Fields that are in the 'person' table. Relationships are defined in App::Usr.has(...)
|
|
28
|
+
# "person_id: person.id", # This column is accessible as 'person_id' on this view
|
|
29
|
+
# "person.first_name",
|
|
30
|
+
# "person.last_name",
|
|
31
|
+
# )
|
|
32
|
+
# end
|
|
33
|
+
# ```
|
|
34
|
+
#
|
|
35
|
+
# This view definition will provide you with the following functionality:
|
|
36
|
+
#
|
|
37
|
+
# ```
|
|
38
|
+
# App::Usr::List.get(x)
|
|
39
|
+
# # Executes the following SQL:
|
|
40
|
+
# SELECT usr.id, usr.usr_name, usr.last_login_date, usr.last_login_host, usr.failed_login_count,
|
|
41
|
+
# person.id AS person_id, person.first_name, person.last_name
|
|
42
|
+
# FROM usr LEFT JOIN person ON usr.id = person.id
|
|
43
|
+
# # And returns a PG::Result (If you are using the PG database connector)
|
|
44
|
+
# ```
|
|
45
|
+
module Waxx::View
|
|
46
|
+
|
|
47
|
+
# The parent (primary) object. For example in App::Usr::List, App::Usr is the @object
|
|
48
|
+
attr :object
|
|
49
|
+
# The table name of the primary object
|
|
50
|
+
attr :table
|
|
51
|
+
# A hash of columns (See Waxx::Pg.has)
|
|
52
|
+
attr :columns
|
|
53
|
+
# A hash of name: join_sql. Normally set automatically when the columns are parsed.
|
|
54
|
+
attr :joins
|
|
55
|
+
# A hash of related tables. Normally set automatically when the columns are parsed.
|
|
56
|
+
attr :relations
|
|
57
|
+
# How to search the view by specific field
|
|
58
|
+
attr :matches
|
|
59
|
+
# How to search the view by the "q" parameter
|
|
60
|
+
attr :searches
|
|
61
|
+
# The default order of the results
|
|
62
|
+
attr :order_by
|
|
63
|
+
# A hash of how you can sort this view
|
|
64
|
+
attr :orders
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Initialize a view. This is normally done automatically when calling `has`.
|
|
68
|
+
#
|
|
69
|
+
# Call init if the table, object, or layouts are non-standard. You can also set the attrs directly like `@table = 'usr'`
|
|
70
|
+
#
|
|
71
|
+
# ```
|
|
72
|
+
# tbl: The name of the table
|
|
73
|
+
# cols: Same as has
|
|
74
|
+
# layouts: The layouts to auto-generate using waxx defaults: json, csv, tab, etc.
|
|
75
|
+
# ```
|
|
76
|
+
def init(tbl: nil, cols: nil, layouts: nil)
|
|
77
|
+
@table = (tbl || App.table_from_class(name)).to_sym
|
|
78
|
+
@object = App.get_const(App, @table)
|
|
79
|
+
@relations = {}
|
|
80
|
+
@orders = {}
|
|
81
|
+
has(*cols) if cols
|
|
82
|
+
as(layouts) if layouts
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Get a column on the view
|
|
87
|
+
#
|
|
88
|
+
# ```
|
|
89
|
+
# App::Usr::Record[:usr_name] => {:type=>"character", :label=>"User Name", :table=>:usr, :column=>:usr_name, :views=>[App::Usr::Record, App::Usr::List, App::Usr::Signup]}
|
|
90
|
+
# ```
|
|
91
|
+
def [](c)
|
|
92
|
+
@columns[c.to_sym]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# Columnas on a view can be defined in multiple ways:
|
|
97
|
+
#
|
|
98
|
+
# ```
|
|
99
|
+
# has(
|
|
100
|
+
# :id, # A field in the parent object
|
|
101
|
+
# :name, # Another field in the parent object
|
|
102
|
+
# "company_name:company.name", # name:rel_name.col_name "name" is the name of the col in the query, rel_name is the join table as defined in object, col_name is the column in the foreign table
|
|
103
|
+
# [:creator, {table: "person", sql_select: "first_name || ' ' || last_name", label: "Creator"}] # Array: [name, column (Hash)]
|
|
104
|
+
# {modifier: {table: "person", sql_select: "first_name || ' ' || last_name", label: "Creator"}}
|
|
105
|
+
# ```
|
|
106
|
+
#
|
|
107
|
+
def has(*cols)
|
|
108
|
+
return @columns if cols.empty?
|
|
109
|
+
init if @object.nil?
|
|
110
|
+
#@joins = {}
|
|
111
|
+
@columns = {}
|
|
112
|
+
cols.each{|c|
|
|
113
|
+
n = col = nil
|
|
114
|
+
case c
|
|
115
|
+
# Get the col from the object
|
|
116
|
+
when Symbol
|
|
117
|
+
n = c
|
|
118
|
+
col = @object[c]
|
|
119
|
+
# A related col (must be defined in the related object)
|
|
120
|
+
when String
|
|
121
|
+
n, col = string_to_col(c)
|
|
122
|
+
# A custom col [name, col] col is a Hash
|
|
123
|
+
when Array
|
|
124
|
+
n, col = c
|
|
125
|
+
# A custom col {name: col}, col is a Hash
|
|
126
|
+
when Hash
|
|
127
|
+
n, col = c.to_a[0]
|
|
128
|
+
end
|
|
129
|
+
if col.nil?
|
|
130
|
+
Waxx.debug "Column #{c} not defined in #{@object}."
|
|
131
|
+
#raise "Column #{c} not defined in #{@object}."
|
|
132
|
+
end
|
|
133
|
+
#Waxx.debug @relations.inspect
|
|
134
|
+
#TODO: Deal with relations that have different names than the tables
|
|
135
|
+
col[:views] << self rescue col[:views] = [self]
|
|
136
|
+
@columns[n.to_sym] = col
|
|
137
|
+
}
|
|
138
|
+
@joins ||= Hash[@relations.map{|n, r| [n, %(#{r/:join} JOIN #{r/:foreign_table} AS #{n} ON #{r/:table}.#{r/:col} = #{n}.#{r/:foreign_col})] }]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# Column defined as a string in the format: name:foreign_table.foreign_col
|
|
143
|
+
# Converted to SQL: foreign_table.foreign_col AS name
|
|
144
|
+
# Also adds entries in the @relations hash. @relations drive the SQL join statement
|
|
145
|
+
# Joins are defined in the primary object of this view.
|
|
146
|
+
def string_to_col(str)
|
|
147
|
+
n, rel_name, col_name = parse_col(str)
|
|
148
|
+
# Look in the primary and related objects for relations
|
|
149
|
+
j = @object.joins/rel_name || @relations.values.map{|foreign_rel|
|
|
150
|
+
#Waxx.debug "REL: #{foreign_rel}"
|
|
151
|
+
o = App.get_const(App, foreign_rel/:foreign_table)
|
|
152
|
+
#Waxx.debug o
|
|
153
|
+
#Waxx.debug o.joins.inspect
|
|
154
|
+
o.joins/rel_name
|
|
155
|
+
}.compact.first
|
|
156
|
+
#Waxx.debug "j:#{j.inspect}, n: #{n}, rel: #{rel_name}, col: #{col_name}"
|
|
157
|
+
begin
|
|
158
|
+
col = (App.get_const(App, j/:foreign_table)/col_name).dup
|
|
159
|
+
col[:table] = rel_name
|
|
160
|
+
rescue NoMethodError => e
|
|
161
|
+
Waxx.debug "ERROR: NoMethodError: #{rel_name} does not define col: #{col_name}"
|
|
162
|
+
raise e
|
|
163
|
+
rescue NameError, TypeError => e
|
|
164
|
+
Waxx.debug "ERROR: Name or Type Error: #{rel_name} does not define col: #{col_name}"
|
|
165
|
+
raise e
|
|
166
|
+
end
|
|
167
|
+
begin
|
|
168
|
+
@relations[rel_name] ||= j #(App.get_const(App, j/:table)).joins
|
|
169
|
+
@orders[n] = App.get_const(App, j/:foreign_table).orders/n
|
|
170
|
+
@orders["_#{n}"] = App.get_const(App, j/:foreign_table).orders/"_#{n}"
|
|
171
|
+
#col[:table] = rel_name
|
|
172
|
+
rescue NoMethodError => e
|
|
173
|
+
if col.nil?
|
|
174
|
+
Waxx.debug "col is nil"
|
|
175
|
+
else
|
|
176
|
+
Waxx.debug "ERROR: App[#{col[:table]}] has no joins in View.has"
|
|
177
|
+
end
|
|
178
|
+
raise e
|
|
179
|
+
end
|
|
180
|
+
#Waxx.debug n
|
|
181
|
+
#Waxx.debug col
|
|
182
|
+
[n, col]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# Parse a column (internal method used by col)
|
|
187
|
+
def parse_col(str)
|
|
188
|
+
nam = rel = col = nil
|
|
189
|
+
parts = str.split(/[:\.]/)
|
|
190
|
+
case str
|
|
191
|
+
# alias:relationship.column
|
|
192
|
+
when /^\w+:\s*\w+\.\w+$/
|
|
193
|
+
nam, rel, col = str.split(/[:\.]/).map{|part| part.strip}
|
|
194
|
+
# relationship.column
|
|
195
|
+
when /^\w+\.\w+$/
|
|
196
|
+
rel, col = str.split(".")
|
|
197
|
+
# alias:column (from primary object/table)
|
|
198
|
+
when /^\w+:\w+$/
|
|
199
|
+
nam, col = str.split(":")
|
|
200
|
+
# column (from primary object/table)
|
|
201
|
+
when /^\w+$/
|
|
202
|
+
col = str
|
|
203
|
+
else
|
|
204
|
+
raise "Could not parse column definition in Waxx::View.parse_col (#{name}). Unknown match: #{str}."
|
|
205
|
+
end
|
|
206
|
+
nam = col if nam.nil?
|
|
207
|
+
rel = @table if rel.nil?
|
|
208
|
+
[nam, rel, col]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
##
|
|
212
|
+
# Turn the @joins attribute into SQL for the JOIN clause
|
|
213
|
+
def joins_to_sql()
|
|
214
|
+
return nil if @joins.nil? or @joins.empty?
|
|
215
|
+
@joins.map{|n,v| v}.join(" ")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# Autogenerate the modules to do standard layouts like Json, Csv, or Tab
|
|
220
|
+
#
|
|
221
|
+
# ```
|
|
222
|
+
# as :json
|
|
223
|
+
# ```
|
|
224
|
+
#
|
|
225
|
+
# This will generate the following code and allow the output of json formatted data for the view
|
|
226
|
+
#
|
|
227
|
+
# ```
|
|
228
|
+
# module App::Usr::List::Json
|
|
229
|
+
# extend Waxx::Json
|
|
230
|
+
# extend self
|
|
231
|
+
# end
|
|
232
|
+
# ```
|
|
233
|
+
def as(*views)
|
|
234
|
+
views.each{|v|
|
|
235
|
+
eval("
|
|
236
|
+
module #{name}::#{v.to_s.capitalize}
|
|
237
|
+
extend Waxx::#{v.to_s.capitalize}
|
|
238
|
+
extend self
|
|
239
|
+
end
|
|
240
|
+
")
|
|
241
|
+
}
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
##
|
|
245
|
+
# An array of columns to match in when passed in as params
|
|
246
|
+
def match_in(*cols)
|
|
247
|
+
@matches = cols.flatten
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
##
|
|
251
|
+
# Any array of columns to automatically search in using the "q" parameter
|
|
252
|
+
def search_in(*cols)
|
|
253
|
+
@searches = cols.flatten
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
##
|
|
257
|
+
# Set the default order. Order is a key of the field name. Use _name to sort descending.
|
|
258
|
+
def default_order(ord)
|
|
259
|
+
@order_by = ord
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
##
|
|
263
|
+
# Gets the data for the view and displays it. This is just a shortcut method.
|
|
264
|
+
#
|
|
265
|
+
# This is normally called from the handler method defined in Object
|
|
266
|
+
#
|
|
267
|
+
# ```
|
|
268
|
+
# App::Usr::List.run(x)
|
|
269
|
+
# # Given a get request with the json extention, the above is a shortcut to:
|
|
270
|
+
# data = App::Usr::List.get(x)
|
|
271
|
+
# App::Usr::List::Json.get(x, data)
|
|
272
|
+
# ```
|
|
273
|
+
def run(x, id:nil, data:nil, where:nil, having:nil, order:nil, limit:nil, offset:nil, message:{}, as:x.ext, meth:x.meth, args:{})
|
|
274
|
+
case meth.to_sym
|
|
275
|
+
when :get, :head
|
|
276
|
+
if data.nil? or data.empty?
|
|
277
|
+
if id
|
|
278
|
+
data = get_by_id(x, id)
|
|
279
|
+
else
|
|
280
|
+
data = get(x, where:where, having:having, order:(order||x['order']), limit:(limit||x['limit']), offset:(offset||x['offset']), args:args)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
when :put, :post, :patch
|
|
284
|
+
data = put_post(x, id, data, args:args)
|
|
285
|
+
when :delete
|
|
286
|
+
delete(x, id, args:args)
|
|
287
|
+
else
|
|
288
|
+
raise "Unknown request method in Waxx::View.run(#{name})"
|
|
289
|
+
end
|
|
290
|
+
layout = const_get(as.to_s.capitalize) rescue nil
|
|
291
|
+
return App.not_found(x, message:"No layout defined for #{as}") if not layout
|
|
292
|
+
if layout.respond_to? meth
|
|
293
|
+
render(x, data, message: message, as: as, meth: meth)
|
|
294
|
+
else
|
|
295
|
+
render(x, data, message: message, as: as, meth: "get")
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
alias view run
|
|
299
|
+
|
|
300
|
+
##
|
|
301
|
+
# Automatically build the where clause of SQL based on the parameters passed in and the definition of matches and searches.
|
|
302
|
+
def build_where(x, args: {}, matches: @matches, searches: @searches)
|
|
303
|
+
return nil if args.nil? or args.empty? or (matches.nil? and searches.nil?)
|
|
304
|
+
w_str = ""
|
|
305
|
+
w_args = []
|
|
306
|
+
q = args/:q || x['q']
|
|
307
|
+
if q and searches
|
|
308
|
+
w_str += "("
|
|
309
|
+
searches.each_with_index{|c, i|
|
|
310
|
+
w_str += " OR " if i > 0
|
|
311
|
+
w_str += "LOWER(#{c}) like $1"
|
|
312
|
+
}
|
|
313
|
+
w_args << "%#{q.downcase}%"
|
|
314
|
+
w_str += ")"
|
|
315
|
+
end
|
|
316
|
+
if matches
|
|
317
|
+
matches.each_with_index{|c, i|
|
|
318
|
+
next if (x/c).to_s == "" and (args/c).to_s == ""
|
|
319
|
+
w_str += " AND " if w_str != ""
|
|
320
|
+
col = self[c.to_sym]
|
|
321
|
+
w_str += "#{c} #{col[:match] || "="} $#{w_args.size + 1}"
|
|
322
|
+
w_args << (args/c || x/c)
|
|
323
|
+
}
|
|
324
|
+
end
|
|
325
|
+
[w_str, w_args]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
##
|
|
329
|
+
# Override this method in a view to change params
|
|
330
|
+
def get(x, where:nil, having:nil, order:nil, limit:nil, offset:nil, args:{}, &blk)
|
|
331
|
+
where ||= build_where(x, args: args)
|
|
332
|
+
order ||= args/:order || @order_by
|
|
333
|
+
limit ||= args/:limit
|
|
334
|
+
offset ||= args/:offset
|
|
335
|
+
@object.get(x,
|
|
336
|
+
view: self,
|
|
337
|
+
where: where,
|
|
338
|
+
joins: joins_to_sql(),
|
|
339
|
+
having: having,
|
|
340
|
+
order: order,
|
|
341
|
+
limit: limit,
|
|
342
|
+
offset: offset
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
##
|
|
347
|
+
# Get a single record of the view based on the primary key of the primary object
|
|
348
|
+
def get_by_id(x, id)
|
|
349
|
+
@object.get_by_id(x, id, view: self)
|
|
350
|
+
end
|
|
351
|
+
alias by_id get_by_id
|
|
352
|
+
|
|
353
|
+
##
|
|
354
|
+
# Save data
|
|
355
|
+
def put_post(x, id, data, args:nil)
|
|
356
|
+
@object.put_post(x, id, data, view: self)
|
|
357
|
+
end
|
|
358
|
+
alias post put_post
|
|
359
|
+
alias put put_post
|
|
360
|
+
|
|
361
|
+
##
|
|
362
|
+
# Delete a record by ID (primary key of the primary object)
|
|
363
|
+
def delete(x, id)
|
|
364
|
+
@object.delete(x, id)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
##
|
|
368
|
+
# Render the view using the layout for meth and as
|
|
369
|
+
#
|
|
370
|
+
# `render(x, data, as: 'json', meth: 'get')`
|
|
371
|
+
#
|
|
372
|
+
# Uses logical defaults based on x.req
|
|
373
|
+
def render(x, data, message: {}, as:x.ext, meth:x.meth)
|
|
374
|
+
return App.not_found(x) unless const_defined?(as.to_s.capitalize)
|
|
375
|
+
const_get(as.to_s.capitalize).send(meth, x, data, message: message)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
##
|
|
379
|
+
# Send a not found message back to the client using the appropriate layout (json, csv, etc)
|
|
380
|
+
def not_found(x, data:{}, message: {type: "NotFound", message:"The record you requested was not found."}, as:x.ext)
|
|
381
|
+
self.const_get(as.to_s.capitalize).not_found(x, data:{}, message: message)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
##
|
|
385
|
+
# A shorcut to Waxx.debug
|
|
386
|
+
def debug(str, level=3)
|
|
387
|
+
Waxx.debug(str, level)
|
|
388
|
+
end
|
|
389
|
+
end
|
data/lib/waxx/waxx.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Waxx Copyright (c) 2016-2017 ePark labs Inc. & Daniel J. Fitzpatrick <dan@eparklabs.com>
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Waxx is a high-performance Ruby web application development framework. See https://www.waxx.io/ for more information.
|
|
17
|
+
module Waxx
|
|
18
|
+
extend self
|
|
19
|
+
|
|
20
|
+
# TODO: Figure this out based on the waxx command opts
|
|
21
|
+
Root = Dir.pwd
|
|
22
|
+
|
|
23
|
+
# A few helper functions
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Shortcut to Waxx::Waxx variables
|
|
27
|
+
def [](str)
|
|
28
|
+
Waxx::Conf[str]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Shortcut to Waxx::Waxx variables
|
|
33
|
+
def /(str)
|
|
34
|
+
Waxx::Conf/str
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Output to the log
|
|
39
|
+
# Waxx.debug(
|
|
40
|
+
# str, # The text to output
|
|
41
|
+
# level # The number 0 (most important) - 9 (least important)
|
|
42
|
+
# )
|
|
43
|
+
# # Set the level in config.yaml (debug.level) of what level or lower to ouutput
|
|
44
|
+
def debug(str, level=3)
|
|
45
|
+
puts str.to_s if level <= Waxx['debug']['level'].to_i
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Get a pseudo-random (non-cryptographically secure) string to use as a temporary password.
|
|
50
|
+
# If you need real random use SecureRandom.random_bytes(size) or SecureRandom.base64(size).
|
|
51
|
+
# 1. size: Length of string
|
|
52
|
+
# 2. type: [
|
|
53
|
+
# any: US keyboard characters
|
|
54
|
+
# an: Alphanumeric (0-9a-zA-Z)
|
|
55
|
+
# anl: Alphanumeric lower: (0-9a-z)
|
|
56
|
+
# chars: Your own character list
|
|
57
|
+
# ]
|
|
58
|
+
# 3. chars: A string of your own characters
|
|
59
|
+
def random_string(size=32, type=:an, chars=nil)
|
|
60
|
+
if not type.to_sym == :chars
|
|
61
|
+
types = {
|
|
62
|
+
any: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@#$%^&*()_-+={[}]|:;<,>.?/',
|
|
63
|
+
an: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
|
64
|
+
anl: '0123456789abcdefghijklmnopqrstuvwxyz'
|
|
65
|
+
}
|
|
66
|
+
chars = types[type.to_sym].split("")
|
|
67
|
+
end
|
|
68
|
+
opts = chars.size
|
|
69
|
+
1.upto(size).map{chars[rand(opts)]}.join
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
|