puffs 0.2.04 → 0.2.05
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/bin/puffs +39 -35
- data/lib/controller_base.rb +55 -55
- data/lib/db_connection.rb +94 -89
- data/lib/puffs.rb +5 -5
- data/lib/relation.rb +161 -159
- data/lib/router.rb +61 -57
- data/lib/server_connection.rb +15 -12
- data/lib/session.rb +28 -20
- data/lib/sql_object/associatable.rb +76 -74
- data/lib/sql_object/sql_object.rb +115 -114
- data/puffs.gemspec +21 -20
- data/readme.md +7 -0
- data/template/config/routes.rb +1 -1
- data/template/db/seeds.rb +23 -21
- metadata +2 -2
data/lib/relation.rb
CHANGED
@@ -1,206 +1,208 @@
|
|
1
1
|
require_relative './../lib/db_connection'
|
2
2
|
require_relative 'sql_object/sql_object'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
base
|
4
|
+
module Puffs
|
5
|
+
class SQLRelation
|
6
|
+
def self.build_association(base, included, method_name)
|
7
|
+
base.included_relations << included
|
8
|
+
|
9
|
+
assoc_options = base.klass.assoc_options[method_name]
|
10
|
+
has_many = assoc_options.class == HasManyOptions
|
11
|
+
|
12
|
+
if has_many
|
13
|
+
i_send = assoc_options.foreign_key
|
14
|
+
b_send = assoc_options.primary_key
|
15
|
+
else
|
16
|
+
i_send = assoc_options.primary_key
|
17
|
+
b_send = assoc_options.foreign_key
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
|
20
|
+
match = proc do
|
21
|
+
selection = included.select do |i_sql_obj|
|
22
|
+
i_sql_obj.send(i_send) == self.send(b_send)
|
23
|
+
end
|
10
24
|
|
11
|
-
|
12
|
-
i_send = assoc_options.foreign_key
|
13
|
-
b_send = assoc_options.primary_key
|
14
|
-
else
|
15
|
-
i_send = assoc_options.primary_key
|
16
|
-
b_send = assoc_options.foreign_key
|
17
|
-
end
|
25
|
+
associated = has_many ? selection : selection.first
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
|
27
|
+
#After we find our values iteratively, we overwrite the method again
|
28
|
+
#to the result values to reduce future lookup time to O(1).
|
29
|
+
new_match = proc { associated }
|
30
|
+
Puffs::SQLObject.define_singleton_method_by_proc(
|
31
|
+
self, method_name, new_match)
|
32
|
+
|
33
|
+
associated
|
22
34
|
end
|
23
35
|
|
24
|
-
|
36
|
+
#we overwrite the association method for each SQLObject in the
|
37
|
+
#collection so that it points to our cached relation and doesn't fire a query.
|
38
|
+
base.collection.each do |b_sql_obj|
|
39
|
+
Puffs::SQLObject.define_singleton_method_by_proc(
|
40
|
+
b_sql_obj, method_name, match)
|
41
|
+
end
|
42
|
+
end
|
25
43
|
|
26
|
-
|
27
|
-
|
28
|
-
new_match = proc { associated }
|
29
|
-
Puffs::SQLObject.define_singleton_method_by_proc(
|
30
|
-
self, method_name, new_match)
|
44
|
+
attr_reader :klass, :collection, :loaded, :sql_count, :sql_limit
|
45
|
+
attr_accessor :included_relations
|
31
46
|
|
32
|
-
|
33
|
-
|
47
|
+
def initialize(options)
|
48
|
+
defaults =
|
49
|
+
{
|
50
|
+
klass: nil,
|
51
|
+
loaded: false,
|
52
|
+
collection: []
|
53
|
+
}
|
34
54
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
Puffs::SQLObject.define_singleton_method_by_proc(
|
39
|
-
b_sql_obj, method_name, match)
|
55
|
+
@klass = options[:klass]
|
56
|
+
@collection = options[:collection] || defaults[:collection]
|
57
|
+
@loaded = options[:loaded] || defaults[:loaded]
|
40
58
|
end
|
41
|
-
end
|
42
59
|
|
43
|
-
|
44
|
-
|
60
|
+
def <<(item)
|
61
|
+
if item.class == klass
|
62
|
+
@collection << item
|
63
|
+
end
|
64
|
+
end
|
45
65
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
loaded: false,
|
51
|
-
collection: []
|
52
|
-
}
|
66
|
+
def count
|
67
|
+
@sql_count = true
|
68
|
+
load
|
69
|
+
end
|
53
70
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
71
|
+
def included_relations
|
72
|
+
@included_relations ||= []
|
73
|
+
end
|
58
74
|
|
59
|
-
|
60
|
-
|
61
|
-
|
75
|
+
def includes(klass)
|
76
|
+
includes_params << klass
|
77
|
+
self
|
62
78
|
end
|
63
|
-
end
|
64
79
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
80
|
+
def includes_params
|
81
|
+
@includes_params ||= []
|
82
|
+
end
|
69
83
|
|
70
|
-
|
71
|
-
|
72
|
-
|
84
|
+
def limit(n)
|
85
|
+
@sql_limit = n
|
86
|
+
self
|
87
|
+
end
|
73
88
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
89
|
+
def load
|
90
|
+
if !loaded
|
91
|
+
puts "LOADING #{table_name}"
|
92
|
+
results = Puffs::DBConnection.execute(<<-SQL, sql_params[:values])
|
93
|
+
SELECT
|
94
|
+
#{sql_count ? "COUNT(*)" : self.table_name.to_s + ".*"}
|
95
|
+
FROM
|
96
|
+
#{self.table_name}
|
97
|
+
#{sql_params[:where]}
|
98
|
+
#{sql_params[:params]}
|
99
|
+
#{order_by_string}
|
100
|
+
#{"LIMIT #{sql_limit}" if sql_limit};
|
101
|
+
SQL
|
78
102
|
|
79
|
-
|
80
|
-
|
81
|
-
end
|
103
|
+
results = sql_count ? results.first.values.first : parse_all(results)
|
104
|
+
end
|
82
105
|
|
83
|
-
|
84
|
-
@sql_limit = n
|
85
|
-
self
|
86
|
-
end
|
106
|
+
results = results || self
|
87
107
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
results = DBConnection.execute(<<-SQL, sql_params[:values])
|
92
|
-
SELECT
|
93
|
-
#{sql_count ? "COUNT(*)" : self.table_name.to_s + ".*"}
|
94
|
-
FROM
|
95
|
-
#{self.table_name}
|
96
|
-
#{sql_params[:where]}
|
97
|
-
#{sql_params[:params]}
|
98
|
-
#{order_by_string}
|
99
|
-
#{"LIMIT #{sql_limit}" if sql_limit};
|
100
|
-
SQL
|
108
|
+
unless includes_params.empty?
|
109
|
+
results = load_includes(results)
|
110
|
+
end
|
101
111
|
|
102
|
-
results
|
112
|
+
results
|
103
113
|
end
|
104
114
|
|
105
|
-
|
115
|
+
def load_includes(relation)
|
116
|
+
includes_params.each do |param|
|
117
|
+
if relation.klass.has_association?(param)
|
118
|
+
puts "LOADING #{param.to_s}"
|
119
|
+
assoc = klass.assoc_options[param]
|
120
|
+
f_k = assoc.foreign_key
|
121
|
+
p_k = assoc.primary_key
|
122
|
+
includes_table = assoc.table_name.to_s
|
123
|
+
in_ids = relation.collection.map do |sqlobject|
|
124
|
+
sqlobject.id
|
125
|
+
end.join(", ")
|
126
|
+
|
127
|
+
has_many = assoc.class == HasManyOptions
|
128
|
+
|
129
|
+
results = Puffs::DBConnection.execute(<<-SQL)
|
130
|
+
SELECT
|
131
|
+
#{includes_table}.*
|
132
|
+
FROM
|
133
|
+
#{includes_table}
|
134
|
+
WHERE
|
135
|
+
#{includes_table}.#{has_many ? f_k : p_k}
|
136
|
+
IN
|
137
|
+
(#{in_ids});
|
138
|
+
SQL
|
139
|
+
included = assoc.model_class.parse_all(results)
|
140
|
+
SQLRelation.build_association(relation, included, param)
|
141
|
+
end
|
142
|
+
end
|
106
143
|
|
107
|
-
|
108
|
-
results = load_includes(results)
|
144
|
+
relation
|
109
145
|
end
|
110
146
|
|
111
|
-
|
112
|
-
|
147
|
+
def method_missing(method, *args, &block)
|
148
|
+
self.to_a.send(method, *args, &block)
|
149
|
+
end
|
113
150
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
f_k = assoc.foreign_key
|
120
|
-
p_k = assoc.primary_key
|
121
|
-
includes_table = assoc.table_name.to_s
|
122
|
-
in_ids = relation.collection.map do |sqlobject|
|
123
|
-
sqlobject.id
|
124
|
-
end.join(", ")
|
125
|
-
|
126
|
-
has_many = assoc.class == HasManyOptions
|
127
|
-
|
128
|
-
results = DBConnection.execute(<<-SQL)
|
129
|
-
SELECT
|
130
|
-
#{includes_table}.*
|
131
|
-
FROM
|
132
|
-
#{includes_table}
|
133
|
-
WHERE
|
134
|
-
#{includes_table}.#{has_many ? f_k : p_k}
|
135
|
-
IN
|
136
|
-
(#{in_ids});
|
137
|
-
SQL
|
138
|
-
included = assoc.model_class.parse_all(results)
|
139
|
-
Puffs::SQLRelation.build_association(relation, included, param)
|
151
|
+
def order(params)
|
152
|
+
if params.is_a?(Hash)
|
153
|
+
order_params_hash.merge!(params)
|
154
|
+
else
|
155
|
+
order_params_hash.merge!(params => :asc)
|
140
156
|
end
|
157
|
+
self
|
141
158
|
end
|
142
159
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
def method_missing(method, *args, &block)
|
147
|
-
self.to_a.send(method, *args, &block)
|
148
|
-
end
|
149
|
-
|
150
|
-
def order(params)
|
151
|
-
if params.is_a?(Hash)
|
152
|
-
order_params_hash.merge!(params)
|
153
|
-
else
|
154
|
-
order_params_hash.merge!(params => :asc)
|
160
|
+
def order_params_hash
|
161
|
+
@order_params_hash ||= {}
|
155
162
|
end
|
156
|
-
self
|
157
|
-
end
|
158
163
|
|
159
|
-
|
160
|
-
|
161
|
-
|
164
|
+
def order_by_string
|
165
|
+
hash_string = order_params_hash.map do |column, asc_desc|
|
166
|
+
"#{column} #{asc_desc.to_s.upcase}"
|
167
|
+
end.join(", ")
|
162
168
|
|
163
|
-
|
164
|
-
|
165
|
-
"#{column} #{asc_desc.to_s.upcase}"
|
166
|
-
end.join(", ")
|
169
|
+
hash_string.empty? ? "" : "ORDER BY #{hash_string}"
|
170
|
+
end
|
167
171
|
|
168
|
-
|
169
|
-
|
172
|
+
def parse_all(attributes)
|
173
|
+
klass.parse_all(attributes).where(where_params_hash).includes(includes_params)
|
174
|
+
end
|
170
175
|
|
171
|
-
|
172
|
-
|
173
|
-
end
|
176
|
+
def sql_params
|
177
|
+
params, values = [], []
|
174
178
|
|
175
|
-
|
176
|
-
|
179
|
+
i = 1
|
180
|
+
where_params_hash.map do |attribute, value|
|
181
|
+
params << "#{attribute} = $#{i}"
|
182
|
+
values << value
|
183
|
+
i += 1
|
184
|
+
end
|
177
185
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
values << value
|
182
|
-
i += 1
|
186
|
+
{ params: params.join(" AND "),
|
187
|
+
where: params.empty? ? nil : "WHERE",
|
188
|
+
values: values }
|
183
189
|
end
|
184
190
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
def table_name
|
191
|
-
klass.table_name
|
192
|
-
end
|
191
|
+
def table_name
|
192
|
+
klass.table_name
|
193
|
+
end
|
193
194
|
|
194
|
-
|
195
|
-
|
196
|
-
|
195
|
+
def to_a
|
196
|
+
self.load.collection
|
197
|
+
end
|
197
198
|
|
198
|
-
|
199
|
-
|
200
|
-
|
199
|
+
def where_params_hash
|
200
|
+
@where_params_hash ||= {}
|
201
|
+
end
|
201
202
|
|
202
|
-
|
203
|
-
|
204
|
-
|
203
|
+
def where(params)
|
204
|
+
where_params_hash.merge!(params)
|
205
|
+
self
|
206
|
+
end
|
205
207
|
end
|
206
208
|
end
|
data/lib/router.rb
CHANGED
@@ -1,74 +1,78 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Puffs
|
2
|
+
# The Puffs Router.
|
3
|
+
class Router
|
4
|
+
attr_reader :routes
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
pattern =~ req.path && req.request_method == http_method.to_s.upcase
|
14
|
-
end
|
15
|
-
|
16
|
-
# use pattern to pull out route params (save for later?)
|
17
|
-
# instantiate controller and call controller action
|
18
|
-
def run(req, res)
|
19
|
-
matches = pattern.match(req.path)
|
20
|
-
route_params = {}
|
6
|
+
def initialize
|
7
|
+
@routes = []
|
8
|
+
end
|
21
9
|
|
22
|
-
|
23
|
-
|
10
|
+
# Adds a new route to the list of routes
|
11
|
+
def add_route(pattern, method, controller_class, action_name)
|
12
|
+
routes << Route.new(pattern, method, controller_class, action_name)
|
24
13
|
end
|
25
14
|
|
26
|
-
|
27
|
-
|
28
|
-
|
15
|
+
# evaluate the proc in the context of the instance
|
16
|
+
# for syntactic sugar :)
|
17
|
+
def draw(&proc)
|
18
|
+
instance_eval(&proc)
|
19
|
+
end
|
29
20
|
|
30
|
-
|
31
|
-
|
21
|
+
# make each of these methods that
|
22
|
+
# when called add route
|
23
|
+
[:get, :post, :put, :delete].each do |http_method|
|
24
|
+
define_method(http_method) do |pattern, controller_class, action_name|
|
25
|
+
add_route(pattern, http_method, controller_class, action_name)
|
26
|
+
end
|
27
|
+
end
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
# should return the route that matches this request
|
30
|
+
def match(req)
|
31
|
+
routes.each do |route|
|
32
|
+
return route if route.matches?(req)
|
33
|
+
end
|
34
|
+
nil
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
# either throw 404 or call run on a matched route
|
38
|
+
def run(req, res)
|
39
|
+
route = match(req)
|
40
|
+
if route
|
41
|
+
route.run(req, res)
|
42
|
+
else
|
43
|
+
res.status = 404
|
44
|
+
res.body = ["Sorry, Charlie. That page doesn't exist."]
|
45
|
+
end
|
46
|
+
end
|
40
47
|
end
|
41
48
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
instance_eval(&proc)
|
46
|
-
end
|
49
|
+
# Router is comprised of Routes.
|
50
|
+
class Route
|
51
|
+
attr_reader :pattern, :http_method, :controller_class, :action_name
|
47
52
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
+
def initialize(pattern, http_method, controller_class, action_name)
|
54
|
+
@pattern = pattern
|
55
|
+
@http_method = http_method
|
56
|
+
@controller_class = controller_class
|
57
|
+
@action_name = action_name
|
53
58
|
end
|
54
|
-
end
|
55
59
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
return route if route.matches?(req)
|
60
|
+
# checks if pattern matches path and method matches request method
|
61
|
+
def matches?(req)
|
62
|
+
pattern =~ req.path && req.request_method == http_method.to_s.upcase
|
60
63
|
end
|
61
|
-
nil
|
62
|
-
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
# use pattern to pull out route params (save for later?)
|
66
|
+
# instantiate controller and call controller action
|
67
|
+
def run(req, res)
|
68
|
+
matches = pattern.match(req.path)
|
69
|
+
route_params = {}
|
70
|
+
|
71
|
+
matches.names.each do |name|
|
72
|
+
route_params[name] = matches[name]
|
73
|
+
end
|
74
|
+
|
75
|
+
controller_class.new(req, res, route_params).invoke_action(action_name)
|
72
76
|
end
|
73
77
|
end
|
74
78
|
end
|
data/lib/server_connection.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
1
|
require 'rack'
|
2
2
|
require './config/routes'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
module Puffs
|
5
|
+
# Invoked through `puffs server` in CLI.
|
6
|
+
class ServerConnection
|
7
|
+
def self.start
|
8
|
+
app = proc do |env|
|
9
|
+
req = Rack::Request.new(env)
|
10
|
+
res = Rack::Response.new
|
11
|
+
ROUTER.run(req, res)
|
12
|
+
res.finish
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
Rack::Server.start(
|
16
|
+
app: app,
|
17
|
+
Port: 3000
|
18
|
+
)
|
19
|
+
end
|
17
20
|
end
|
18
21
|
end
|
data/lib/session.rb
CHANGED
@@ -1,28 +1,36 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
cookie
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module Puffs
|
4
|
+
# Session token.
|
5
|
+
class Session
|
6
|
+
# find the cookie for this app
|
7
|
+
# deserialize the cookie into a hash
|
8
|
+
def initialize(req)
|
9
|
+
cookie = req.cookies['_rails_lite_app']
|
10
|
+
|
11
|
+
if cookie
|
12
|
+
@session_data = JSON.parse(cookie)
|
13
|
+
else
|
14
|
+
@session_data = {}
|
15
|
+
end
|
12
16
|
end
|
13
|
-
end
|
14
17
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
def [](key)
|
19
|
+
@session_data[key]
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
def []=(key, val)
|
23
|
+
@session_data[key] = val
|
24
|
+
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
# serialize the hash into json and save in a cookie
|
27
|
+
# add to the responses cookies
|
28
|
+
def store_session(res)
|
29
|
+
res.set_cookie(
|
30
|
+
'_rails_lite_app',
|
31
|
+
path: '/',
|
32
|
+
value: @session_data.to_json
|
33
|
+
)
|
34
|
+
end
|
27
35
|
end
|
28
36
|
end
|