kwipper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +2 -0
- data/app/controllers/comments_controller.rb +32 -0
- data/app/controllers/post_favorites_controller.rb +17 -0
- data/app/controllers/posts_controller.rb +41 -0
- data/app/controllers/sessions_controller.rb +32 -0
- data/app/controllers/users_controller.rb +70 -0
- data/app/models/comment.rb +20 -0
- data/app/models/post.rb +31 -0
- data/app/models/post_favorite.rb +6 -0
- data/app/models/session.rb +12 -0
- data/app/models/user.rb +21 -0
- data/app/views/edit_user.erb +26 -0
- data/app/views/fave_button.erb +8 -0
- data/app/views/home.erb +126 -0
- data/app/views/layout.erb +110 -0
- data/app/views/login_user.erb +24 -0
- data/app/views/new_comment.erb +18 -0
- data/app/views/new_post.erb +11 -0
- data/app/views/new_user.erb +26 -0
- data/app/views/not_found.erb +11 -0
- data/app/views/pagination.erb +29 -0
- data/app/views/posts.erb +7 -0
- data/app/views/posts_list.erb +44 -0
- data/app/views/reply_button.erb +4 -0
- data/app/views/server_error.erb +17 -0
- data/app/views/show_post.erb +37 -0
- data/app/views/show_user.erb +11 -0
- data/app/views/users.erb +38 -0
- data/db/.DS_Store +0 -0
- data/kwipper.gemspec +27 -0
- data/lib/kwipper.rb +46 -0
- data/lib/kwipper/application.rb +87 -0
- data/lib/kwipper/controller.rb +36 -0
- data/lib/kwipper/controller_helpers.rb +22 -0
- data/lib/kwipper/errors.rb +5 -0
- data/lib/kwipper/http_parser.rb +51 -0
- data/lib/kwipper/http_server.rb +61 -0
- data/lib/kwipper/inflect.rb +19 -0
- data/lib/kwipper/model.rb +174 -0
- data/lib/kwipper/paginator.rb +71 -0
- data/lib/kwipper/renders_views.rb +18 -0
- data/lib/kwipper/request.rb +35 -0
- data/lib/kwipper/request_headers.rb +36 -0
- data/lib/kwipper/response.rb +85 -0
- data/lib/kwipper/version.rb +3 -0
- data/public/css/bootstrap-lumen.min.css +7 -0
- data/public/css/styles.css +48 -0
- data/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/public/js/bootstrap.min.js +7 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e5274064f603e0f05b4a350a54d02ca0daffe742
|
4
|
+
data.tar.gz: 254cb60d948a74f41e1cbd58d5206f31ba1d4ef8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ca2c1888f7ed78138981511db0853b1ca00b638e6070a7d27c80c8e3b2800f6d3fd1428edb94cb6ecdc448bffc0c8857eb2805aa7a55e9368aac6974ab9aad70
|
7
|
+
data.tar.gz: d094eba7c7f0e6b6a10dd1cc6830dd9251a9bde863cb25e31ff2259f7b4ff856f5c4c25fb58fd6dc40c5e17ca5c483fa3520e4d493bd2000c3c2df78deae1862
|
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Diego Salazar
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Kwipper
|
2
|
+
|
3
|
+
Kwipper is the reference example app for the Kwipper Programming Challenge. See more information on the live app [demo](#soon).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ git clone git@github.com:DiegoSalazar/kwipper_challenge.git
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
Create yourself a user by starting the console:
|
12
|
+
|
13
|
+
$ irb -r ./lib/kwipper -r ./app/models/user
|
14
|
+
|
15
|
+
And running:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
User.create 'username' => 'a username', 'email' => 'optional@mail.com', 'hashed_password' => '123'
|
19
|
+
```
|
20
|
+
|
21
|
+
Start the server:
|
22
|
+
|
23
|
+
$ ruby lib/kwipper.rb
|
24
|
+
|
25
|
+
Open the app in a browser at [http://localhost:7335](http://localhost:7335).
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
|
29
|
+
1. Fork it ( https://github.com/DiegoSalazar/kwipper/fork )
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class CommentsController < Controller
|
3
|
+
add_routes({
|
4
|
+
[:GET, '/kwips/comments/new'] => :new,
|
5
|
+
[:POST, '/kwips/comments/create'] => :create
|
6
|
+
})
|
7
|
+
|
8
|
+
def new
|
9
|
+
require_login!
|
10
|
+
@post = Post.find params['id']
|
11
|
+
render :new_comment
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
require_login!
|
16
|
+
|
17
|
+
post = Post.find params['id']
|
18
|
+
comment = Comment.new({
|
19
|
+
'user_id' => current_user.id,
|
20
|
+
'post_id' => post.id,
|
21
|
+
'created_at' => Time.now.httpdate,
|
22
|
+
'content' => params['content']
|
23
|
+
})
|
24
|
+
|
25
|
+
if comment.save
|
26
|
+
redirect "/kwips/show?id=#{post.id}"
|
27
|
+
else
|
28
|
+
redirect "/kwips/comments/new?id=#{post.id}", :bad_request
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class PostFavoritesController < Controller
|
3
|
+
add_routes [:POST, '/favorites/create'] => :create
|
4
|
+
|
5
|
+
def create
|
6
|
+
require_login!
|
7
|
+
|
8
|
+
post = Post.find params['id']
|
9
|
+
fave = PostFavorite.create({
|
10
|
+
'user_id' => current_user.id,
|
11
|
+
'post_id' => post.id
|
12
|
+
})
|
13
|
+
|
14
|
+
redirect "/kwips/show?id=#{post.id}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class PostsController < Controller
|
3
|
+
add_routes({
|
4
|
+
[:GET, '/kwips'] => :posts,
|
5
|
+
[:GET, '/kwips/show'] => :show,
|
6
|
+
[:GET, '/kwips/new'] => :new,
|
7
|
+
[:POST, '/kwips/create'] => :create
|
8
|
+
})
|
9
|
+
|
10
|
+
def posts
|
11
|
+
@paginator = Paginator.new Post, page: params['page'], per: 10, path: '/kwips'
|
12
|
+
@posts = @paginator.get "SELECT * FROM posts ORDER BY created_at DESC"
|
13
|
+
render :posts
|
14
|
+
end
|
15
|
+
|
16
|
+
def show
|
17
|
+
@post = Post.find params['id']
|
18
|
+
@comments = @post.comments
|
19
|
+
render :show_post
|
20
|
+
end
|
21
|
+
|
22
|
+
def new
|
23
|
+
require_login!
|
24
|
+
render :new_post
|
25
|
+
end
|
26
|
+
|
27
|
+
def create
|
28
|
+
require_login!
|
29
|
+
post = Post.new params.merge({
|
30
|
+
'user_id' => current_user.id,
|
31
|
+
'created_at' => Time.now.httpdate
|
32
|
+
})
|
33
|
+
|
34
|
+
if post.save
|
35
|
+
redirect '/kwips'
|
36
|
+
else
|
37
|
+
redirect '/kwips/new', :bad_request
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class SessionsController < Controller
|
3
|
+
add_routes({
|
4
|
+
[:POST, '/sessions/create'] => :create,
|
5
|
+
[:GET, '/logout'] => :destroy
|
6
|
+
})
|
7
|
+
|
8
|
+
def create
|
9
|
+
@user = User.authenticate params['username'], params['password']
|
10
|
+
|
11
|
+
if @user
|
12
|
+
Session.create({
|
13
|
+
'id' => response.session_cookie_value,
|
14
|
+
'user_id' => @user.id,
|
15
|
+
'created_at' => Time.now.httpdate
|
16
|
+
})
|
17
|
+
|
18
|
+
redirect '/users'
|
19
|
+
else
|
20
|
+
redirect '/users/login', :bad_request
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy
|
25
|
+
if current_session
|
26
|
+
current_session.destroy
|
27
|
+
response.remove_session_cookie
|
28
|
+
end
|
29
|
+
redirect '/'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class UsersController < Controller
|
3
|
+
add_routes({
|
4
|
+
[:GET, '/users'] => :users,
|
5
|
+
[:GET, '/users/show'] => :show,
|
6
|
+
[:GET, '/users/new'] => :new,
|
7
|
+
[:POST, '/users/create'] => :create,
|
8
|
+
[:GET, '/users/edit'] => :edit,
|
9
|
+
[:POST, '/users/update'] => :update,
|
10
|
+
[:POST, '/users/destroy'] => :destroy,
|
11
|
+
[:GET, '/login'] => :login
|
12
|
+
})
|
13
|
+
|
14
|
+
def users
|
15
|
+
require_login!
|
16
|
+
@users = User.all
|
17
|
+
render :users
|
18
|
+
end
|
19
|
+
|
20
|
+
def show
|
21
|
+
@user = User.find params['id']
|
22
|
+
@posts = @user.posts
|
23
|
+
render :show_user
|
24
|
+
end
|
25
|
+
|
26
|
+
def new
|
27
|
+
require_login!
|
28
|
+
render :new_user
|
29
|
+
end
|
30
|
+
|
31
|
+
def create
|
32
|
+
require_login!
|
33
|
+
user = User.new params
|
34
|
+
|
35
|
+
if user.save
|
36
|
+
redirect '/users'
|
37
|
+
else
|
38
|
+
redirect '/users/new', :bad_request
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def edit
|
43
|
+
require_login!
|
44
|
+
@user = User.find params['id']
|
45
|
+
render :edit_user
|
46
|
+
end
|
47
|
+
|
48
|
+
def update
|
49
|
+
require_login!
|
50
|
+
user = User.find params['id']
|
51
|
+
|
52
|
+
if user.update params
|
53
|
+
redirect '/users'
|
54
|
+
else
|
55
|
+
redirect '/users/new', :bad_request
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def destroy
|
60
|
+
require_login!
|
61
|
+
user = User.find params['id']
|
62
|
+
user.destroy
|
63
|
+
redirect '/users'
|
64
|
+
end
|
65
|
+
|
66
|
+
def login
|
67
|
+
render :login_user
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class Comment < Model
|
3
|
+
column 'user_id', :to_i
|
4
|
+
column 'post_id', :to_i
|
5
|
+
column 'content', :to_s
|
6
|
+
column 'created_at', :to_s
|
7
|
+
|
8
|
+
def user
|
9
|
+
@user ||= User.find user_id
|
10
|
+
end
|
11
|
+
|
12
|
+
def username
|
13
|
+
user.username
|
14
|
+
end
|
15
|
+
|
16
|
+
def post
|
17
|
+
@post ||= Post.find post_id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/app/models/post.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class Post < Model
|
3
|
+
column 'user_id', :to_i
|
4
|
+
column 'content', :to_s
|
5
|
+
column 'created_at', :to_s
|
6
|
+
|
7
|
+
def self.recent(statement = "SELECT * FROM posts")
|
8
|
+
all(statement).sort_by { |post| Time.parse post.created_at }.reverse
|
9
|
+
end
|
10
|
+
|
11
|
+
def user
|
12
|
+
@user ||= User.find user_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def username
|
16
|
+
user.username
|
17
|
+
end
|
18
|
+
|
19
|
+
def comments
|
20
|
+
Comment.all "SELECT * FROM comments WHERE post_id = #{id}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def comments_count
|
24
|
+
Model.count "SELECT COUNT(id) FROM comments WHERE post_id = #{id}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def faves_count
|
28
|
+
Model.count "SELECT COUNT(id) FROM post_favorites WHERE post_id = #{id}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/app/models/user.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Kwipper
|
2
|
+
class User < Model
|
3
|
+
column 'username', :to_s
|
4
|
+
column 'email', :to_s
|
5
|
+
column 'hashed_password', :to_s
|
6
|
+
|
7
|
+
def self.authenticate(username, password)
|
8
|
+
user = where(username: username).first
|
9
|
+
user && user.hashed_password == password && user
|
10
|
+
end
|
11
|
+
|
12
|
+
def favorite?(post)
|
13
|
+
result = sql("SELECT COUNT(id) FROM post_favorites WHERE user_id = #{id} AND post_id = #{post.id} LIMIT 1").first
|
14
|
+
result.first && result.first > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def posts
|
18
|
+
Post.recent "SELECT * FROM posts WHERE user_id = #{id}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<h1>Edit User</h1>
|
2
|
+
|
3
|
+
<div class="row">
|
4
|
+
<div class="col-sm-12">
|
5
|
+
<form action="/users/update?id=<%= @user.id %>" method="post">
|
6
|
+
<div class="form-group">
|
7
|
+
<label>Username</label>
|
8
|
+
<input value="<%= @user.username %>" type="text" name="username" class="form-control">
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="form-group">
|
12
|
+
<label>Email</label>
|
13
|
+
<input value="<%= @user.email %>" type="text" name="email" class="form-control">
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div class="form-group">
|
17
|
+
<label>Password</label>
|
18
|
+
<input value="<%= @user.hashed_password %>" type="text" name="hashed_password" class="form-control">
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<div class="form-group">
|
22
|
+
<input type="submit" value="Update" class="btn btn-primary">
|
23
|
+
</div>
|
24
|
+
</form>
|
25
|
+
</div>
|
26
|
+
</div>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<% faved = current_user && current_user.favorite?(@post) %>
|
2
|
+
<form action="/favorites/create?id=<%= @post.id %>" method="post" class="pull-left">
|
3
|
+
<span class="fa fa-thumb-o-up"> </span>
|
4
|
+
<button type="submit" class="btn btn-xs btn-<%= faved ? 'success' : 'default' %>"<%= faved ? ' disabled="disabled"' : nil %>>
|
5
|
+
<span class="fa fa-thumbs-<%= faved ? 'up' : 'o-up' %>"> </span>
|
6
|
+
<%= faved ? "Fave'd" : 'Fave' %>
|
7
|
+
</button
|
8
|
+
</form>
|
data/app/views/home.erb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
<div class="jumbotron">
|
2
|
+
<h1>Kwipper</h1>
|
3
|
+
|
4
|
+
<p>
|
5
|
+
This web app is a Twitter clone that was created as a reference example for a programming challenge designed to teach the fundamentals of web development in Ruby. Kwipper is a self contained app that uses no web application frameworks, ORMs or Active gems, and no third party server.
|
6
|
+
<a href="#rules">Read more</a> about the challenge below.
|
7
|
+
</p>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<p class="alert alert-info">
|
11
|
+
<span class="fa fa-github fa-lg"> </span>
|
12
|
+
You can see the complete source code for this app on
|
13
|
+
<a href="https://github.com/DiegoSalazar/kwipper_challenge">Github</a>.
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<h2>Components that were built from scratch:</h2>
|
17
|
+
|
18
|
+
<ul>
|
19
|
+
<li>[HTTP Server](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/http_server.rb): using Ruby's TCPServer class</li>
|
20
|
+
<li>[HTTP Parser](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/http_parser.rb): plain old Ruby</li>
|
21
|
+
<li>[Request class](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/request.rb): represents the parsed HTTP request and headers</li>
|
22
|
+
<li>[Response class](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/response.rb): holds response headers and renders HTTP responses to the socket. Sets the session.</li>
|
23
|
+
<li>[View Rendering module](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/renders_views.rb): using ERB</li>
|
24
|
+
<li>[Model base class](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/model.rb): The most complex class, it implements interaction with SQLite and defines ORM-like methods.</li>
|
25
|
+
<li>[Controller base class](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/controller.rb): implements routing and authentication</li>
|
26
|
+
<li>[App micro framework](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/application.rb): app directory with views, models and controllers that are automatically loaded on startup</li>
|
27
|
+
<li>[Paginator class](https://github.com/DiegoSalazar/kwipper_challenge/blob/master/lib/kwipper/paginator.rb): SQL based pagination</li>
|
28
|
+
</ul>
|
29
|
+
|
30
|
+
<hr>
|
31
|
+
|
32
|
+
<h2 id="rules">Challenge Rules</h2>
|
33
|
+
|
34
|
+
<div class="well">
|
35
|
+
<h3>Write a Twitter clone without using any frameworks or Active* gems. </h3>
|
36
|
+
|
37
|
+
<p>
|
38
|
+
The purpose of this challenge is to increase your knowledge of the fundamentals of web development and to understand what happens “under the hood” of the frameworks we use such as Rack and Rails. How you build the app is not important, just that it satisfies the Required Features and follows the Rules. This challenge was designed to encourage you to learn the fundamental concepts that a web developer should know. Once these ideas are grokked you will be closer to obtaining the skills necessary to freely move between web-based technologies.
|
39
|
+
</p>
|
40
|
+
|
41
|
+
<p>
|
42
|
+
Our trade evolves at an exponentially increasing rate. So too, must we constantly increase our understanding of our tools and techniques in order to keep up with the pace of advancement and innovation. By reaching for the forefront of our craft we can better hope to contribute our own innovations.
|
43
|
+
</p>
|
44
|
+
|
45
|
+
<p>The challenge will be divided into two phases, which are described in the phase sections below.</p>
|
46
|
+
|
47
|
+
<h3>Things To Learn:</h3>
|
48
|
+
<ul>
|
49
|
+
<li>Write a basic HTTP server (for example: using Ruby’s TCPServer class)</li>
|
50
|
+
<li>Parse the HTTP protocol (similar to how CSV is parsed but more complex)</li>
|
51
|
+
<li>Handle HTTP headers to set cookies and manage sessions</li>
|
52
|
+
<li>Handle GET and POST data in requests</li>
|
53
|
+
<li>Abstract away the HTTP layer so your app can focus on its own domain logic</li>
|
54
|
+
<li>Connect to a database without using ActiveRecord</li>
|
55
|
+
<li>Write SQL statements to perform CRUD operations</li>
|
56
|
+
<li>Manually validate and sanitize user input before inserting into the database</li>
|
57
|
+
<li>Generate HTML with dynamic content</li>
|
58
|
+
</ul>
|
59
|
+
|
60
|
+
<h2>Kwipper: A witty Twitter Clone</h2>
|
61
|
+
|
62
|
+
<h3>Required Features</h3>
|
63
|
+
<ul>
|
64
|
+
<li>User sign up, login, and logout using cookie based sessions</li>
|
65
|
+
<li>User can create, edit, delete their own text Posts</li>
|
66
|
+
<li>Index page displays timeline of Posts by all Users</li>
|
67
|
+
<li>Pagination of timeline</li>
|
68
|
+
<li>Server logs requests to STDOUT</li>
|
69
|
+
</ul>
|
70
|
+
|
71
|
+
<h3>Rules</h3>
|
72
|
+
|
73
|
+
<ul>
|
74
|
+
<li>The App must be run by executing `ruby app.rb`</li>
|
75
|
+
<li>The App logs requests to STDOUT (the console)</li>
|
76
|
+
<li>The App must be plain Ruby and HTML</li>
|
77
|
+
<li>Must not use any app frameworks (e.g. Rack, Rails, Sinatra)</li>
|
78
|
+
<li>Must not use any of the Active* gems (e.g. ActiveRecord, ActiveSupport)</li>
|
79
|
+
</ul>
|
80
|
+
|
81
|
+
<h3>Phase 1</h3>
|
82
|
+
<p>
|
83
|
+
Complete the HTTP server, database connection, and user signup, login and logout. These basic features form the foundation of every web application and require knowledge of HTTP, SQL, HTTP headers and the concept of session management.
|
84
|
+
</p>
|
85
|
+
|
86
|
+
<h3>Phase 2</h3>
|
87
|
+
<p>Complete the app to meet the required features.</p>
|
88
|
+
|
89
|
+
<h3>Bonus Features</h3>
|
90
|
+
<p>If you’re looking for more of a challenge you can optionally add:</p>
|
91
|
+
|
92
|
+
<ul>
|
93
|
+
<li>Users can comment on posts</li>
|
94
|
+
<li>Users can tag posts</li>
|
95
|
+
<li>Users can favorite posts</li>
|
96
|
+
<li>Add a tag cloud to the homepage</li>
|
97
|
+
<li>Make it look good on mobile</li>
|
98
|
+
</ul>
|
99
|
+
|
100
|
+
<h3>Deeper Concepts to Learn</h3>
|
101
|
+
<p>
|
102
|
+
Through completing the challenge you’ll maybe come to understand deeper concepts underlying what you’re building and the technology and protocols you’ll be using. Take these points as a source of motivation for further learning.
|
103
|
+
</p>
|
104
|
+
|
105
|
+
<p>In diving deeper into the ideas touched on by the points in the Things To Learn section:</p>
|
106
|
+
|
107
|
+
<ul>
|
108
|
+
<li>
|
109
|
+
The difference between TCP and HTTP and how they fit together. Why was a text protocol chosen for HTTP but a binary protocol for TCP? What exactly is TCP? What is HTTP?
|
110
|
+
</li>
|
111
|
+
<li>
|
112
|
+
What are sessions? What are cookies? Why are they necessary? How are they stored in memory on the server? How does the server know which users correspond with which sessions?
|
113
|
+
</li>
|
114
|
+
<li>
|
115
|
+
What are the differences between GET and POST data? When it is appropriate to use each and why do we get the “resubmit form data” warning in some apps?
|
116
|
+
</li>
|
117
|
+
<li>
|
118
|
+
What is the point of ActiveRecord? Why doesn’t it cover all use cases? What is the difference between modeling data, a data model, and a Model?
|
119
|
+
Where should validation be implemented? Why should we sanitize input? Why was this an important point?
|
120
|
+
</li>
|
121
|
+
<li>
|
122
|
+
How can we generate dynamic content without templating frameworks like ERB or HAML? At what point does a web page become a web app? What is HTML and what does it do?
|
123
|
+
</li>
|
124
|
+
</ul>
|
125
|
+
|
126
|
+
</div>
|