├── .rspec
├── .gitignore
├── .rvmrc
├── config.ru
├── .autotest
├── spec
├── spec_helper.rb
└── olpc_spec.rb
├── views
├── delete.erb
├── layout.erb
├── edit.erb
└── home.erb
├── Gemfile
├── Rakefile
├── README.markdown
├── olpc-tasks.rb
├── public
├── style.css
└── reset.css
└── Gemfile.lock
/.rspec:
--------------------------------------------------------------------------------
1 | --format nested
2 | --color
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.db
2 | *.swo
3 | *.swp
4 |
--------------------------------------------------------------------------------
/.rvmrc:
--------------------------------------------------------------------------------
1 | rvm use --create 1.9.3@sinatra
2 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | require './olpc-tasks.rb'
2 | run Sinatra::Application
3 |
--------------------------------------------------------------------------------
/.autotest:
--------------------------------------------------------------------------------
1 | require 'autotest/fsevent'
2 | require 'autotest/growl'
3 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require_relative '../olpc-tasks.rb'
2 | require 'rack/test'
3 |
4 | set :environment, :test
5 |
6 | def app
7 | Sinatra::Application
8 | end
9 |
10 |
--------------------------------------------------------------------------------
/views/delete.erb:
--------------------------------------------------------------------------------
1 | <% if @task %>
2 |
Are you sure you want to delete the following task: "<%= @task.title %>"?
3 |
7 | <% else %>
8 | Task not found.
9 | <% end %>
10 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source :gemcutter
2 | #source "http://ruby.taobao.com"
3 |
4 | gem 'rake'
5 | gem 'sinatra'
6 | gem 'datamapper'
7 | gem 'heroku'
8 |
9 | group :production do
10 | gem "pg"
11 | gem "dm-postgres-adapter"
12 | end
13 |
14 | group :development, :test do
15 | gem "sqlite3"
16 | gem 'dm-sqlite-adapter'
17 | end
18 |
19 | group :test do
20 | gem 'rack-test'
21 | gem 'rspec'
22 | gem 'autotest'
23 | end
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rspec/core/rake_task'
2 |
3 | desc 'Default: run specs.'
4 | task :default => :spec
5 |
6 | task :spec do
7 | desc "TDD"
8 | RSpec::Core::RakeTask.new do |t|
9 | #t.rspec_opts = %w{--colour --format progress --loadby mtime}
10 | t.pattern = "./spec/**/*_spec.rb"
11 | end
12 |
13 | desc "Generate code coverage"
14 | RSpec::Core::RakeTask.new(:coverage) do |t|
15 | t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
16 | t.rcov = true
17 | t.rcov_opts = ['--exclude', 'spec']
18 | end
19 | end
20 |
21 |
--------------------------------------------------------------------------------
/views/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= @title + "| OLPC volunteers Tasks" %>
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Tasks for China OLPC volunteers
14 |
15 |
16 |
17 |
18 | <%= yield %>
19 |
20 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/views/edit.erb:
--------------------------------------------------------------------------------
1 | <% if @task %>
2 |
11 | Delete
12 | <% else %>
13 | Task not found.
14 | <% end %>
15 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # api
2 |
3 | sinatra
4 | rspec
5 |
6 | # shell
7 |
8 | ruby olpc-tasks.rb
9 | rake spec
10 |
11 | # heroku
12 |
13 | heroku create olpc-tasks
14 | heroku addons:add shared-database:5mb
15 | heroku config
16 | git push heroku master
17 |
18 | # database
19 | sudo -u postgres createdb olpc-tasks
20 | sudo -u postgres psql
21 | sudo -u postgres psql
22 | postgres=# \password $USER
23 |
24 | heroku db:push postgres://postgres:postgres@localhost/olpc-tasks
25 | heroku pg:info
26 |
27 | # debug
28 |
29 | heroku console
30 | >> ENV["DATABASE_URL"]
31 |
32 | # ci
33 | travis-ci
34 |
--------------------------------------------------------------------------------
/views/home.erb:
--------------------------------------------------------------------------------
1 |
8 |
9 | <% # display tasks %>
10 | <% @tasks.each do |task| %>
11 | <% task_status ="running" if !task.volunteer.empty? %>
12 | <% task_status ="complete" if task.complete %>
13 |
14 |
15 |
<%= task.title %>
16 | [edit]
17 |
18 |
19 |
20 | volunteer: <%= task.volunteer %>
21 |
22 |
23 | <%= task.url %>
24 |
25 |
26 | ↯
27 |
28 |
29 | <% end %>
30 |
--------------------------------------------------------------------------------
/olpc-tasks.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'sinatra'
3 | require 'data_mapper'
4 |
5 | #DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/olpc-tasks.db")
6 | DataMapper.setup(:default, ENV['DATABASE_URL'] || "postgres://postgres:postgres@localhost/olpc-tasks")
7 |
8 | class Task
9 | include DataMapper::Resource
10 | property :id, Serial
11 | property :title, Text, :required => true
12 | property :url, Text, :required => true
13 | property :complete, Boolean, :required => true, :default => false
14 | property :volunteer, String, :default => ""
15 | end
16 |
17 | # automatically create the post table
18 | Task.auto_migrate! unless Task.storage_exists?
19 |
20 | configure :development do
21 | use Rack::Reloader
22 | end
23 |
24 | get '/' do
25 | @tasks = Task.all :order => :id.desc
26 | @title = 'All tasks'
27 | erb :home
28 | end
29 |
30 | post '/create' do
31 | t = Task.new
32 | t.title = params[:title]
33 | t.url = params[:url]
34 | t.save
35 | redirect '/'
36 | end
37 |
38 | get '/:id' do
39 | @task = Task.get params[:id]
40 | @title = "Edit task ##{params[:id]}"
41 | erb :edit
42 | end
43 |
44 | post '/:id/update' do
45 | t = Task.get params[:id]
46 | t.title = params[:title]
47 | t.url = params[:url]
48 | t.volunteer = params[:volunteer]
49 | t.save
50 | redirect "/"
51 | end
52 |
53 | get '/:id/delete' do
54 | @task = Task.get params[:id]
55 | @title = "Confirm deletion of task ##{params[:id]}"
56 | erb :delete
57 | end
58 |
59 | post '/:id/destroy' do
60 | t = Task.get params[:id]
61 | t.destroy
62 | redirect '/'
63 | end
64 |
65 | get '/:id/complete' do
66 | t = Task.get params[:id]
67 | t.complete = t.complete ? 0 : 1 # flip it
68 | t.save
69 | redirect '/'
70 | end
71 |
--------------------------------------------------------------------------------
/public/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 35px auto;
3 | width: 640px;
4 | }
5 |
6 | header {
7 | text-align: center;
8 | margin: 0 0 20px;
9 | }
10 |
11 | header h1 {
12 | display: inline;
13 | font-size: 32px;
14 | }
15 |
16 | header h1 a:link, header h1 a:visited {
17 | color: #444;
18 | text-decoration: none;
19 | }
20 |
21 | header h2 {
22 | font-size: 16px;
23 | font-style: italic;
24 | color: #999;
25 | }
26 |
27 |
28 | #main {
29 | margin: 0 0 20px;
30 | }
31 |
32 | #add {
33 | margin: 0 0 20px;
34 | }
35 |
36 | #add textarea {
37 | height: 20px;
38 | width: 160px;
39 | padding: 10px;
40 | border: 1px solid #ddd;
41 | }
42 |
43 | #add textarea[name="url"] {
44 | height: 20px;
45 | width: 320px;
46 | padding: 10px;
47 | border: 1px solid #ddd;
48 | }
49 |
50 | #add input[type=submit] {
51 | height: 30px;
52 | width: 100px;
53 | margin: -50px 0 0;
54 | border: 1px solid #ddd;
55 | background: white;
56 | }
57 |
58 | #edit textarea {
59 | height: 20px;
60 | width: 360px;
61 | padding: 10px;
62 | border: 1px solid #ddd;
63 | }
64 |
65 | #edit textarea[name="url"] {
66 | height: 20px;
67 | width: 560px;
68 | padding: 10px;
69 | border: 1px solid #ddd;
70 | }
71 |
72 | #edit input[type=submit] {
73 | height: 30px;
74 | width: 100px;
75 | border: 1px solid #ddd;
76 | background: white;
77 | }
78 |
79 | #edit input[type=checkbox] {
80 | height: 20px;
81 | width: 26px;
82 | }
83 |
84 | article {
85 | border: 1px solid #eee;
86 | border-top: none;
87 | padding: 15px 10px;
88 | }
89 |
90 | article:first-of-type {
91 | border: 1px solid #eee;
92 | }
93 |
94 | article:nth-child(even) {
95 | background: #fafafa;
96 | }
97 |
98 | article.complete {
99 | background: #99CC32;
100 | }
101 |
102 | article.running {
103 | background: #FF7F00;
104 | }
105 |
106 | article h2 {
107 | font-size: 16px;
108 | font-style: italic;
109 | }
110 |
111 | article span {
112 | font-size: 0.8em;
113 | }
114 |
115 | p {
116 | margin: 0 0 5px;
117 | }
118 |
119 | .meta {
120 | font-size: 0.8em;
121 | font-style: italic;
122 | color: #888;
123 | }
124 |
125 | .links {
126 | font-size: 1.8em;
127 | line-height: 0.8em;
128 | float: right;
129 | margin: -10px 0 0;
130 | }
131 |
132 | .links a {
133 | display: block;
134 | text-decoration: none;
135 | }
136 |
--------------------------------------------------------------------------------
/spec/olpc_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative 'spec_helper.rb'
2 |
3 | describe 'Home' do
4 | include Rack::Test::Methods
5 |
6 | it "should load the home page" do
7 | get '/'
8 | last_response.should be_ok
9 | last_response.body.should include('All tasks')
10 | end
11 |
12 | it "should return to home with the created task" do
13 | title = "title-#{Time.now}"
14 | url = "http://www.google.com/#{Time.now}"
15 | post '/create', params = { :title => title, :url => url}
16 | last_response.should be_redirect; follow_redirect!
17 | last_request.url.should include("/")
18 | last_response.body.should include(title)
19 | end
20 |
21 | end
22 |
23 | describe 'Edit task' do
24 | include Rack::Test::Methods
25 |
26 | before do
27 | @task = Task.first
28 | end
29 |
30 | it "should load task to edit" do
31 | get "/#{@task.id}"
32 | last_response.should be_ok
33 | last_response.body.should include('Edit task')
34 | end
35 |
36 | it "should return to home with the updated task" do
37 | title = "title-#{Time.now}"
38 | url = "http://www.google.com/#{Time.now}"
39 | volunteer = "volunteer-#{Time.now}"
40 | post "/#{@task.id}/update", params = { :title => title, :url => url, :volunteer => volunteer}
41 | last_response.should be_redirect; follow_redirect!
42 | last_request.url.should include("/")
43 | last_response.body.should include(volunteer)
44 | end
45 |
46 | end
47 |
48 | describe 'Delete task' do
49 | include Rack::Test::Methods
50 |
51 | before do
52 | @task = Task.first
53 | end
54 |
55 | it "should let user confirm the delete operation" do
56 | get "/#{@task.id}/delete"
57 | last_response.should be_ok
58 | last_response.body.should include('Confirm deletion of task')
59 | end
60 |
61 | it "should return to home without the deleted task" do
62 | post "/#{@task.id}/destroy"
63 | last_response.should be_redirect; follow_redirect!
64 | last_request.url.should include("/")
65 | end
66 |
67 | end
68 |
69 | describe 'Complete task' do
70 | include Rack::Test::Methods
71 |
72 | before do
73 | @task = Task.first
74 | end
75 |
76 | it "should return to home with the completed task" do
77 | get "/#{@task.id}/complete"
78 | last_response.should be_redirect; follow_redirect!
79 | last_request.url.should include("/")
80 | last_response.should be_ok
81 | end
82 |
83 | end
84 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | ZenTest (4.6.2)
5 | addressable (2.2.7)
6 | autotest (4.4.6)
7 | ZenTest (>= 4.4.1)
8 | bcrypt-ruby (3.0.1)
9 | data_objects (0.10.8)
10 | addressable (~> 2.1)
11 | datamapper (1.2.0)
12 | dm-aggregates (~> 1.2.0)
13 | dm-constraints (~> 1.2.0)
14 | dm-core (~> 1.2.0)
15 | dm-migrations (~> 1.2.0)
16 | dm-serializer (~> 1.2.0)
17 | dm-timestamps (~> 1.2.0)
18 | dm-transactions (~> 1.2.0)
19 | dm-types (~> 1.2.0)
20 | dm-validations (~> 1.2.0)
21 | diff-lcs (1.1.3)
22 | dm-aggregates (1.2.0)
23 | dm-core (~> 1.2.0)
24 | dm-constraints (1.2.0)
25 | dm-core (~> 1.2.0)
26 | dm-core (1.2.0)
27 | addressable (~> 2.2.6)
28 | dm-do-adapter (1.2.0)
29 | data_objects (~> 0.10.6)
30 | dm-core (~> 1.2.0)
31 | dm-migrations (1.2.0)
32 | dm-core (~> 1.2.0)
33 | dm-postgres-adapter (1.2.0)
34 | dm-do-adapter (~> 1.2.0)
35 | do_postgres (~> 0.10.6)
36 | dm-serializer (1.2.1)
37 | dm-core (~> 1.2.0)
38 | fastercsv (~> 1.5.4)
39 | json (~> 1.6.1)
40 | json_pure (~> 1.6.1)
41 | multi_json (~> 1.0.3)
42 | dm-sqlite-adapter (1.2.0)
43 | dm-do-adapter (~> 1.2.0)
44 | do_sqlite3 (~> 0.10.6)
45 | dm-timestamps (1.2.0)
46 | dm-core (~> 1.2.0)
47 | dm-transactions (1.2.0)
48 | dm-core (~> 1.2.0)
49 | dm-types (1.2.1)
50 | bcrypt-ruby (~> 3.0.0)
51 | dm-core (~> 1.2.0)
52 | fastercsv (~> 1.5.4)
53 | json (~> 1.6.1)
54 | multi_json (~> 1.0.3)
55 | stringex (~> 1.3.0)
56 | uuidtools (~> 2.1.2)
57 | dm-validations (1.2.0)
58 | dm-core (~> 1.2.0)
59 | do_postgres (0.10.8)
60 | data_objects (= 0.10.8)
61 | do_sqlite3 (0.10.8)
62 | data_objects (= 0.10.8)
63 | fastercsv (1.5.4)
64 | heroku (2.20.1)
65 | launchy (>= 0.3.2)
66 | rest-client (~> 1.6.1)
67 | rubyzip
68 | term-ansicolor (~> 1.0.5)
69 | json (1.6.5)
70 | json_pure (1.6.5)
71 | launchy (2.0.5)
72 | addressable (~> 2.2.6)
73 | mime-types (1.17.2)
74 | multi_json (1.0.4)
75 | pg (0.13.2)
76 | rack (1.4.1)
77 | rack-protection (1.2.0)
78 | rack
79 | rack-test (0.6.1)
80 | rack (>= 1.0)
81 | rake (0.9.2.2)
82 | rest-client (1.6.7)
83 | mime-types (>= 1.16)
84 | rspec (2.8.0)
85 | rspec-core (~> 2.8.0)
86 | rspec-expectations (~> 2.8.0)
87 | rspec-mocks (~> 2.8.0)
88 | rspec-core (2.8.0)
89 | rspec-expectations (2.8.0)
90 | diff-lcs (~> 1.1.2)
91 | rspec-mocks (2.8.0)
92 | rubyzip (0.9.6.1)
93 | sinatra (1.3.2)
94 | rack (~> 1.3, >= 1.3.6)
95 | rack-protection (~> 1.2)
96 | tilt (~> 1.3, >= 1.3.3)
97 | sqlite3 (1.3.5)
98 | stringex (1.3.2)
99 | term-ansicolor (1.0.7)
100 | tilt (1.3.3)
101 | uuidtools (2.1.2)
102 |
103 | PLATFORMS
104 | ruby
105 |
106 | DEPENDENCIES
107 | autotest
108 | datamapper
109 | dm-postgres-adapter
110 | dm-sqlite-adapter
111 | heroku
112 | pg
113 | rack-test
114 | rake
115 | rspec
116 | sinatra
117 | sqlite3
118 |
--------------------------------------------------------------------------------
/public/reset.css:
--------------------------------------------------------------------------------
1 | /*
2 | HTML5 ✰ Boilerplate
3 |
4 | style.css contains a reset, font normalization and some base styles.
5 |
6 | credit is left where credit is due.
7 | much inspiration was taken from these projects:
8 | yui.yahooapis.com/2.8.1/build/base/base.css
9 | camendesign.com/design/
10 | praegnanz.de/weblog/htmlcssjs-kickstart
11 | */
12 |
13 | /*
14 | html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
15 | v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
16 | html5doctor.com/html-5-reset-stylesheet/
17 | */
18 |
19 | html, body, div, span, object, iframe,
20 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
21 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
22 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
23 | fieldset, form, label, legend,
24 | table, caption, tbody, tfoot, thead, tr, th, td,
25 | article, aside, canvas, details, figcaption, figure,
26 | footer, header, hgroup, menu, nav, section, summary,
27 | time, mark, audio, video {
28 | margin:0;
29 | padding:0;
30 | border:0;
31 | font-size:100%;
32 | font: inherit;
33 | vertical-align:baseline;
34 | }
35 |
36 | article, aside, details, figcaption, figure,
37 | footer, header, hgroup, menu, nav, section {
38 | display:block;
39 | }
40 |
41 | blockquote, q { quotes:none; }
42 |
43 | blockquote:before, blockquote:after,
44 | q:before, q:after { content:''; content:none; }
45 |
46 | ins { background-color:#ff9; color:#000; text-decoration:none; }
47 |
48 | mark { background-color:#ff9; color:#000; font-style:italic; font-weight:bold; }
49 |
50 | del { text-decoration: line-through; }
51 |
52 | abbr[title], dfn[title] { border-bottom:1px dotted; cursor:help; }
53 |
54 | table { border-collapse:collapse; border-spacing:0; }
55 |
56 | hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; }
57 |
58 | input, select { vertical-align:middle; }
59 |
60 | /* END RESET CSS */
61 |
62 | /* font normalization inspired by from the YUI Library's fonts.css: developer.yahoo.com/yui/ */
63 | body { font:13px/1.231 sans-serif; *font-size:small; } /* hack retained to preserve specificity */
64 | select, input, textarea, button { font:99% sans-serif; }
65 |
66 | /* normalize monospace sizing
67 | * en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
68 | pre, code, kbd, samp { font-family: monospace, sans-serif; }
69 |
70 | /*
71 | * minimal base styles
72 | */
73 |
74 | body, select, input, textarea {
75 | /* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */
76 | color: #444;
77 | /* set your base font here, to apply evenly */
78 | /* font-family: Georgia, serif; */
79 | }
80 |
81 | /* headers (h1,h2,etc) have no default font-size or margin. define those yourself. */
82 | h1,h2,h3,h4,h5,h6 { font-weight: bold; }
83 |
84 | /* always force a scrollbar in non-IE: */
85 | html { overflow-y: scroll; }
86 |
87 | /* accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */
88 | a:hover, a:active { outline: none; }
89 |
90 | a, a:active, a:visited { color: #607890; }
91 | a:hover { color: #036; }
92 |
93 | ul, ol { margin-left: 2em; }
94 | ol { list-style-type: decimal; }
95 |
96 | /* remove margins for navigation lists */
97 | nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
98 |
99 | small { font-size: 85%; }
100 | strong, th { font-weight: bold; }
101 |
102 | td { vertical-align: top; }
103 |
104 | /* set sub, sup without affecting line-height: gist.github.com/413930 */
105 | sub, sup { font-size: 75%; line-height: 0; position: relative; }
106 | sup { top: -0.5em; }
107 | sub { bottom: -0.25em; }
108 |
109 | pre {
110 | /* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */
111 | white-space: pre; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word;
112 | padding: 15px;
113 | }
114 |
115 | textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */
116 |
117 | .ie6 legend, .ie7 legend { margin-left: -7px; } /* thnx ivannikolic! */
118 |
119 | /* align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */
120 | input[type="radio"] { vertical-align: text-bottom; }
121 | input[type="checkbox"] { vertical-align: bottom; }
122 | .ie7 input[type="checkbox"] { vertical-align: baseline; }
123 | .ie6 input { vertical-align: text-bottom; }
124 |
125 | /* hand cursor on clickable input elements */
126 | label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
127 |
128 | /* webkit browsers add a 2px margin outside the chrome of form elements */
129 | button, input, select, textarea { margin: 0; }
130 |
131 | /* colors for form validity */
132 | input:valid, textarea:valid { }
133 | input:invalid, textarea:invalid {
134 | border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red;
135 | }
136 | .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
137 |
138 | /* These selection declarations have to be separate.
139 | No text-shadow: twitter.com/miketaylr/status/12228805301
140 | Also: hot pink. */
141 | ::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; }
142 | ::selection { background:#FF5E99; color:#fff; text-shadow: none; }
143 |
144 | /* j.mp/webkit-tap-highlight-color */
145 | a:link { -webkit-tap-highlight-color: #FF5E99; }
146 |
147 | /* make buttons play nice in IE:
148 | www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */
149 | button { width: auto; overflow: visible; }
150 |
151 | /* bicubic resizing for non-native sized IMG:
152 | code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */
153 | .ie7 img { -ms-interpolation-mode: bicubic; }
154 |
--------------------------------------------------------------------------------