diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..9151357bd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# See https://help.github.com/articles/dealing-with-line-endings + +# Set default behaviour, in case users don't have core.autocrlf set. +* text=auto + diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml new file mode 100644 index 000000000..e62589a47 --- /dev/null +++ b/.github/workflows/rspec.yml @@ -0,0 +1,28 @@ +name: RSpec + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + rspec: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Install dependencies + run: bundle install + + - name: Run test and linter + run: + bundle exec rake diff --git a/.gitignore b/.gitignore index a90f79a61..5aa12c79e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .DS_Store +.bundle +.env .idea -Thumbs.db .rvmrc -.ruby-version .sass-cache +Thumbs.db +.byebug_history diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..be94e6f53 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.2.2 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..f2a971aa7 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.2.2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 468120b0f..937117101 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,8 +3,8 @@ RailsBridge wouldn't be what it is without your help. Thanks for contributing. We ask that contributions be made as pull requests via GitHub. If those words -are totally foreign to you -[see here](#its-my-first-time-on-github-ever-what-do-i-do) +are totally foreign to you, +[see here](#its-my-first-time-on-github-ever-what-do-i-do). # When Submitting a Pull Request @@ -16,18 +16,18 @@ maintainers to love you. :heart:* - If you have an existing fork, please make sure it's up to date. It just makes your life easier! If not, make sure you fork *before* cloning, otherwise you'll need to spend some time juggling remotes. - Look at the section "Pull in upstream changes" in GitHub's + Look at the section "Keep your fork synced" in GitHub's [Fork A Repo](https://help.github.com/articles/fork-a-repo) article. - Create a local topic branch before you start working. This branch is going to be named for what you plan to change. `fix-typo-in-slides`, `move-resources`, and `mountain-lion-support` are all good names for topic branches. If you've never created a local branch before, you can use `git checkout -b - new-branch-name` + new-branch-name`. ## Before Submitting -- Please, please, *please* run `rake` from the terminal before you submit. It +- Please, please, *please* run `bundle exec rake` from the terminal before you submit. It not only runs our test suite but also checks the docs for syntax errors. - Push to a branch on GitHub. Just like you developed in a local branch, you @@ -39,25 +39,20 @@ maintainers to love you. :heart:* ## Submitting a Pull Request -- Read the article ["Using Pull Requests"](https://help.github.com/articles/using-pull-requests) - on GitHub. - -- When you submit a pull request (PR), make sure your topic branch is selected - on the right-hand side of the Pull Request Preview page, like this: - - ![choosing pull request branch](https://github-images.s3.amazonaws.com/help/change-branches.png) +- Read the GitHub Guide on [Forking](https://guides.github.com/activities/forking/), especially the part about + [Pull Requests](https://guides.github.com/activities/forking/#making-a-pull-request). - Remember, pull requests are submitted *from* your repo, but show up on the *upstream* repo. -## Discussion and Waiting On A Merge +## Discussion and Waiting On a Merge - Every pull request will receive a response from the team. - Not every pull request will be merged as is. - Not every pull request will be merged at all. - If a pull request falls significantly behind master, we may ask that you close it, rebase your changes off of master, and submit a new pull request. -- feel free to "ping" the team by adding a short comment to your pull request +- Feel free to "ping" the team by adding a short comment to your pull request if it's been more than a week with no reply ## After your merge has been accepted @@ -74,21 +69,23 @@ maintainers to love you. :heart:* # It's My First Time on GitHub Ever What Do I Do? -Relax, you came to the right place. In order to contribute you'll need to be +Relax, you came to the right place. In order to contribute, you'll need to be able to familiarize yourself with some concepts from git and GitHub. It's going -to be a lot of information, but you're :sparkles:awesome:sparkles:! So you it'll +to be a lot of information, but you're :sparkles:awesome:sparkles:! So you'll be fine. First, you'll need a GitHub account, which is totally free. You can sign up -[here](https://github.com/signup/free). +[here](https://github.com/join). -Next, browse the [GitHub Help site](https://help.github.com). +Next, browse the [GitHub Help site](https://help.github.com) and the +[GitHub Guides](https://guides.github.com/). The Help Site is more technical, and the +Guides are very easy to understand tutorials. You'll want to read about [forking](https://help.github.com/articles/fork-a-repo) and then make your own fork of [railsbridge/docs](https://github.com/railsbridge/docs). Once you've done so, you can clone it and get started by reading up on [what to do when -submitting a pull request](#when-submitting-a-pull-request), read up on +submitting a pull request](#when-submitting-a-pull-request), and read up on [pull requests](https://help.github.com/articles/using-pull-requests) themselves. @@ -98,15 +95,13 @@ anyone who wants to contribute do so. His email is behind that profile link. # Closing -If you haven't taken the time to go through the Git Immersion lab. - -Do it. It's worth it no matter how much git-fu you have. -http://gitimmersion.com +If you haven't taken the time yet, go through the [Git Immersion lab](http://gitimmersion.com) +at . Do it. It's worth it no matter how much git-fu you have. Also, [Pro Git](http://git-scm.com/book) is a great (and free!) book about Git. -We apologize for how long this document is! Hopefully it addressed -most of your concerns about git, contributing, and github. Feel free +We apologize for how long this document is! Hopefully, it addressed +most of your concerns about git, contributing, and GitHub. Feel free to ask more questions on the [railsbridge-workshops](http://groups.google.com/group/railsbridge-workshops) mailing list. And we're open to any suggestions about improvements, diff --git a/Gemfile b/Gemfile index 90f2f606e..32eb465d5 100644 --- a/Gemfile +++ b/Gemfile @@ -1,24 +1,32 @@ -source :rubygems +source "https://rubygems.org/" -gem 'active_support' -gem "erector", "~>0.9.0" +ruby '3.2.2' + +gem 'activesupport' +gem "erector", github: "erector/erector" gem "sinatra" +gem "sinatra-contrib" gem "nokogiri" gem "thin" -gem "eventmachine", "1.0.0.rc.4" gem 'rack-codehighlighter' gem 'coderay' -gem "deckrb", ">=0.3.0" +gem "deckrb" gem "sass" gem "redcarpet" +gem "rubyzip" +gem "i18n" +gem 'font-awesome-sass' +gem 'bootstrap-sass' +gem 'jquery-cdn' +gem 'sprockets' +gem 'ffi' +gem 'backports' group :development do - gem "wrong", ">=0.6.2" gem "rspec" gem "rerun" gem "rake" gem "rack-test" - gem "files", ">=0.3.0" - gem "bourbon" # do 'cd public/css; bourbon update' to get new bourbon + gem 'files', github: "alexch/files" gem 'rb-fsevent', :platform => :ruby end diff --git a/Gemfile.lock b/Gemfile.lock index 13410a5a2..346a686d9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,15 +1,41 @@ +GIT + remote: https://github.com/alexch/files.git + revision: 4938dd85ed79f421b808e8798764661a71cab5a4 + specs: + files (0.4.0) + +GIT + remote: https://github.com/erector/erector.git + revision: 32cb38e3381889295183230caf8bf334d2f0e82a + specs: + erector (0.10.0) + haml + jeweler + nokogiri + sass + treetop (>= 1.2.3) + GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: - active_support (3.0.0) - activesupport (= 3.0.0) - activesupport (3.0.0) - bourbon (3.0.1) - sass (>= 3.2.0) - thor - coderay (1.0.8) - daemons (1.1.9) - deckrb (0.5.0) + activesupport (6.0.6.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.4.0) + autoprefixer-rails (10.4.15.0) + execjs (~> 2) + backports (3.23.0) + bootstrap-sass (3.4.1) + autoprefixer-rails (>= 5.2.1) + sassc (>= 2.0.0) + builder (3.2.4) + coderay (1.1.3) + concurrent-ruby (1.1.9) + daemons (1.2.4) + deckrb (0.5.2) coderay erector (>= 0.9.0) json @@ -19,79 +45,158 @@ GEM redcarpet (~> 2) thin trollop - diff-lcs (1.1.3) - erector (0.9.0) - treetop (>= 1.2.3) - eventmachine (1.0.0.rc.4) - eventmachine (1.0.0.rc.4-x86-mingw32) - files (0.3.1) - json (1.7.6) - listen (0.7.2) - nokogiri (1.5.6) - nokogiri (1.5.6-x86-mingw32) - polyglot (0.3.3) - predicated (0.2.6) - rack (1.5.2) - rack-codehighlighter (0.5.0) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + diff-lcs (1.5.0) + eventmachine (1.2.3) + execjs (2.8.1) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + ffi (1.15.5) + font-awesome-sass (6.4.2) + sassc (~> 2.0) + git (1.11.0) + rchardet (~> 1.8) + github_api (0.16.0) + addressable (~> 2.4.0) + descendants_tracker (~> 0.0.4) + faraday (~> 0.8, < 0.10) + hashie (>= 3.4) + mime-types (>= 1.16, < 3.0) + oauth2 (~> 1.0) + haml (6.2.3) + temple (>= 0.8.2) + thor + tilt + hashie (5.0.0) + highline (2.1.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jeweler (2.3.9) + builder + bundler + git (>= 1.2.5) + github_api (~> 0.16.0) + highline (>= 1.6.15) + nokogiri (>= 1.5.10) + psych + rake + rdoc + semver2 + jquery-cdn (2.2.4) + sprockets (>= 2) + json (2.6.1) + jwt (2.7.1) + listen (3.0.8) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + mime-types (2.99.3) + mini_portile2 (2.8.0) + minitest (5.20.0) + multi_json (1.15.0) + multi_xml (0.6.0) + multipart-post (2.3.0) + mustermann (3.0.0) + ruby2_keywords (~> 0.0.1) + nokogiri (1.14.5) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) + oauth2 (1.4.8) + faraday (>= 0.8, < 3.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + polyglot (0.3.5) + psych (5.1.1.1) + stringio + racc (1.7.2) + rack (2.2.4) + rack-codehighlighter (0.6.0) nokogiri (>= 1.4.1) - rack (>= 1.0.0) - rack-protection (1.3.2) - rack - rack-test (0.6.2) + rack (>= 2.0.0) + rack-protection (3.1.0) + rack (~> 2.2, >= 2.2.4) + rack-test (0.6.3) rack (>= 1.0) - rake (10.0.3) - rb-fsevent (0.9.3) - redcarpet (2.2.2) - rerun (0.8.0) - listen - rspec (2.12.0) - rspec-core (~> 2.12.0) - rspec-expectations (~> 2.12.0) - rspec-mocks (~> 2.12.0) - rspec-core (2.12.2) - rspec-expectations (2.12.1) - diff-lcs (~> 1.1.3) - rspec-mocks (2.12.2) - ruby2ruby (2.0.3) - ruby_parser (~> 3.1) - sexp_processor (~> 4.0) - ruby_parser (3.1.1) - sexp_processor (~> 4.1) - sass (3.2.5) - sexp_processor (4.1.4) - sinatra (1.3.4) - rack (~> 1.4) - rack-protection (~> 1.3) - tilt (~> 1.3, >= 1.3.3) - thin (1.5.0) - daemons (>= 1.0.9) - eventmachine (>= 0.12.6) - rack (>= 1.0.0) - thor (0.17.0) - tilt (1.3.3) - treetop (1.4.12) - polyglot - polyglot (>= 0.3.1) - trollop (2.0) - wrong (0.7.0) - diff-lcs (~> 1.1.2) - predicated (~> 0.2.6) - ruby2ruby (>= 2.0.1) - ruby_parser (>= 3.0.1) - sexp_processor (>= 4.0) + rake (13.1.0) + rb-fsevent (0.9.8) + rb-inotify (0.9.8) + ffi (>= 0.5.0) + rchardet (1.8.0) + rdoc (6.5.0) + psych (>= 4.0.0) + redcarpet (2.3.0) + rerun (0.14.0) + listen (~> 3.0) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sassc (2.4.0) + ffi (~> 1.9) + semver2 (3.4.2) + sinatra (3.1.0) + mustermann (~> 3.0) + rack (~> 2.2, >= 2.2.4) + rack-protection (= 3.1.0) + tilt (~> 2.0) + sinatra-contrib (3.1.0) + multi_json + mustermann (~> 3.0) + rack-protection (= 3.1.0) + sinatra (= 3.1.0) + tilt (~> 2.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + stringio (3.0.8) + temple (0.10.3) + thin (1.8.1) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0, >= 1.0.4) + rack (>= 1, < 3) + thor (1.3.0) + thread_safe (0.3.6) + tilt (2.0.11) + treetop (1.6.8) + polyglot (~> 0.3) + trollop (2.1.2) + tzinfo (1.2.10) + thread_safe (~> 0.1) + zeitwerk (2.6.12) PLATFORMS ruby - x86-mingw32 DEPENDENCIES - active_support - bourbon + activesupport + backports + bootstrap-sass coderay - deckrb (>= 0.3.0) - erector (~> 0.9.0) - eventmachine (= 1.0.0.rc.4) - files (>= 0.3.0) + deckrb + erector! + ffi + files! + font-awesome-sass + i18n + jquery-cdn nokogiri rack-codehighlighter rack-test @@ -100,7 +205,15 @@ DEPENDENCIES redcarpet rerun rspec + rubyzip sass sinatra + sinatra-contrib + sprockets thin - wrong (>= 0.6.2) + +RUBY VERSION + ruby 3.2.2p53 + +BUNDLED WITH + 2.3.26 diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..3d55702ae --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,40 @@ +The documentation (including anything under the `sites` subdir as well as some hardcoded text elsewhere) is licensed under a Creative Commons license ([CC-BY, specifically](http://creativecommons.org/licenses/by/3.0/)), which means you're welcome to share, remix, or use our content commercially. We just ask for attribution. + + You are free to: + Share — copy and redistribute the material in any medium or format + Adapt — remix, transform, and build upon the material for any purpose, even commercially. + The licensor cannot revoke these freedoms as long as you follow the license terms. + + Under the following terms: + Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. + No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. + + Notices: + You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. + No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. + + +Source code is under an [MIT License](http://opensource.org/licenses/MIT) (MIT): + + Copyright (c) 2010-2014 by RailsBridge + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + + diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..e06ad9dc8 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: rackup -s thin -p $PORT diff --git a/README.md b/README.md index 54c19cfdc..6eeccd756 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,77 @@ -# The Railsbridge Documentation Project +# The RailsBridge Documentation Project [![Build Status](https://travis-ci.org/railsbridge/docs.png)](https://travis-ci.org/railsbridge/docs) -[List of Contributors](https://github.com/railsbridge/docs/contributors) -- join us! +## Overview -# Usage +This is a Sinatra app, deployed at . The RailsBridge documentation project is home to a few subprojects, including the RailsBridge installfest instructions, which leads students through the various complicated setup instructions for getting Ruby, Rails, Git, etc. installed on their computer (whatever combination of computer, OS, and version they happened to bring to the workshop!), as well as the RailsBridge workshop "Suggestotron" curriculum. - bundle install - rake run - -If the above fails (say, because `rerun` doesn't work on your system), try - - rackup - -Then open in a web browser. - -If you make any changes, and especially before a pull request, run - - rake spec - -which will run some unit tests and also do syntax validation on all pages, to make sure you didn't break anything. - -When you submit a Pull Request, Travis CI will also run all the tests. - -# Overview - -This is a Sinatra app, deployed at . The Railsbridge documentation project is home to a few subprojects, including the Railsbridge installfest instructions, which leads students through the various complicated setup instructions for getting Ruby, Rails, Git, etc. installed on their computer (whatever combination of computer, OS, and version they happened to bring the the workshop!), as well as the Railsbridge workshop "Suggestotron" curriculum. - -Each subproject (a "site") comprises files stored under the "sites" directory; for instance, the installfest instructions are located at ROOT/sites/installfest, while the curriculum can be found under ROOT/sites/curriculum. +Each subproject (a "site") comprises files stored under the "sites" directory; for instance, the installfest instructions are located at ROOT/sites/en/installfest, while the intro rails curriculum can be found under ROOT/sites/en/intro-to-rails. (The "en" means "English" -- see "Locales" below.) These files can be in any of these formats: -* `.step` for StepFile -* `.md` for Markdown -* `.mw` for MediaWiki -* `.deck.md` for deck.rb - -(If multiple files exist with the same base name, .step is preferred over .md, and .md over .mw.) - -Markdown is a lightweight markup language designed by John Gruber. The syntax is described at the [Daring Fireball Markdown Page](http://daringfireball.net/projects/markdown/syntax) plus [GitHub Flavored Markdown](http://github.github.com/github-flavored-markdown/) extensions. (This README is written in Markdown.) - -MediaWiki is the format of pages on the Devchix Wiki. This format is not fully supported and is provided as a temporary bridge while we move materials from the Devchix Wiki into this app. - -StepFile is a new, Ruby-based DSL for describing complex, nested instructions in clear, reusable chunks. - -[Deck.rb](https://github.com/alexch/deck.rb) converts Markdown files into an interactive in-browser HTML+JavaScript slide deck. - -#Organizer Instructions - -Slide contents that change with each workshop are contained in three files under the workshop project. The 'hello and welcome, this is when the breaks are' presentation slides are in current.deck.md. The 'this is what we will learn today' slides are in welcome.deck.md. And the 'this is what we have learned' slides are in closing.deck.md. - -To change those contents, clone this repo, make changes, and then to include your changes in the publicly available repo, send a pull request. - -##clone the repo - -On a command line cd into a directory for the installfest app to live on your machine. For example, to put it in your home directory: `cd ~` - -Then clone the repository from github: -`git clone git://github.com/railsbridge/docs.git` - -And open it in the editor of your choice. - -## changes +* `.step` for [StepFile](step_file_reference.md) +* `.md` for [Markdown](http://daringfireball.net/projects/markdown/syntax) +* `.mw` for MediaWiki (temporary) +* `.deck.md` for [deck.rb](https://github.com/alexch/deck.rb) -If you want to change the name of the hosts and sponsors in the intro slides, open current.deck.md, find and replace the name of the previous hosts and sponsors on the slide deck. To include their logos, drop an image inside the public/img folder. If their are more or fewer hosts and sponsors than in the previous workshop, create new slides! +(If multiple files exist with the same base name, `.step` is preferred over `.md`, and `.md` over `.mw`.) -Check that the current.deck.md and closing.deck.md files fit the workshop. +## Usage -## pull request - -When you're happy with how you've changed the repository, commit it. In the command line, add your changes with `git add .` and commit them locally with `git commit -m "super descriptive message "`. Push it to your own fork of the repository with`git push`. The next step, submitting a pull request is used to incorporate your changes into RailsBridge's version of the repository. Navigate to your forked version of the repository on github (and check the commits tab to see your changes!). There's a pull request button near the top of the page, and after clicking you can add a title and explanations of your changes. After submitting, it may take a while for people to review and accept your changes. Poke people. Make sure they check it out. Check out Github's help page on the pull requests. - -# StepFile Reference - -A StepFile is a [DSL](http://en.wikipedia.org/wiki/domain+specific+language) for describing a series of instructions, possibly nested inside other instructions. Technically speaking it is an *internal Ruby DSL* which means it parses as Ruby code. Nested blocks use Ruby's do..end structures, named options use Ruby's hash syntax, and string options may use any of Ruby's myriad string formats (double-quote, single-quote, here doc, percent-q, etc.) - -Here Docs are especially useful with `message`s since you can just dump in markdown between `<<-MARKDOWN` and `MARKDOWN` declarations. - -## steps - -* steps support nested content via `do` and `end` -* indents the nested block content -* inside the nested block, the step count is reset, then resumes afterwards, e.g. - - Step 1: steal underpants - Step 2: do the hokey pokey - | Step 1: put your left foot in - | Step 2: take your left foot out - | Step 3: put your left foot in again - | Step 4: shake it all about - Step 3: profit! - -`step "name"` - - * creates a new step heading - * maintains a count of steps at the same level - * prefixes name with e.g. "Step 1:" - -`link "name"` - - * links to a step whose file name is `filename` - -`next_step "name"` - - * makes a new step named "Next Step:" - * links to a step whose file name is `filename` - -`choice` - - * creates a step which is named "Choose between..." - * choice doesn't make any sense without a nested block - * nested block usually contains `option` steps - -`choice "name"` - - * same as above, but named "Choose name" - * if you want ellipses, add them yourself - -`option "name"` - - * creates a step which is named "Option 1: foo" instead "Step 1: foo" - * supports nested blocks, which reset the step count again - -`verify` - -`verify "name"` - - * usually contains `console` and `result` notes - * kind of like a step, but doesn't increment the number count + bundle install + bundle exec rake run -`tip "name"` +If the above fails (say, because `rerun` doesn't work on your system), try - * called out in a blue box - * the name is *not* markdown, but is a bold title for the tip box - * content should be inside a nested block + rackup -## messages +Then open in a web browser, and verify that you can navigate the installfest slides. -`message "text"` +## Locales - * makes a paragraph of text anywhere in the document - * the text parameter is passed through a Markdown converter - -`important "text"` +To serve sites from "sites/en", use `bundle exec rake run` or a vanilla deploy. - * like a message, but called out in a red box +To server sites from another locale (say, "es" or Spanish)... -`todo "text"` +### Run Localized Site Locally - * meant as a note to future authors - * set aside from surrounding text (in brackets and italics) - * [should these be made invisible for students?] - -## special + $ SITE_LOCALE=es bundle exec rake run -Special elements do *not* format their text as Markdown. +The server listens on `0.0.0.0:9292`. -`console "text"` +Now you have to set up subdomain mappings for the site. If you have Pow, run: - * indicates that the student should type something in the terminal - * says "type this in the terminal:" and then puts the text in a `pre` block - * [should this be named `terminal` or `type` instead?] + $ echo 9292 > ~/.pow/railsbridge # works for any subdomain -`result "text"` +If you don't have Pow, add the following line to `/etc/hosts`: - * indicates that the student should see some output in the terminal - * says "expected result:" and then puts the text in a `pre` block + 127.0.0.1 es.railsbridge.dev # works for single subdomain +Now you can access `http://es.railsbridge.dev:9292` for debugging. +### Running on a Server -## erector elements +Just make sure the server responds to a locale subdomain: `http://es.railsbridge.org` -StepFile is an [Erector](http://erector.rubyforge.org)-based DSL, so if you want to insert HTML tags or other stuff, use the appropriate Erector methods, e.g. +### Temporary Testing - step "figure out your OS version" do - message "Mac OS has code names, including:" - table do - tr do - th "Leopard" - td "OS X 10.5" - end - tr do - th "Snow Leopard" - td "OS X 10.6" - end - end - end +Use a `locale` or `l` parameter: `http://docs.railsbridge.org/?l=es`. +Note that in this mode, links are not rewritten, so if they fail you will have to manually add the parameter again. -# TODO (features) +## Contributing -* big fat checkboxes -* checking the box should make the step go green -* unchecked steps should be orange -* maybe all unchecked but the next step should be gray -* JS expando-collapso doohickeys -* inlined steps, esp. verify, to share code without switching pages -* readme: examples -* `result` should take a regexp; then it will have an input field where the user can paste in what they got and it'll check it... for partial version strings -* `console` should start with a $ - * but what about multi-line commands (with e.g. config data on line 2), or multiple commands? -* put a [?] next to all the `terminal` boxes with a popup or link explaining what a terminal is -* links to other docs (the `link` directive) - * rounded boxes with arrows - * add a "back" link (or "next") to go back to the linking list - * side scrolling effect -* add a `tip` directive -* add a `details` block (nested, maybe collapsed) -* each page/step could say which OS it's for, either for information, or to actively hide other OS step -* "result" should take "or" -* "result" needs to distinguish between literal console output, and a description of what you will see -* "yay" directive ("Congratulations!") -* prerequisites -* "sudo" directive -- for danger color and popup "what is sudo?" help +Check out [CONTRIBUTING.md](CONTRIBUTING.md) to see how to join our [list of contributors](https://github.com/railsbridge/docs/contributors)! -# TODO (technical) -* test all pages during "startup" - * growl if broken -* 404 should show page with TOC and stuff -* StepFile object -* move fonts local +## License -# TODO (content) -* install ALL the operating systems! -* troubleshooting page -* look into installation scripts - https://github.com/joshfng/railsready - +The documentation (including anything under the `sites` subdir as well as some hardcoded text elsewhere) is licensed under a Creative Commons license ([CC-BY, specifically](http://creativecommons.org/licenses/by/3.0/)), which means you're welcome to share, remix, or use our content commercially. We just ask for attribution. +The code is licensed under an [MIT license](http://opensource.org/licenses/MIT), like Ruby itself. Copyright (c) 2010-2014 by RailsBridge. -# Credits +## Other Resources -some icons from https://github.com/kennethreitz/open-icons +- [StepFile Reference](step_file_reference.md) +- Workshop organizers: See http://docs.railsbridge.org/workshop for example slide decks you can use in your opening/closing presentations. diff --git a/Rakefile b/Rakefile index e7e02c395..835c63778 100644 --- a/Rakefile +++ b/Rakefile @@ -1,17 +1,21 @@ -require 'rspec/core/rake_task' - -task :default => :spec - def windows? Rake::Win32.windows? end -RSpec::Core::RakeTask.new(:spec) do |t| - t.pattern = "spec/**/*_spec.rb" - t.rspec_opts = - "--format d --backtrace" - t.rspec_opts += " --color" if !windows? - # t.ruby_opts="-w" +begin + require 'rspec/core/rake_task' + + task :default => :spec + + desc "Run all specs" + RSpec::Core::RakeTask.new(:spec) do |t| + t.pattern = "spec/**/*_spec.rb" + t.rspec_opts = + "--format d" + t.rspec_opts += " --color" unless windows? + # t.ruby_opts="-w" + end +rescue LoadError # swallow Heroku deploy error end def rerun cmd, rerun_opts = nil @@ -24,5 +28,5 @@ end desc "run the site locally (visit http://localhost:9292)" task :run do - rerun "rackup -s thin" + rerun "rackup -s thin -p #{ENV['PORT'] || 9292}" end diff --git a/app.rb b/app.rb index 58794ac5b..9070515e4 100755 --- a/app.rb +++ b/app.rb @@ -1,9 +1,13 @@ require 'sinatra' +require 'sinatra/cookies' require 'digest/md5' require 'erector' - -# require 'wrong' -# include Wrong::D +require 'i18n' +require 'i18n/backend/fallbacks' +require 'font-awesome-sass' +require 'bootstrap-sass' +require 'zip' +require 'tmpdir' here = File.expand_path File.dirname(__FILE__) lib = File.expand_path "#{here}/lib" @@ -16,25 +20,59 @@ require "raw_page" require "deck" require "deck/rack_app" +require "titleizer" +require "site" +require 'sprockets' +require 'jquery-cdn' -class InstallFest < Sinatra::Application # should this be Sinatra::Base instead? +class InstallFest < Sinatra::Application # todo: use Sinatra::Base instead, with more explicit config include Erector::Mixin + # Set available locales in Array of Strings; this is also used when + # checking availability in dynamic locale assignment, they must be strings. + AVAILABLE_LOCALES = %w[en es zh-tw].freeze + DEFAULT_SITE = "docs" + + set :assets, Sprockets::Environment.new + settings.assets.append_path "assets/stylesheets" + settings.assets.append_path "assets/javascripts" + settings.assets.append_path "public/fonts" + settings.assets.append_path Bootstrap.javascripts_path + JqueryCdn.install(settings.assets) + + if settings.environment == :development + set :cookie_options, domain: nil + end + + configure do + I18n::Backend::Simple.include(I18n::Backend::Fallbacks) + I18n.load_path = Dir[File.join(settings.root, 'locales', '**/*.yml')] + I18n.backend.load_translations + + I18n.available_locales = AVAILABLE_LOCALES + I18n.enforce_available_locales = true + I18n.default_locale = :en + end + def initialize super @here = File.expand_path(File.dirname(__FILE__)) - @default_site = "installfest" - set_downstream_app # todo: test end attr_reader :here attr_writer :default_site + # todo: test + # returns the most-specific hostname component, e.g. "foo" for "foo.example.com" + def subdomain + host.split(".").first + end + def default_site - if host && sites.include?(site = host.split(".").first) + if host && sites.include?(site = subdomain) site else - @default_site + DEFAULT_SITE end end @@ -46,20 +84,47 @@ def site_dir "#{sites_dir}/#{params[:site]}" end - def sites_dir= dir - @sites_dir = dir.tap { set_downstream_app } + def sites_dir + Site.sites_dir end - def set_downstream_app - @app = ::Deck::RackApp.public_file_server + def sites + Dir["#{sites_dir}/*"].map { |path| File.basename(path) } end - def sites_dir - @sites_dir || "#{@here}/sites" + def redirect_sites + { + 'curriculum' => 'intro-to-rails', + 'intermediate-rails' => 'message-board' + } end - def sites - Dir["#{sites_dir}/*"].map{|path| path.split('/').last} + def redirect_pages + { + 'installfest/macintosh' => 'installfest/macOS' + } + end + + before do + begin + I18n.locale = dynamic_locale + rescue I18n::InvalidLocale + I18n.locale = I18n.default_locale + end + end + + after '/:site/*' do + # Any real page (starts with a site and doesn't end with an extension) + # gets saved as the 'back' for the next pageload. + if sites.include?(params[:site]) && !request.fullpath.match(/\..+\z/) + cookies[:docs_back_path] = request.fullpath + end + end + + def dynamic_locale + (params && (params[:locale] || params[:l])) || + (host && AVAILABLE_LOCALES.include?(subdomain) && subdomain) || + (ENV['SITE_LOCALE']) end def src @@ -67,13 +132,13 @@ def src end def ext - doc_path.split('.').last + $1 if doc_path.match(/\.(.*)/) end def doc_path @doc_path ||= begin base = "#{site_dir}/#{params[:name]}" - %w{step md deck.md mw}.each do |ext| + Site::DOC_TYPES.each do |ext| path = "#{base}.#{ext}" return path if File.exist?(path) end @@ -81,50 +146,64 @@ def doc_path end end - def title_for_page page_name - page_name.split(/[-_]/).map do |w| - w == "osx" ? "OS X" : w.capitalize - end.join(' ') + def back_path + path_parts = cookies[:docs_back_path]&.split('/') + return unless path_parts && path_parts.length > 2 + + current_path_parts = request.fullpath.split('/') + prev_site, prev_page = path_parts[1..2] + this_site, this_page = current_path_parts[1..2] + + return unless prev_site == this_site + return if prev_page == this_page + + prev_page end def render_page begin options = { - site_name: params[:site], - page_name: params[:name], - doc_title: title_for_page(params[:name]), - doc_path: doc_path, - back: params[:back], - src: src, + site: Site.named(params[:site]), + page_name: params[:name], + doc_title: Titleizer.title_for_page(params[:name]), + doc_path: doc_path, + back: back_path, + src: src, + locale: I18n.locale, } case ext - when "md" - if doc_path =~ /\.deck\.md$/ # todo: refactor - # todo: render with page nav elements too - slides = Deck::Slide.split(src) - Deck::SlideDeck.new(:slides => slides).to_pretty - else + when "deck.md", "deck" + render_deck + + when "md" MarkdownPage.new(options).to_html - end - when "mw" - MediaWikiPage.new(options).to_html + when "mw" + MediaWikiPage.new(options).to_html - when "step" - StepPage.new(options).to_html + when "step" + StepPage.new(options).to_html - else - raise "unknown file type #{doc_path}" + else + raise "unknown file type #{doc_path}" end - rescue Errno::ENOENT => e p e + e.backtrace.each do |line| + break if line =~ /sinatra\/base.rb/ + puts "\t"+line + end halt 404 end end + def render_deck + slides = Deck::Slide.split(src) + Deck::SlideDeck.new(:slides => slides).to_pretty + end + before do expires 3600, :public end @@ -133,6 +212,22 @@ def render_page halt 404 end + get "/assets/:file.:ext" do + mime_type = { + 'js' => 'application/javascript', + 'css' => 'text/css', + 'ttf' => 'application/font-ttf', + 'woff' => 'application/font-woff' + }[params[:ext]] + content_type mime_type if mime_type + settings.assets["#{params[:file]}.#{params[:ext]}"] + end + + get '/fonts/font-awesome/:file' do + font_path = File.join(FontAwesome::Sass.gem_path, 'assets', 'fonts', 'font-awesome', params[:file]) + send_file font_path + end + get "/" do redirect "/#{default_site}/" end @@ -140,11 +235,12 @@ def render_page get "/:site/:name/src" do begin RawPage.new( - site_name: params[:site], - page_name: params[:name], - doc_title: doc_path.split('/').last, - doc_path: doc_path, - src: src + site: Site.named(params[:site], I18n.locale), + page_name: params[:name], + doc_title: File.basename(doc_path), + doc_path: doc_path, + src: src, + locale: I18n.locale, ).to_html rescue Errno::ENOENT => e p e @@ -152,53 +248,83 @@ def render_page end end + get "/:site/:name.zip" do + manifest_path = "#{site_dir}/#{params[:name]}.zip-manifest" + if File.exist?(manifest_path) + manifest_files = File.read(manifest_path).split("\n") + zip_path = File.join(Dir.tmpdir, "#{params[:name]}.zip") + FileUtils.rm_rf(zip_path) + Zip::File.open(zip_path, Zip::File::CREATE) do |zipfile| + manifest_files.each do |filename| + filename_without_first_directory = filename.split(File::SEPARATOR)[1..-1].join(File::SEPARATOR) + location_in_zip = File.join(params[:name], filename_without_first_directory) + location_on_disk = File.join(site_dir, filename) + zipfile.add(location_in_zip, location_on_disk) + end + end + send_file zip_path + end + end + get "/:site/:name.:ext" do if sites.include?(params[:site]) - send_file "#{site_dir}/#{params[:name]}.#{params[:ext]}" - else - forward # send it on to the downstream file server + if params[:ext] == "deck" # to show a markdown page as slides, change the ".md" to ".deck" + render_deck + else + send_file "#{site_dir}/#{params[:name]}.#{params[:ext]}" + end end end - # todo: make this work in a general way, without hardcoded 'img' - get "/:site/img/:name.:ext" do + get "/:site/:subdir/:name.:ext" do if sites.include?(params[:site]) - send_file "#{site_dir}/img/#{params[:name]}.#{params[:ext]}" - else - forward # send it on to the downstream file server + send_file "#{site_dir}/#{params[:subdir]}/#{params[:name]}.#{params[:ext]}" end end get "/:site/:name/" do # remove any extraneous slash from otherwise well-formed page URLs - redirect "#{params[:site]}/#{params[:name]}" + redirect request.fullpath.chomp('/') end get "/:site/:name" do - render_page + site_name = params[:site] + if redirect_sites[site_name] + redirect "#{redirect_sites[site_name]}/#{params[:name]}" + return + end + + page_url = "#{params[:site]}/#{params[:name]}" + if redirect_pages[page_url] + redirect redirect_pages[page_url] + else + render_page + end end - get "/:file.:ext" do - # treat root URLs with dots in them like static assets and serve them - # from the downstream file server (coderay.css, jquery-1.7.2.js) - forward + get "/:site/:name/:section/" do + # remove any extraneous slash from otherwise well-formed page URLs + redirect request.fullpath.chomp('/') + end + + get "/:site/:name/:section" do + render_page end get "/:site" do # add a slash to any URLs that contain only a site - # (otherwise paths in that site's pages would resolve - # relative to the root) - redirect "#{params[:site]}/" + # (otherwise paths in that site's pages would resolve relative to the root) + redirect "#{request.fullpath}/" end get "/:site/" do site_name = params[:site] - if sites.include? site_name + if redirect_sites[site_name] + redirect "#{redirect_sites[site_name]}/" + elsif sites.include? site_name # render the site's index page params[:name] = site_name render_page - else - forward # send it on to the downstream file server end end end diff --git a/assets/javascripts/analytics.js b/assets/javascripts/analytics.js new file mode 100644 index 000000000..dc021eb87 --- /dev/null +++ b/assets/javascripts/analytics.js @@ -0,0 +1,7 @@ +(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) +})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + +ga('create', 'UA-40977319-4', 'auto'); +ga('send', 'pageview'); diff --git a/assets/javascripts/application.js b/assets/javascripts/application.js new file mode 100644 index 000000000..18a61343f --- /dev/null +++ b/assets/javascripts/application.js @@ -0,0 +1,6 @@ +//= require 'jquery' +//= require 'doc_page' +//= require 'bootstrap/dropdown' +//= require 'analytics' +//= require 'md5' +//= require 'checkboxes' diff --git a/assets/javascripts/checkboxes.js b/assets/javascripts/checkboxes.js new file mode 100644 index 000000000..67778cfa3 --- /dev/null +++ b/assets/javascripts/checkboxes.js @@ -0,0 +1,30 @@ +$(document).ready(function () { + function saveCheckboxValue (hashcode, value) { + try { + localStorage['checkbox_' + hashcode] = value; + } catch (e) { } + } + + function fetchCheckboxValue (hashcode) { + try { + return localStorage['checkbox_' + hashcode]; + } catch (e) { + return false; + } + } + + var $checkboxes = $('.big_checkbox'); + + $checkboxes.each(function () { + var $checkbox = $(this); + var content = $checkbox.closest('.step').text(); + var hashcode = md5(content); + $checkbox.data('hashcode', hashcode); + $checkbox.prop('checked', fetchCheckboxValue(hashcode) === 'true'); + }); + + $checkboxes.on('change', function (event) { + var $checkbox = $(event.target); + saveCheckboxValue($checkbox.data('hashcode'), $checkbox.prop('checked')); + }); +}); diff --git a/assets/javascripts/doc_page.js b/assets/javascripts/doc_page.js new file mode 100644 index 000000000..bdd7412be --- /dev/null +++ b/assets/javascripts/doc_page.js @@ -0,0 +1,25 @@ +// HTML5 Shims... +document.createElement('main'); +document.createElement('footer'); + +$(document).ready(function () { + $('[data-toggle-selector]').on('click', function (event) { + event.preventDefault(); + var toToggle = $(event.target).data('toggle-selector'); + var originallyVisible = $(toToggle).hasClass('visible'); + $('.toc').removeClass('visible'); + $(toToggle).toggleClass('visible', !originallyVisible); + return false; + }); + + $('.toggler').on('click', function (e) { + e.preventDefault(); + $(this).closest('.collapsable').toggleClass('closed'); + }); + + $('.expand-all').on('click', function (e) { + e.preventDefault(); + $('.closed').removeClass('closed'); + $('.expand-all').remove(); + }); +}); diff --git a/assets/javascripts/md5.js b/assets/javascripts/md5.js new file mode 100644 index 000000000..114b8f82a --- /dev/null +++ b/assets/javascripts/md5.js @@ -0,0 +1,2 @@ +// https://github.com/blueimp/JavaScript-MD5 +!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t),e=(n>>16)+(t>>16)+(r>>16);return e<<16|65535&r}function r(n,t){return n<>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<>>9<<4)+14]=r;var e,i,a,h,d,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t>5]|=(255&n.charCodeAt(t/8))<16&&(o=i(o,8*n.length)),r=0;16>r;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(h(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="0123456789abcdef",o="";for(r=0;r>>4&15)+e.charAt(15&t);return o}function v(n){return unescape(encodeURIComponent(n))}function m(n){return d(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this); diff --git a/assets/stylesheets/application.css b/assets/stylesheets/application.css new file mode 100644 index 000000000..c837fdd8d --- /dev/null +++ b/assets/stylesheets/application.css @@ -0,0 +1,10 @@ +//= require 'opensans' +//= require 'aleo' +//= require 'doc_page' +//= require 'flags' +//= require 'big_checkbox' +//= require 'header' +//= require 'toc' +//= require 'step' +//= require 'font_awesome' +//= require 'coderay' diff --git a/assets/stylesheets/big_checkbox.scss b/assets/stylesheets/big_checkbox.scss new file mode 100644 index 000000000..b2d9ec41d --- /dev/null +++ b/assets/stylesheets/big_checkbox.scss @@ -0,0 +1,28 @@ +$big_checkbox_size: 20px; + +input.big_checkbox[type=checkbox] { + display:none; + + label { + height: $big_checkbox_size; + width: $big_checkbox_size; + display:inline-block; + padding: 2px; + margin: 0 12px -8px 0; + background-color: white; + z-index: 2; + border: 2px solid #dadada; + + &:hover { + background-image: url(http://github.com/img/check-dim.png); + background-size: cover; + cursor: pointer; + } + } + + &:checked { + + label { + background-image: url(http://github.com/img/check.png); + background-size: cover; + } + } +} diff --git a/public/css/coderay.css b/assets/stylesheets/coderay.css similarity index 100% rename from public/css/coderay.css rename to assets/stylesheets/coderay.css diff --git a/assets/stylesheets/doc_page.scss b/assets/stylesheets/doc_page.scss new file mode 100644 index 000000000..4da6eaefe --- /dev/null +++ b/assets/stylesheets/doc_page.scss @@ -0,0 +1,145 @@ +body { + font-family: 'Open Sans', helvetica, arial, sans-serif; + padding: 0; + margin: 0; +} + +h1 { + font-size: 1.5em; + margin: 0; +} + +main, footer { + display: block; +} + +body > footer { + clear: both; + min-height: 60px; + text-align: center; + border-top: 1px solid #ccc; + font-size: 0.8em; +} + +.visible { + display: block !important; +} + +main { + position: relative; + margin-left: 300px; + margin-right: 20px; + padding-left: 15px; +} + +@media (max-width: 1100px) { + main { + margin-left: 40px; + } +} + +main h1.doc_title { + font-family: "Aleo Bold", serif; + color: #6c266f; + font-weight: 100; + font-size: 2em; + padding-top: 5px; + margin-bottom: .5em; + padding-bottom: .25em; + border-bottom: 1px solid #ccc; +} + +.doc { + max-width: 50em; + + pre { + background: #f2f2f2; + padding: .5em 1em; + font-size: 13pt; + overflow-x: auto; + } + + code { + font-size: 13pt; + background: #f2f2f2; + padding-left: 4px; + padding-right: 4px; + } + + h1 { + border-bottom: 2px solid #EEE; + } + + blockquote { + margin: 0.5em 0 .5em 20px; + } +} + +img { + margin: auto; + display: block; /* otherwise centering doesn't happen */ + max-width: 100%; +} + +.model-diagram { + margin: 0 auto; + border: 1px solid black; + border-collapse: collapse; + + th { + background-color: lightblue; + } + + td { + padding: 0 5px; + border: 1px solid black; + } +} + +.horiz-line { + position: absolute; + height: 1px; + border-bottom: 1px solid blue; +} + +.vert-line { + position: absolute; + width: 1px; + border-left: 1px solid blue; +} + +.arrow-left { + position: absolute; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + + border-right: 8px solid blue; +} + +/* Clearfix */ +.cf:before, +.cf:after { + display: table; + line-height: 0; +} + +.cf:after { + clear: both; +} + +@media print { + main { + margin-left: 20px; + font-size: 13px; + } + + .doc pre { + font-size: 10px; + overflow-x: visible; + background-color: white; + border: 1px solid #ccc; + page-break-inside: avoid; + } +} diff --git a/assets/stylesheets/flags.scss b/assets/stylesheets/flags.scss new file mode 100644 index 000000000..200f9df35 --- /dev/null +++ b/assets/stylesheets/flags.scss @@ -0,0 +1,19 @@ +ul.flags { + min-width: 0; + position: relative; + + > li { + display: block; + + > a { + position: relative; + display: block; + padding: 10px 14px; + line-height: 20px; + } + + > img:hover { + background-color: #eee; + } + } +} diff --git a/assets/stylesheets/font_awesome.scss b/assets/stylesheets/font_awesome.scss new file mode 100644 index 000000000..6fccacddb --- /dev/null +++ b/assets/stylesheets/font_awesome.scss @@ -0,0 +1 @@ +$fa-font-path: '/fonts/font-awesome/'; diff --git a/assets/stylesheets/header.scss b/assets/stylesheets/header.scss new file mode 100644 index 000000000..f5c8a34b8 --- /dev/null +++ b/assets/stylesheets/header.scss @@ -0,0 +1,202 @@ +$railsbridge_purple: #6c266f; +$railsbridge_blue: #2493b8; +$railsbridge_darkerblue: #428bca; + +$header-height: 50px; + +nav *, nav *:before, nav *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.dropdown { + position: relative; +} + +.dropdown-toggle:focus { + outline: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #fff; + border: 1px solid #ccc; + border-color: rgba(0, 0, 0, 0.15); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; + color: #333; + border-radius: 4px; + + &:before { + content: ""; + position: absolute; + height: 0; + width: 0; + top: -19px; + right: 20px; + border: 10px solid transparent; + border-bottom-color: white; + } + + li { + a, &.current { + padding: 3px 25px; + color: #333; + } + &.current, &.category { + font-weight: bold; + } + &.category { + text-decoration: underline; + padding: 0 10px 2px 10px; + font-weight: bold; + } + + a { + display: block; + font-weight: normal; + line-height: 1.428571429; + white-space: nowrap; + } + } + + .active { + a, + a:hover, + a:focus { + color: #fff; + text-decoration: none; + background-color: $railsbridge_darkerblue; + outline: 0; + } + } +} + +.open { + > .dropdown-menu { + display: block; + } + + > a { + outline: 0; + } +} + +.nav { + padding: 0; + float: right; + margin: 0; + + .dropdown-menu { + right: 0; + left: auto; + } + + li, li a { + font-size: 18px; + } + + > li { + position: relative; + display: block; + float: left; + + > a { + height: $header-height; + line-height: $header-height; + position: relative; + display: block; + padding: 0 15px; + + img { + display: inline; + vertical-align: middle; + } + } + + > a:hover, + > a:focus { + text-decoration: none; + background-color: #eee; + } + + > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; + } + } + + li a:active, + li a:hover, + li a:focus { + background-color: $railsbridge_blue; + color: white; + } +} + +.navbar-header { + float: left; +} + +.top { + position: relative; + z-index: 1000; + min-height: $header-height; + margin-bottom: 20px; + border: 0; + background-color: $railsbridge_purple; + background-image: url("http://github.com/img/navbar_texture.gif"); + border-bottom-width: 12px; + border-bottom-color: $railsbridge_blue; + border-bottom-style: solid; + + .navbar-header { + min-height: $header-height; + } + + .title { + font-family: "Aleo Regular", serif; + font-size: 28px; + color: white; + font-weight: 100; + padding-top: 8px; + margin-left: 35px; + } + + a { + text-decoration: none; + color: white; + } +} + +@media print { + .top_links { + display: none; + } + + .top { + padding-left: 0; + margin-left: 20px; + margin-bottom: 0; + background-color: white; + } + + .top .title { + margin-left: 0; + padding: 0; + width: auto; + display: inline; + } +} diff --git a/public/css/step.css b/assets/stylesheets/step.scss similarity index 52% rename from public/css/step.css rename to assets/stylesheets/step.scss index 3eeb0ed53..031ddbd36 100644 --- a/public/css/step.css +++ b/assets/stylesheets/step.scss @@ -1,25 +1,34 @@ @charset "UTF-8"; -.step h1 { - border: 0; -} - span.prefix { - font-size: 0.7em; padding-left: 8px; } +h1 { + padding-top: 30px; + font-family: "Aleo Regular" ; +} + .step h1 { - font-size: 1em; - padding: 10px 0 0 10px; + font-size: 1.5em; + margin-top: 10px; + padding-top: 10px; + border: 0; +} + +h1.option { + font-size: 1.3em; } .step h1 span.prefix { - font-size: 0.9em; padding-left: 0; padding-right: 8px; } +h2 { + font-size: 1.2em; +} + img.noborder { border: none; display: inline; @@ -48,25 +57,36 @@ img.noborder { } .important { - border: 5px solid maroon; + background-color: #FFEBDD; + border: solid 5px #E65D00; + border-radius: 4px; + min-height: 43px; } -.pertinent-info { - padding: 1em; - font-size: 0.8em; - font-color: rgb(60, 60, 60); - border: 3px solid grey; - border-radius: 10px; +.important > p { + margin: 0; } -.pertinent-info h3 { - text-align: center; - margin: 0; +.important > i { + color: #E65D00; + float: left; +} + +.important > i, .tip > i, .discussion > i { + padding-right: 20px; } .tip { padding: 1em; - border: 10px solid #eee; + background-color: #F7FDFF; + border: solid 5px #006E92; + border-radius: 4px; + min-height: 43px; +} + +.tip > i { + color: #006E92; + float: left; } .tip > span { @@ -82,6 +102,44 @@ img.noborder { color: #E77902; } +.discussion_box { + padding: 1em; + background-color: #FFF5AE; + border-radius: 4px; + margin: 30px 0 10px 0; +} + +.discussion_box > h4, .error_box > h4 { + text-align: center; + margin: 0; + font-family: 'Aleo Regular'; + font-weight: 100; + font-size: 1.2em; +} + +.discussion_box > hr { + border-color: black; + width: 60%; +} + +.error_box > h4 { + padding-bottom: 10px; +} + +.error_box { + padding: 1em; + color: black; + background-color: rgb(202, 255, 178); + border-radius: 4px; + min-height: 43px; +} + +.error_box > p:first-of-type { + background: white; + padding: 10px 5px; + margin: 2px; +} + .step.next_step p.link:after { content: " →"; } @@ -90,15 +148,9 @@ div.back:before { content: "← "; } -.doc > div { - padding-bottom: 1em; -} - table.bordered td, table.bordered tr { - border-style: solid; - border-width: 1px; - border-color: black; + border: 1px solid black; padding-left: 5px; padding-right: 5px; } @@ -112,6 +164,7 @@ table.bordered tr { .explanation > h1:before, .deploying > h1:before, .overview > h1:before, +.challenge > h1:before, .further-reading > h1:before { content: "\00a0"; display: inline-block; @@ -127,6 +180,10 @@ table.bordered tr { background-color: #FBFFCA; } +.challenge > h1:before { + background-color: #FFD188; +} + .explanation > h1:before { background-color: #C8FFC9; } @@ -170,6 +227,10 @@ table.bordered tr { color: white; } +.console { + margin-top: 10px; +} + .fuzzy-result { margin: 20px; } @@ -206,3 +267,79 @@ table.bordered tr { -o-border-radius: 4px; border-radius: 4px; } + +.os-button { + width: 200px; + display: inline-block; + margin-right: 20px; +} + +.os-button img { + max-height: 150px; +} + +.os-button a { + text-align: center; +} + +.vertical-centerer { + display: table; + box-sizing: border-box; + -moz-box-sizing: border-box; + width: 100%; +} + +.vertically-centered { + display: table-cell; + vertical-align: middle; + width: 100%; +} + +.vertically-centered > *:first-child, +.vertically-centered > *:first-child > *:first-child { + margin-top: 0; +} + +.vertically-centered > *:last-child, +.vertically-centered > *:last-child > *:last-child { + margin-bottom: 0; +} + +.half-width { + display: inline-block; + width: calc(50% - 10px); + border: 1px solid grey; + border-radius: 5px; + padding: 10px; + box-sizing: border-box; + -moz-box-sizing: border-box; + vertical-align: top; + margin-right: 10px; + min-height: 145px; +} + +.half-width:last-child{ + margin-right: 0; +} + +.no-style-type { + list-style-type: none; + margin: 0; + padding: 0; +} + +.site-desc p { + margin: 0; +} + +.site-desc h2 { + margin: 25px 0 5px 0; +} + +#chunk-game { + background-color: #000000; + display: block; + margin-left: auto; + margin-right: auto; +} + diff --git a/assets/stylesheets/toc.scss b/assets/stylesheets/toc.scss new file mode 100644 index 000000000..76e47d99c --- /dev/null +++ b/assets/stylesheets/toc.scss @@ -0,0 +1,128 @@ +.toc { + display: none; + padding: 0 0 1em 12px; + margin: 0 0 1em 1em; + min-width: 200px; + position: absolute; + right: 0; + top: 55px; + + &.page-list { + position: static; + display: block; + float: left; + width: 17em; + } + + ul { + margin-left: 8px; + padding-left: 0; + list-style-type: none; + + ul { + margin-left: 3px; + padding-left: 6px; + } + } + + li a { + font-size: 11pt; + padding: 1px 0; + text-decoration: none; + + display: block; /* fill the entire containing li */ + } + + li.collapsable { + .toggler { + cursor: pointer; + display: inline-block; + text-align: center; + margin-right: 5px; + font-size: 15px; + color: black; + + .fa-plus-square-o { + display: none; + } + .fa-minus-square-o { + display: block; + } + } + + &.closed { + ul { + display: none; + } + .toggler { + .fa-plus-square-o { + display: block; + } + .fa-minus-square-o { + display: none; + } + } + } + } + + li a:hover { + cursor: pointer; + text-decoration: underline; + } + + .current { + font-weight: bold; + } + + .expand-all { + i { + margin-right: 5px; + } + } +} + +@media (max-width: 768px) { + .hidden-sm { + display: none !important; + } +} + +.show-when-small { + display: none !important; +} + +@media (max-width: 1100px) { + .show-when-small { + display: block !important; + } + + .toc, .toc.page-list { + display: none; + position: absolute; + right: 0; + top: 55px; + z-index: 2; + background-color: white; + border: 1px solid grey; + } +} + +body.no-toc { + .toc { + display: none; + } + + main { + margin-left: 40px; + } + + .show-when-small { + display: none !important; + } +} + +@media print { + .toc { + display: none; + } +} \ No newline at end of file diff --git a/config.ru b/config.ru index fc2ced9d4..9538bdb93 100644 --- a/config.ru +++ b/config.ru @@ -14,4 +14,7 @@ use Rack::Codehighlighter, :coderay, # Thin::Logging.debug = true require './app' -run InstallFest +run Rack::Cascade.new([ + Deck::RackApp.public_file_server, + InstallFest + ]) diff --git a/lib/big_checkbox.rb b/lib/big_checkbox.rb index 25a9abf33..3d358392e 100644 --- a/lib/big_checkbox.rb +++ b/lib/big_checkbox.rb @@ -1,39 +1,6 @@ require 'erector_scss' class BigCheckbox < Erector::Widget - # needs :size => "48px" # todo - - external :style, scss(<<-CSS) - $big_checkbox_size: 20px; - - input.big_checkbox[type=checkbox] { - display:none; - + label { - height: $big_checkbox_size; - width: $big_checkbox_size; - display:inline-block; - padding: 2px; - margin: 0 12px -8px 0; - background-color: white; - z-index: 2; - border: 2px solid #dadada; - } - - + label:hover { - background-image: url(http://github.com/img/check-dim.png); - background-size: cover; - cursor: pointer; - } - } - - input.big_checkbox[type=checkbox]:checked { - + label { - background-image: url(http://github.com/img/check.png); - background-size: cover; - } - } - CSS - # for testing -- set the next number def self.number= checkbox_number @@checkbox_number = checkbox_number @@ -45,7 +12,7 @@ def content # and http://stackoverflow.com/questions/3772273/pure-css-checkbox-image-replacement # and https://gist.github.com/592332 checkbox_number = (@@checkbox_number ||= 0) - input.big_checkbox type:"checkbox", name: "big_checkbox_#{checkbox_number}", value:"valuable", id:"big_checkbox_#{checkbox_number}" + input.big_checkbox type: "checkbox", name: "big_checkbox_#{checkbox_number}", value: "valuable", id: "big_checkbox_#{checkbox_number}" label for: "big_checkbox_#{checkbox_number}" @@checkbox_number += 1 end diff --git a/lib/contents.rb b/lib/contents.rb index 2b9c27486..9527890ec 100755 --- a/lib/contents.rb +++ b/lib/contents.rb @@ -1,48 +1,61 @@ +require 'titleizer' class Contents < Erector::Widget - attr_accessor :site_dir - attr_accessor :page_name - needs :page_name, :site_name, :site_dir => nil + attr_accessor :page_name, :site + needs :page_name, :site def initialize options super options self.page_name = options[:page_name] - - if options.include? :site_dir - self.site_dir = options[:site_dir] - else - here = File.expand_path File.dirname(__FILE__) - top = File.expand_path "#{here}/.." - self.site_dir = "#{top}/sites/#{@site_name}" - end end def site_files ext - Dir.glob("#{site_dir}/*.{#{ext}}").sort + Dir.glob(File.join(site.dir, "*.{#{ext}}")).sort + end + + def parseable_site_files + site_files("mw,md,step") + end + + def site_page_files + parseable_site_files.reject { |file| File.basename(file).start_with?('_') } + end + + def content_for filename + open(File.join(site.dir, filename)).read end def subpages_for filename + return [] if filename.match(/deck\.md/) + links = [] - content = open("#{site_dir}/#{filename}").read() + content = content_for(filename) # (markdown) links of the form: [link text](link_page) - content.scan /\[.*?\]\((.*?)\)/ do |link, _| + # but NOT images of the form ![alt text](image_link.jpg) + content.scan /[^!]\[.*?\]\((.*?)\)/ do |link, _| next if (link =~ /^http/) - next if (link =~ /(jpg|png)$/) - links.push(link) if !links.include? link + next if (link =~ %r(^.+/)) # cross-links to other sites, e.g. /installfest/editors + next if (link =~ %r(^//)) # protocol-less absolute links e.g. //google.com + links.push(link) end # (stepfiles) links of the form: link "next page" content.scan /link\s*["'](.*?)["']/ do |link, _| - links.push(link) if !links.include? link + links.push(link) + end + + # (stepfiles) links of the form: site_desc "some site" + content.scan /site_desc\s*["'](.*?)["']/ do |link, _| + links.push('/' + link) end - links + links.uniq end def next_step_for filename - content = open("#{site_dir}/#{filename}").read() + content = content_for(filename) # (stepfiles) links of the form: stepfile "next page" content.scan /next_step\s*["'](.*?)["']/ do |link, _| @@ -92,7 +105,7 @@ def find_next_page page # while another goes to next_step "that"). def hierarchy result = [] - next_page = File.basename(site_dir) + next_page = File.basename(site.dir) while next_page do this_page = next_page @@ -104,7 +117,7 @@ def hierarchy end def all_pages - site_files("mw,md,step").map { |file| File.basename(file).sub(/(\..*)$/, '') }.sort + site_page_files.map { |file| File.basename(file).sub(/(\..*)$/, '') }.sort end def orphans @@ -112,7 +125,7 @@ def orphans end def _page_links type="subpages" - site_files("mw,md,step").inject({}) do |result, filename| + site_page_files.inject({}) do |result, filename| page = File.basename(filename) page_no_ext = page.sub(/(\..*)$/, '') result[page_no_ext] = send("#{type}_for", page) @@ -128,43 +141,98 @@ def nextpages @nextpages ||= _page_links("next_step") end - def toc_link page - link_text = page.sub(%r{^/}, '').split(/[-_]/).map{|s|s.capitalize}.join(' ') - path = page.start_with?('/') ? page : "/#{@site_name}/" + page - li do + def _toggler + span(class: 'toggler') do + i(class: 'fa fa-plus-square-o') + i(class: 'fa fa-minus-square-o') + end + end + + def toc_link page, options = {} + link_text = Titleizer.title_for_page(page.sub(%r{^/}, '')) + path = page.start_with?('/') ? page : "/#{site.name}/" + page + collapse_classes = if options[:collapsable] + options[:collapsed] ? 'collapsable closed' : 'collapsable' + else + '' + end + li(class: collapse_classes) do if page == page_name + _toggler if options[:collapsable] span link_text, class: 'current' else - a link_text, href: path + a href: path do + _toggler if options[:collapsable] + text link_text + end end yield if block_given? end end - def toc_list toc_items + # Given the nested set of arrays produced by the 'hierarchy' method above, + # annotate each item noting whether it should be collapsed + # An element is 'collapsed' if it does not contain the page currently + # being viewed. + def mark_open_and_closed hierarchy + result = {items: [], contains_current_page: false} + hierarchy.each do |toc_item| + contains_current_page = nil + if toc_item.instance_of? Array + parent = toc_item[0] + children = toc_item[1..toc_item.length] + inner_result = mark_open_and_closed(children) + contains_current_page = (self.page_name == parent) || inner_result[:contains_current_page] + result[:items] << [{title: parent, collapsed: !contains_current_page}] + inner_result[:items] + else + contains_current_page ||= (self.page_name == toc_item) + result[:items] << {title: toc_item, collapsed: !contains_current_page} + end + result[:contains_current_page] ||= contains_current_page + end + result + end + + def toc_list toc_items, level = 0 ul do toc_items.each do |toc_item| if toc_item.instance_of? Array - toc_link toc_item.first do - toc_list toc_item[1..toc_item.length] + item = toc_item.first + toc_link item[:title], collapsable: true, collapsed: item[:collapsed] do + toc_list toc_item[1..toc_item.length], level + 1 end else - toc_link toc_item + toc_link toc_item[:title] end end end end + def has_collapsables toc_items + toc_items.any? do |toc_item| + if toc_item.instance_of? Array + return true + end + end + end + def content div id: "table_of_contents", class: "toc page-list" do - toc_list hierarchy + toc_list(mark_open_and_closed(hierarchy)[:items]) unless orphans.empty? - h1 "Other Pages" + h1 I18n.t("general.other_pages") ul do orphans.each { |orphan| toc_link orphan } end end + + if has_collapsables(hierarchy) + span class: "expand-all" do + i class: "fa fa-arrows-alt" + text I18n.t("general.expand_all") + end + end end end end diff --git a/lib/doc_page.rb b/lib/doc_page.rb index 406489db6..9f4d9e813 100644 --- a/lib/doc_page.rb +++ b/lib/doc_page.rb @@ -1,94 +1,47 @@ require 'erector' require "contents" require "site_index" - -class InstallfestExternalRenderer < ExternalRenderer - # render \n" + source_code :html, < +My Sample HTML page + + +HTML message "Now save the file and refresh your browser. Everything should look the same." end step do message "Add some styles that will apply to your `h1` and `p` tags." - source_code "HTML", "" - message "When you save and refresh your browser, you should see the styles you added:" - message "" - message "The `type` and `media` attributes inside of the ` +HTML + message <<-MARKDOWN +When you save and refresh your browser, you should see the styles you added: + + + +The `type` and `media` attributes inside of the `" + source_code :html, <<-HTML + + HTML message <<-MARKDOWN +In CSS you write comments by wrapping the comment in inside a `/*` (start a comment) and `*/` (finish a comment). A comment can go over several lines. + * A CSS tag looks like this when it's a link: MARKDOWN - source_code "HTML", "" + source_code :html, <<-HTML + + HTML end -consider_deploying_to_github +insert 'consider_deploying_to_github' next_step "add_more_elements" diff --git a/sites/frontend/make_columns.step b/sites/frontend/make_columns.step index 56d264c49..0d5d66720 100644 --- a/sites/frontend/make_columns.step +++ b/sites/frontend/make_columns.step @@ -1,49 +1,49 @@ goals do - message "These challenges will have you diving into the CSS side of things. Open up that layout.css file and take a look." + message "These challenges will have you diving into the CSS side of things. Open up that **layout.css** file and take a look." goal "Give your main content a border" goal "Make two columns and line them up side by side" end steps do - + step do - message "CHALLENGE: Find the div with the id 'main' in your HTML. See if you can give it a one-pixel border on its left hand side by modifying the existing stylesheet, 'layout.css'." - message "If you get hung up on what CSS property to use, see if you can look it up on [dochub](http://dochub.io/#css/)." + message "**Challenge:** Find the div with the id `main` in your HTML. See if you can give it a one-pixel border on its left hand side by modifying the existing stylesheet, **layout.css**." + message "**Hint**: If you get hung up on what CSS property to use, see if you can look it up on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference)." end - + step do - message "CHALLENGE: If you styled the #main div in the previous step by using a style rule like `#main { property: value; }`, see if you can come up with a second rule that could also apply the same style, but use a different selector." - message "Hint: You can select an element that's _inside_ of another element by listing one selector after another. The selector `.content div {}` would apply to any divs inside of an element with class `content`" + message "**Challenge:** If you styled the `#main` div in the previous step by using a style rule like `#main { property: value; }`, see if you can come up with a second rule that could also apply the same style, but use a different selector." + message "**Hint**: You can select an element that's _inside_ of another element by listing one selector after another. The selector `.content div {}` would apply to any `div`s inside of an element with class `content`." end - - step do - message "CHALLENGE: See if you can turn the page into a two-column layout. Make the 'nav' div the left column, and the 'main' div the right column." - message "Two hints: it may be helpful to give these divs a fixed width, and you may need the `float` property." + + step do + message "**Challenge:** See if you can turn the page into a two-column layout. Make the `#nav` `div` the left column, and the `#main` div the right column." + message "**Hints**: it may be helpful to give these divs a fixed width; you may need the `float` property." end message "" end explanation do - + message <<-MARKDOWN ## More CSS! -It takes a long time to master CSS, but there are lots of great-looking resources out there -to help. One cool resource is the [CSS Zen Garden](http://www.csszengarden.com/), a site with dozens of examples of -the _same_ HTML page styled with different user-submitted stylesheets. It's a great place -to get a sense of how limitless the possibilities are with CSS. +For some great examples of the power of CSS, check out the [CSS Zen Garden](http://www.csszengarden.com/). It takes +the same HTML, but shows it with stylesheets from a bunch of different designers applied to +it. Here are two different designers' interpretations of the same HTML: + + ### CSS Reference Sites * [Sitepoint](http://reference.sitepoint.com/css/elements-structural) * [Mozilla Developer Network](https://developer.mozilla.org/en/CSS) -* [Dochub](http://dochub.io/#css/) MARKDOWN end -consider_deploying_to_github +insert 'consider_deploying_to_github_again' next_step "basic_javascript" diff --git a/sites/frontend/resources.step b/sites/frontend/resources.step index 284957184..6e8e7b0cd 100644 --- a/sites/frontend/resources.step +++ b/sites/frontend/resources.step @@ -2,17 +2,27 @@ message <" end - choice "your operating system" do + step "Choose your operating system" do option "Windows" do message "On Windows, the easiest way to install git and set everything up is with the Rails Installer. " span "Head over to our " a "Rails Installer instructions", :href => "/installfest/windows", :class => 'link' span " (courtesy the Rails workshop Installfest instructions). Come back at the end of the page. Do not make a Heroku account!" important "For real: come back at the bottom of the page, after step 9!" - span "After getting done with RailsInstaller, come back here and " + span "After getting done with RailsInstaller, come back here and " a 'create a GitHub account', :href => "#github-account" span "." end @@ -21,35 +21,34 @@ steps do message "If you've been to a RailsBridge before, you might have git installed. Try the following" console "git --version" message "If you see something like the git version below, then you can return to the mini-installfest" - fuzzy_result "git version 1{FUZZY}.x.x{/FUZZY}" + fuzzy_result "git version {FUZZY}2.x.x{/FUZZY}" message "If you see something like `git: command not found` then proceed" end step "Check if XCode is installed" do message "Look in your Applications folder for an application called XCode." - message "If you see XCode, choose __Option 1: Installing Homebrew__." - message "If you do __not__ see XCode, choose __Option 2: Installing binary Git__." + message "If you see XCode, choose __Installing Homebrew__." + message "If you do __not__ see XCode, choose __Installing binary Git__." - choice do - option "Installing Homebrew" do - a "Instructions for Installing Homebrew", :href => "/installfest/install_homebrew", :class => "link" - - message "After installing hombrew, use it to install git" - console "brew install git" + option_half "Installing Homebrew" do + div do + a "Instructions for Installing Homebrew", :href => "http://brew.sh/", :class => "link" end - option "Installing binary git" do - message "In another browser tab open the link below and download the latest (topmost) release of Git for OS X." - a "Git OS X Installer Downloads", :href => "http://code.google.com/p/git-osx-installer/downloads/list?can=3" - end + message "After installing homebrew, use it to install git" + console "brew install git" + end + + option_half "Installing binary git" do + message "In another browser tab open the link below and download the latest (topmost) release of Git for OS X." + a "Git OS X Installer Downloads", :href => "http://code.google.com/p/git-osx-installer/downloads/list?can=3" end end step "Install a text editor" do - a "Download and install Sublime Text 2", :href => "http://www.sublimetext.com/2" - message "Install Sublime Text 2 by double clicking the file you downloaded, then dragging the Sublime Text 2 icon into the Applications folder. Finish up by clicking the eject icon for Sublime Text 2 in your finder window." - a "or Install Komodo Edit for Mac OS X", :href => "/installfest/install_komodo_edit_for_mac" - message "FYI: Komodo Edit is open source, whereas Sublime Text 2 is a paid program that you can download and try out for free. If you keep using Sublime Text 2 after the workshop, you'll need to buy a license." + a "Download and install Atom", :href => "https://atom.io/" + message "Install Atom by double clicking the file you downloaded, then dragging the Atom icon into the Applications folder. Finish up by clicking the eject icon for Atom in your finder window." + a "or install a different editor", :href => "/installfest/editors" end end end diff --git a/sites/frontend/zip_content/.gitignore b/sites/frontend/zip_content/.gitignore new file mode 100644 index 000000000..e8dcdf163 --- /dev/null +++ b/sites/frontend/zip_content/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea +Thumbs.db +.rvmrc +.sass-cache diff --git a/sites/frontend/zip_content/index.html b/sites/frontend/zip_content/index.html new file mode 100755 index 000000000..7c6963013 --- /dev/null +++ b/sites/frontend/zip_content/index.html @@ -0,0 +1,77 @@ + + + + About Me + + + + + + + +
+ + + + +
+

Projects

+

Describe the kinds of projects you like to work on and give a brief overview of how you accomplished them...

+ +

Project #1 Title

+

Project #1 Description...

+ +

Project #2 Title

+

Project #2 Description...

+ +

Hobbies

+ +

What else do you do in your spare time?

+ +

Item #1 Title

+

Use this paragraph to explain Item #1

+
    +
  • Cool thing I did
  • +
  • Another cool thing I did
  • +
  • Final cool thing I did
  • +
+ +

Item #1 Title

+

Use this paragraph to explain Item #1

+
    +
  • Cool thing I did
  • +
  • Another cool thing I did
  • +
  • Final cool thing I did
  • +
+ +

Fun Facts

+

Subhead

+

Use this list to tell us some fun things about yourself.

+
    +
  • Cool thing I did
  • +
  • Another cool thing I did
  • +
  • Final cool thing I did
  • +
+ + +
+
+ + + + diff --git a/sites/frontend/zip_content/resources/javascript.js b/sites/frontend/zip_content/resources/javascript.js new file mode 100755 index 000000000..a96422740 --- /dev/null +++ b/sites/frontend/zip_content/resources/javascript.js @@ -0,0 +1 @@ +// Your Javascript will go here! \ No newline at end of file diff --git a/sites/frontend/zip_content/resources/layout.css b/sites/frontend/zip_content/resources/layout.css new file mode 100755 index 000000000..3ffabc67f --- /dev/null +++ b/sites/frontend/zip_content/resources/layout.css @@ -0,0 +1,58 @@ +/* + * Add your CSS styles here + * + */ + +body { + font-family: Helvetica, Arial, sans-serif; + background: #f4f4f4; +} + +h1, h2, h3 { + color: #222; +} + +p { + line-height: 24px; + color: #555; +} + +a { + color: green; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.content { + width: 940px; + margin: 0 auto; +} + +#header { + border-bottom: 1px solid #999; +} + +#nav { + margin: 0 20px 0 0; + /* CHALLENGE 3: how could you get this to float to the left side of the screen? */ +} + +#main { + margin: 0; + /* CHALLENGE 3: how could you get this to float to the left side of the screen? */ + /* CHALLENGE 2: how could you give this element a one-pixel border down its left side? */ +} + +#copyright { + margin-top: 30px; + margin-bottom: 30px; +} + + +/* Ignore this for now, it is special code needed to fix the layout after using the "float" rules */ +.clearfix:before, .clearfix:after { content:""; display:table; } +.clearfix:after { clear:both; } +.clearfix { zoom:1; } \ No newline at end of file diff --git a/sites/frontend/zip_content/resources/picture.jpg b/sites/frontend/zip_content/resources/picture.jpg new file mode 100755 index 000000000..ded62653d Binary files /dev/null and b/sites/frontend/zip_content/resources/picture.jpg differ diff --git a/sites/installfest/DeveloperToolsPage.png b/sites/installfest/DeveloperToolsPage.png deleted file mode 100644 index 39946c91f..000000000 Binary files a/sites/installfest/DeveloperToolsPage.png and /dev/null differ diff --git a/sites/installfest/Komodo-Edit-5.png b/sites/installfest/Komodo-Edit-5.png deleted file mode 100644 index a2b4b40b1..000000000 Binary files a/sites/installfest/Komodo-Edit-5.png and /dev/null differ diff --git a/sites/installfest/MacOSXSnowLeopard.png b/sites/installfest/MacOSXSnowLeopard.png deleted file mode 100644 index 6021a8fc0..000000000 Binary files a/sites/installfest/MacOSXSnowLeopard.png and /dev/null differ diff --git a/sites/installfest/WinXPRailsInstaller.jpg b/sites/installfest/WinXPRailsInstaller.jpg deleted file mode 100644 index 9697b2a4d..000000000 Binary files a/sites/installfest/WinXPRailsInstaller.jpg and /dev/null differ diff --git a/sites/installfest/_command-line-glossary.md b/sites/installfest/_command-line-glossary.md new file mode 100644 index 000000000..0084d032f --- /dev/null +++ b/sites/installfest/_command-line-glossary.md @@ -0,0 +1,37 @@ +**~** Called 'tilde'. On OS X or Linux, this is a shortcut to the **home directory** for your user. + +**\** When you use an 'escape sequence' in front of a character, the normal interpretation of that character is not applied. + +**=** Assignment to make whatever follows, to be treated as true (_Name="Veronica"_). + +**==** Boolean, a data type with only 2 possible values: True or False (_Is name=="Veronica"? True_). + +**cd (or cd ~)** Change into your **home directory**. + +**cd** _directory_ **(or cd ..)** Change into the parent directory of your current directory. + +**cd ../../** Go up 2 levels / multiple levels. + +**cd** _foo_ Change into the directory named _foo_. + +**cp** _original.rb copy.rb_ Makes a copy of the _original.rb_ file. + +**ls** List the contents of your current directory. + +**ls** _directory_ Shows all contents (files and folders) of the directory. + +**pwd** Shows the full path of the directory you are currently in (e.g. _/home/heidi/tehcodez/Railsbridge_). + +**-h (or --help)** Can be run with all commands to list more helpful information. + +**git branch** Shows you the branch that you're currently in. + +**git status** Shows you any pending changes that you've created since your last commit. + +**git add -A** Will add all your changes to your next commit. + +**git add** _file1.md file2.md file3.md_ Will add only the files you specify to your next commit. + +**git commit -m** _"some useful message for your future self"_ Commits all your changes with your descriptive message to git. + +**git push origin** remote\_branch\_name This pushes the code in your current branch to the remote\_branch\_name branch on the remote repo named 'origin'. diff --git a/sites/installfest/_general-glossary.md b/sites/installfest/_general-glossary.md new file mode 100644 index 000000000..e96914968 --- /dev/null +++ b/sites/installfest/_general-glossary.md @@ -0,0 +1,45 @@ +**Classes and Instances:** Types and objects. If we have a class (type) called _Car_, then _Peugeot 406_ and _Fiat Ritmo_ will be instances (objects) of this class. There may also be other instances unique to _Peugeot 406_, differentiated by, for example, their _Model number_. + +**Command Line Interface (or Console / Terminal):** Allows you to interact with your computer (add, delete, modify files and much more) solely by typing instructions (commands). + +**Function:** Code that can be re-used; if a formula has to change, you only need to change it in one place. + +**Git:** A type of **Version Control Software (VCS)**. + +**GitHub:** A site that hosts git repositories. GitHub also adds a number of tools that aid interaction between developers collaborating on software. + +**Grouping:** Indents and whitespaces are used for grouping code together and simplify readability. Ruby also uses do … end to group. + +**Heroku:** A site that allows you to host your Ruby on Rails applications. Heroku also supports other languages and frameworks. + +**Home directory:** On a OS X or Linux computer, this is where the files and settings belonging to your user are stored. + +**Loop:** While _something is true, do this_. When _something becomes not true_ then break the loop / stop. + +**Markdown:** A lightweight markup language that allows you to write using an easy-to-read, easy-to-write plain text format, and then convert it to structurally valid XHTML (or HTML). + +**Rake:** A Ruby application that can be used to perform a number of custom tasks. It is often used to carry out maintenance or ad-hoc jobs. + +**Repository (or Repo):** A grouping of files within **Version Control Software**. When using **Git**, you will need to have access to a particular repository before you can start making changes to the source code. + +**Ruby:** A programming language. + +**Ruby on Rails:** A web framework written in **Ruby**. It has been designed in such a way as to make web development as fast and as easy as possible. + +**RubyGem:** A package manager for the Ruby programming language that provides a standard format for distributing Ruby programs and libraries (in a self-contained format called a "gem"), a tool designed to easily manage the installation of gems and a server for distributing them. + +**RVM:** The Ruby Version Manager. An application that makes it easy to have multiple versions of Ruby on your computer at once. + +**Script:** A term often used to describe a text file containing a series of instructions, written in Ruby, that will be used to produce the output visible in a browser. + +**Shell:** The software that interprets your commandline instructions. Often used as a synonym for the **Command Line Interface**. + +**SSH:** Secure Shell. A way to securely connect to a remote computer. + +**String:** Text only, typed between inverted commas. + +**Text editor:** A program that allows you to create and modify the text files that will be used by **Ruby on Rails** to run your command. Some text editors are optimised for software development and provide tools that increase productivity. + +**Variable:** A storage location and an associated symbolic name (an identifier) which contains some known or unknown quantity or information (a value). The variable name is the usual way to reference the stored value; this separation of name and content allows the name to be used independently of the exact information it represents. + +**Version Control Software:** Software that tracks changes to your source code and also allows you to undo or combine changes. diff --git a/sites/installfest/_install_atom_for_mac.step b/sites/installfest/_install_atom_for_mac.step new file mode 100644 index 000000000..983f2a84d --- /dev/null +++ b/sites/installfest/_install_atom_for_mac.step @@ -0,0 +1,21 @@ +message "We'll be using the Atom text editor during the workshop, though you are free to use a different editor if you prefer. It must be a plain-text editor, such as vi or TextMate." + +important <<-MARKDOWN + Microsoft Word and other word processing programs, including TextEdit and Notepad, will not work. + + When in doubt, use Atom. + MARKDOWN + +step "Download Atom" do + message "Download the [Atom installer](https://atom.io/download/mac)." +end + +step 'Find the downloaded file in Finder' do + message "If you weren't asked where to save it, it's probably in the Downloads folder." +end + +step "Extract Atom and move it to your Applications folder." do + message "Double click the Atom `.zip` file to extract it. Drag Atom into your Applications folder." + + img src: 'img/install_atom_mac.gif', alt: 'Animation of extracting the Atom zip file and moving the contents to the Applications folder' +end diff --git a/sites/installfest/_install_homebrew.step b/sites/installfest/_install_homebrew.step new file mode 100644 index 000000000..094167a18 --- /dev/null +++ b/sites/installfest/_install_homebrew.step @@ -0,0 +1,14 @@ + +console %q{ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"} + +message "You may have to press 'ENTER' when prompted and type in your password." + +important "If that doesn't work, visit and follow the instructions there." + +verify do + console "brew -v" + fuzzy_result <<-TEXT + Homebrew 1{FUZZY}.1.13-19-g55c02ae77 + Homebrew/homebrew-core (git revision 4075; last commit 2017-04-18){/FUZZY} + TEXT +end diff --git a/sites/installfest/_install_ruby.step b/sites/installfest/_install_ruby.step new file mode 100644 index 000000000..b3e0a71fa --- /dev/null +++ b/sites/installfest/_install_ruby.step @@ -0,0 +1,29 @@ +console <<-BASH + rvm install #{version_string(:ruby_short)} +BASH + +message "This downloads and compiles Ruby, which takes a while." +console "rvm use #{version_string(:ruby_short)}" +console "rvm --default use #{version_string(:ruby_short)}" + +verify do + console "ruby -v" + fuzzy_result "ruby #{version_string(:ruby_short)}{FUZZY}.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]{/FUZZY}" +end + +div do + h1 "Troubleshooting" + important "If `rvm install #{version_string(:ruby_short)}` says `autoreconf was not found in the PATH`" do + div do + option_half "Mac OS" do + console "brew install automake" + end + + option_half "Ubuntu Linux" do + console "sudo apt-get install automake" + end + end + + message "Once that completes, retry `rvm install #{version_string(:ruby_short)}`" + end +end diff --git a/sites/installfest/_install_rvm.step b/sites/installfest/_install_rvm.step new file mode 100644 index 000000000..17d857cca --- /dev/null +++ b/sites/installfest/_install_rvm.step @@ -0,0 +1,38 @@ +message <<-MARKDOWN +RVM stands for Ruby Version Manager and is the easiest way to install and manage Ruby. The [official RVM install instructions](https://rvm.io/rvm/install) are available here, but this should work for you: +MARKDOWN + +step "Install RVM" do + console "curl -L get.rvm.io | bash -s stable" + message "This will download and install RVM." + + important do + message <<-EOT + You might see an error like "**GPG signature verification failed**" + + If so, run the suggested command to add RVM's GPG keys: + + `gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3`" + + (the command may start with `gpg` or `gpg2` -- use whichever was provided in the RVM error message) + + Afterward, try installing RVM again: + + `curl -L get.rvm.io | bash -s stable` + EOT + end +end + +step "Configure your shell" do + + message "Every time you open a new terminal window, rvm will be active inside it. Close your terminal window and open a new one." + + verify do + console "type rvm | head -1" + result "rvm is a function" + + console "rvm -v" + fuzzy_result "rvm 1{FUZZY}.x.x (stable) by Wayne E. Seguin (wayneeseguin@gmail.com), Michal Papis [https://rvm.io/]{/FUZZY}" + end + +end diff --git a/sites/installfest/_install_textmate.step b/sites/installfest/_install_textmate.step new file mode 100644 index 000000000..22f586fd4 --- /dev/null +++ b/sites/installfest/_install_textmate.step @@ -0,0 +1,22 @@ +message <<-MARKDOWN + +Atom isn't compatible with versions of OS X lower than 10.6 so we'll use Textmate (which is still widely used by Rails developers), you are free to use a different editor if you prefer. It must be a plain-text editor. + +MARKDOWN + +important "Microsoft Word and other word processing programs, including TextEdit and Notepad, will not work." + +step "Download Textmate 1.5.11" do + message "Download the [Textmate installer](http://archive.textmate.org/TextMate_1.5.11_r1635.zip)." +end + +step 'Select "Open with Archive Utility" in the file save dialog' do + message "This should be the default." + + message "It will extract from the zip archive the Textmate application inside of your Downloads folder." + +end + +step "Drag the Textmate application into your Applications folder." do + message "Drag the Textmate application into your Applications folder." +end diff --git a/sites/installfest/_ruby-and-rails-glossary.md b/sites/installfest/_ruby-and-rails-glossary.md new file mode 100644 index 000000000..911ac0b55 --- /dev/null +++ b/sites/installfest/_ruby-and-rails-glossary.md @@ -0,0 +1,19 @@ +**puts** _something_ Prints its argument to the console. Can be used in Rails apps to print something in the console where the server is running. + +**rails new** _NameApp_ Creates a new Rails application with the entire Rails directory structure to run your application. + +**rails server (or rails s)** Launches a web server named Puma that you will use any time you want to access your application through a web browser. + +**rails generate (or rails g)** Uses templates to create a bunch of directories and files in your application. + +**rails generate scaffold** Creates a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data and a test suite for each of the above. + +**rake** Rake is ‘Ruby Make', used to build up a list of tasks. + +**rails console (or rails c)** Lets you interact with your Rails application from the command line, useful for testing out quick ideas with code and changing data server-side without touching the website. + +**rails console --sandbox** If you wish to test out some code without changing any data. + +**rails dbconsole (or rails db)** Used to figure out which database you're using and drops you into whichever command line interface you would use with. It supports MySQL, PostgreSQL, SQLite and SQLite3. + +**rails destroy (or rails d)** Does the opposite of generate. It will figure out what generate did and undo it. diff --git a/sites/installfest/_switch_to_home_directory.step b/sites/installfest/_switch_to_home_directory.step new file mode 100644 index 000000000..93fcaf006 --- /dev/null +++ b/sites/installfest/_switch_to_home_directory.step @@ -0,0 +1,14 @@ +message "`cd` stands for change directory." + +message "If you are still in `irb`, type `quit` to exit." + +div do + option_half "Windows" do + console "cd c:\\Sites" + message "`cd c:\\Sites` sets our Sites directory to our current directory." + end + option_half "Mac or Linux" do + console "cd ~" + message "`cd ~` sets our home directory to our current directory." + end +end diff --git a/sites/installfest/appstore.jpg b/sites/installfest/appstore.jpg deleted file mode 100644 index be6e6bb34..000000000 Binary files a/sites/installfest/appstore.jpg and /dev/null differ diff --git a/sites/installfest/choose_your_operating_system.step b/sites/installfest/choose_your_operating_system.step index f01af6775..f42e8cbea 100644 --- a/sites/installfest/choose_your_operating_system.step +++ b/sites/installfest/choose_your_operating_system.step @@ -1,10 +1,20 @@ -div do -message <<-MARKDOWN +h2 "What's your operating system?" -## Click here if you are using a [Macintosh](macintosh) - -## Click here if you are using a [PC running Windows](windows) - -## Click here if you are using a [PC running Linux](linux) - MARKDOWN +div(class: "os-button") do + simple_link "macOS" do + img src: "/img/macos.png" + h3 "macOS" + end +end +div(class: "os-button") do + simple_link "windows" do + img src: "/img/windows.png" + h3 "Windows" + end +end +div(class: "os-button") do + simple_link "linux" do + img src: "/img/tux-trans.png" + h3 "Linux" + end end diff --git a/sites/installfest/clean_up.step b/sites/installfest/clean_up.step index 6ac286c6d..989f97c15 100644 --- a/sites/installfest/clean_up.step +++ b/sites/installfest/clean_up.step @@ -9,17 +9,14 @@ end step "Delete the test_app from your computer" do option "Windows" do - message "From the Explorer, right click on the test_app folder (inside C:\\Sites) and choose Delete." + message "From the Explorer, right click on the test_app folder (inside C:\\Sites\\railsbridge) and choose Delete." + message "**- or -**" + console_with_message "From the command prompt, run the following command:", "rmdir /s /q c:\\Sites\\railsbridge\\test_app" end option "Mac" do message "Open your home directory and drag the test_app folder to the trash." end option "Linux" do - console "rm -rf ~/test_app" + console "rm -rf ~/railsbridge/test_app" end -end -step "Delete the sticker app from your computer" do - message "Do the same for the sticker folder." - message "Now you're really done!" end - diff --git a/sites/installfest/configure_git.step b/sites/installfest/configure_git.step index 8e5ba473f..28c749077 100644 --- a/sites/installfest/configure_git.step +++ b/sites/installfest/configure_git.step @@ -1,9 +1,9 @@ message "(If you used RailsInstaller on Windows then you should already have `user.name` and `user.email` configured.)" -console <<-BASH -git config --global user.name "Your Actual Name" -git config --global user.email "Your Actual Email" -BASH +message "In the next commands, don't copy-paste the whole thing. Use the name and email address you usually use. Don't use a username (like your github username)." + +console 'git config --global user.name "Your Actual Name, In Quotes"' +console 'git config --global user.email "Your Actual Email, In Quotes"' important "Use the same email address for heroku, git, github, and ssh." @@ -16,15 +16,12 @@ end tip "Colored output in Git" do console <<-BASH -git config --global color.diff auto -git config --global color.status auto -git config --global color.branch auto +git config --global color.ui auto BASH important do - message "If you are using Windows, you may need to install AnsiCon to see colors (otherwise you'll see gibberish). If you didn't do this earlier, then you should do it now." - - link_without_toc "windows_colors" + message "If you are using Windows, the output of Git will not have colors." end end +next_step "create_an_ssh_key" diff --git a/sites/installfest/configure_the_windows_terminal.step b/sites/installfest/configure_the_windows_terminal.step deleted file mode 100644 index c721380b0..000000000 --- a/sites/installfest/configure_the_windows_terminal.step +++ /dev/null @@ -1,14 +0,0 @@ -step "Configure the Windows Terminal" do - message "Right-click on the menu bar and select **Properties**" - - message "Under the *Options* tab, check the box for **QuickEdit Mode**. This will let you visually cut and paste." - - message "Under the *Font* tab, select Lucida Console from the font chooser box. This lets you view non-ascii characters." - - message "In the *Layout* tab, adjust *Window Size* so the window is about half as wide as your screen, and is as tall as possible without covering the task bar (this may require some trial and error)." - - message "In the *Layout* tab, increase **Screen Buffer Size** Height to 1000." - - message "Click OK. In the dialog that comes up, select **Modify shortcut that started this window.**" -end - diff --git a/sites/installfest/create_a_github_account.step b/sites/installfest/create_a_github_account.step new file mode 100644 index 000000000..8fd22db10 --- /dev/null +++ b/sites/installfest/create_a_github_account.step @@ -0,0 +1,79 @@ +message "You can use GitHub to store your code online and access it from anywhere. This step isn't necessary to deploy your apps to the web though." + +step "Visit the GitHub web site" do + url "https://github.com/" +end + +step "Create an account" do + + message "Click the green **Sign Up for GitHub** button (it's about halfway down the page)" + message "Enter a username, your email address and a password." + important "Use the same email address for heroku, git, github, and ssh. Be sure to use an email account you can log into immediately." + +end + +step "Select a plan" do + + message "GitHub provides several levels of account plans but you can create unlimited public repositories with a free plan so for now, you can select that one. Hit **Finish sign up**." + +end + + +step "Set up SSH authentication with GitHub" do + + message "Adding an SSH key to GitHub allows you to pull and push data without typing in your password all the time. First we'll copy the key we generated in the **Create an SSH Key** step and add it to your GitHub account. We'll use a terminal command to do that, so that we don't add any newlines or whitespace that could cause an error." + + h2 "Mac users" + console "pbcopy < ~/.ssh/id_rsa.pub" + + h2 "Linux users" + console <<-BASH + sudo apt-get install xclip + xclip -sel clip < ~/.ssh/id_rsa.pub + BASH + + message "`sudo apt-get install xclip` installs a tiny application, xclip, that lets us copy the contents of a file without opening it. Mac and Windows users have similar things already installed (`pbcopy` and `clip`)." + + h2 "Windows users" + console "clip < \"%userprofile%\\.ssh\\id_rsa.pub\"" + + message "Now that you have copied the key to your clipboard, you can add it to the GitHub account you created earlier." + + h1 "Add your SSH key to GitHub" + + message "Navigate to github.com and make sure you are logged in. On any page on the GitHub site, click your profile photo in the top right corner to the right of the plus sign. In the dropdown menu, click **Settings** to go to the account settings page." + + message "On the account settings page, select **SSH and GPG keys** from the column on the left." + + message "At the top right of this page, click the button that says **New SSH key**. In the title field, give a name for your SSH key, you might call it **My PC** or **Personal MacBook**. In the key field, paste the key you copied." + + message "Click **Add SSH key**" + + message "Confirm the action by providing your GitHub Password" +end + +step "Confirm SSH Authentication" do + + message "Confirm that you have successfully set up SSH Authentication for GitHub" + + message "Windows users cannot perform this step." + + console "ssh -T git@github.com" + + result <<-OUTPUT + The authenticity of host 'github.com (207.97.227.239)' can't be established. + RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48. + Are you sure you want to continue connecting (yes/no)? + OUTPUT + + message "You might see a message like this to confirm your key, if it's your correct key continue connecting by typing yes" + + result <<-OUTPUT + Hi username! You've successfully authenticated, but GitHub does not + provide shell access. + OUTPUT + + message "If the username is your username, you've successfully authenticated GitHub with SSH!" + + next_step 'create_a_rails_app' +end diff --git a/sites/installfest/create_a_heroku_account.step b/sites/installfest/create_a_heroku_account.step index ff7d02a41..1f4d06294 100644 --- a/sites/installfest/create_a_heroku_account.step +++ b/sites/installfest/create_a_heroku_account.step @@ -1,3 +1,5 @@ +message "We'll be using Heroku to put our apps online because it's simple and free for the tier we'll be needing. If you know you already have a Heroku account, you can jump to step 4 on this page." + step "Visit the Heroku web site" do url "http://heroku.com/" end @@ -5,7 +7,7 @@ end step "Create an account" do message "Click the big **Sign Up** button (it's about halfway down the page)" message "Enter your email address." - important "Use the same email address for heroku, git, github, and ssh." + important "Use the same email address for heroku, git, github, and ssh. Be sure to use an email account you can log into immediately." end step "Activate your account" do @@ -14,29 +16,32 @@ step "Activate your account" do end -step "Install the Heroku toolbelt" do +step "Install the Heroku CLI" do - message "Visit https://toolbelt.heroku.com/, click the download link, and install." + message "Visit https://devcenter.heroku.com/articles/heroku-cli and follow the instructions relevant to your operating system. Afterwards:" verify do console "heroku version" - result "2.26.7 or higher" - end -end - -step "Create an SSH key" do - option "On OS X or Linux" do - link "create_an_ssh_key" - end - option "On Windows" do - message "RailsInstaller created a key for you. Go on to the next step!" + fuzzy_result <<-TEXT + {FUZZY}heroku-toolbelt/3.30.6 (x86_64-darwin10.8.0) ruby/1.9.3{/FUZZY} + heroku{FUZZY}/7.6.0 darwin-x64 node-v10.6.0{/FUZZY} + {FUZZY}You have no installed plugins.{/FUZZY} + TEXT end + message "The output could be different, depending on whether this is the first time you've installed the Heroku CLI, but it should always have `heroku` in it." + message "If `heroku version` doesn't work, try the command again in a new terminal window." end step "Add your SSH key to your Heroku account" do console "heroku keys:add" - - message "hit enter to accept the default key file to use" + + message "Enter your Heroku email and password, if prompted, and accept the defaults." +end + +section 'Optional Step: Create a GitHub account' do + message "Since by now you should have both Git and an SSH key, you can optionally [Create a GitHub account](create_a_github_account) to share code with your friends." + + message "You don't need a GitHub account to complete the InstallFest, or the Intro To Rails, Job Board, or Message Board courses." end -next_step "create_and_deploy_a_rails_app" +next_step "create_a_rails_app" diff --git a/sites/installfest/create_a_rails_app.step b/sites/installfest/create_a_rails_app.step new file mode 100755 index 000000000..f5a2f370f --- /dev/null +++ b/sites/installfest/create_a_rails_app.step @@ -0,0 +1,105 @@ +tip do + console_with_message "From here on, this guide assumes you have Rails 5.0.x. To check your Rails version, type this in the terminal:", "rails -v" + fuzzy_result "Rails 5.0{FUZZY}.x{/FUZZY}" + message "If your computer reports a Rails version less than 5.0, ask a TA help get you back on track." +end + +step "Change to your home directory" do + insert 'switch_to_home_directory' +end + +step "Create a railsbridge directory" do + console "mkdir railsbridge" + message "`mkdir` stands for make directory (folder)." + message "We've made a folder called `railsbridge`." +end + +step "Change to your new railsbridge directory" do + console "cd railsbridge" +end + +step "Create a new Rails app" do + + console "rails new test_app" + + message "The command's output is voluminous, and will take some time to complete, with a long pause in the middle, after all the 'create...' statements ending in 'bundle install'. When it fully completes, it will return you to your home prompt. Look for the 'Bundle complete!' message just above." + + console "cd test_app" + console "rails server" + + tip "In OS X, you may need to let Ruby accept incoming network connections through your firewall. Select 'allow' in the pop up." + + tip "In Windows, you may need to let Ruby and Rails communicate through your firewall. Say yes to the pop up." + + tip "Shortcut: Just type 'rails s'" do + message <<-MARKDOWN + Throughout your Rails programming career you're going to type `rails server` a + lot. Simply typing `rails s` is the same as `rails server`. + MARKDOWN + end + + message <<-MARKDOWN + The first command should produce no output. + If `rails server` starts up with no errors, you're golden! It'll look something like this: + MARKDOWN + + fuzzy_result <<-TEXT + => Booting Puma + => Rails 5.0{FUZZY}.0{/FUZZY} application starting in development on http://localhost:3000 + => Run `rails server -h` for more startup options + Puma starting in single mode... + * Version {FUZZY}3.4.0{/FUZZY} (ruby 2.{FUZZY}2.2-p95{/FUZZY}), codename: {FUZZY}Owl Bowl Brawl{/FUZZY} + * Min threads: 5, max threads: 5 + * Environment: development + * Listening on tcp://localhost:3000 + Use Ctrl-C to stop + TEXT + + message "If it does, congratulations! You've successfully installed Ruby AND Rails and started your server." + + tip "If it doesn't work, ask a TA for help." + message "* In your browser, go to " + img src: "img/successful_rails_install.png", alt: "Screenshot of the browser on localhost 3000 showing the rails intro page" + message <<-MARKDOWN + + * Back in the Terminal window where you ran rails server, type **Control-C** (don't type this into the console, but hold the Control and C keys at the same time) to kill(stop) the server. Windows will ask "Terminate batch job (Y/N)?". Type "Y". + MARKDOWN + + important "On Windows, sometimes Control-C doesn't work. In that case, look for the key called 'Break' or 'Pause' and press Control-Break, then answer Y at the prompt. If there is no Pause/Break key on your keyboard, you can run `ruby script/rails server` instead of `rails server` which should allow Control-C to stop the server." +end + +step "Generate a database model" do + tip "If your prompt doesn't already show that you are (still) in the test_app folder" do + console "cd test_app" + end + + console <<-BASH + rails generate scaffold drink name:string temperature:integer + BASH + console <<-BASH + rails db:migrate + BASH + console <<-BASH + rails server + BASH + + message <<-MARKDOWN + **Note:** the above are three separate commands. Type each line into the terminal separately, not as one single command. + + Wait until your console shows that the Puma server has started (just like before). Then, in the browser, visit + + 1. Click on "New drink" + 2. Enter Cappuccino for the name + 3. Enter 135 for the temperature. + 4. Click on "Create Drink". + + (The window where you ran `rails server` will display debugging information as you do this.) + + You should see: ![Screenshot of the drink detail page](img/get_a_sticker_you_should_see.png) + + In your terminal, Hold Control and hit C (or on Windows, Control-Break, Y) to stop the rails server. + MARKDOWN +end + +next_step "deploy_a_rails_app" + diff --git a/sites/installfest/create_an_ssh_key.step b/sites/installfest/create_an_ssh_key.step index e34325ea4..431cb02d3 100644 --- a/sites/installfest/create_an_ssh_key.step +++ b/sites/installfest/create_an_ssh_key.step @@ -1,55 +1,88 @@ message "An SSH key uniquely identifies you (and your computer) when your computer is communicating with other computers. Think of an SSH key as a fancy password." -message "You'll need one of these to create your Heroku and Github accounts." +message "You'll need one of these to create your Heroku and GitHub accounts." option "Did you use RailsInstaller on Windows?" do message "Congratulations, you already have an ssh key!" + link_without_toc 'create_a_heroku_account' end -option "otherwise" do -important "Use the same email address for heroku, git, github, and ssh." +option "Do you have a preexisting SSH key for some other reason?" do + message "Maybe you went to a previous RailsBridge workshop, or generated an SSH key to push some code to GitHub? You can check with the following command:" + console "ls ~/.ssh/id_rsa" + message "If you see the message **No such file or directory**, you don't have an SSH key yet." + option_half "'No such file or directory'" do + message "Go on to Generate an SSH key" + end + option_half "'/Users/[something]/.ssh/id_rsa'" do + link_without_toc 'create_a_heroku_account' + end +end -important "**REPLACE** `student@example.com` with your **actual email address** below." +a name: "generate-key" +option "Generate an SSH key" do + important "Use the same email address for heroku, git, github, and ssh." -console "ssh-keygen -C student@example.com -t rsa" + important "**REPLACE** `student@example.com` with your **actual email address** below." -message "Press enter to accept the default key save location." + console "ssh-keygen -C student@example.com -t rsa" -message "Next, you'll be asked for a keyphrase." + message "Press enter to accept the default key save location." -h1 "Choose keyphrase" -blockquote do - option "No keyphrase" do - message "Hit enter to accept blank passphrase, then hit enter again." - end - option "Keyphrase" do - message "If your computer is shared with other people, as in a work laptop, you should choose and enter a real passphrase. Twice." + message "Next, you'll be asked for a passphrase." + + h1 "Choose whether to use a passphrase" + blockquote do + option_half "No passphrase" do + message "Hit enter to accept blank passphrase, then hit enter again." + end + option_half "Passphrase" do + message "If your computer is shared with other people, as in a work laptop, you should choose and enter a real passphrase. Twice." + end end -end -message "After key generation is complete, you'll have output that looks like this." + message "After key generation is complete, you'll have output that looks like this." -result <<-OUTPUT + result <<-OUTPUT Generating public/private rsa key pair. -Enter file in which to save the key (/Users/student/.ssh/id_rsa): +Enter file in which to save the key (/Users/student/.ssh/id_rsa): Created directory '/Users/student/.ssh'. -Enter passphrase (empty for no passphrase): -Enter same passphrase again: +Enter passphrase (empty for no passphrase): +Enter same passphrase again: Your identification has been saved in /Users/student/.ssh/id_rsa. Your public key has been saved in /Users/student/.ssh/id_rsa.pub. The key fingerprint is: 88:54:ab:77:fe:5c:c3:7s:14:37:28:8c:1d:ef:2a:8d student@example.com OUTPUT -verify do - message "Your brand-new public key is now stored at `~/.ssh/id_rsa.pub`" - tip "Public vs. Private Keys" do - message "If you look inside `~/.ssh/`, you will notice two files with the same name: `id_rsa` and `id_rsa.pub`." - message "`id_rsa.pub` is your **public key** and can be shared freely." - message "`id_rsa` is your **private key** and must be kept secret." - message "If someone else gets your private key and your passphrase, then they can pretend to be you and log on to your Heroku or Github accounts and cause mischief!" + verify do + message "Your brand-new public key is now stored at `~/.ssh/id_rsa.pub`" + tip "Public vs. Private Keys" do + message "If you look inside `~/.ssh/`, you will notice two files with the same name: `id_rsa` and `id_rsa.pub`." + message "`id_rsa.pub` is your **public key** and can be shared freely." + message "`id_rsa` is your **private key** and must be kept secret." + message "If someone else gets your private key and your passphrase, then they can pretend to be you and log on to your Heroku or GitHub accounts and cause mischief!" + end end -end + message "Add your generated key to the authentication agent using the following command:" + + console "ssh-add ~/.ssh/id_rsa" + + result <<-OUTPUT +Enter passphrase for /Users/student/.ssh/id_rsa: +Identity added: /Users/student/.ssh/id_rsa (/Users/student/.ssh/id_rsa)" + OUTPUT + + tip "Could not open a connection to your authentication agent" do + message "If the ssh-agent is not running, you will come across this error. Here are a few commands that you can try to use to start the ssh-agent:" + console_with_message "For some Windows machines:", "eval `ssh-agent -s`" + console_with_message "For others (confirmed on some Windows 7, 8, 8.1, and 10 setups):", "eval $(ssh-agent)" + console_with_message "For Linux:", "eval `ssh-agent`" + message <<-MARKDOWN +

For additional options, this StackOverflow thread has been helpful: http://stackoverflow.com/questions/17846529/could-not-open-a-connection-to-your-authentication-agent

+ MARKDOWN + end end +next_step 'create_a_heroku_account' diff --git a/sites/installfest/create_and_deploy_a_rails_app.step b/sites/installfest/create_and_deploy_a_rails_app.step deleted file mode 100644 index 8261d20da..000000000 --- a/sites/installfest/create_and_deploy_a_rails_app.step +++ /dev/null @@ -1,258 +0,0 @@ -step "Change to your home directory" do - option "Windows" do - console "cd c:\\Sites" - end - option "Mac or Linux" do - console "cd ~" - end -end - -step "Create a new Rails app" do - - console "rails new test_app" - - message "The command's output is voluminous, and will take some time to complete, with a long pause in the middle, after all the 'create...' statements ending in 'bundle install'. When it fully completes, it will return you to your home prompt. Look for the 'Your bundle is complete!' message just above." - - tip do - message "On Linux, you may have to enable this line to your Gemfile:" - pre "gem 'therubyracer', :platforms => :ruby" - message "Just delete the '#' in front of the statement" - end - - console "cd test_app" - console "rails server" - - tip "Shortcut: Just type 'rails s'" do - message <<-MARKDOWN -Throughout your Rails programming career you're going to type `rails server` a -lot. In fact, you'll type this so much that DHH and the Rails Core team -decided to save you 5 keystrokes per server restart. Simply typing `rails s` -is the same as `rails server`. - MARKDOWN - end - - message <<-MARKDOWN -The first command should produce no output. -If `rails server` starts up with no errors, you're golden! It'll look something like this: - MARKDOWN - - fuzzy_result <<-TEXT -=> Booting WEBrick -=> Rails 3.2{FUZZY}.x{/FUZZY} application starting in development on http://0.0.0.0:3000 -=> Call with -d to detach -=> Ctrl-C to shutdown server -[2010-09-30 21:04:12] INFO WEBrick 1.3.1 -[2010-09-30 21:04:12] INFO ruby 1.9{FUZZY}.3 (2012-11-10) [x86_64-darwin10.4.2]{/FUZZY} -[2010-09-30 21:04:12] INFO WEBrick::HTTPServer#start: pid={FUZZY}24805{/FUZZY} port=3000 - TEXT - -message "If it does, congratulations! You've successfully installed Ruby AND Rails and started your server." - -tip "If it doesn't work..." do - link "diagnostics_for_rvm" -end - -message <<-MARKDOWN - -* In your browser, go to - -* In Windows, you may need to let Ruby and Rails communicate through your firewall. Say yes. - -![Successful Rails Install](successful-rails-install.jpg) - -* Back in the Terminal window where you ran rails server, type **Control-C** (don't type this into the console, but hold the Control and C keys at the same time) to kill(stop) the server. - - MARKDOWN - important "On Windows, sometimes Control-C doesn't work. In that case, look for the key called 'Break' or 'Pause' and press Control-Break, then answer Y at the prompt. If there is no Pause/Break key on your keyboard, you can run `ruby script/rails server` instead of `rails server` which should allow Control-C to stop the server." - end - -step "Generate a database model" do - tip "If your prompt doesn't already show that you are (still) in the test_app folder" do - console "cd test_app" - end - - console <<-BASH -rails generate scaffold user name:string email:string address:text active:boolean - BASH - console <<-BASH -rake db:migrate - BASH - console <<-BASH -rails server - BASH - - message "**Note:** the above are three separate commands. Type each line into the terminal separately, not as one single command." - - message "Wait until your console shows that the Webrick server has started (just like before). Then, in the browser, visit " - - message "Click *New user* to create a user to make sure we can save to the database. Click *Back* to see your results. (The window where you ran `rails server` will display debugging information as you do so.)" - - message "In your Terminal window where you ran `rails server`, type Control-C (or Control-Break,Y) to kill(stop) the server." - -end - -step "Use git" do - tip "If your prompt doesn't already show that you are (still) in the test_app folder" do - console "cd test_app" - end - - console <<-BASH -git init - BASH - - result "Initialized empty Git repository in c:/Sites/test_app/.git/" - - console "git add -A" - tip "git add" do - message <<-MARKDOWN -With Git, there are usually many ways to do very similar things. - - * `git add foo.txt` adds a file named `foo.txt` - * `git add .` ("git add dot") adds all new files and changed files, but *keeps* files that you've deleted - * `git add -A` adds everything, including deletions - -"Adding deletions" may sound weird, but if you think of a version control system as keeping track of *changes*, it might make more sense. - - MARKDOWN - end - - console "git commit -m \"initial commit\"" - result "a lot of lines like create mode 100644 Gemfile" - - console "git log" - result "(Your git name and \"initial commit\" message.)" -end - -step "Deploy your app to Heroku" do - - step "Create a Heroku application from this local Rails application." do - - message "The very first time you use `heroku` you must enter your Heroku email address and password. Your password may not be shown as you type it, but don't worry, it's being entered! If you have already provided your credentials before, you won't be prompted for them again." - - console "heroku create" - result <<-OUTPUT -Enter your Heroku credentials. -Email: myemail@example.com -Password: -Uploading ssh public key /Users/smei/.ssh/id_rsa.pub -Creating floating-winter-18... done, stack is cedar -http://floating-winter-18.heroku.com/ | git@heroku.com:floating-winter-18.git -Git remote heroku added - OUTPUT - - message "Heroku apps are automatically given lyrical names that look like '[adjective]-[noun]-[number]'. Each name is different." - - console "git remote show" - result "heroku" - - message "If you get messages here complaining about public keys it's probably due to some confusion with SSH key usage by another app on your computer. Call a volunteer over to help you figure it out. Luckily this only needs to be done the first time you create a Heroku app." - end - - step "Prepare your rails app for deploying to Heroku" do - message <<-MARKDOWN - -Launch your text editor and open the "Gemfile" file located inside of your test_app folder. (On Windows, this should be in `C:\\Sites\\test_app` and on Linux/OS X, it should be under `~/test_app`.) - -Inside this file, change the line: -MARKDOWN - source_code :ruby, <<-RUBY -gem 'sqlite3' - RUBY - - message <<-MARKDOWN -To this: - MARKDOWN - - source_code :ruby, <<-RUBY -group :development, :test do - gem 'sqlite3' -end -group :production do - gem 'pg' -end - RUBY - - message "Save the file." - - console <<-BASH -bundle install --without production - BASH - - message "Again, wait for the console prompt, and look for the 'Your bundle is complete!' message just above. If this fails, get a volunteer to help you edit `config/environments/production.rb` " - end - - step "Add the changes to git" do - - console <<-BASH -git add . -git commit -m "Updates for heroku deployment" - BASH - end - - step "Deploy (push) to heroku" do - - console "git push heroku master" - - message "It may ask: \"The authenticity of host 'heroku.com (75.101.145.87)' can't be established. RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad. Are you sure you want to continue connecting (yes/no)?\" Type yes and hit *enter*." - - result <<-OUTPUT -The authenticity of host 'heroku.com (75.101.145.87)' can't be established. -RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad. -Are you sure you want to continue connecting (yes/no)? yes -Warning: Permanently added 'heroku.com,75.101.145.87' (RSA) to the list of known hosts. -Counting objects: 60, done. -Compressing objects: 100% (54/54), done. -Writing objects: 100% (60/60), 79.03 KiB, done. -Total 60 (delta 10), reused 0 (delta 0) - ------> Heroku receiving push ------> Rails app detected - Compiled slug size is 080K ------> Launching...... done - App deployed to Heroku - -To git@heroku.com:floating-winter-18.git - * [new branch] master -> master - OUTPUT - - important "Be sure to find and learn your Heroku application name in the output." - - message "This process will probably take about twice as long as your 'bundle install' and then will return you to your console prompt. If it takes longer than that, click: [What to do if git seems stuck](what_to_do_if_git_seems_stuck)" - - console "heroku run rake db:migrate" - - result <<-OUTPUT -Running rake db:migrate attached to terminal... up, run.1 -Migrating to CreateUsers (20111204065949) -== CreateUsers: migrating ==================================================== --- create_table(:users) - -> 0.0122s -== CreateUsers: migrated (0.0123s) =========================================== - OUTPUT - - message "The long number after CreateUsers is a timestamp. Yours will be different!" - end - - step "Visit your new application" do - - message "In the browser, go to your application's URL. You'll need your Heroku application name." - - tip "To find your Heroku application name" do - console "heroku info" - end - - tip "To quickly open your heroku application in a browser" do - console "heroku open" - end - - message "The URL for your app is *application name*.heroku.com -- so with the example output in the previous step, it would be floating-winter-18.heroku.com. Verify you see the welcome page. Leave this browser window open." - - message "In the browser, add /users to the end of the URL and hit *enter*. Verify you see the user list page." - - message "Create and save a new user to verify you can write to the database on Heroku." - - end -end - -next_step "get_a_sticker" - diff --git a/sites/installfest/deploy_a_rails_app.step b/sites/installfest/deploy_a_rails_app.step new file mode 100644 index 000000000..0de03a9ed --- /dev/null +++ b/sites/installfest/deploy_a_rails_app.step @@ -0,0 +1,185 @@ +step "Use git" do + tip "If your prompt doesn't already show that you are (still) in the test_app folder" do + console "cd test_app" + end + + console <<-BASH + git init + BASH + + result "Initialized empty Git repository in c:/Sites/railsbridge/test_app/.git/" + + console "git add -A" + tip do + message <<-MARKDOWN + With Git, there are usually many ways to do very similar things. + + * `git add foo.txt` adds a file named `foo.txt` + * `git add .` ("git add dot") adds all new files and changed files, but *keeps* files that you've deleted + * `git add -A` adds everything, including deletions + + "Adding deletions" may sound weird, but if you think of a version control system as keeping track of *changes*, it might make more sense. + MARKDOWN + end + + console "git commit -m \"initial commit\"" + result "a lot of lines like create mode 100644 Gemfile" + + console "git log" + result "(Your git name and \"initial commit\" message.)" +end + +step "Deploy your app to Heroku" do + + step "Create a Heroku application from this local Rails application." do + + message "The very first time you use `heroku` you must enter your Heroku email address and password. Your password may not be shown as you type it, but don't worry, it's being entered! If you have already provided your credentials before, you won't be prompted for them again." + + console "heroku create" + result <<-OUTPUT + Enter your Heroku credentials. + Email: myemail@example.com + Password: + Uploading ssh public key /Users/smei/.ssh/id_rsa.pub + Creating floating-winter-18... done, stack is cedar + http://floating-winter-18.heroku.com/ | git@heroku.com:floating-winter-18.git + Git remote heroku added + OUTPUT + + message "Heroku apps are automatically given lyrical names that look like '[adjective]-[noun]-[number]'. Each name is different." + + console "git remote show" + result "heroku" + + message "If you get messages here complaining about public keys it's probably due to some confusion with SSH key usage by another app on your computer. Call a volunteer over to help you figure it out. Luckily this only needs to be done the first time you create a Heroku app." + end + + step "Prepare your rails app for deploying to Heroku" do + message <<-MARKDOWN + Launch your text editor and open the "Gemfile" file located inside of your test_app folder. (On Windows, this should be in `C:\\Sites\\railsbridge\\test_app` and on Linux/OS X, it should be under `~/railsbridge/test_app`.) + + Inside this file, change the line: + MARKDOWN + source_code :ruby, <<-RUBY + gem 'sqlite3' + RUBY + + message "To this:" + + source_code :ruby, <<-RUBY + group :development, :test do + gem 'sqlite3' + end + + group :production do + gem 'pg', '~> 0.18' + end + RUBY + + message "Save the file." + + tip do + message 'When you saved the file, your text editor may have added colors to the code, much like the example above. It recognizes the file contains Ruby, and will mark up different kinds of words with different colors. This is called "syntax highlighting", which makes it easier to read code.' + end + tip "Why Sqlite (sqlite3) and PostgreSQL (pg)?" do + message "SQLite and PostgreSQL are different kinds of databases. We're using SQLite for our development and test environments because it's simple to install. We're using PostgreSQL in our production environment because Heroku has done the hard work of installing it for us and it's more powerful than SQLite. We have separate test, development and production databases by default in Rails." + end + + console "gem install bundler" + + fuzzy_result <<-OUTPUT + Successfully installed bundler-{FUZZY}1.14.3{/FUZZY} + 1 gem installed + OUTPUT + + console "bundle install --without production" + + message "Again, wait for the console prompt, and look for the 'Bundle complete!' message just above." + end + + step "Set the root route" do + message "Use your editor to open the file routes.rb (`C:\\sites\\railsbridge\\test_app\\config\\routes.rb` or `~/railsbridge/test_app/config/routes.rb`) and find the line containing:" + + source_code :ruby, <<-RUBY + Rails.application.routes.draw do + RUBY + + message "After this line, add a new line with the following:" + + source_code :ruby, <<-RUBY + root 'drinks#index' + RUBY + + message "Save the file." + end + + step "Add the changes to git" do + + message "Before running the following command (to add to your local git repository), make sure that you are in the `test_app` directory." + + console 'git add .' + console 'git commit -m "Updates for heroku deployment"' + end + + step "Deploy (push) to heroku" do + + console "git push heroku master" + + message "It may ask: \"The authenticity of host 'heroku.com (75.101.145.87)' can't be established. RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad. Are you sure you want to continue connecting (yes/no)?\" Type yes and hit *enter*." + + result <<-OUTPUT + The authenticity of host 'heroku.com (75.101.145.87)' can't be established. + RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad. + Are you sure you want to continue connecting (yes/no)? yes + Warning: Permanently added 'heroku.com,75.101.145.87' (RSA) to the list of known hosts. + Counting objects: 60, done. + Compressing objects: 100% (54/54), done. + Writing objects: 100% (60/60), 79.03 KiB, done. + Total 60 (delta 10), reused 0 (delta 0) + + -----> Heroku receiving push + -----> Rails app detected + Compiled slug size is 080K + -----> Launching...... done + App deployed to Heroku + + To git@heroku.com:floating-winter-18.git + * [new branch] master -> master + OUTPUT + + important "Be sure to find and learn your Heroku application name in the output." + + message "This process will probably take about twice as long as your 'bundle install' and then will return you to your console prompt. If it takes longer than that, talk to a TA." + + console "heroku run rails db:migrate" + + result <<-OUTPUT + Migrating to CreateDrinks (20160706063236) + == 20160706063236 CreateDrinks: migrating ===================================== + -- create_table(:drinks) + -> 0.0084s + == 20160706063236 CreateDrinks: migrated (0.0085s) ============================ + OUTPUT + + message "The long number after CreateDrinks is a timestamp. Yours will be different!" + end + + step "Visit your new application" do + + message "Now that the app is deployed, you can visit it in a browser." + + tip "To quickly open your heroku application in a browser" do + console "heroku open" + end + + message "The URL for your app is *application-name*.heroku.com -- something like floating-winter-18.heroku.com." + + message "Verify you see the welcome page. Leave this browser window open." + + message "Create and save a new drink to verify you can write to the database on Heroku." + + message "If you want to see a little more info about your app, you can run `heroku info`." + end +end + +next_step "get_a_sticker" diff --git a/sites/installfest/diagnostics_for_rvm.step b/sites/installfest/diagnostics_for_rvm.step deleted file mode 100644 index 402f01ecc..000000000 --- a/sites/installfest/diagnostics_for_rvm.step +++ /dev/null @@ -1,12 +0,0 @@ -console "which git" -result "/usr/local/bin/git" - -console "which ruby" -result "~/.rvm/rubies/ruby-1.9.3-p0/bin/ruby" - -console "which rails" -result "~/.rvm/gems/ruby-1.9.3-p0@global/bin/rails" - -console "gem list --local" -message "this will show a lot of gems... check with a TA to make sure you have all the gems you need" - diff --git a/sites/installfest/editors.step b/sites/installfest/editors.step new file mode 100644 index 000000000..102e63955 --- /dev/null +++ b/sites/installfest/editors.step @@ -0,0 +1,23 @@ +message <<-MARKDOWN + +There are a number of different editors designed for programming. You may already have a favorite editor; if so you can continue using that editor. If you have never used a programming editor, there are a number of good editors available, several of them free of charge. Many RailsBridge instructors use the Atom editor, so you may want to install Atom; if you end up asking for help during your class it may be easier for an instructor or TA to help you if you are using Atom. Here is a list of some of the more widely used editors: + +* Atom is a free, open-source editor that can be customized to your liking using its rich ecosystem of add-on packages. A download is available for OS X 10.8+, Windows 7/8, and Ubuntu/RedHat linux. +* Sublime Text is popular with many Ruby and Rails users. You can use it free for evaluation, then must pay to continue using it. +* Komodo is a very good free programming editor, that is not used as widely as in the past. It is relatively easy to use. +* TextMate is very popular in the Ruby and Rails community. It is not free. +* Aptana Studio is a free, full-featured, development IDE (Integrated Development Environment) for Ruby and Rails. It has many powerful features to assist you while you develop your code. You can install Aptana as either a stand-along program or as an Eclipse plugin. +* RubyMine is used by many companies for their Ruby and Rails software development. Is is also a full-featured IDE, very similar to Aptana. RubyMine is not free, but has a 30-day evaluation period. + +There are other programming editors available, but these are among the most popular. Some people use simple non-programming editors, such as NotePad (or WordPad or TextEdit in "plain text" mode). It is fine to use a simple editor, though at some point you should look into using an editor designed for programming as using a programming editor or IDE will improve your programming productivity. + +MARKDOWN + +important do + message "Microsoft Word is **not** a text editor. It is a *word processor*." + message "(Word Processors add lots of weird invisible formatting stuff to the files they edit. You must use a text editor to write code.)" +end + +message "Some programmers use command-line text editors like `emacs` or `vim`. These have a steep learning curve, so we recommend you use a graphical editor like those listed above." + +next_step "installfest" diff --git a/sites/installfest/get_a_sticker.step b/sites/installfest/get_a_sticker.step index aa2542f73..7f7c77edd 100644 --- a/sites/installfest/get_a_sticker.step +++ b/sites/installfest/get_a_sticker.step @@ -1,281 +1,39 @@ step "Have a volunteer check your tool versions" do -message "Find a volunteer and have them watch the next steps." + message "Find a volunteer and have them watch the next steps." -verify "tool installation" do + verify "tool installation" do - tip "Most of the time, the version numbers don't have to match exactly. In general, if the *first two* numbers match, or if the full number you have is *greater* than the one below, then you're cool." + tip "Most of the time, the version numbers don't have to match exactly. In general, if the *first two* numbers match, or if the full number you have is *greater* than the one below, then you're cool." - h3 "If you're on OSX or Linux:" + h3 "If you're on OSX or Linux:" - console "rvm -v" - fuzzy_result "rvm 1{FUZZY}.x.x by Wayne E. Seguin (wayneeseguin@gmail.com) [https://rvm.beginrescueend.com/]{/FUZZY}" + console "rvm -v" + fuzzy_result "rvm 1{FUZZY}.x.x by Wayne E. Seguin (wayneeseguin@gmail.com) [https://rvm.io/]{/FUZZY}" - console "sqlite3 --version" - fuzzy_result "3{FUZZY}.x.x{/FUZZY}" + h3 "On all operating systems:" - h3 "On all operating systems:" + console "ruby -v" + fuzzy_result "ruby 2.3{FUZZY}.3p222 (2015-12-16 revision 53155) [x86_64-darwin13]{/FUZZY}" - console "ruby -v" - fuzzy_result "ruby 1.9{FUZZY}.3p125 (2012-02-16 revision 34643) [x86_64-darwin11.3.0]{/FUZZY}" + tip "As long as your Ruby version is #{version_string(:ruby_short)} or above, you're good to go." - console "git --version" - fuzzy_result "git version 1{FUZZY}.x.x{/FUZZY}" + console "bundle -v" + fuzzy_result "Bundler version 1{FUZZY}.x.x{/FUZZY}" - console "gem -v" - fuzzy_result "1.8{FUZZY}.x{/FUZZY}" + console "rails -v" + fuzzy_result "Rails 5.0{FUZZY}.x{/FUZZY}" - console "rake --version" - fuzzy_result "rake, version {FUZZY}10.0.3{/FUZZY}" - - console "bundle -v" - fuzzy_result "Bundler version 1{FUZZY}.x.x{/FUZZY}" - - console "rails -v" - fuzzy_result "Rails 3.2{FUZZY}.x{/FUZZY}" -end - -message "If this works - proceed to build the sticker app." - -end - -step "Build the sticker app" do - -verify "rails" do - - section "Change to your home directory" do - option "Windows" do - console "cd c:\\Sites" - end - option "Mac or Linux" do - console "cd ~" - end - end - - console "rails new sticker" - result "# ...most output omitted for brevity... -Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed." - - tip do - message "On Linux, you may have to add this line to your Gemfile:" - pre "gem 'therubyracer', :platforms => :ruby" + tip 'The RailsBridge curriculum is written for Rails 5, so if you still have Rails 4.x or earlier, you need to install Rails 5 with `gem install rails`.' end - - console "cd sticker\nrails generate scaffold drink name:string temperature:integer" - result " - invoke active_record - create db/migrate/20120331005122_create_drinks.rb - create app/models/drink.rb - invoke test_unit - create test/unit/drink_test.rb - create test/fixtures/drinks.yml - route resources :drinks - invoke scaffold_controller - create app/controllers/drinks_controller.rb - invoke erb - create app/views/drinks - create app/views/drinks/index.html.erb - create app/views/drinks/edit.html.erb - create app/views/drinks/show.html.erb - create app/views/drinks/new.html.erb - create app/views/drinks/_form.html.erb - invoke test_unit - create test/functional/drinks_controller_test.rb - invoke helper - create app/helpers/drinks_helper.rb - invoke test_unit - create test/unit/helpers/drinks_helper_test.rb - invoke assets - invoke coffee - create app/assets/javascripts/drinks.js.coffee - invoke scss - create app/assets/stylesheets/drinks.css.scss - invoke scss - create app/assets/stylesheets/scaffolds.css.scss" - - console "rake db:migrate" - result <<-RESULT -== CreateDrinks: migrating =================================================== --- create_table(:drinks) - -> 0.0030s -== CreateDrinks: migrated (0.0033s) ========================================== - RESULT - - - console "rails server" - fuzzy_result <<-RESULT -=> Booting WEBrick -=> Rails 3.2{FUZZY}.x{/FUZZY} application starting in development on http://0.0.0.0:3000 -=> Call with -d to detach -=> Ctrl-C to shutdown server -[2012-03-30 17:56:02] INFO WEBrick 1.3.1 -[2012-03-30 17:56:02] INFO ruby 1.9{FUZZY}.3 (2012-02-16) [x86_64-linux]{/FUZZY} -[2012-03-30 17:56:02] INFO WEBrick::HTTPServer#start: pid={FUZZY}27148{/FUZZY} port=3000 - RESULT - - message <<-BROWSER_VERIFY -In your web browser, go to - -1. Click on "New drink" -2. Enter Cappuccino for the name -3. Enter 135 for the temperature. -4. Click on "Create Drink". - -You should see: ![Drink was successfully created](get_a_sticker_you_should_see.png) - -In your terminal, Hold Control and hit C to stop the rails server. - BROWSER_VERIFY - - fuzzy_result <<-TERMINAL -=> Booting WEBrick -=> Rails 3.2{FUZZY}.x{/FUZZY} application starting in development on http://0.0.0.0:3000 -=> Call with -d to detach -=> Ctrl-C to shutdown server -[2012-03-30 17:56:02] INFO WEBrick 1.3.1 -[2012-03-30 17:56:02] INFO ruby 1.9{FUZZY}.3 (2012-02-16) [x86_64-linux]{/FUZZY} -[2012-03-30 17:56:02] INFO WEBrick::HTTPServer#start: pid={FUZZY}27148{/FUZZY} port=3000 -# ...most output omitted for brevity... -^C>> Stopping ... - -18:00:58 - Installfest stopping ->> Stopping ... - TERMINAL -end - -verify "git" do - message "Make sure you're still in the sticker directory." - - console "git init" - result "Initialized empty Git repository in /home/steven/Code/ruby/sticker/.git/" - - console "git add ." - console "git status" - result <<-RESULT -# On branch master -# -# Initial commit -# -# Changes to be committed: -# (use "git rm --cached ..." to unstage) -# -# new file: .gitignore -# new file: Gemfile -# new file: Gemfile.lock -# new file: README.rdoc -# new file: Rakefile -# ...most output omitted for brevity... - RESULT - - console 'git commit -m "Getting that sticker"' - result " -[master (root-commit) 8cd0e44] Getting that sticker - 54 files changed, 1531 insertions(+) - create mode 100644 .gitignore -# ...most output omitted for brevity... - create mode 100644 test/unit/helpers/drinks_helper_test.rb - create mode 100644 vendor/assets/javascripts/.gitkeep - create mode 100644 vendor/assets/stylesheets/.gitkeep - create mode 100644 vendor/plugins/.gitkeep" - - console "git status" - result " -# On branch master -nothing to commit (working directory clean)" -end - -verify "heroku deploy" do - - message "Use your editor to open the Gemfile and find the line containing:" - - source_code :ruby, <<-RUBY -gem 'sqlite3' - RUBY - - message "Remove this line and replace it with:" - - source_code :ruby, <<-RUBY -group :development do - gem 'sqlite3' -end - -group :production do - gem 'pg' -end - RUBY - - console "bundle install --without production" - result "Fetching gem metadata from https://rubygems.org/......... -# ...most output omitted for brevity... -Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed. -" - - console <<-GIT_COMMIT -git add . -git commit -m "Add pg gem for Heroku." -GIT_COMMIT - result "[master 4a275be] Add pg gem for Heroku. - 2 files changed, 6 insertions(+)" - - message "The name of your heroku app will be different. That is fine." - - console "heroku create" - result <<-HEROKU_CREATE -Creating evening-wind-5284... done, stack is cedar -http://evening-wind-5284.heroku.com/ | git@heroku.com:evening-wind-5284.git -Git remote heroku added - HEROKU_CREATE - - console "git push heroku master" - result <<-HEROKU_PUSH -Counting objects: 7, done. -Delta compression using up to 2 threads. -Compressing objects: 100% (4/4), done. -Writing objects: 100% (4/4), 453 bytes, done. -Total 4 (delta 3), reused 0 (delta 0) - ------> Heroku receiving push ------> Ruby/Rails app detected ------> Detected Rails is not set to serve static_assets - Installing rails3_serve_static_assets... done ------> Configure Rails 3 to disable x-sendfile - Installing rails3_disable_x_sendfile... done ------> Configure Rails to log to stdout - Installing rails_log_stdout... done ------> Gemfile detected, running Bundler version 1.0.7 - Unresolved dependencies detected; Installing... - Using --without development:test -# ...most output omitted for brevity... ------> Compiled slug size is 6.1MB ------> Launching... - HEROKU_PUSH - - console "heroku run rake db:migrate" - result <<-HEROKU_DB_MIGRATE -Migrating to CreateDrinks (20120428044153) -== CreateDrinks: migrating =================================================== --- create_table(:drinks) - -> 0.0084s -== CreateDrinks: migrated (0.0085s) ========================================== - HEROKU_DB_MIGRATE - - console "heroku open" - result "Opening http://evening-wind-5284.heroku.com/" - - message <<-BROWSER_VERIFY -Append `drinks` to the end of your heroku url. For example, if your url was -`http://evening-wind-5284.heroku.com/`, then change it to `http://evening-wind-5284.heroku.com/drinks`. - -You should see the same screen as you did at `http://localhost:3000/drinks` and be able to click "New Drink" and "Create Drink" as expected. - BROWSER_VERIFY - end -message "If this works -- find a volunteer and show them" - +step "Show your Heroku app to a volunteer" do + message "Visit the heroku site you made on the previous page and create a new drink." end step "Congratulations!" do - message "Show a volunteer -- you get a sticker!" + message "You get a sticker! (Or a high-five if there aren't any stickers)." end next_step "clean_up" diff --git a/sites/installfest/glossary.step b/sites/installfest/glossary.step new file mode 100644 index 000000000..d11221fa0 --- /dev/null +++ b/sites/installfest/glossary.step @@ -0,0 +1,24 @@ +h2 "If you are ever stuck trying to make sense of all this alphabet soup - these glossaries may be helpful!" + +span "Have a look at the " +a 'general Glossary', href: '#general-glossary' +span ", the " +a 'Command Line Glossary', href: '#command-line-glossary' +span ", and the " +a 'Ruby and Rails Glossary', href: '#ruby-and-rails-glossary' +span "." + +a name: 'general-glossary' +situation "General Glossary" do + insert 'general-glossary' +end + +a name: 'command-line-glossary' +situation "Command Line Glossary" do + insert 'command-line-glossary' +end + +a name: 'ruby-and-rails-glossary' +situation "Ruby and Rails Glossary" do + insert 'ruby-and-rails-glossary' +end diff --git a/sites/installfest/img/AboutThisMac.png b/sites/installfest/img/AboutThisMac.png new file mode 100644 index 000000000..b884185dd Binary files /dev/null and b/sites/installfest/img/AboutThisMac.png differ diff --git a/sites/installfest/img/WinRailsInstaller.jpg b/sites/installfest/img/WinRailsInstaller.jpg new file mode 100644 index 000000000..33214260c Binary files /dev/null and b/sites/installfest/img/WinRailsInstaller.jpg differ diff --git a/sites/installfest/img/appstore.jpg b/sites/installfest/img/appstore.jpg new file mode 100644 index 000000000..31da13e35 Binary files /dev/null and b/sites/installfest/img/appstore.jpg differ diff --git a/sites/installfest/img/directory.png b/sites/installfest/img/directory.png new file mode 100644 index 000000000..a87c6ed77 Binary files /dev/null and b/sites/installfest/img/directory.png differ diff --git a/sites/installfest/get_a_sticker_you_should_see.png b/sites/installfest/img/get_a_sticker_you_should_see.png similarity index 100% rename from sites/installfest/get_a_sticker_you_should_see.png rename to sites/installfest/img/get_a_sticker_you_should_see.png diff --git a/sites/installfest/img/install_atom_mac.gif b/sites/installfest/img/install_atom_mac.gif new file mode 100644 index 000000000..35a83eb5f Binary files /dev/null and b/sites/installfest/img/install_atom_mac.gif differ diff --git a/sites/installfest/railsbridge_ubuntu12-checkbox.png b/sites/installfest/img/railsbridge_ubuntu12-checkbox.png similarity index 100% rename from sites/installfest/railsbridge_ubuntu12-checkbox.png rename to sites/installfest/img/railsbridge_ubuntu12-checkbox.png diff --git a/sites/installfest/img/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png b/sites/installfest/img/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png new file mode 100644 index 000000000..0b948b3f9 Binary files /dev/null and b/sites/installfest/img/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png differ diff --git a/sites/installfest/img/railsbridge_windowsScreenshot-commandprompt_ror.png b/sites/installfest/img/railsbridge_windowsScreenshot-commandprompt_ror.png new file mode 100644 index 000000000..a2e955bda Binary files /dev/null and b/sites/installfest/img/railsbridge_windowsScreenshot-commandprompt_ror.png differ diff --git a/sites/installfest/img/railsbridge_windows_findingCommandPrompt_win7.png b/sites/installfest/img/railsbridge_windows_findingCommandPrompt_win7.png new file mode 100644 index 000000000..c1ab27d97 Binary files /dev/null and b/sites/installfest/img/railsbridge_windows_findingCommandPrompt_win7.png differ diff --git a/sites/installfest/img/railsbridge_windows_findingCommandPrompt_win8.png b/sites/installfest/img/railsbridge_windows_findingCommandPrompt_win8.png new file mode 100644 index 000000000..f3c8cfaac Binary files /dev/null and b/sites/installfest/img/railsbridge_windows_findingCommandPrompt_win8.png differ diff --git a/sites/installfest/img/successful_rails_install.png b/sites/installfest/img/successful_rails_install.png new file mode 100644 index 000000000..397e03a19 Binary files /dev/null and b/sites/installfest/img/successful_rails_install.png differ diff --git a/sites/installfest/img/xcode-prefs.jpg b/sites/installfest/img/xcode-prefs.jpg new file mode 100644 index 000000000..260af6b52 Binary files /dev/null and b/sites/installfest/img/xcode-prefs.jpg differ diff --git a/sites/installfest/img/xcode-tools-install.png b/sites/installfest/img/xcode-tools-install.png new file mode 100644 index 000000000..bf5917e0d Binary files /dev/null and b/sites/installfest/img/xcode-tools-install.png differ diff --git a/sites/installfest/install_homebrew.step b/sites/installfest/install_homebrew.step deleted file mode 100644 index e743b1acf..000000000 --- a/sites/installfest/install_homebrew.step +++ /dev/null @@ -1,15 +0,0 @@ - -console %q{ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"} - -important "If that doesn't work, visit and follow the instructions there." - -verify do - console "brew -v" - fuzzy_result "Homebrew 0.9{FUZZY}.4{/FUZZY}" -end - -tip < [https://rvm.beginrescueend.com/]{/FUZZY}" - end - -end - -step "Install Ruby 1.9.3 using RVM" do - console "rvm install 1.9.3" - - important "If you get an entire page of instructions, beginning with 'Ruby (and needed base gems) for your selection...', you should press Q to skip." - - message "This downloads and compiles Ruby, which takes a while." - - important "If `rvm install 1.9.3` says `autoreconf was not found in the PATH`" do - option "Mac OS" do - console "brew install automake" - end - - option "Ubuntu Linux" do - console "sudo apt-get install automake" - end - - message "Once that completes, retry `rvm install 1.9.3`" - end - - console <<-BASH -rvm use 1.9.3 -rvm --default use 1.9.3 - BASH - - verify do - console "ruby -v" - fuzzy_result "ruby 1.9.3{FUZZY}p125 (2012-02-16 revision 34643) [x86_64-darwin11.4.0]{/FUZZY}" - end -end - diff --git a/sites/installfest/install_sublime_text.png b/sites/installfest/install_sublime_text.png deleted file mode 100644 index d46afbe36..000000000 Binary files a/sites/installfest/install_sublime_text.png and /dev/null differ diff --git a/sites/installfest/install_sublime_text_2_for_mac.step b/sites/installfest/install_sublime_text_2_for_mac.step deleted file mode 100644 index 2fab870a5..000000000 --- a/sites/installfest/install_sublime_text_2_for_mac.step +++ /dev/null @@ -1,28 +0,0 @@ -message <<-MARKDOWN - -We'll be using the Sublime Text 2 text editor during the workshop, though you are free to use a different editor if you prefer. It must be a plain-text editor, such as vi or Textmate. - -MARKDOWN - -important "Microsoft Word and other word processing programs, including TextEdit and Notepad, will not work." - -step "Download Sublime Text 2" do - message "Download the [Sublime Text 2 installer](http://c758482.r82.cf2.rackcdn.com/Sublime%20Text%202.0.1.dmg)." -end - -step 'Select "Open with DiskImageMounter" in the file save dialog' do - message "This should be the default." - - message "It will open an installer with a Sublime Text 2 icon and a picture of your Applications folder. Something like this:" - - message "![install_sublime_text.png](install_sublime_text.png)" - -end - -step "Drag Sublime Text 2 into your Applications folder." do - message "It will open an installer with a Sublime Text 2 icon and a picture of your Applications folder. Drag Sublime Text 2 into your Applications folder." -end - -step "Unmount the installer disk image" do - message "by dragging it from your desktop to the trash" -end diff --git a/sites/installfest/install_xcode.step b/sites/installfest/install_xcode.step index 83e1bf88c..4f9573021 100644 --- a/sites/installfest/install_xcode.step +++ b/sites/installfest/install_xcode.step @@ -1,26 +1,25 @@ -message "Installing XCode is straightforward with one caveat. When it asks you about the components to install you need to make sure \"Unix Tools\" is selected. Other than that just click Continue/Okay/Yes/etc." - -message "**Special note for folks who upgraded from Leopard to Snow Leopard:** The SQLite gem does not compile with an older version of XCode installed. If you upgraded your machine from an older version of OS X, and you have an older XCode, you still need to reinstall it from your **Snow Leopard** DVD. The steps below should work fine to upgrade it." - -choice "installing from a drive or from the App Store" do +option "OSX 10.9 Mavericks - OSX 10.11 El Capitan" do + link "install_xcode_command_line_tools" +end - option "install XCode from a thumb drive or DVD" do - link "install_xcode_from_dvd" - end +option "OSX 10.7 Lion - OSX 10.8 Mountain Lion" do + link "install_xcode_from_app_store" +end - option "Install XCode from the Apple App Store" do - link "install_xcode_from_app_store" - end - - option "Install Command Line Tools for XCode" do - message "Visit the [Apple Developer Downloads Page](https://developer.apple.com/downloads)" - message "Download and install the latest **Command Line Tools for Xcode** package" - end +option "OSX 10.6 Snow Leopard" do + link "install_xcode_from_dvd" +end +option "Install Command Line Tools for XCode" do + message "Visit the [Apple Developer Downloads Page](https://developer.apple.com/downloads)" + message "Download and install the latest **Command Line Tools for Xcode** package" end verify "your compiler was installed" do console "gcc --version" result "i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)" + + message "Your results might differ somewhat. Basically, as long as it doesn't say something like 'command not found', you're probably cool." end +next_step 'osx_rvm' diff --git a/sites/installfest/install_xcode_command_line_tools.step b/sites/installfest/install_xcode_command_line_tools.step new file mode 100644 index 000000000..5cd5cc040 --- /dev/null +++ b/sites/installfest/install_xcode_command_line_tools.step @@ -0,0 +1,30 @@ +message "Xcode Command Line Tools include lots of tools your computer needs to set up new software." + +verify "if you already have the tools installed" do + console "gcc --version" + result "Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/usr/include/c++/4.2.1 +Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn) +Target: x86_64-apple-darwin14.0.0 +Thread model: posix" + message "If you see something about Darwin and LLVM, you are good to go. If not, use one of the steps below to install the tools. Come back afterwords and run this command to make sure it works!" +end + +option "Install XCode Command Line Tools using software update" do + message "You can install these tools by trying to run a command that requires them." + + step "Run the xcode-select command" do + console "xcode-select --install" + end + + step "Install the tools" do + message "Click on the \"Install\" button. When the download finishes, go back and run the test to make sure it works." + img src: 'img/xcode-tools-install.png', alt: "install dialog" + end +end + +option "Install Command Line Tools for XCode" do + message "Visit the [Apple Developer Downloads Page](https://developer.apple.com/downloads)" + message "Download and install the latest **Command Line Tools for Xcode** package for your operating system. This requires you to register for an Apple Developer account." +end + +next_step 'osx_rvm' diff --git a/sites/installfest/install_xcode_from_app_store.step b/sites/installfest/install_xcode_from_app_store.step index c211da39d..414a2fa59 100644 --- a/sites/installfest/install_xcode_from_app_store.step +++ b/sites/installfest/install_xcode_from_app_store.step @@ -6,7 +6,7 @@ end step "Install XCode" do message "Search for 'XCode' and click the 'Install' button" - message "![XCode in the App Store](appstore.jpg)" + img src: 'img/appstore.jpg', alt: 'XCode in the App Store' end step "Launch XCode" @@ -14,6 +14,8 @@ step "Launch XCode" step "Install the Command Line Tools" do message "Open the Preferences menu and select the Downloads tab." message "Then click the 'Install' button next to 'Command Line Tools'." - message "![XCode Preferences](xcode-prefs.jpg)" + img src: 'img/xcode-prefs.jpg', alt: 'XCode Preferences showing the location of the install button for command line tools' end +next_step 'osx_rvm' + diff --git a/sites/installfest/install_xcode_from_dvd.step b/sites/installfest/install_xcode_from_dvd.step index 9cebe7748..fb67a1cc2 100644 --- a/sites/installfest/install_xcode_from_dvd.step +++ b/sites/installfest/install_xcode_from_dvd.step @@ -1,3 +1,5 @@ +message "Installing XCode is straightforward with one caveat. When it asks you about the components to install you need to make sure \"Unix Tools\" is selected. Other than that just click Continue/Okay/Yes/etc." + step "Insert your OS X DVD or Thumb Drive" step "Locate Xcode.mpkg" do @@ -11,15 +13,17 @@ end step "Keep on clicking..." do li 'Click "Continue" at the Introduction screen.' li 'Click "Continue" at the License screen.' - li 'Click "Agree" in the popup to agree to the license.' + li 'Click "Agree" in the pop up to agree to the license.' li 'Click "Continue" at the Destination Select screen.' li "Click \"Continue\" at the Installation Type screen. (Don't change the checkboxes.)" end step 'Click "Install".' do - message "Enter your user password in the popup to REALLY start installing. It takes a while." + message "Enter your user password in the pop up to REALLY start installing. It takes a while." end step 'Click "Close"' do message 'once you get "The installation was successful" on the Summary screen.' end + +next_step 'osx_rvm' diff --git a/sites/installfest/installfest.step b/sites/installfest/installfest.step index 31e86219b..7aced5e1d 100644 --- a/sites/installfest/installfest.step +++ b/sites/installfest/installfest.step @@ -1,12 +1,14 @@ -message "This [InstallFest](http://installfest.railsbridge.org) is a set of step-by-step instructions for installing Ruby, Rails, and other important tools on Mac, Windows, or Linux computers. Originally developed for the [Railsbridge Workshops](http://railsbridge.org), it is a community-supported open-source documentation project. If you want to contribute, click the [git] link at the top-right corner of any page and submit your change on GitHub." +message "This is a set of step-by-step instructions for installing Ruby, Rails, and other important tools on Mac, Windows, or Linux computers. Originally developed for the [Railsbridge Workshops](http://railsbridge.org), it is a community-supported open-source documentation project. If you want to contribute, click the [git] link at the top-right corner of any page and submit your change on GitHub." -step "Prepare for the Install Fest" do +message "**You'll repeat all these steps tomorrow, but more slowly so you can better understand what's going on under the hood.** Today we just want to check that everything's set up and ready for tomorrow." + +step "Prepare for the Installfest" do message <<-MARKDOWN ## You must bring: -* **Your laptop.** You need to have a working wifi connection and a browser. +* **Your laptop.** You need to have a working wifi connection, a browser and an email account you can readily access. * If you have a choice between a Mac and a Windows laptop, please bring the Mac. * Linux is an acceptable alternative, but the Installfest is only tested on Ubuntu. * **Power cord for your laptop** @@ -36,20 +38,17 @@ end step "Read This Overview" do message <<-MARKDOWN -You will be installing the following tools: - -* Ruby -* Rails -* Git -* Heroku -* Sublime Text 2 (or some editor) -* Various useful "ruby gems", including... - * bundler - * sqlite +Here's a list of tools you'll be installing. As you go through the workshop, we'll explain what each one is for and how to use it. -You will also create an account on Heroku, an application hosting platform. +* **Ruby**. A programming language. +* **Rails**. A framework for making web applications with Ruby. It does a lot of the setting up work for you, which is really handy, but we'll cover this in more detail a little later. +* **Git**. A revision or source control system. It creates a _repository_ (sometimes called a _repo_ for short), which is a complete history of your changes to what you're working on. This means you can undo changes and roll back to previous versions of your work if something has gone wrong. +* **GitHub** (optional). A place to store your Git repository online, so you can access it from any computer. +* **Heroku**. An _application server_, which hosts your application while you're working on it. This allows you to access your app online and interact with it from any browser, instead of just on your local computer. +* **Atom** (or [some other editor](editors)). To write programs in Ruby, you need a text editor to create, edit and save Ruby files. +* A couple of useful **Ruby gems**. Ruby gems are useful bits of Ruby code that someone has created for reuse, so you don't have to write it yourself. -If you already have an account on Heroku, make sure you know your username and password. +If you've already installed the above tools and are confident they are setup correctly, skip ahead to the Get a Sticker step. MARKDOWN end diff --git a/sites/installfest/linux.step b/sites/installfest/linux.step index 860f3ded6..5aedf715a 100644 --- a/sites/installfest/linux.step +++ b/sites/installfest/linux.step @@ -1,7 +1,89 @@ -div do +important <<-MARKDOWN + These steps apply to **Ubuntu Linux** only. If you are using some other flavor of Linux, get help from a TA. +MARKDOWN + message <<-MARKDOWN -## Click here if you are using [Ubuntu Linux](ubuntu) +You will need sudo access in order to complete these directions (The first user account has this by default). +MARKDOWN + +step "Install packaged software and libraries" do + message <<-MARKDOWN +Open a terminal (Applications > Accessories > Terminal). You may want to right-click on the terminal icon and select "Add to panel" so the icon will appear next to the default help and Firefox icons in the top panel. + MARKDOWN + + apts = %w{ + autoconf + automake + bison + build-essential + curl + git-core + libapr1 + libaprutil1 + libc6-dev + libltdl-dev + libreadline6 + libreadline6-dev + libsqlite3-0 + libsqlite3-dev + libssl-dev + libtool + libxml2-dev + libxslt-dev + libxslt1-dev + libyaml-dev + ncurses-dev + nodejs + openssl + sqlite3 + zlib1g + zlib1g-dev + } + + console "sudo apt-get install #{apts.join(' ')}" + + important <<-MARKDOWN + If you get an error during `apt-get` resembling -If you are using any other version of Linux, ask for help now! + `Failed to fetch cdrom://Ubuntu`... + + it is because `apt-get` is trying to install packages from your Ubuntu install CD. If you don't have that CD on hand, you can try disabling it as a package source by following this Stack Overflow post: + + http://askubuntu.com/questions/125111/failed-to-download-repository-information-due-to-missing-cdrom MARKDOWN end + +step "Install git" do + console "sudo apt-get install git" + + message "If this results in an error, you should be ok, as the prior step should have installed `git-core`" +end + +step "Install RVM" do + important do + message "If you're using Ubuntu 12.04, Ubuntu 14.04 or the latest version of Mint, ensure that the Run command as login shell option is checked under the Title and Command tab in Profile Preferences (located in Terminal's Edit menu). After changing this setting, you may need to exit your console session and start a new one before the changes take effect." + img src: 'img/railsbridge_ubuntu12-checkbox.png', alt: "Ubuntu 12.04 terminal settings" + end + insert "install_rvm" +end + +step "Install Ruby" do + insert "install_ruby" +end + +step "Install Rails" do + message "Using a terminal again, execute the following command to install rails." + + console "gem install rails" +end + +step "Atom" do + message <<-MARKDOWN + +If you don't have a preferred code editor, you can install Atom. + +Checkout this section of the [Atom manual](http://flight-manual.atom.io/getting-started/sections/installing-atom/#platform-linux) for the latest instructions. +MARKDOWN +end + +next_step "configure_git" diff --git a/sites/installfest/macOS.step b/sites/installfest/macOS.step new file mode 100644 index 000000000..16dd27abc --- /dev/null +++ b/sites/installfest/macOS.step @@ -0,0 +1,43 @@ +step "Learn your Mac OS X Version" do + message <<-MARKDOWN + +* Click on the Apple icon in the top left of your screen. +* Select "About This Mac" +* In the window that comes up, under the title "Mac OS X" there will be a version number. + * If it starts with 10.13, you have **High Sierra**. + * If it starts with 10.12, you have **Sierra**. + * If it starts with 10.11, you have **El Capitan**. + * If it starts with 10.10, you have **Yosemite**. + * If it starts with 10.9, you have **Mavericks**. + * If it starts with 10.8, you have **Mountain Lion**. + * If it starts with 10.7, you have **Lion**. + * If it starts with 10.6, you have **Snow Leopard**. + * If it starts with 10.5, you have **Leopard**. + * If it starts with 10.4, you have **Tiger**. + * If it starts with 10.3, you have **Panther**. + +* Write down the Mac OS X version you have. +* In addition, to the right of the "Processor", there will be the a processor type. + * If it ends with **Intel Core i7**, **Intel Core i5**, **Intel Core i3**, or **Intel Core m3** you are good to go. + * If it ends with **Intel Core 2 Duo**, you are good to go. + * If it ends with **Intel Core Duo** or something else, you are **NOT** good to go. Please flag down a volunteer. + +* Once complete, you may close the "About This Mac" window. + +Below is an example. + +![OS X Snow Leopard](img/AboutThisMac.png) + + MARKDOWN +end + +step "Choose your instructions" do + + option "High Sierra/Sierra/El Capitan/Yosemite/Mavericks/Mountain Lion/Lion/Snow Leopard" do + link "osx_rvm" + end + option "Earlier than Snow Leopard" do + important "You should try upgrading to at least **Snow Leopard**. Apple doesn't sell it the Apple store anymore, so you'll have to [buy it from them online](http://store.apple.com/us/product/MC573Z/A/mac-os-x-106-snow-leopard) or find a copy from a friend." + message "It may be possible to get Ruby and Rails installed if your OS is older than Snow Leopard, but you're likely to encounter a lot of tough roadblocks that will require some intense Googling." + end +end diff --git a/sites/installfest/macintosh.step b/sites/installfest/macintosh.step deleted file mode 100644 index 86f54f19e..000000000 --- a/sites/installfest/macintosh.step +++ /dev/null @@ -1,51 +0,0 @@ -div do -message <<-MARKDOWN - MARKDOWN -end - -step "Learn your Mac OS X Version" do - message <<-MARKDOWN - -* Click on the Apple icon in the top left of your screen. -* Select "About this Mac" -* In the window that comes up, under the title "Mac OS X" there will be a version number. - * If it starts with 10.8, you have **Mountain Lion**. - * If it starts with 10.7, you have **Lion**. - * If it starts with 10.6, you have **Snow Leopard**. - * If it starts with 10.5, you have **Leopard**. - * If it starts with 10.4, you have **Tiger**. - * If it starts with 10.3, you have **Panther**. - -* Write down the one you have and close the "About this Mac" window. - -Below is an example. - -![OS X Snow Leopard](MacOSXSnowLeopard.png) - - MARKDOWN -end - -step "Choose your instructions" do - - option "Mountain Lion/Lion/Snow Leopard" do - option "RailsInstaller" do - message "RailsInstaller is an easy way to get up and running with Ruby and Rails on OSX. Try it first." - link "osx_railsinstaller" - end - option "Manually" do - message "If something went wrong with RailsInstaller, fall back to these instructions." - link "osx_lion" - end - end - - option "Panther" do - link "osx_panther" - end - - option "Tiger, or Leopard" do - important "Panther and Tiger are **very** old and you should seriously consider upgrading, or buying a new computer!" - link "osx_panther" - message "Follow the Panther instructions but be prepared for some things to be difficult." - end -end - diff --git a/sites/installfest/osx_panther.step b/sites/installfest/osx_panther.step deleted file mode 100644 index 891a4c090..000000000 --- a/sites/installfest/osx_panther.step +++ /dev/null @@ -1,107 +0,0 @@ -div do -message <<-MARKDOWN -OS X 10.3-10.5 (Panther, Tiger, Leopard) - - MARKDOWN -end - -step "XCode tools " do - message <<-MARKDOWN - -These are part of OS X, but are not installed by default. - -* Insert your OS X DVD. - -* Look for Xcode.mkpg - it may be under "Optional Installs" - -* Double click to run the installer. It will take a while - the XCode tools are large. - - MARKDOWN -end - -step "Git " do - - option "Leopard or Panther" do - url "http://code.google.com/p/git-osx-installer/" - - message %q{Click "Download the packages here" which will take you to a list of downloads. Pick Intel if you have an Intel Mac, and Universal Binary if you have a PowerPC Mac.} - - message <<-MESSAGE, class: "pertinent-info" - -### Do I have an Intel or PowerPC Mac? - -PowerPC (PPC) Macs are Apple's older line of machines with Motorola chips. Macs purchased since 2006 are almost all Intel. To find out for sure: - -* Click on the Apple menu in the top left corner of your screen. -* Select "About this Mac." -* It should have a line that starts with "Processor." If somewhere in that line it says "Intel," then you have an Intel Mac. Otherwise, you have a PPC Mac. - -For more information, see the [Apple Transition Timeline](http://en.wikipedia.org/wiki/Apple%E2%80%93Intel_transition#Timeline) or the [exhaustive list of Macintosh models](http://en.wikipedia.org/wiki/Timeline_of_Macintosh_models#2000s) on Wikipedia. - MESSAGE - end - - option "Tiger" do - message "ask a TA to help you install git on Tiger" - message "(you probably want to use MacPorts)" - end - -end - -step "Ruby, RubyGems, and Rails " do - - option "Tiger (10.4) and Panther (10.3)" do - message "visit " - message "Select the correct download in the right side of the page" - end - - option "Leopard (10.5)" do - message "use the built-in version of Ruby" - important "Whenever you install a gem using the built-in Ruby you should precede 'gem install' with 'sudo'" - end - console <<-BASH -sudo gem install rubygems-update -sudo update_rubygems -sudo gem update --system -sudo gem install rails - BASH - - message <<-MARKDOWN - -If you are having this error: - ERROR: Error installing rubygems-update: hoe requires RubyGems version >= 1.3.1 - -Try the steps in this order: - - sudo gem update --system - sudo gem install rubygems-update - sudo update_rubygems - sudo gem install rails - -For Tiger ONLY: - -* Install the "X11 User" optional package from your OS X DVD. - -* Install [ MacPorts]. - -* Ruby: sudo port install ruby - -* Gems: - * download [ RubyGems] - * unpack: tar xzf rubygems-1.3.5.tgz - * cd rubygems-1.3.5 - * Install:ruby setup.rb - -* Rails: sudo gem install rails --version 2.3.9 --no-rdoc --no-ri - - MARKDOWN -end - -step "Install Sublime Text 2" do - link "install_sublime_text_2_for_mac" -end - -step "Create an SSH key" do - link "create_an_ssh_key" -end - -next_step "create_a_heroku_account" diff --git a/sites/installfest/osx_railsinstaller.step b/sites/installfest/osx_railsinstaller.step deleted file mode 100644 index 1cfd4fff0..000000000 --- a/sites/installfest/osx_railsinstaller.step +++ /dev/null @@ -1,51 +0,0 @@ -message "These instructions should work on Snow Leopard, Lion, and Mountain Lion." - -step "Run RailsInstaller" do - - message "RailsInstaller includes Rails, Ruby, Git, and SQLite." - - message "Go to and download the kit." - - important "**Don't watch the video!** After you download the file, come back to this site!" - - message "Click on the downloaded file to run the install wizard. Click Next at each step to accept the defaults. Enter your full name and email when prompted." -end - -step "Open Terminal" do - message "Look for **Terminal.app** inside Applications -> Utilities." - - message "Add it to your dock; you'll be using it a lot. (To add it to the dock, click and hold the dock icon once Terminal is open. Select options -> keep in dock.)" - - tip "Arrange your windows so that Terminal and your web browser are next to each other. You will want to *read* from your browser and *type into* your terminal *at the same time*." -end - -step "Install Sublime Text 2" do - message "We'll be using the Sublime Text 2 text editor during the workshop, though you are free to use a different editor if you prefer. It must be a plain-text editor, such as vi or TextMate. Microsoft Word and other word processing programs won't work." - - important "When in doubt, use Sublime Text 2." - - link "install_sublime_text_2_for_mac" -end - -verify "successful installation" do - console "which git" - result "/usr/bin/git" - - console "which ruby" - fuzzy_result "/usr/local/rvm/rubies/ruby-1.9.3{FUZZY}-p194{/FUZZY}/bin/ruby" - - console "which rails" - fuzzy_result "/usr/local/rvm/gems/ruby-1.9.3{FUZZY}-p194{/FUZZY}/bin/rails" - - console "ruby -v" - fuzzy_result "ruby 1.9.3{FUZZY}p194{/FUZZY}" - - console "rails -v" - fuzzy_result "Rails 3.2{FUZZY}.x{/FUZZY}" -end - -step "Generate an ssh public key" do - link "create_an_ssh_key" -end - -next_step "create_a_heroku_account" diff --git a/sites/installfest/osx_lion.step b/sites/installfest/osx_rvm.step similarity index 50% rename from sites/installfest/osx_lion.step rename to sites/installfest/osx_rvm.step index 232acf197..da0f3da10 100644 --- a/sites/installfest/osx_lion.step +++ b/sites/installfest/osx_rvm.step @@ -1,5 +1,5 @@ step "Open Terminal" do - message "Look for **Terminal.app** inside Applications -> Utilities." + message "Look for **Terminal.app** inside Applications -> Utilities, or use Spotlight search (Command + Space Bar) to find it." message "Add it to your dock; you'll be using it a lot. (To add it to the dock, click and hold the dock icon once Terminal is open. Select options -> keep in dock.)" @@ -10,67 +10,62 @@ step "Install A Compiler (XCode or GCC)" do link "install_xcode" end +step "Install Homebrew" do + insert "install_homebrew" +end + step "Install Git" do message "Next we'll install Git, the most popular version control system in the Ruby world:" console "git --version" - fuzzy_result "git version 1.{FUZZY}x.x{/FUZZY}" + fuzzy_result "git version {FUZZY}2.x.x{/FUZZY}" message <<-MARKDOWN - If you see that output, you have Git. Move on to the next step. - -Otherwise, [Install Homebrew](install_homebrew). Come back here when you're done. MARKDOWN - console_with_message "After installing Homebrew, type this in the terminal:", "brew install git" + console_with_message "Otherwise, type this in the terminal:", "brew install git" verify do console "git --version" - fuzzy_result "git version 1.{FUZZY}x.x{/FUZZY}" + fuzzy_result "git version {FUZZY}2.x.x{/FUZZY}" end - - link "configure_git" end a name: "install-rvm" step "Install RVM, the Ruby Version Manager ", {:anchor_name => 'install_rvm_and_ruby'} do - link "install_rvm_and_ruby" + insert "install_rvm" +end + +step "Configure RVM to use Homebrew" do + console "rvm autolibs homebrew" +end + +step "Install Ruby" do + insert "install_ruby" end step "Install Rails" do console "gem install rails" verify do console "rails -v" - fuzzy_result "Rails 3.2{FUZZY}.x{/FUZZY}" + fuzzy_result "Rails 5.{FUZZY}0.x{/FUZZY}" end end -step "Install Sublime Text 2" do - message "We'll be using the Sublime Text 2 text editor during the workshop, though you are free to use a different editor if you prefer. It must be a plain-text editor, such as vi or TextMate. Microsoft Word and other word processing programs won't work." - - important "When in doubt, use Sublime Text 2." - - link "install_sublime_text_2_for_mac" +step "Install Atom" do + insert "install_atom_for_mac" end verify "successful installation" do console "git --version" - fuzzy_result "git version 1.{FUZZY}x.x{/FUZZY}" + fuzzy_result "git version {FUZZY}2.x.x{/FUZZY}" console "which ruby" - fuzzy_result "/Users/alex/.rvm/rubies/ruby-1.9{FUZZY}.3-p290{/FUZZY}/bin/ruby" + fuzzy_result "/Users/{FUZZY}alex{/FUZZY}/.rvm/rubies/ruby-{FUZZY}2.0.0-p247{/FUZZY}/bin/ruby" console "which rails" - fuzzy_result "/Users/alex/.rvm/gems/ruby-1.9{FUZZY}.3-p290{/FUZZY}/bin/rails" -end - -step "Generate an ssh public key" do - link "create_an_ssh_key" -end - -step "Congratulations!" do - message "You have everything you need to write a Ruby on Rails application." + fuzzy_result "/Users/{FUZZY}alex{/FUZZY}/.rvm/gems/ruby-{FUZZY}2.0.0-p247{/FUZZY}/bin/rails" end -next_step "create_a_heroku_account" +next_step "configure_git" diff --git a/sites/installfest/railsbridge_findingCommandPrompt.png b/sites/installfest/railsbridge_findingCommandPrompt.png deleted file mode 100644 index 8208053a3..000000000 Binary files a/sites/installfest/railsbridge_findingCommandPrompt.png and /dev/null differ diff --git a/sites/installfest/railsbridge_setupScreenshot-2TerminalWindows.jpg b/sites/installfest/railsbridge_setupScreenshot-2TerminalWindows.jpg deleted file mode 100644 index eee0e5815..000000000 Binary files a/sites/installfest/railsbridge_setupScreenshot-2TerminalWindows.jpg and /dev/null differ diff --git a/sites/installfest/railsbridge_windowsScreenshot-command-railss.png b/sites/installfest/railsbridge_windowsScreenshot-command-railss.png deleted file mode 100644 index f686dcdfb..000000000 Binary files a/sites/installfest/railsbridge_windowsScreenshot-command-railss.png and /dev/null differ diff --git a/sites/installfest/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png b/sites/installfest/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png deleted file mode 100644 index 96d7e1cdc..000000000 Binary files a/sites/installfest/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png and /dev/null differ diff --git a/sites/installfest/railsbridge_windowsScreenshot-commandprompt_ror.jpg b/sites/installfest/railsbridge_windowsScreenshot-commandprompt_ror.jpg deleted file mode 100644 index 4e6c65e78..000000000 Binary files a/sites/installfest/railsbridge_windowsScreenshot-commandprompt_ror.jpg and /dev/null differ diff --git a/sites/installfest/railsbridge_windowsScreenshot-railsdefault.png b/sites/installfest/railsbridge_windowsScreenshot-railsdefault.png deleted file mode 100644 index 5e816c799..000000000 Binary files a/sites/installfest/railsbridge_windowsScreenshot-railsdefault.png and /dev/null differ diff --git a/sites/installfest/successful-rails-install.jpg b/sites/installfest/successful-rails-install.jpg deleted file mode 100644 index 778a88a83..000000000 Binary files a/sites/installfest/successful-rails-install.jpg and /dev/null differ diff --git a/sites/installfest/ubuntu.step b/sites/installfest/ubuntu.step deleted file mode 100644 index 7f0db3885..000000000 --- a/sites/installfest/ubuntu.step +++ /dev/null @@ -1,80 +0,0 @@ -message <<-MARKDOWN -You will need sudo access in order to complete these directions (The first user account has this by default). -MARKDOWN - -step "Install packaged software and libraries" do - message <<-MARKDOWN -Open a terminal (Applications > Accessories > Terminal). You may want to right-click on the terminal icon and select "Add to panel" so the icon will appear next to the default help and firefox icons in the top panel. - MARKDOWN - - apts = %w{ - autoconf - automake - bison - build-essential - curl - git-core - libapr1 - libaprutil1 - libc6-dev - libltdl-dev - libreadline6 - libreadline6-dev - libsqlite3-0 - libsqlite3-dev - libssl-dev - libtool - libxml2-dev - libxslt-dev - libxslt1-dev - libyaml-dev - ncurses-dev - openssl - sqlite3 - zlib1g - zlib1g-dev - } - - console "sudo apt-get install #{apts.join(' ')}" - -end - -step "Install git" do - console "sudo apt-get install git" - - message "If this results in an error, you should be ok, as the prior step should have installed `git-core`" -end - -step "Configure Git" do - link_without_toc "configure_git" -end - -step "Install RVM" do - important do - message "If you're using Ubuntu 12.04 or the latest version of Mint, ensure that the Run command as login shell option is checked under the Title and Command tab in Profile Preferences. After changing this setting, you may need to exit your console session and start a new one before the changes take affect." - img src: 'railsbridge_ubuntu12-checkbox.png', alt: "Ubuntu 12.04 terminal settings" - end - link "install_rvm_and_ruby" - message "Restart your terminal before continuing." -end - -step "Install Rails" do - message "Using a terminal again, execute the following command to install rails." - - console "gem install rails" -end - -step "Sublime Text 2" do - message <<-MARKDOWN - -If you don't have a preferred code editor, you can install Sublime Text 2. It's available for [32-bit machines](http://c758482.r82.cf2.rackcdn.com/Sublime%20Text%202.0.1.tar.bz2) and [64-bit machines](http://c758482.r82.cf2.rackcdn.com/Sublime%20Text%202.0.1%20x64.tar.bz2) - -Check for the latest version. -MARKDOWN - - tip "How many bits?" do - message %q{If you're not sure, you're probably 32-bit. Run `uname -i` and see. "x86" in the filename means 32-bit, and "x86_64" means 64-bit.} - end -end - -next_step "create_a_heroku_account" diff --git a/sites/installfest/what_to_do_if_git_seems_stuck.md b/sites/installfest/what_to_do_if_git_seems_stuck.md deleted file mode 100644 index 962c81f68..000000000 --- a/sites/installfest/what_to_do_if_git_seems_stuck.md +++ /dev/null @@ -1,12 +0,0 @@ -Sometimes during a git remote push (i.e., to github or heroku), the client seems to stall. - - smei@NINOHE ~/Desktop/ruby_on_rails/test_app_2 (master) - $ git push heroku master - Warning: Permanently added the RSA host key for IP address '75.101.163.44' to the list of known hosts. - Counting objects: 63, done. - Compressing objects: 100% (57/57), done. - Writing objects: 25% (15/63), 19.30 KiB - -When this happens, control-c to end the process, then close the Git Bash window. Open the task manager (right click on the lower menu bar, select Task Manager). Switch tab to "Processes" and click "Image Name" to sort by process name. Scroll down and see if there are any zombie ssh.exe processes. If there are, select them, and hit End Process. - -Then, reopen Git Bash. cd to the test_app directory and do a git remote. Make sure the remote you're trying to push to (origin or heroku) is there - if not, re-add it before trying the push again. diff --git a/sites/installfest/windows.step b/sites/installfest/windows.step index 555222a32..729cec802 100644 --- a/sites/installfest/windows.step +++ b/sites/installfest/windows.step @@ -1,28 +1,23 @@ - message "These instructions should work for all versions of Windows from XP on to Windows 8." +message <<-MARKDOWN +These instructions should work for all versions of Windows from XP to Windows 8. - message "You may need to login as Administrator, or give the Administrator password when installing some programs, depending on your Windows version and user settings." +You may need to login as Administrator, or give the Administrator password when installing some programs, depending on your Windows version and user settings. +MARKDOWN step "Run RailsInstaller" do - - message "RailsInstaller includes Rails, Ruby, Git, and SQLite." - - message "Go to and download the kit." - - important "**Don't watch the video!** After you download the file, come back to this site!" - + message "RailsInstaller includes Rails, Ruby, Git and SQLite." + message "Go to , scroll to the 'Downloads' section, and download the RailsInstaller for Windows/Ruby #{version_string(:ruby_short)}." message "Click on the downloaded file to run the install wizard. Click Next at each step to accept the defaults." + message "Be sure to check the boxes for *Install git (recommended)* and *Add executables for Ruby, DevKit Git (if checked above) to the PATH*" - message "Be sure to check the box for *Add executables for Ruby, Git and DevKit to the PATH*" - - message "![WinXPRailsInstaller.jpg](WinXPRailsInstaller.jpg)" + img src: "img/WinRailsInstaller.jpg", alt: "Screenshot showing location of checkboxes for ensuring that Git is installed" message "Rails will be installed in C:\\RailsInstaller and the directory for your Rails projects will be C:\\Sites" - end step "Configure your git and ssh environment" do - important "At the end of the installer there will be a checkbox asking 'configure your git and ssh environment'. **Leave this box checked.** It will open a terminal window that you need to **type into**." + important "At the end of the installer there will be a checkbox asking 'Configure git and ssh when installation has completed'. **Leave this box checked.** It will open a terminal window that you need to **type into**." message "When it asks *Please enter your name, for example mine is: Wayne E. Seguin*" message "Type *your actual full name* into the console and press **[enter]**" @@ -36,29 +31,26 @@ step "Configure your git and ssh environment" do message "After this step you will have some **git config settings** and you will also have an **ssh key**." end -end + message "Here is a summary of your settings after this process:" -step "Windows 8 Only — Install Node.js" do + img src: 'img/railsbridge_windowsScreenshot-commandprompt_ror.png', alt: "Screenshot showing correct git, ruby, rails and ssh settings" - message "Go to and download the installer" + message "Close the command prompt window." +end - message "Click on the downloaded file to run the install wizard. Click Next at each step to accept the defaults." +step "Update Git" do - message "**Reboot your computer.**" + message "The version of Git that comes with RailsInstaller is old, so we will be updating that next." - message "Once your computer is back up, load **Command Prompt with Ruby and Rails** (see below) and..." + message "Go to and download the installer. You want version 1.9.5 or newer." - console "node -v" - fuzzy_result "v0{FUZZY}.8.x{/FUZZY}" -end + message "Run the installer, and it will ask you where you want to install it. Change it FROM C:\\Program Files\\Git TO, C:\\RailsInstaller\\Git like the picture below." -step "Sanity Check" do + img src: 'img/directory.png', alt: "Screenshot showing the step of installation where the location to install git is set" - console "ruby -v" - fuzzy_result "ruby 1.9.3{FUZZY}p125{/FUZZY}" + message "It will warn you that the directory already exists. Click yes to install to that folder anyway." - console "rails -v" - fuzzy_result "Rails 3.2{FUZZY}.x{/FUZZY}" + message "Keep going using the default selections and you'll have the latest version of Git installed!" end @@ -69,52 +61,111 @@ step "Open a Terminal" do message "RailsInstaller made a special Terminal for you to use when you're working on Ruby, Rails, or Git." - message "To open the special Terminal, choose **All Programs** on the Start menu, then choose **RailsInstaller** and then **Command Prompt with Ruby and Rails**. You should do all your Rails work in this RailsInstaller DOS window." + message "To open the special Terminal, search for **Command Prompt with Ruby and Rails**." - message "![railsbridge_findingCommandPrompt.png](railsbridge_findingCommandPrompt.png)" + div do + option_half "Windows 7" do + img src: 'img/railsbridge_windows_findingCommandPrompt_win7.png', alt: "Screenshot showing search for command prompt" + end - tip "Pin It to Win It" do - message "![Pin the Command Prompt to the Task Bar](railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png)" + option_half "Windows 8" do + img src: 'img/railsbridge_windows_findingCommandPrompt_win8.png', alt: "Screenshot showing search for command prompt" + end end - message "Here is a summary of your settings after this process:" + message "You should do all your Rails work in this RailsInstaller DOS window." - message "![railsbridge_windowsScreenshot-commandprompt_ror.jpg](railsbridge_windowsScreenshot-commandprompt_ror.jpg)" + tip "Pin It to Win It" do + message 'Pinning the Command Prompt to the taskbar is a handy shortcut.' + img src: 'img/railsbridge_windowsScreenshot-commandprompt-pinnedtotaskbar.png', alt: 'Pin the Command Prompt to the Task Bar' + end end -step "Configure the Windows Terminal" do - message "Take a moment to configure the *Command Prompt with Ruby and Rails* so that it is a good height and width and has a nice font. Hint -- click to open the link below, and when you finish those steps, hit the **back** button in your browser to resume this procedure." +step "Update RubyGems" do + message "The version of RubyGems that comes with RailsInstaller may be outdated. Follow these steps to upgrade it!" - link "configure_the_windows_terminal" + step "Check to see if you need to update" do + console "gem -v" - tip "clearing the terminal" do - message "If you ever want to clear the terminal of all its cluttered output" - console "cls" + message "If the output is **#{version_string(:windows_rubygems_min)}** or later, skip to the next step!" end - tip "command history" do - message "The terminal window stores a \"command history.\" To view and re-run previous commands, use the <up arrow> and <down arrow> keys. You can also edit a previous command and run it -- this is handy for long commands, or fixing mistakes, or for cycles (series of commands) that you repeat." - end + step "Install the latest version of RubyGems" do + message "Otherwise, run the following command:" - tip "copy and paste" do + console_without_message "gem update --system --no-document" - message "It's usually better to type commands in yourself. It takes a little longer but you learn more." + message "**Close and reopen your command prompt**, then verify you have the upgraded RubyGems by typing this in the terminal:" - message "But if you must... in these instructions, where it says: \"Type this in the terminal:\", you can usually copy the command from this page, and right click in the menu bar or terminal window, then click on \"Paste\", then hit the **[enter]** key." + console_without_message "gem -v" + fuzzy_result "#{version_string(:windows_rubygems_min_short)}{FUZZY}.9{/FUZZY}" + + message "As long as the version is at least **#{version_string(:windows_rubygems_min)}**, you should be fine!" end end -step "Install Windows Colors" do +a name: 'install-node' +step "Install Node.js" do + + message "Go to and download the installer. Pick the one that is \"recommended for most users.\"" + + message "Click on the downloaded file to run the install wizard. Click Next at each step to accept the defaults." + + message "**Reboot your computer.**" + + message "Once your computer is back up, load **Command Prompt with Ruby and Rails** and..." + + console "node -v" + fuzzy_result "v6{FUZZY}.x.x{/FUZZY}" +end + +step "Update Rails" do + message "Currently, RailsInstaller installs Rails 4.1.x, but we want the latest. Upgrading Rails is pretty easy:" + + console "gem install rails --no-document" + + message "...and you're done. New Rails! Woo." +end + +step "Sanity Check" do - message "This step is optional, but **highly** recommended. Hint -- click to open the link below, and when you finish those steps, hit the **back** button in your browser to resume this procedure." + console "ruby -v" + fuzzy_result "ruby #{version_string(:ruby_short)}{FUZZY}.4{/FUZZY}" - link "windows_colors" + console "rails -v" + fuzzy_result "Rails 5.0{FUZZY}.x{/FUZZY}" + console "git --version" + fuzzy_result "git version 2{FUZZY}.9.2.windows.1{/FUZZY}" end -step "Configure Git" do - link "configure_git" +step "Configure the Windows Terminal" do + message "Take a moment to configure the *Command Prompt with Ruby and Rails* so that it is a good height and width and has a nice font." + + message "Right-click on the menu bar and select **Properties**" + + message "Under the *Font* tab, select Lucida Console from the font chooser box. This lets you view non-ascii characters." + + message "In the *Layout* tab, adjust *Window Size* so the window is about half as wide as your screen, and is as tall as possible without covering the task bar (this may require some trial and error)." + + message "Click OK." + + tip "Clearing the Terminal" do + message "If you ever want to clear the terminal of all its cluttered output" + console "cls" + end + + tip "Command History" do + message "The terminal window stores a \"command history.\" To view and re-run previous commands, use the <up arrow> and <down arrow> keys. You can also edit a previous command and run it -- this is handy for long commands, or fixing mistakes, or for cycles (series of commands) that you repeat." + end + + tip "Copy and Paste" do + + message "It's usually better to type commands in yourself. It takes a little longer but you learn more." + + message "But if you must... in these instructions, where it says: \"Type this in the terminal:\", you can usually copy the command from this page, and right click in the menu bar or terminal window, then click on \"Paste\", then hit the **[enter]** key." + end end step "Install a Text Editor" do @@ -122,13 +173,11 @@ step "Install a Text Editor" do message "Notepad is not a good programming editor, but it will work in a pinch." - important "**When in doubt, use Sublime Text 2.**" - - message "Download Sublime Text 2 here: " - - message "[Sublime Text 2](http://www.sublimetext.com/2) is a good option, if you don't have one yet." + important "**When in doubt, use Atom.**" + message "[Atom](https://atom.io) is a good option, if you don't have one yet." + message "Download Atom here: " end next_step "create_a_heroku_account" diff --git a/sites/installfest/windows_colors.step b/sites/installfest/windows_colors.step deleted file mode 100644 index 34574ae1b..000000000 --- a/sites/installfest/windows_colors.step +++ /dev/null @@ -1,30 +0,0 @@ -message "The Rails server logging and test results are much easier to read with colored output, but the 'Command Prompt with Ruby and Rails' defaults to white on black. Add colors by following these steps." - -step "Download ANSICON" do - message "In the browser, visit " - message "Click the `ANSICON v1.61` link to download the file (or whatever the most recent version is)" - message "Double-click the downloaded file to unzip it" -end - -step "Determine if you have a 32 or 64 bit Operating System" do - message <<-MARKDOWN - * In Windows 7 or Vista, from the Start menu, type system in the Start Search box, and then click system in the Programs list. System Type under System will say whether it's a 32 or 64 bit Operating System - * In Windows XP, right click on the "My Computer" icon, choose Properties, then General - * if System says Windows XP Professional x64 Edition Version then yours is 64 bit - * if it says Windows XP Professional Version then it's 32 bit - MARKDOWN -end - -option "if your OS is 32 bit" do - message "open the subdirectory 'x86' of the downloaded files, select and copy all the files, and paste them into C:\\Windows\\system32; provide admin permissions when prompted" -end - -option "if your OS is 64 bit" do - message "open the subdirectory 'x64' of the downloaded files, select and copy all the files, and paste them into C:\\Windows\\system32; provide admin permissions when prompted" -end - -step "Set up colors" do - message "open Console with Ruby and Rails" - console "ansicon" - message "now, when you use git and rspec, the output will not be all white-on-black" -end diff --git a/sites/installfest/xcode-prefs.jpg b/sites/installfest/xcode-prefs.jpg deleted file mode 100644 index 18bf445a7..000000000 Binary files a/sites/installfest/xcode-prefs.jpg and /dev/null differ diff --git a/sites/intermediate-rails/intermediate-rails.step b/sites/intermediate-rails/intermediate-rails.step deleted file mode 100644 index 3bd8fc5bd..000000000 --- a/sites/intermediate-rails/intermediate-rails.step +++ /dev/null @@ -1,40 +0,0 @@ -message <<-MARKDOWN -# Let's build a message board! - -## Assumptions made by this curriculum -* You’ve gone through the standard RailsBridge installfest and have successfully completed the [Get a Sticker](http://installfest.railsbridge.org/installfest/get_a_sticker) step. -* You’ve gone through the RailsBridge [Suggestotron curriculum](http://installfest.railsbridge.org/curriculum/) at least once before, or maybe a couple of times, or maybe you feel decently comfortable with Rails for some other reason. -* You want to learn more Rails!!! - -## Goals -* Make a thing! -* Understand what the models, views, and controllers of a Rails app do and how they work together. -* Get more comfortable using error messages as guideposts and not as scary things. - -## What’s Going to Happen? -* We’re going to build a message board system, where there are posts on the front page and you click through to see the original post plus discussion below. - -* We’ve divided this into challenges: - * Challenge 1: create a new rails app with a static home page - * Challenge 2: install devise - * Challenge 3: make it pretty with bootstrap - * Challenge 4: add pages to create and look at individual posts - * Challenge 5: make a posts index page - * Challenge 6: add replying - * Challenge 7: inline replying on a post - * Challenge ∞: other features of your choice - -* Each time you get your app into a functional state, before adding any more features, COMMIT TO GIT! The new features will probably break things, which is neat, but you’ll want to be able to roll back to a prior version if necessary. - -MAJORLY IMPORTANT NOTE: We called the sections challenges because they are challenging! This curriculum will be most fun as a collaboration — talk things through with your teacher, TAs, and other students. This is a very different style of curriculum than Suggestotron, so don’t be discouraged if you aren’t quite sure what to do next. - -## How to use this curriculum -* Challenges are the big chunks of stuff to work on at a given time. These are gated by requirements — once you (or your group) has completed a set of requirements, go on to the next set. -* Requirements in this curriculum are not entirely divorced from what a requirement is in the agile software development methodology — things that should be in the application as defined by whoever is designing/deciding things about the product. Except here, we’ve also thrown in some non-application requirements, like the ability to explain a concept to a peer. -* Discussions are led by the teacher; this is the closest this curriculum gets to lecture-y teaching. -* Hints are what they sound like, as are Tools & References. - -* Teachers only!: there's a teaching guide at http://bit.ly/int-railsbridge-guide -MARKDOWN - -next_step "create_a_new_rails_app_with_a_static_home_page" diff --git a/sites/intro-to-rails/CRUD_with_scaffolding.step b/sites/intro-to-rails/CRUD_with_scaffolding.step new file mode 100644 index 000000000..c4a0fbb6d --- /dev/null +++ b/sites/intro-to-rails/CRUD_with_scaffolding.step @@ -0,0 +1,109 @@ +--- +goals { + + message <<-MARKDOWN + At the core, most database driven web sites are the same. They need to store records and provide a way to do the following: + + * **C**reate new records in the database + * **R**ead or show the records in the database + * **U**pdate existing records + * **D**estroy or delete records + + Because these 4 actions (CRUD) are so common, Rails includes the scaffold command to make creating them easier. + MARKDOWN +} + +steps { + + step { + console "rails server" + + tip "Now is a good time to figure out how to have multiple tabs or windows of your terminal or command prompt. Starting and stopping the Rails server all day is tedious, so it's good to have one terminal tab or window for running commands, and a separate one for the server." + } + + step { + message "Point your browser to [http://localhost:3000/topics](http://localhost:3000/topics)" + message "You should see the \"Topics\" page with headers for title and description, and a link to add a new topic:" + + img src: "img/Seattle_topic_list_page.png", alt: "Screenshot of topic list page" + } + + step { + message <<-MARKDOWN + * Click "New Topic" + * Fill in the form and click "Create Topic" + * You should see a page showing your new topic with a message that your topic was successfully created: + MARKDOWN + + img alt: "Screenshot of topic detail page with confirmation message", src: "img/Seattle_topic_created.png" + } + + step { + message <<-MARKDOWN + * Click "Back" + * You should see the topic list again, this time with your new topic listed: + + ![Screenshot of topic list with new topic](img/Seattle_list_with_topic.png) + + * Try the "Show", "Edit", and "Destroy" links to see what they do + * You've created a basic database driven web site, congrats! + MARKDOWN + } +} + +explanation { + + message <<-MARKDOWN + How did all those pages get created and hooked together? The Rails scaffold did it for you. + + Let's take a closer look at some of the files Rails created: + + * `app/models/topic.rb` + * This file contains code for our topic model. If you look at it, + it's nearly blank. Creating, reading, updating, and deleting + records are built into Rails. + + * `app/views/topics` + * This folder contains all the views for our topics model. This is + where the code for the forms you used above is stored. Rails + created all of these pages as part of the scaffold. + * If you've written HTML before, many lines in the views should + look familiar. Rails views are HTML with some extra code added + to display data from the database. + + * `app/views/topics/index.html.erb` + * This is the code for the page that lists all the topics. + * Index is the name given to the "default" page for a web site or a + section of a web site. When you navigate to + http://localhost:3000/topics the topics index page is what is + sent to your computer. + + * `app/views/topics/show.html.erb` + * This is the page you get when you click the "Show" link on the + "Listing topics" page. + + * `app/views/topics/new.html.erb` + * This is the page you get when you click "New Topic". + + * `app/views/topics/edit.html.erb` + * This is the page you get when you click "Edit". + + * `app/views/topics/_form.html.erb` + * You may have noticed that the page for new topics and the page + to edit topics looked similar. That's because they both use the + code from this file to show a form. This file is called a + partial since it only contains code for part of a page. Partials + always have filenames starting with an underscore character. + + * Challenge question: Can you find the line of code in new.html.erb + and edit.html.erb that makes the form partial appear? + + * `app/controllers/topics_controller.rb` + * This is the controller file that Rails created as part of the scaffold + * If you look you'll see a method (a line beginning with + def) for each of the views listed above (except + `_form.html.erb`) + MARKDOWN +} + +next_step "setting_the_default_page" diff --git a/sites/intro-to-rails/_consider_deploying.step b/sites/intro-to-rails/_consider_deploying.step new file mode 100644 index 000000000..79dcf7e68 --- /dev/null +++ b/sites/intro-to-rails/_consider_deploying.step @@ -0,0 +1,7 @@ +div :class => "deploying" do + h1 "Deploying" + blockquote do + message "Before the next step, you could try deploying your app to Heroku!" + link 'deploying_to_heroku' + end +end diff --git a/sites/intro-to-rails/_consider_deploying_again.step b/sites/intro-to-rails/_consider_deploying_again.step new file mode 100644 index 000000000..93c2bee43 --- /dev/null +++ b/sites/intro-to-rails/_consider_deploying_again.step @@ -0,0 +1,13 @@ +div :class => "deploying" do + h1 "Deploying" + blockquote do + + message <<-MARKDOWN + Before the next step, you could try deploying your app to Heroku! + + * If you have already deployed your app to Heroku, go on to [Deploying to Heroku again](deploying_to_heroku_again). + * If this is your first time deploying your app, start at [Deploying to Heroku](deploying_to_heroku) + MARKDOWN + + end +end diff --git a/sites/intro-to-rails/_deploying_to_heroku.step b/sites/intro-to-rails/_deploying_to_heroku.step new file mode 100644 index 000000000..7fd11fb29 --- /dev/null +++ b/sites/intro-to-rails/_deploying_to_heroku.step @@ -0,0 +1,43 @@ +step "Create a Heroku application" do + console "heroku create" + message "`heroku create` registers a new application on Heroku's system. You should see some output including your new app's URL." +end + +step "Edit the Gemfile" do + important "Each application has its own `Gemfile`. Be sure you're opening the one inside your app's folder." + + message "Heroku will run our application slightly differently than our development computer does, which requires us to make a small change to our `Gemfile`." + + message "Open the file called `Gemfile` in Atom, or your preferred editor, and find the line containing:" + + source_code :ruby, <<-RUBY +gem 'sqlite3' + RUBY + + message "**Remove that line** and replace it with:" + + source_code :ruby, <<-RUBY +group :development, :test do + gem 'sqlite3' +end + +group :production do + gem 'pg', '~> 0.18' +end + RUBY +end + +step "Apply the Gemfile changes" do + console "bundle install --without production" + message "Every time the `Gemfile` changes, you need to run ``bundle install`` for the changes to be processed. The processed version of the changes is stored in another file called ``Gemfile.lock``." + message "Here we are bundling without the gems in the group ``production``." +end + +step "Commit the Gemfile changes" do + message "There are now changes to `Gemfile` and `Gemfile.lock` that need to be committed before we can push to Heroku." + console <<-SHELL +git add . +git commit -m "Changed Gemfile for Heroku" + SHELL + tip "There is a period after the word add in the first line." +end diff --git a/sites/intro-to-rails/_deploying_to_heroku_again.step b/sites/intro-to-rails/_deploying_to_heroku_again.step new file mode 100644 index 000000000..753f43c7a --- /dev/null +++ b/sites/intro-to-rails/_deploying_to_heroku_again.step @@ -0,0 +1,61 @@ +step "Commit any pending changes to git" do + message "Heroku will only receive the files we've committed into our local git repository. So we need to make sure all changed files have been committed." + console "git status" + message "`git status` shows you any pending changes you've created. If it has no output, you're already ready to deploy! Otherwise..." + + console <<-SHELL +git add . +git commit -m "Some helpful message for your future self" + SHELL + message "Your commit message should reference whatever your outstanding +changes are: something like \"Added votes to the topics index\"." +end + +step "Push changes to Heroku" do + console "git push heroku main" + message "This takes all changes you've committed locally and pushes them to Heroku." +end + +step "Run database migrations on Heroku" do + console "heroku run rails db:migrate" + message "This tells Heroku to run your migrations on its database, like +running `rails db:migrate` locally." + message "Heroku's database is separate from the one on your computer, which +means it needs to be updated every time you make changes to the structure of +your database." + message "It also means that you'll not see any of the data you entered into +the `sqlite3` database on your computer." +end + +step "Visit your application" do + console "heroku open" + message "This opens the new application in your browser." +end + +explanation do + message <<-MARKDOWN + First, we had to do some work to make Heroku happy with our application. This +required updating the `Gemfile` and bundling. + + * The `Gemfile` is a list of all the Ruby libraries your application needs. + What we've declared here is that we want to use the `sqlite3` library + while we're developing on our computer (the development group) but when + deploying to Heroku (the production group) we want to use the `pg` library, + which is made for the type of database that Heroku uses. + + * Bundler is how Ruby projects keep track of the gems that they use. We told + Bundler what we wanted to use in the `Gemfile`, now we need to make sure those + gems are installed. Since we don't have the type of database Heroku does, we + skip the production gems. Don't worry though! Bundler still logs them so + Heroku will install them when they get your code. + MARKDOWN + + message "You should be able to deploy your application any time it's in a good, working state. Your typical workflow will look like:" + img src: "img/workflow.png", alt: "Diagram showing git workflow of making changes, committing them, and pushing to Heroku.", style: "border: none" + ol do + li { message "Add or change some code" } + li { message "Commit your changes (`git commit`)" } + li { message "(repeat)" } + end + message "Any time your changes are committed, you should feel free to `git push heroku main` and boom! Your changes are live!" +end diff --git a/sites/intro-to-rails/_switch_to_home_directory.step b/sites/intro-to-rails/_switch_to_home_directory.step new file mode 100644 index 000000000..01a5603e1 --- /dev/null +++ b/sites/intro-to-rails/_switch_to_home_directory.step @@ -0,0 +1,12 @@ +message "`cd` stands for **c**hange **d**irectory." + +message "A directory is the same thing as a folder." + +option "Windows" do + console "cd c:\\Sites" + message "`cd c:\\Sites` sets our Sites directory to our current directory." +end +option "Mac or Linux" do + console "cd ~" + message "`cd ~` sets our home directory to our current directory." +end diff --git a/sites/intro-to-rails/_working_effectively_and_efficiently.md b/sites/intro-to-rails/_working_effectively_and_efficiently.md new file mode 100644 index 000000000..f37b0dd36 --- /dev/null +++ b/sites/intro-to-rails/_working_effectively_and_efficiently.md @@ -0,0 +1,18 @@ +# Working Effectively and Efficiently + +We highly recommend you do the following: + +* Bring up your terminal and open 2 tabs: + * One is for regular terminal stuffs + * One will be for IRB (a.k.a. Rails console). We'll explain later. +* Open your browser fresh or hide any windows you already have open. + * Bring up one window with two tabs + * One for this content + * One for interacting with your app. +* Open your text editor and don't close it. +* Hide all extra applications. Turn off Twitter, Facebook, IM, and all other distractions. + +By minimizing the number of things you interact with, you reduce the +amount of time spent switching between them and the context lost as +you work through the lessons. Having 50 tabs open in your web +browser gets confusing. diff --git a/sites/intro-to-rails/add_the_project_to_a_git_repo.step b/sites/intro-to-rails/add_the_project_to_a_git_repo.step new file mode 100644 index 000000000..8b52a6464 --- /dev/null +++ b/sites/intro-to-rails/add_the_project_to_a_git_repo.step @@ -0,0 +1,70 @@ + +goals do + goal "Create a local git repository" + goal "Add all our files to the git repository" + + message "In order to publish our application, we need to add our application and any changes we make over time to a \"revision control system\". In our case we're going to use *git* because it is (relatively) easy and it is what our app server, *Heroku*, uses." +end + +steps do + + step do + console "git init" + + message "It doesn't look like anything really happened, but `git init` +initialized a new repository in a hidden directory called `.git`." + message "You can see this by typing `ls -a` (**l**i**s**t **a**ll files)." + end + + step do + console "git status" + + message "`git status` tells you everything git sees as modified, new, or missing." + message "You should see a ton of stuff under `Untracked files`." + end + + step do + console "git add ." + message "`git add .` tells git that you want to add the current directory (aka `.`) and everything under it to the repo." + tip "git add" do + message <<-MARKDOWN + With Git, there are usually many ways to do very similar things. + + * `git add foo.txt` adds a file named `foo.txt` + * `git add .` adds all new files and changed files, but *keeps* files that you've deleted + * `git add -A` adds everything, including deletions + + "Adding deletions" may sound weird, but if you think of a version control system as keeping + track of *changes*, it might make more sense. Most people use `git add .` but `git add -A` + can be safer. No matter what, `git status` is your friend. + MARKDOWN + end + end + + step do + console "git status" + + message "Now you should see a bunch of files listed under `Changes to be committed`." + end + + step do + console "git commit -m \"Added all the things\"" + message "`git commit` tells git to actually _do_ all things you've said you wanted to do." + message "This is done in two steps so you can group multiple changes together." + message "`-m \"Added all the things\"` is just a shortcut to say what your commit message is. You can skip that part and git will bring up an editor to fill out a more detailed message." + end + + step do + console "git status" + + message "Now you should see something like `nothing to commit, working directory clean` which means you've committed all of your changes." + end +end + +explanation do + message <<-MARKDOWN +By checking your application into git now, you're creating a record of your starting point. Whenever you make a change during today's workshop, we'll add it to git before moving on. This way, if anything ever breaks, or you make a change you don't like, you can use git as an all-powerful "undo" technique. But that only works when you remember to commit early and often! + MARKDOWN +end + +next_step "running_your_application_locally" diff --git a/sites/intro-to-rails/allow_people_to_vote.step b/sites/intro-to-rails/allow_people_to_vote.step new file mode 100644 index 000000000..851d1dc94 --- /dev/null +++ b/sites/intro-to-rails/allow_people_to_vote.step @@ -0,0 +1,85 @@ +goals { + message "Now we're going to add a button people can click to cast a vote." +} + +steps { + + step "Add a new controller action for voting" do + message "Edit `app/controllers/topics_controller.rb` and add this method at the end of the controller, above the `private` keyword:" + + source_code :ruby, <<-RUBY + def upvote + @topic = Topic.find(params[:id]) + @topic.votes.create + redirect_to(topics_path) + end + RUBY + + message <<-MARKDOWN + * `@topic = Topic.find(params[:id])` finds the topic in the database with that id and stores it in the variable `@topic`. + * `@topic.votes.create` creates a new vote for the current topic and saves it in the database. + * `redirect_to(topics_path)` tells the browser to go back to `topics_path` (the topics list). + MARKDOWN + end + + step "Add a new route for voting" do + message "Open `config/routes.rb` and find the section that looks like this:" + source_code :ruby, <<-RUBY + resources :topics + RUBY + + message "Replace that line so it looks like this:" + source_code :ruby, <<-RUBY + resources :topics do + member do + post 'upvote' + end + end + RUBY + + message <<-MARKDOWN + Verify that upvote route was added successfully by checking the output of `rails routes` or [http://localhost:3000/rails/info](http://localhost:3000/rails/info). You should see a line that looks like this: + + ``` + Prefix Verb URI Pattern Controller#Action + upvote_topic POST /topics/:id/upvote(.:format) topics#upvote + ``` + MARKDOWN + end + + step "Add the button to the view" do + + message "Edit `app/views/topics/index.html.erb` so that the bottom loop looks like this:" + + source_code :erb, <<-HTML + <% @topics.each do |topic| %> + + <%= topic.title %> + <%= topic.description %> + <%= pluralize(topic.votes.count, "vote") %> + <%= button_to '+1', upvote_topic_path(topic), method: :post %> + <%= link_to 'Show', topic %> + <%= link_to 'Edit', edit_topic_path(topic) %> + <%= link_to 'Destroy', topic, method: :delete, data: { confirm: 'Are you sure?' } %> + + <% end %> + HTML + + message <<-MARKDOWN + * `pluralize(topic.votes.count, "vote")` displays the number of votes the topic has, plus the word "vote" or "votes" accordingly. + * `button_to '+1'` creates an HTML button with the text "+1". + * `upvote_topic_path(topic)` creates the appropriate URL for the action we want to invoke. In this case, we want to upvote the current topic. + * `upvote_topic_path(topic)` would return `/topics/42/upvote` (if topic.id was 42) + * `method: :post` ensures we do the create action of CRUD, not the read action. + MARKDOWN + end + + step "Confirm your changes in the browser" do + message "Go back to and play." + message "Revel in the fact that you didn't have to restart the server to see these changes. Hawt, no?" + end +} + +insert 'consider_deploying_again' + +next_step "redirect_to_the_topics_list_after_creating_a_new_topic" diff --git a/sites/curriculum/clean_up_links_on_the_topics_list.step b/sites/intro-to-rails/clean_up_links_on_the_topics_list.step similarity index 95% rename from sites/curriculum/clean_up_links_on_the_topics_list.step rename to sites/intro-to-rails/clean_up_links_on_the_topics_list.step index 775f1eabc..811edf3f6 100644 --- a/sites/curriculum/clean_up_links_on_the_topics_list.step +++ b/sites/intro-to-rails/clean_up_links_on_the_topics_list.step @@ -35,10 +35,10 @@ explanation { message <<-MARKDOWN * The two links we removed were show and edit. We did this because the title now links to the show page and from the show page you can reach the edit page. - * The second change we made was to make the link text 'Delete' instead of destroy. + * The second change we made was to make the link text 'Delete' instead of 'Destroy'. MARKDOWN } -consider_deploying +insert 'consider_deploying_again' next_step "credits_and_next_steps" diff --git a/sites/intro-to-rails/creating_a_migration.step b/sites/intro-to-rails/creating_a_migration.step new file mode 100644 index 000000000..72d30f929 --- /dev/null +++ b/sites/intro-to-rails/creating_a_migration.step @@ -0,0 +1,44 @@ +goals { + + model_diagram header: 'Topics', fields: %w(id title description) + + message "The suggestotron has a list of topics that people can vote on. We'll store our topics in the database. In this step you'll do the following:" + + goal "Create a simple *Table* in the database for topics with a title and a description" + goal "Automatically generate the corresponding *Scaffold* in Rails (namely, the *Model*, the *View*, and the *Controller*)." + +} + +steps { + + step { + console "rails generate scaffold topic title:string description:text" + message <<-MARKDOWN +* `generate scaffold` tells Rails to create everything necessary to get up and running with topics. +* `topic` tells Rails the name of the new model. +* `title:string` says that topics have a title, which is a "string". +* `description:text` says that topics have a description which is a "text". (What's the difference between "string" and "text"? Basically "text" is for strings that might be very long.) + + MARKDOWN + message "If you want, take some time to poke around the files listed in this step. You can learn about them in the [Rails Architecture](rails_architecture) page." + link "rails_architecture" + } + + step { + console "rails db:migrate" + message "This tells Rails to update the database to include a table for our new model." + } +} + +explanation { + + message <<-MARKDOWN +Here, `rails db:migrate` is a command provided by the Rails framework. It uses the migration file we just created (`db/migrate/201xxxxxxxxxxx_create_topics.rb`) to change the database. Database migration files can be crucial to code collaboration. + + MARKDOWN + + tip "You can run `rails --help` to see a list of all the `rails` commands your app currently responds to, along with a short description of each one." +} + + +next_step "CRUD_with_scaffolding" diff --git a/sites/intro-to-rails/credits_and_next_steps.step b/sites/intro-to-rails/credits_and_next_steps.step new file mode 100644 index 000000000..d2639cdcd --- /dev/null +++ b/sites/intro-to-rails/credits_and_next_steps.step @@ -0,0 +1,43 @@ +message < Add Project Folder...` menu option: + +![Atom Project menu screenshot](img/atom_add_folder_to_project.png) + +Select your `suggestotron` folder from the file picker that opens. If everything works out Atom should show the directories of your app in a tree structure on the left: + +![Screenshot of Suggestotron project folder tree in Atom](img/atom_project_as_folder.png) + MARKDOWN + end + + message <<-MARKDOWN + You can see that rails new created a lot directories and + files. The ones we want to focus on today are: + MARKDOWN + + table border: "1", cellspacing: "0", cellpadding: "3", align: "center" do + tr { + th "File/Folder" + th "Purpose" + } + tr { + td "app/" + td "Contains the controllers, models, and views for your application. You will do most of your work here." + } + tr { + td "config/" + td "Configure your application's runtime rules, routes, database, and more." + } + tr { + td "db/" + td "Shows your current database schema, as well as the database migrations." + } + tr { + td "public/" + td "The only folder seen to the world as-is. If you put files in here, they will be served directly without any processing by Rails." + } + tr { + td "app/assets/" + td "This is where your images, JavaScript, stylesheets (CSS), and other static files should go. Modern Rails apps use something called the Assets Pipeline, which combines all the JavaScript and CSS files in this directory into a single file for speediness." + } + end + + message "There is a lot more that `rails new` created. Probably enough to fill a book, so we're going to ignore them for now." +end + +next_step "add_the_project_to_a_git_repo" diff --git a/sites/intro-to-rails/glossary.step b/sites/intro-to-rails/glossary.step new file mode 100644 index 000000000..4ee39d03f --- /dev/null +++ b/sites/intro-to-rails/glossary.step @@ -0,0 +1,24 @@ +h2 "If you are ever stuck trying to make sense of all this alphabet soup - these glossaries may be helpful!" + +span "Have a look at the " +a 'general Glossary', href: '#general-glossary' +span ", the " +a 'Command Line Glossary', href: '#command-line-glossary' +span ", and the " +a 'Ruby and Rails Glossary', href: '#ruby-and-rails-glossary' +span "." + +a name: 'general-glossary' +situation "General Glossary" do + insert '../installfest/general-glossary' +end + +a name: 'command-line-glossary' +situation "Command Line Glossary" do + insert '../installfest/command-line-glossary' +end + +a name: 'ruby-and-rails-glossary' +situation "Ruby and Rails Glossary" do + insert '../installfest/ruby-and-rails-glossary' +end diff --git a/sites/intro-to-rails/hooking_up_votes_and_topics.step b/sites/intro-to-rails/hooking_up_votes_and_topics.step new file mode 100644 index 000000000..42502a734 --- /dev/null +++ b/sites/intro-to-rails/hooking_up_votes_and_topics.step @@ -0,0 +1,127 @@ +goals { + + div(style: 'margin: 0 auto; width: 250px; height: 120px;') do + model_diagram header: 'Topics', fields: %w(id title description), style: "float: left;" + div(style: 'float: left; position: relative; width: 60px; height: 100px;') do + div(class: 'arrow-left', style: 'left: 0; top: 30px;') + div(class: 'horiz-line', style: 'left: 5px; top: 37px; width: 25px;') + div(class: 'vert-line', style: 'left: 30px; top: 38px; height: 25px;') + div(class: 'horiz-line', style: 'right: 0; top: 62px; width: 30px;') + end + model_diagram header: 'Votes', fields: %w(id topic_id), style: "float: left;" + end + + message "Because there is an explicit relationship between a topic and its + votes, we need to specify that. In this step, we'll explicitly + declare the relationship between votes and topics." +} + +steps { + + step "Teach the Topic model about Votes" do + message "Edit `app/models/topic.rb` so that it looks like this:" + + source_code :ruby, <<-RUBY +class Topic < ApplicationRecord + has_many :votes, dependent: :destroy +end + RUBY + end + + step "Teach the Vote model about Topics" do + message "Edit `app/models/vote.rb` so that it looks like this:" + source_code :ruby, <<-RUBY +class Vote < ApplicationRecord + belongs_to :topic +end + RUBY + end + + step "Play around with Topics and Votes in the Rails console" do + message "First, make sure you've made at least one topic on the site." + + console_with_message "Next, open a Rails console in a terminal window:", "rails c" + + result <<-CONSOLE + $ rails c + Loading development environment (Rails 5.0.0) + 2.3.0 :001 > + CONSOLE + + message "At the console, try the following things" + + console_with_message "See how many topics exist:", "Topic.count" + + console_with_message "Save the first topic into a variable:", "my_topic = Topic.first" + + tip "`my_topic` here could have been any variable name, but we'll stick with `my_topic` for consistency." + + console_with_message "Change the title of that topic to something else:", "my_topic.update_attributes(title: 'Edited in the console')" + + console_with_message "Add a vote to that topic:", "my_topic.votes.create" + + console_with_message "See how many votes that topic has:", "my_topic.votes.count" + + console_with_message "Remove a vote from that topic:", "my_topic.votes.first.destroy" + + message "Note that the things you can do to **Model classes** (like **Topic** and **Vote**), differ from the things you can do to **Model instances** (like my\\_topic, here). **my\\_topic.votes** is an **association**, and here behaves mostly like a model class." + + div do + half_width "Model class / association methods" do + ul class: 'no-style-type' do + li "Topic.first" + li "Topic.last" + li "Topic.all" + li "Topic.count" + li "Topic.find_by_id(5)" + li "Topic.destroy_all" + li "my_topic.votes.count" + li "my_topic.votes.create" + li "my_topic.votes.destroy_all" + end + end + + half_width "Model instance methods" do + ul class: 'no-style-type' do + li "my_topic.title" + li "my_topic.title = 'New title'" + li "my_topic.update_attributes(title: 'New title')" + li "my_topic.save" + li "my_topic.save!" + li "my_topic.destroy" + li "my_topic.votes.first.destroy" + end + end + end + + message <<-TEXT + An exhaustive list of things you can do to models and associations can be found in the Active Record Query Interface RailsGuide. + TEXT + end +} + +explanation { + + message <<-MARKDOWN + +`has_many` and `belongs_to`: + +* In Rails, relationships between models are called associations. +* Associations (usually) come in pairs. +* A topic will have many votes so we put `has_many :votes` in the + topic model. + * When you ask a topic for its votes, you get an array of votes + for that topic. +* A vote is for a particular topic, so we put `belongs_to :topic` + in the vote model. + * When you ask a vote for its topic, you get the topic for that + vote. + +It can still be important to clean up after yourself! `dependent: :destroy` + on `has_many :votes` means when a **Topic** gets destroyed, all + the **votes** that correspond to it will be destroyed, too. Without + `dependent :destroy`, those votes would live on the database forever. + MARKDOWN +} + +next_step "allow_people_to_vote" diff --git a/sites/intro-to-rails/img/Seattle_list_with_topic.png b/sites/intro-to-rails/img/Seattle_list_with_topic.png new file mode 100644 index 000000000..e693d8f27 Binary files /dev/null and b/sites/intro-to-rails/img/Seattle_list_with_topic.png differ diff --git a/sites/intro-to-rails/img/Seattle_topic_created.png b/sites/intro-to-rails/img/Seattle_topic_created.png new file mode 100644 index 000000000..77a8816f4 Binary files /dev/null and b/sites/intro-to-rails/img/Seattle_topic_created.png differ diff --git a/sites/intro-to-rails/img/Seattle_topic_list_page.png b/sites/intro-to-rails/img/Seattle_topic_list_page.png new file mode 100644 index 000000000..f4ef61124 Binary files /dev/null and b/sites/intro-to-rails/img/Seattle_topic_list_page.png differ diff --git a/sites/intro-to-rails/img/Start_page.png b/sites/intro-to-rails/img/Start_page.png new file mode 100644 index 000000000..d5fbf50f6 Binary files /dev/null and b/sites/intro-to-rails/img/Start_page.png differ diff --git a/sites/intro-to-rails/img/atom_add_folder_to_project.png b/sites/intro-to-rails/img/atom_add_folder_to_project.png new file mode 100644 index 000000000..154db6d95 Binary files /dev/null and b/sites/intro-to-rails/img/atom_add_folder_to_project.png differ diff --git a/sites/intro-to-rails/img/atom_project_as_folder.png b/sites/intro-to-rails/img/atom_project_as_folder.png new file mode 100644 index 000000000..0996dd84a Binary files /dev/null and b/sites/intro-to-rails/img/atom_project_as_folder.png differ diff --git a/sites/intro-to-rails/img/finished_app.png b/sites/intro-to-rails/img/finished_app.png new file mode 100644 index 000000000..c24d282e6 Binary files /dev/null and b/sites/intro-to-rails/img/finished_app.png differ diff --git a/sites/curriculum/img/mvc.png b/sites/intro-to-rails/img/mvc.png similarity index 100% rename from sites/curriculum/img/mvc.png rename to sites/intro-to-rails/img/mvc.png diff --git a/sites/intro-to-rails/img/rails4_rails_info_routing.png b/sites/intro-to-rails/img/rails4_rails_info_routing.png new file mode 100644 index 000000000..463b07d50 Binary files /dev/null and b/sites/intro-to-rails/img/rails4_rails_info_routing.png differ diff --git a/sites/curriculum/img/workflow.png b/sites/intro-to-rails/img/workflow.png similarity index 100% rename from sites/curriculum/img/workflow.png rename to sites/intro-to-rails/img/workflow.png diff --git a/sites/curriculum/curriculum.step b/sites/intro-to-rails/intro-to-rails.step similarity index 55% rename from sites/curriculum/curriculum.step rename to sites/intro-to-rails/intro-to-rails.step index 952a66ad9..69f0a102d 100755 --- a/sites/curriculum/curriculum.step +++ b/sites/intro-to-rails/intro-to-rails.step @@ -12,7 +12,7 @@ allow users to: You've sketched up an initial screenshot of what you want it to look like: -![screenshot](img/finished_app.png) +![Browser window with topic titles that can be voted on, ordered by number of votes](img/finished_app.png) ### Meta-Goal @@ -20,33 +20,33 @@ When you have completed today's goal of getting the basic application online you should understand: * Basic Ruby syntax -* How to try your Ruby code (irb) +* How to try your Ruby code (IRB) * How to go from requirements to a new working Rails application * How to get your application online -* The basic tools a RoR developer uses (source control, editor, console, local server) +* The basic tools a RoR (Ruby on Rails) developer uses (source control, editor, console, local server) ### Schedule -* 1-ish hour of ruby -* 4-ish hours of rails, broken up in 1-ish hour steps +* 1-ish hour of Ruby +* 4-ish hours of Rails, broken up in 1-ish hour steps This is just a rough guideline, not a mandate. Some steps you'll go over and some you'll go under. It'll all work out by the end of the -day... unless I really screwed something up. :D +day. Probably. ### Requirements We're going to be working with: -* ruby 1.9.3 installed via rvm (mac or linux) or railsinstaller (windows) -* rails 3.2.x -* bundler -* sqlite -* the text editor of your choice +* Ruby 2.3 or greater installed via RVM (Mac or Linux) or RailsInstaller (Windows) +* Rails 5.0.x +* Bundler +* SQLite +* The text editor of your choice Everything should be set up the night before during our install-fest. Please ensure you have everything working _before_ you -show up for railsbridge on Saturday. +show up for RailsBridge on Saturday. You can verify that you have everything working by trying this out in your terminal: @@ -62,26 +62,11 @@ $ If you can do that, you are probably good to go. +MARKDOWN -### Working Effectively and Efficiently - -I highly recommend you do the following: - -* Bring up your terminal and open 2 tabs: - * One is for regular terminal stuffs - * One will be for irb (aka rails console). We'll explain later -* Open your browser fresh or hide any windows you already have open. - * Bring up one window with two tabs - * One for this content - * One for interacting with your app. -* Open your text editor and _do not ever close it_. We're not quitters. -* Hide all extra applications. Turn off twitter, IM, and all other distractions. - -By minimizing the number of things you interact with, you reduce the -amount of time spent switching between them and the context lost as -you work through the lessons. Having 50 tabs open in your web -browser gets confusing and wastes time. +insert 'working_effectively_and_efficiently' +message <<-MARKDOWN ### Format Each lesson will look like this: @@ -91,7 +76,7 @@ Each lesson will look like this:

Goal:

Description of the current step. -

Red because big goals are scary. +

Red because big goals are important.

Steps:

@@ -102,11 +87,11 @@ def code_to_write 1 + 1 end
-

Yellow because we've gotten it done, but we have no clue what's going on. +

Yellow because we've gotten it done, but we're not sure yet what's going on.

Explanation

-

Details of what the steps actually did... spell out the cause and effect. +

Details of what the steps actually did, explaining the cause and effect.

Green because we can tie everything together now.

diff --git a/sites/curriculum/make_the_topic_title_a_link.step b/sites/intro-to-rails/make_the_topic_title_a_link.step similarity index 92% rename from sites/curriculum/make_the_topic_title_a_link.step rename to sites/intro-to-rails/make_the_topic_title_a_link.step index dade9ad9d..b27772f59 100644 --- a/sites/curriculum/make_the_topic_title_a_link.step +++ b/sites/intro-to-rails/make_the_topic_title_a_link.step @@ -36,7 +36,7 @@ explanation { source_code :erb, "<%= topic.description %>" - message "This line was getting the description using .description and just printing it out." + message "This line was getting the description using `.description` and just printing it out." source_code :erb, "Description" @@ -53,7 +53,7 @@ explanation { message " Here's another use of `link_to` to create a link on the page. This `link_to` creates a link using the text from the topic title and goes - to the topic#show page." + to the topic show page." } next_step "clean_up_links_on_the_topics_list" diff --git a/sites/intro-to-rails/rails_architecture.step b/sites/intro-to-rails/rails_architecture.step new file mode 100644 index 000000000..e356d0df2 --- /dev/null +++ b/sites/intro-to-rails/rails_architecture.step @@ -0,0 +1,53 @@ +goals { + + goal "Create a database table for topics with a title and a description" + + message "In this step we'll learn a bit about Rails architecture. By the end of this step you should understand the following concepts:" + + ul { + li "Table" + li "Model" + li "View" + li "Controller" + } +} + +explanation { + + h2 "Rails architecture and its relation to the database" + + img(src: "img/mvc.png", alt: "MVC") + + message "Rails implements a very specific notion of the **Model/View/Controller** pattern, which guides how you structure your web applications." + + h3 "Model" + message <<-MARKDOWN +* For all the Models we create in RailsBridge, Model objects have a corresponding record in the database. The name of the table in the database is the plural version of the Model's class name. For example, if the Model is called 'Duck', it will automatically query or write to the 'ducks' table in the database. +* Methods internal to Rails make it easy to automatically write records to the database and query the database to get the records again later. +* The Model is a bridge between the database and your application's code. + MARKDOWN + + h3 "View" + message <<-MARKDOWN +* The View generates the HTML that will be displayed in the browser. +* View files are written in ERB, a templating language. It contains HTML with Ruby code embedded in it. The ruby variables in the view stand as placeholders for content that will be filled in when a user requests the page. +* (There are several other templating languages available, but in RailsBridge we always stick to ERB.) + MARKDOWN + + h3 "Controller" + message <<-MARKDOWN +* Controllers pass Ruby objects between the Models and the Views. +* Each url corresponds to a specific method in a Controller. +* After this step, when you visit any page in your application, that request will be handled by a method in a Controller. + MARKDOWN + + message <<-MARKDOWN +When Models, Views and Controllers are all put together, they follow a pattern: Given a URL, Rails will look up which Controller method (also called an "Action") to use. The Controller Action will use methods in a corresponding Model. The Model will need to read or write to the database, and return an object containing that data to the Controller. The Controller will take that object and pass it to the View. (Actions normally have a corresponding View file, and Rails will automatically find and use that file.) + +Models, Views and Controllers each have specific jobs. Separating responsibilities like this make it easier to develop, especially as it gets bigger. (When each file has a clear responsibility it's easier to fix problems and add new features.) + MARKDOWN + + message <<-MARKDOWN + If you want to learn more about Rails Architecture, you may want to watch this video explanation (3 min 30 sec) [MVC architecture *Youtube*](https://www.youtube.com/watch?v=eTdVkgF_Slo) + MARKDOWN +} diff --git a/sites/curriculum/redirect_to_the_topics_list_after_creating_a_new_topic.step b/sites/intro-to-rails/redirect_to_the_topics_list_after_creating_a_new_topic.step similarity index 50% rename from sites/curriculum/redirect_to_the_topics_list_after_creating_a_new_topic.step rename to sites/intro-to-rails/redirect_to_the_topics_list_after_creating_a_new_topic.step index 67e611872..8c60ed7c1 100644 --- a/sites/curriculum/redirect_to_the_topics_list_after_creating_a_new_topic.step +++ b/sites/intro-to-rails/redirect_to_the_topics_list_after_creating_a_new_topic.step @@ -1,11 +1,13 @@ goals { - message " - When a user creates a new topic they are currently shown a page with - just that topic. For our voting app it makes more sense that they - would be taken back to the topic list." + message <<-MARKDOWN + When a user creates a new topic, or edits an existing topic, they are + currently shown a page with just that topic. For our voting app it makes + more sense that they would be taken back to the topic list. - goal "In this step we'll change the flow of our app so that the user is taken back to the topics list after they add a new topic." + In this step we'll change the flow of our app so that the user is taken back to the topics list after they add a new topic (create) or + edit an existing topic (update). + MARKDOWN } steps { @@ -27,31 +29,30 @@ steps { source_code :ruby, <<-RUBY def create - @topic = Topic.new(params[:topic]) + @topic = Topic.new(topic_params) respond_to do |format| if @topic.save format.html { redirect_to topics_path, notice: 'Topic was successfully created.' } - format.json { render json: @topic, status: :created, location: @topic } + format.json { render :show, status: :created, location: @topic } else - format.html { render action: "new" } + format.html { render :new } format.json { render json: @topic.errors, status: :unprocessable_entity } end end end RUBY - end - step "Add the flash message to your application view" do - message "Open `app/views/layouts/application.html.erb`." +message "In the same file, locate the update method. " + + message "Find the line:" + + source_code :ruby, "format.html { redirect_to @topic, notice: 'Topic was successfully updated.' }" + - message "Find the `` HTML tag and immediately after add the following code:" + message 'and change `@topic` to `topics_path` like before:' - source_code :ruby, <<-RUBY -<% flash.each do |name, msg| %> -
<%= msg %>
-<% end %> - RUBY + source_code :ruby, "format.html { redirect_to topics_path, notice: 'Topic was successfully updated.' }" end step "Confirm your changes" do @@ -64,9 +65,9 @@ explanation { message <<-MARKDOWN * `format.html { redirect_to topics_path, notice: 'Topic was successfully created.' }`: - * `format.html` means that the server should send html back to the browser - * `redirect_to topics_path` means show the **topics list page** when we're done creating a topic - * `notice: 'Topic was successfully created.'` puts the message into the flash so it will be displayed on the topics list + * `format.html` means that the server should send HTML back to the browser + * `redirect_to topics_path` means show the **topics list page** when we're done creating or updating a topic + * `notice: 'Topic was successfully created/updated.'` puts the message into the flash so it will be displayed on the topics list MARKDOWN } diff --git a/sites/curriculum/ruby_language.step b/sites/intro-to-rails/ruby_language.step similarity index 72% rename from sites/curriculum/ruby_language.step rename to sites/intro-to-rails/ruby_language.step index b3d48f1c0..0d2754848 100644 --- a/sites/curriculum/ruby_language.step +++ b/sites/intro-to-rails/ruby_language.step @@ -9,13 +9,13 @@ end steps do step do - message "Type this in the terminal to start the Interactive Ruby Shell, a program which lets try out Ruby code:" + message "Type this in the terminal to start the Interactive Ruby Shell, a program which lets you try out Ruby code:" console_without_message "irb" message "Yours might look different, but it should look something like this:" - console_without_message "1.9.3p362 :001 > " + console_without_message "irb(main):001:0>" end step do @@ -94,7 +94,7 @@ fruits.each do |fruit| puts fruit end RUBY - message "This takes the first item from the `fruits` array (`\"kiwi\"`), assigns it to the variable `fruit`, and runs the code between `do` and `end`. Then it does the same thing for each other item in the list. The code above should print a list of the fruits." + message "This takes the first item from the `fruits` array (`\"strawberry\"`), assigns it to the variable `fruit`, and runs the code between `do` and `end`. Then it does the same thing for each other item in the list. The code above should print a list of the fruits." end step do @@ -109,6 +109,16 @@ end message "This prints `YAY!` if the value stored in `my_variable` is greater than 1." message "Try changing the `>` in the conditional to a `<`." + + message "If you want to do something else when the statement evaluates to false, you can use an `else`:" + + console_without_message <<-RUBY +if my_variable > 1 + puts "YAY!" +else + puts "BOO!" +end + RUBY end step do @@ -122,8 +132,29 @@ pluralize("kiwi") message "Methods take **parameters**, which are the variables they work on. In this case, we made a method called pluralize that takes one parameter, a word." - message "Methods can also return data. In this case, pluralize returns the word with an 's' added to the end of it. In Ruby, functions return whatever the last line of the function evaluates to." + message "Methods can also return data. In this case, pluralize returns the word with an 's' added to the end of it. In Ruby, methods return whatever the last line of the method evaluates to." + end + + step do + message "Putting it all together, let's make a method that says your opinion of some fruits:" + message "**Don't try to type this all in!** Just paste it into irb and see what happens." + console_without_message <<-RUBY +def my_opinion(fruits) + fruits.each do |fruit| + if fruit == "pizza" + puts "pizza is the best!!!" + else + puts pluralize(fruit) + " are pretty good, I guess..." + end + end +end +my_opinion(["apple", "pizza", "orange"]) + RUBY + + message "Try changing this method to say what your favorite fruit is." end end +important "Before you move on to the next step you must exit IRB by typing 'exit'" + next_step "getting_started" diff --git a/sites/intro-to-rails/running_your_application_locally.step b/sites/intro-to-rails/running_your_application_locally.step new file mode 100644 index 000000000..1aadd1447 --- /dev/null +++ b/sites/intro-to-rails/running_your_application_locally.step @@ -0,0 +1,38 @@ +goals do + goal "Let's fire up the application locally" +end + + +steps do + step do + message "Make sure that you're in the `suggestotron` folder. You can type `pwd` (**p**rint **w**orking **d**irectory) in the terminal to see what folder you are in." + + console "rails server" + message "This will print some stuff and stay running forever, printing more stuff +every time you visit a page in your app." + end + step do + text "Point your web browser to " + url "http://localhost:3000/" + message "See your web app actually running!" + end + + step do + message "While the server is running, whatever you type in that terminal tab +will be ignored." + message "To get back to the terminal, you can stop the server by typing +`Control-c`." + result <<-STOPPING_RAILS_SERVER +^CExiting + STOPPING_RAILS_SERVER + end +end + +explanation do + message "`rails server` ran your application locally just like Heroku will be running it on their servers." + message "This provides a very simple means to see your changes before you commit and push them to Heroku." + message "`Control-c` is a way of closing or cancelling terminal programs. Since +`rails server` runs forever, you need to interrupt it with `Control-c`." +end + +next_step "creating_a_migration" diff --git a/sites/curriculum/setting_the_default_page.step b/sites/intro-to-rails/setting_the_default_page.step similarity index 53% rename from sites/curriculum/setting_the_default_page.step rename to sites/intro-to-rails/setting_the_default_page.step index 153194f87..a8f3db533 100644 --- a/sites/curriculum/setting_the_default_page.step +++ b/sites/intro-to-rails/setting_the_default_page.step @@ -1,31 +1,31 @@ goals { - goal "Now that the structure is complete, let's make the flow work smoothly." + message <<-MARKDOWN + Now that the structure is complete, let's make the flow work smoothly. - message "Currently when you go to you see the \"Welcome - aboard\" message." + Currently when you go to you see the "Welcome aboard" message. - message "It would be easier to use our app if went directly to the topics list." + It would be easier to use our app if went directly to the topics list. - message "In this step we'll make that happen and learn a bit about routes in Rails." + In this step we'll make that happen and learn a bit about routes in Rails. + MARKDOWN } steps { step "Add a root route" do - message "Open `config/routes.rb`. Near the end of the file but before the final end add `root :to => 'topics#index'`. When you are done the last few lines should look like this:" + message "Open the file `config/routes.rb` in an editor (In the InstallFest yesterday, we suggested that you install and use **Atom** as your editor)." + + message "Look for the line `Rails.application.routes.draw` at the beginning of the file, and add the line `root 'topics#index'` after it. When you are done the start of the file should look like this:" source_code :ruby, <<-RUBY -root :to => 'topics#index' +Rails.application.routes.draw do + root 'topics#index' RUBY - end - - step "Remove the static welcome file" do - - message " You also need to remove the welcome aboard page for the new route to work." - - console "git rm public/index.html" + em do + message "(Rails 3.x users should add `root to: 'topics#index'` and will need to remove their `public/index.html` file)." + end end step "Confirm your changes" do @@ -36,7 +36,7 @@ root :to => 'topics#index' explanation { message <<-MARKDOWN - * `root :to => 'topics#index'` is a rails route that says the default + * `root 'topics#index'` is a Rails route that says the default address for your site is `topics#index`. `topics#index` is the topics list page (the topics controller with the index action). * Rails routes control how URLs (web addresses) get matched with @@ -48,35 +48,36 @@ explanation { possible URLs. To explore the URLs in more detail we can use the terminal. - At the terminal type `rake routes`. You should get something that + At the terminal type `rails routes`. You should get something that looks like this: ```` - $ rake routes - - votes GET /votes(.:format) votes#index - POST /votes(.:format) votes#create - new_vote GET /votes/new(.:format) votes#new - edit_vote GET /votes/:id/edit(.:format) votes#edit - vote GET /votes/:id(.:format) votes#show - PUT /votes/:id(.:format) votes#update - DELETE /votes/:id(.:format) votes#destroy + $ rails routes + + Prefix Verb URI Pattern Controller#Action topics GET /topics(.:format) topics#index POST /topics(.:format) topics#create new_topic GET /topics/new(.:format) topics#new edit_topic GET /topics/:id/edit(.:format) topics#edit topic GET /topics/:id(.:format) topics#show + PATCH /topics/:id(.:format) topics#update PUT /topics/:id(.:format) topics#update DELETE /topics/:id(.:format) topics#destroy - root / topics#index + root GET / topics#index ```` This shows all the URLs your application responds to. The code that starts with colons are variables so :id means the id number of the record. The code in parenthesis is optional. + You can also get this information on your site in development. Go to http://localhost:3000/rails/info and you'll see something like this: + + Screenshot of browser-based Rails routing info page + + You'll also see that table in whenever you try to access an invalid route (try http://localhost:3000/sandwich) + ### Exploring Routes (optional) Now you can have a look at the paths that are available in your app. Let's try looking at one of the topics routes we just generated. - Open up your rails console and play: + Open up your Rails console and play: $ rails console >> app.topics_path @@ -92,4 +93,6 @@ explanation { MARKDOWN } -next_step "redirect_to_the_topics_list_after_creating_a_new_topic" +insert 'consider_deploying' + +next_step "voting_on_topics" diff --git a/sites/curriculum/voting_on_topics.step b/sites/intro-to-rails/voting_on_topics.step similarity index 92% rename from sites/curriculum/voting_on_topics.step rename to sites/intro-to-rails/voting_on_topics.step index 0a39e6432..d4776198b 100644 --- a/sites/curriculum/voting_on_topics.step +++ b/sites/intro-to-rails/voting_on_topics.step @@ -9,8 +9,8 @@ goals { steps { console <<-SHELL -rails generate resource vote topic_id:integer -rake db:migrate +rails generate model vote topic_id:integer +rails db:migrate SHELL } diff --git a/sites/javascript-snake-game/img/browser_console.png b/sites/javascript-snake-game/img/browser_console.png new file mode 100644 index 000000000..965ff66c6 Binary files /dev/null and b/sites/javascript-snake-game/img/browser_console.png differ diff --git a/sites/javascript-snake-game/javascript-snake-game.step b/sites/javascript-snake-game/javascript-snake-game.step new file mode 100644 index 000000000..ac318c35a --- /dev/null +++ b/sites/javascript-snake-game/javascript-snake-game.step @@ -0,0 +1,45 @@ +markdown <<-MARKDOWN + + ### Goal + + Today, we're going to recreate the classic game ~SNAKE!~ in the Javascript programming language. + By the end of this session, you should understand the basics of the JavaScript language and how you can use it in a web browser. + + The game you are going to create is going to work quite a bit like this: + +MARKDOWN + +canvas id: 'chunk-game', height: 600, width: 800 + +script src: 'js/chunk.js' + +script src: 'js/snake.js' + +markdown <<-MARKDOWN + + ### Meta-Goal + + When you have completed today's goal of creating a browser based game you should understand: + + * The basic syntax of JavaScript + * How to create, view and debug JavaScript + * How to use JavaScript with external libraries. + * How to incrementally build out a JavaScript application. + + ### Schedule + + * 2-ish hours of basic JavaScript syntax. + * 3-ish hours of creating and playing with a Javascript game and animations. + + This is just a rough guideline, not a mandate. Some steps you'll go + over and some you'll go under. It'll all work out by the end of the + day. Probably. + + Before you can start the tutorial, you'll need to download the tutorial + to your computer to edit the source code. + + Once you've downloaded the zip archive file, you will need to extract the contents and open index.html with your browser. If you have trouble extracting the files or opening them, get an instructor to help you out. + +MARKDOWN + +next_step 'lesson-1' \ No newline at end of file diff --git a/sites/javascript-snake-game/js-snake-game-tutorial.zip-manifest b/sites/javascript-snake-game/js-snake-game-tutorial.zip-manifest new file mode 100644 index 000000000..33a706ca8 --- /dev/null +++ b/sites/javascript-snake-game/js-snake-game-tutorial.zip-manifest @@ -0,0 +1,3 @@ +zip_content/index.html +zip_content/snake.js +js/chunk.js diff --git a/sites/javascript-snake-game/js/chunk.js b/sites/javascript-snake-game/js/chunk.js new file mode 100644 index 000000000..be6278b20 --- /dev/null +++ b/sites/javascript-snake-game/js/chunk.js @@ -0,0 +1,102 @@ +var CHUNK = { + canvasWidth: 800, + canvasHeight: 600, + pixelSize: 40, + KEY_MAPPING: { + 39: "right", + 40: "down", + 37: "left", + 38: "up" + }, + started: true, + attrs: {}, + gameHeight: function() { + return this.attrs.gameHeight || (this.attrs.gameHeight = this.canvasHeight / this.pixelSize); + }, + gameWidth: function() { + return this.attrs.gameWidth || (this.attrs.gameWidth = this.canvasWidth / this.pixelSize); + }, + canvas: function() { + if (CHUNK.context) { return CHUNK.context; } + var canvas = document.getElementById("chunk-game"); + CHUNK.context = canvas.getContext("2d"); + return CHUNK.context; + }, + executeNTimesPerSecond: function(tickCallback, gameSpeed) { + tickCallback(); + CHUNK.processID = setInterval(function() { + tickCallback(); + }, 1000 / gameSpeed); + }, + onArrowKey: function(callback) { + document.addEventListener('keydown', function(e) { + if (CHUNK.KEY_MAPPING[e.which]) { + e.preventDefault(); + callback(CHUNK.KEY_MAPPING[e.which]); + } + }); + }, + endGame: function() { + this.started = false + clearInterval(CHUNK.processID); + }, + draw: function(objects) { + if (this.started) { + CHUNK.clear(); + CHUNK.drawObjects(objects); + } + }, + clear: function() { + CHUNK.canvas().clearRect(0, 0, CHUNK.canvasWidth, CHUNK.canvasHeight); + }, + drawObjects: function(objects) { + var ui = this; + objects.forEach(function(object) { + object.pixels.forEach(function(pixel) { + ui.drawPixel(object.color, pixel); + }); + }); + }, + drawPixel: function(color, pixel) { + CHUNK.canvas().fillStyle = color; + var translatedPixel = CHUNK.translatePixel(pixel); + CHUNK.context.fillRect(translatedPixel.left, translatedPixel.top, CHUNK.pixelSize, CHUNK.pixelSize); + }, + translatePixel: function(pixel) { + return { left: pixel.left * CHUNK.pixelSize, + top: pixel.top * CHUNK.pixelSize } + }, + gameBoundaries: function() { + if (this.attrs.boundaries) { return this.attrs.boundaries; } + this.attrs.boundaries = []; + for (var top = -1; top <= CHUNK.gameHeight(); top++) { + this.attrs.boundaries.push({ top: top, left: -1}); + this.attrs.boundaries.push({ top: top, left: this.gameWidth() + 1}); + } + for (var left = -1; left <= CHUNK.gameWidth(); left++) { + this.attrs.boundaries.push({ top: -1, left: left}); + this.attrs.boundaries.push({ top: this.gameHeight() + 1, left: left }); + } + return this.attrs.boundaries; + }, + detectCollisionBetween: function(objectA, objectB) { + return objectA.some(function(pixelA) { + return objectB.some(function(pixelB) { + return pixelB.top === pixelA.top && pixelB.left === pixelA.left; + }); + }); + }, + randomLocation: function() { + return { + top: Math.floor(Math.random()*CHUNK.gameHeight()), + left: Math.floor(Math.random()*CHUNK.gameWidth()), + } + }, + flashMessage: function(message) { + var canvas = document.getElementById("chunk-game"); + var context = canvas.getContext('2d'); + context.font = '20pt Calibri'; + context.fillStyle = 'yellow'; + context.fillText(message, 275, 100); + } +} diff --git a/sites/javascript-snake-game/js/lesson-10.js b/sites/javascript-snake-game/js/lesson-10.js new file mode 100644 index 000000000..1d7ef10da --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-10.js @@ -0,0 +1,54 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var segmentFurtherForwardThan = function(index, snake) { + if (snake[index - 1] === undefined) { + return snake[index]; + } else { + return snake[index - 1]; + } +} + +var moveSnake = function(snake) { + return snake.map(function(oldSegment, segmentIndex) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = segmentFurtherForwardThan(segmentIndex, snake).direction; + return newSegment; + }); +} + +var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + drawSnake(snake); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/lesson-11.js b/sites/javascript-snake-game/js/lesson-11.js new file mode 100644 index 000000000..d75d3da83 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-11.js @@ -0,0 +1,53 @@ +var draw = function(snakeToDraw, apple) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableApple = { color: "red", pixels: [apple] }; + var drawableObjects = [drawableSnake, drawableApple]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var segmentFurtherForwardThan = function(index, snake) { + return snake[index - 1] || snake[index]; +} + +var moveSnake = function(snake) { + return snake.map(function(oldSegment, segmentIndex) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = segmentFurtherForwardThan(segmentIndex, snake).direction; + return newSegment; + }); +} + +var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + draw(snake, apple); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var apple = { top: 8, left: 10 }; + +var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/lesson-12.js b/sites/javascript-snake-game/js/lesson-12.js new file mode 100644 index 000000000..cdeea5a85 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-12.js @@ -0,0 +1,63 @@ +var draw = function(snakeToDraw, apple) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableApple = { color: "red", pixels: [apple] }; + var drawableObjects = [drawableSnake, drawableApple]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var segmentFurtherForwardThan = function(index, snake) { + return snake[index - 1] || snake[index]; +} + +var moveSnake = function(snake) { + return snake.map(function(oldSegment, segmentIndex) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = segmentFurtherForwardThan(segmentIndex, snake).direction; + return newSegment; + }); +} + +var growSnake = function(snake) { + var indexOfLastSegment = snake.length - 1; + var lastSegment = snake[indexOfLastSegment]; + snake.push({ top: lastSegment.top, left: lastSegment.left }); + return snake; +} + +var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween([apple], snake)) { + snake = growSnake(snake); + apple = CHUNK.randomLocation(); + } + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + draw(snake, apple); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var apple = CHUNK.randomLocation(); +var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/lesson-13.js b/sites/javascript-snake-game/js/lesson-13.js new file mode 100644 index 000000000..8b3134a5f --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-13.js @@ -0,0 +1,77 @@ +var draw = function(snakeToDraw, apple) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableApple = { color: "red", pixels: [apple] }; + var drawableObjects = [drawableSnake, drawableApple]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var segmentFurtherForwardThan = function(index, snake) { + return snake[index - 1] || snake[index]; +} + +var moveSnake = function(snake) { + return snake.map(function(oldSegment, segmentIndex) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = segmentFurtherForwardThan(segmentIndex, snake).direction; + return newSegment; + }); +} + +var growSnake = function(snake) { + var tipOfTailIndex = snake.length - 1; + var tipOfTail = snake[tipOfTailIndex]; + snake.push({ top: tipOfTail.top, left: tipOfTail.left }); + return snake; +} + +var ate = function(snake, otherThing) { + var head = snake[0]; + return CHUNK.detectCollisionBetween([head], otherThing); +} + +var advanceGame = function() { + var newSnake = moveSnake(snake); + + if (ate(newSnake, snake)) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! You ate yourself!"); + } + + if (ate(newSnake, [apple])) { + newSnake = growSnake(newSnake); + apple = CHUNK.randomLocation(); + } + + if (ate(newSnake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + + snake = newSnake; + draw(snake, apple); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var apple = CHUNK.randomLocation(); +var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/lesson-2.js b/sites/javascript-snake-game/js/lesson-2.js new file mode 100644 index 000000000..1840eec22 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-2.js @@ -0,0 +1,4 @@ +var snake = [{ top: 0, left: 0}]; +var drawableSnake = { color: "green", pixels: snake }; +var drawableObjects = [drawableSnake]; +CHUNK.draw(drawableObjects); diff --git a/sites/javascript-snake-game/js/lesson-3.js b/sites/javascript-snake-game/js/lesson-3.js new file mode 100644 index 000000000..1788fb86a --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-3.js @@ -0,0 +1,7 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} +var snake = [{ top: 0, left: 0}]; +drawSnake(snake); diff --git a/sites/javascript-snake-game/js/lesson-4.js b/sites/javascript-snake-game/js/lesson-4.js new file mode 100644 index 000000000..a60931b6a --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-4.js @@ -0,0 +1,15 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + +var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = { top: oldSegment.top + 1, left: oldSegment.left }; + var newSnake = [newSegment]; + return newSnake; +} + +var snake = [{ top: 0, left: 0}]; +drawSnake(snake); diff --git a/sites/javascript-snake-game/js/lesson-5.js b/sites/javascript-snake-game/js/lesson-5.js new file mode 100644 index 000000000..0b2c2ba93 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-5.js @@ -0,0 +1,21 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + + +var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = { top: oldSegment.top + 1, left: oldSegment.left }; + var newSnake = [newSegment]; + return newSnake; +} + +var advanceGame = function() { + snake = moveSnake(snake); + drawSnake(snake); +} + +var snake = [{ top: 0, left: 0}]; +CHUNK.executeNTimesPerSecond(advanceGame, 1); diff --git a/sites/javascript-snake-game/js/lesson-6.js b/sites/javascript-snake-game/js/lesson-6.js new file mode 100644 index 000000000..dbc3d9cce --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-6.js @@ -0,0 +1,35 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + if (segment.direction === "down") { + return { top: segment.top + 1, left: segment.left } + } else if (segment.direction === "up") { + return { top: segment.top - 1, left: segment.left } + } else if (segment.direction === "right") { + return { top: segment.top, left: segment.left + 1 } + } else if (segment.direction === "left") { + return { top: segment.top, left: segment.left - 1 } + } + return segment; +} + +var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = moveSegment(oldSegment); + newSegment.direction = oldSegment.direction; + var newSnake = [newSegment]; + return newSnake; +} + + +var advanceGame = function() { + snake = moveSnake(snake); + drawSnake(snake); +} + +var snake = [{ top: 0, left: 0, direction: "down" }]; +CHUNK.executeNTimesPerSecond(advanceGame, 1); diff --git a/sites/javascript-snake-game/js/lesson-7.js b/sites/javascript-snake-game/js/lesson-7.js new file mode 100644 index 000000000..ef1a0e578 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-7.js @@ -0,0 +1,41 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = moveSegment(oldSegment); + newSegment.direction = oldSegment.direction; + var newSnake = [newSegment]; + return newSnake; +} + +var advanceGame = function() { + snake = moveSnake(snake); + drawSnake(snake); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var snake = [{ top: 0, left: 0, direction: "down" }]; +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/lesson-8.js b/sites/javascript-snake-game/js/lesson-8.js new file mode 100644 index 000000000..41466e510 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-8.js @@ -0,0 +1,46 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = moveSegment(oldSegment); + newSegment.direction = oldSegment.direction; + var newSnake = [newSegment]; + return newSnake; +} + +var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + drawSnake(snake); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var snake = [{ top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/lesson-9.js b/sites/javascript-snake-game/js/lesson-9.js new file mode 100644 index 000000000..d80607e46 --- /dev/null +++ b/sites/javascript-snake-game/js/lesson-9.js @@ -0,0 +1,49 @@ +var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var moveSnake = function(snake) { + var newSnake = []; + snake.forEach(function(oldSegment) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = oldSegment.direction; + newSnake.push(newSegment); + }); + + return newSnake; +} + +var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + drawSnake(snake); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/js/snake.js b/sites/javascript-snake-game/js/snake.js new file mode 100644 index 000000000..1af3ef07d --- /dev/null +++ b/sites/javascript-snake-game/js/snake.js @@ -0,0 +1,81 @@ +var draw = function(snakeToDraw, apple) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableApple = { color: "red", pixels: [apple] }; + var drawableObjects = [drawableSnake, drawableApple]; + CHUNK.draw(drawableObjects); +} + +var moveSegment = function(segment) { + switch(segment.direction) { + case "down": + return { top: segment.top + 1, left: segment.left }; + case "up": + return { top: segment.top - 1, left: segment.left }; + case "right": + return { top: segment.top, left: segment.left + 1 } + case "left": + return { top: segment.top, left: segment.left - 1 } + default: + return segment; + } +} + +var segmentFurtherForwardThan = function(index, snake) { + return snake[index - 1] || snake[index]; +} + +var moveSnake = function(snake) { + return snake.map(function(oldSegment, segmentIndex) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = segmentFurtherForwardThan(segmentIndex, snake).direction; + return newSegment; + }); +} + +var growSnake = function(snake) { + var tipOfTailIndex = snake.length - 1; + var tipOfTail = snake[tipOfTailIndex]; + snake.push({ top: tipOfTail.top, left: tipOfTail.left }); + return snake; +} + + +// The last thing we need to do is check to see if the snake ran into itself. +var ate = function(snake, otherThing) { + var head = snake[0]; + return CHUNK.detectCollisionBetween([head], otherThing); +} + +var advanceGame = function() { + var newSnake = moveSnake(snake); + + if (ate(newSnake, snake)) { + CHUNK.endGame(); + return CHUNK.flashMessage("Whoops! You ate yourself!"); + } + // Now we just have to check if the newSnake ate it's previous self to see if + // there was a collision! + + if (ate(newSnake, [apple])) { + newSnake = growSnake(newSnake); + apple = CHUNK.randomLocation(); + } + + if (ate(newSnake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + return CHUNK.flashMessage("Whoops! you hit a wall!"); + } + + snake = newSnake; + draw(snake, apple); +} + +var changeDirection = function(direction) { + snake[0].direction = direction; +} + +var apple = CHUNK.randomLocation(); +var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + +CHUNK.executeNTimesPerSecond(advanceGame, 1); +CHUNK.onArrowKey(changeDirection); diff --git a/sites/javascript-snake-game/lesson-1.step b/sites/javascript-snake-game/lesson-1.step new file mode 100644 index 000000000..18b53bc7b --- /dev/null +++ b/sites/javascript-snake-game/lesson-1.step @@ -0,0 +1,165 @@ +goals do + goal "Setup your programming environment" + goal "Be able to use the basic building blocks of JavaScript code" + goal "Do simple calculations" + goal "Use and understand variables" + goal "Use and understand arrays" + goal "Use loops and conditional statements" +end + +overview do + message <<-MARKDOWN + JavaScript is a fully featured programming language with variables, loops, + and conditionals. Just like Ruby, Java, Python, and PHP, you can use JavaScript + to do math, model large systems, and perform complex calculations – all in your browser! + In this lesson, we'll learn about the fundamentals of the JavaScript programming language. + MARKDOWN +end + +steps do + step "Launch your programming environment" do + message <<-MARKDOWN + + When programming, you'll generally want these tools on hand: + + * Your browser to see the code running (I recommend Chrome) + * A text editor to change the code (Railsbridge recommends Atom, but you can try a different editor) + * A javascript console so you can experiment and print out debugging + messages. This is built into your browser. + + Some helpful links for browser consoles: + + * How to launch dev tools in chrome. + * How to launch dev tools in internet explorer. + * How to launch dev tools in firefox. + * How to launch dev tools in safari. + + Once you have these tools available, we need to open the files we'll be working with: + + * Open game/snake.js in your text editor + * Open game/index.html in your browser + + + Now open up your browser's developer tools, and click over to the console tab. Type the following code: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + console.log('programming!'); + JAVASCRIPT + + message <<-MARKDOWN + Press enter. Your browser should now look like this: + image of browser console with console.log + + Congrats, you just wrote your first lines of JavaScript code! __console.log__ is an important function – + it allows you to print information to the browser's console. It's very helpful in debugging! You can also + use the __alert__ function to make a message pop up in the browser. Try it out! + MARKDOWN + end + + step do + message "Next try some simple math that's built into JavaScript. Type these lines into console:" + + source_code :javascript, <<-JAVASCRIPT + 3 + 3 + 7 * 6 + JAVASCRIPT + end + + step do + message "**Variables** are names with values assigned to them." + + source_code :javascript, <<-JAVASCRIPT + var myVariable = 5 + JAVASCRIPT + + message "This assigns the value `5` to the name `myVariable`." + end + + step do + message "You can also do math with variables:" + source_code :javascript, <<-JAVASCRIPT + myVariable + 2 + myVariable * 3 + JAVASCRIPT + end + + step do + message "Variables can also hold more than just numbers and text. Another type of data in Javascript is called an **array**." + + source_code :javascript, 'var fruits = ["kiwi", "strawberry", "plum"]' + message <<-MARKDOWN + Here we're using the variable `fruits` to hold a collection of fruit names. + An array, designated by the `[ ]` (square-brackets), is a list of data that can be referenced by its index. + The index is the position inside the array, each separated by a comma. Arrays use 0-based indices, so the first element in an array is at Index 0, the second is at index 1, and so on. + For example, in the array above, to get the string 'strawberry' in Javascript, you would type 'fruits[1]', which Javascript understands as: get the value at index 1 in the array fruits. + MARKDOWN + end + + step do + message "Arrays are a type of __object__ in JavaScript that are designated by the use of square brackets. Objects of all types in javascript often include helpful attributes!" + + source_code :javascript, <<-JAVASCRIPT + fruits.length + JAVASCRIPT + + message <<-MARKDOWN +Objects can also have __functions__, which can be helpful for altering objects and learning more about them. +Functions are __invoked__ with parentheses, which causes them to run. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + fruits.push("orange") + fruits.slice(1) + JAVASCRIPT + + message <<-MARKDOWN +The __push__ function allows us to add new items to an array. the slice function returns a new array with +with everything to the right of the __index__ we provided. Here, we passed the function the number 1, so +slice returned an array with everything after the first element in the array. (Note that the first element is assigned 0 as its index rather than 1.) + MARKDOWN + end + + step do + message "You can also make your own functions:" + + source_code :javascript, <<-JAVASCRIPT + var pluralize = function(word) { + return word + "s" + } + pluralize("kiwi") + JAVASCRIPT + + message "Functions take **parameters**, which are the variables they work on. In this case, we made a function called pluralize that takes one parameter, a word." + + message "Functions can also return data. In this case, pluralize returns the word with an 's' added to the end of it. In order for a function to return something, you have to use the __return__ keyword." + end + + + step do + message "Arrays have a function called **forEach** which iterates through the list running code on each item. It takes another function as a parameter." + source_code :javascript, <<-JAVASCRIPT + fruits.forEach(function(fruit) { + console.log(fruit) + }) + JAVASCRIPT + message "This takes the first item from the `fruits` array (`\"strawberry\"`), assigns it to the variable `fruit`, and runs the code between curly brackets. Then it does the same thing for each other item in the list. The code above should print a list of the fruits." + end + + step do + message "A **conditional** runs code only when a statement evaluates to true." + + source_code :javascript, <<-JAVASCRIPT + if(myVariable > 1) { + console.log('YAY') + } + JAVASCRIPT + + message "This prints `YAY!` if the value stored in `myVariable` is greater than 1." + + message "Try changing the `>` in the conditional to a `<`." + end + +end + +next_step "lesson-2" diff --git a/sites/javascript-snake-game/lesson-10.step b/sites/javascript-snake-game/lesson-10.step new file mode 100644 index 000000000..966eaf991 --- /dev/null +++ b/sites/javascript-snake-game/lesson-10.step @@ -0,0 +1,73 @@ +# Lesson 10 - Making the Tail Follow the Head + +markdown <<-MARKDOWN + To make the tail follow the head, we're going to assign the direction of the + segment further forward in snake to the current segment when it moves. This way + all segments follow the same path! + + Let's define a new function called `segmentFurtherForwardThan` which takes a + segment index and a snake and returns the segment closer to the head. Let's + start with: + + ```js + var segmentFurtherForwardThan = function(index, snake) { + return snake[index - 1] + } + ``` + + While this seems OK, in reality it has a nefarious bug! Open your console and type: + + ```js + segmentFurtherForwardThan(1, snake); + ``` + + This returns a segment object, which looks great! But what happens if we try + a index of 0? + + ```js + segmentFurtherForwardThan(0, snake); + ``` + + Instead of a segment, we get `undefined`! This means we can't get a + direction! Let's use an `if` statement to return the segment at the index if + there isn't a segment further forward. Change `segmentFurtherForwardThan` to: + + + ```js + var segmentFurtherForwardThan = function(index, snake) { + if (snake[index - 1] === undefined) { + return snake[index]; + } else { + return snake[index - 1]; + } + } + ``` + + Now when you run `segmentFurtherForwardThan(0, snake)` it will return the + snakes head instead of undefined. Excellent! Now we get to change moveSnake to + use `segmentFurtherForwardThan` when it assigns the segments direction. Change `moveSnake` to: + + ```js + var moveSnake = function(snake) { + return snake.map(function(oldSegment, segmentIndex) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = segmentFurtherForwardThan(segmentIndex, snake).direction; + return newSegment; + }); + } + ``` + + Great! Now each segment will follow the segment ahead of it! +MARKDOWN + +js_expected_results 'lesson-10' + +markdown <<-MARKDOWN + ### Syntax Breakdown + + `array.map` and `array.forEach` pass 3 arguments to the function it is given: the + first is the element in the collection, the second is its index (or location in the collection) and + the third is the collection itself. +MARKDOWN + +next_step 'lesson-11' diff --git a/sites/javascript-snake-game/lesson-11.step b/sites/javascript-snake-game/lesson-11.step new file mode 100644 index 000000000..07a4515a8 --- /dev/null +++ b/sites/javascript-snake-game/lesson-11.step @@ -0,0 +1,46 @@ +# Lesson 11 - Making an Apple Appear on the Screen + +markdown <<-MARKDOWN + Let's make it so an apple appears on the screen! + + ```js + var draw = function(snakeToDraw, apple) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableApple = { color: "red", pixels: [apple] }; + var drawableObjects = [drawableSnake, drawableApple]; + CHUNK.draw(drawableObjects); + } + ``` + + We renamed the `drawSnake` function and let it take multiple arguments so it + can draw an apple. + + Next, let's make sure we pass the `apple` to the `draw` function, when we call it! + + ```js + var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + draw(snake, apple); + } + ``` + + Finally, let's create an apple to be drawn! + + ```js + var apple = { top: 8, left: 10 }; + ``` +MARKDOWN + +js_expected_results 'lesson-11' + +markdown <<-MARKDOWN + ### Play Time! + + * Why do you think we put `[]` around the apple in the `draw` function? +MARKDOWN + +next_step 'lesson-12' diff --git a/sites/javascript-snake-game/lesson-12.step b/sites/javascript-snake-game/lesson-12.step new file mode 100644 index 000000000..f78637899 --- /dev/null +++ b/sites/javascript-snake-game/lesson-12.step @@ -0,0 +1,56 @@ +# Lesson 12 - Growing the snake when it eats an apple! + +markdown <<-MARKDOWN + Now that an apple appears on the screen, we can make eating it grow + the snake. + + To do that, we'll create a stationary copy of the snakes last segment. When + the snake moves next the copied segment will stay in place, causing it to + "grow" on the screen. + + ```js + var growSnake = function(snake) { + var indexOfLastSegment = snake.length - 1; + var lastSegment = snake[indexOfLastSegment]; + snake.push({ top: lastSegment.top, left: lastSegment.left }); + return snake; + } + ``` + + Now that we have a function to grow the snake, let's check for a collision + between the apple and the snake whenever the game advances. Let's change the + `advanceGame` function so it looks like this: + + ```js + var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween([apple], snake)) { + snake = growSnake(snake); + apple = CHUNK.randomLocation(); + } + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + draw(snake, apple); + } + ``` +MARKDOWN + +js_expected_results 'lesson-12' + +markdown <<-MARKDOWN + ### Syntax Breakdown + + `array.length` tells us how many elements are in a list. Arrays are zero + indexed, so we subtract one to get the index of the last element in the + list. + + ### Play Time! + + * When is the newly added segment given a direction to move in? + * Why do we put the `apple` variable inside of `[]` when we call + `detectCollision` +MARKDOWN + +next_step 'lesson-13' diff --git a/sites/javascript-snake-game/lesson-13.step b/sites/javascript-snake-game/lesson-13.step new file mode 100644 index 000000000..4c7670176 --- /dev/null +++ b/sites/javascript-snake-game/lesson-13.step @@ -0,0 +1,60 @@ +# Lesson 13 - Make the snake die when it eats itself! + +markdown <<-MARKDOWN + The last thing we need to do is check to see if the snake ran into itself. + + ```js + var ate = function(snake, otherThing) { + var head = snake[0]; + return CHUNK.detectCollisionBetween([head], otherThing); + } + ``` + + We've created an `ate` function that accepts a snake and the previous version of the snake. We can also think about passing the `ate` function other things, which we'll get into in a bit. In `ate` we also reuse the `detectCollisionBetween` to find out if there's a collision. + + Now we just have to check if the `newSnake` ate it's previous self to see if there was a collision! We do this by calling the `ate` function inside of `advanceGame`. + + ```js + var advanceGame = function() { + var newSnake = moveSnake(snake); + + if (ate(newSnake, snake)) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! You ate yourself!"); + } + + if (ate(newSnake, [apple])) { + newSnake = growSnake(newSnake); + apple = CHUNK.randomLocation(); + } + + if (ate(newSnake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + + snake = newSnake; + draw(snake, apple); + } + ``` + + Notice, we've abstracted out the `ate` function by reusing it. It can now determine if the snake ate itself, an apple or the wall. + + If your snake no longer moves after this step, make sure that you assigned `snake = newSnake` in the `advanceGame` function. +MARKDOWN + +js_expected_results 'lesson-13' + +markdown <<-MARKDOWN + ### Congratulations! You've built the snake game! + + If you want your friends and family to be able to play your game, you'll need + to put it online. Lesson 14 includes instructions on putting your game online. + + ### Helpful Links + + 1. Article on Abstraction from Princeton +MARKDOWN + +next_step 'lesson-14' diff --git a/sites/javascript-snake-game/lesson-14.step b/sites/javascript-snake-game/lesson-14.step new file mode 100644 index 000000000..c45903cde --- /dev/null +++ b/sites/javascript-snake-game/lesson-14.step @@ -0,0 +1,10 @@ +insert '../javascript-to-do-list/_deploying_your_site' + +message <<-MARKDOWN +# Credits + +This curriculum was originally written by +[Zassmin Montes de Oca](http://zassmin.com/) and +[Zee Spencer](http://www.zeespencer.com/), +and has been improved by all sorts of [lovely RailsBridge volunteers](https://github.com/railsbridge/docs/commits/master/sites/en/javascript-snake-game). +MARKDOWN diff --git a/sites/javascript-snake-game/lesson-2.step b/sites/javascript-snake-game/lesson-2.step new file mode 100644 index 000000000..ef1924d9f --- /dev/null +++ b/sites/javascript-snake-game/lesson-2.step @@ -0,0 +1,119 @@ +goals do + goal "Create your first snake" + goal "How to use a third party library as an 'engine' to power your game" +end + +steps do + + step "Now back to our snakes" do + + message "To get started, we want to create a variable that represents the snake. Add the following line to the beginning of game/snake.js:" + + source_code :javascript, <<-JAVASCRIPT + var snake = [{ top: 0, left: 0}]; + JAVASCRIPT + + markdown <<-MARKDOWN +

Now that we have this amazing snake we want to show it off, right? The + CHUNK game engine makes it easy to draw objects on the screen. Let's tell CHUNK + to 'draw' our snake!

+ MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var drawableSnake = { color: "green", pixels: snake }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); + JAVASCRIPT + + markdown <<-MARKDOWN + CHUNK's 'draw' function expects to be given a collection of objects. This + means we must create an array and place the drawableSnake inside of it. Then we + can pass that array into 'CHUNK.draw' + + To see what your code does you'll need to save your 'snake.js' file and + refresh the browser window that has your index.html in it. We recommend doing + this early and often! + + Congratulations! You've drawn a (very short!) snake on the screen. + MARKDOWN + end + +js_expected_results 'lesson-2' + + step "What is CHUNK?" do + markdown <<-MARKDOWN + When programming, you'll frequently need to solve problems that have been + solved before. In many cases someone who solved the problem already has + packaged their code into a 'library'. + + Good libraries tend to: + + * Help you think about the problem you're solving more clearly + * Be well documented + * Solve a small set of closely related problems + + We'll be using the CHUNK library throughout this tutorial. CHUNK is an + example of a library whose job is to: + + * Draw chunky-pixels on the screen! + * Respond to user input + * Start and end a game + * Check for things running into each other + * Be reasonably approachable for novice developers + MARKDOWN + end + + step "Understanding the Code" do + message "The code you wrote used the CHUNK library to draw a 1px snake on the screen. Let's try to understand the code as a whole a bit better." + + source_code :javascript, <<-JAVASCRIPT + var snake = [{ top: 0, left: 0}]; + var drawableSnake = { color: "green", pixels: snake }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); + JAVASCRIPT + + markdown <<-MARKDOWN + The first line declares the variable 'snake' and assigns an array to it. Within that array is one item which is defined by a set of curly-braces. These curly-braces represent another Javascript data type called an Object which is also referred to as a 'hash map'. + + Data stored in an hash map object involves two parts, a key and a value. In our code, we have two pieces of data in the object, a 'top' value and a 'left' value. The key is used to get the data from the object. So in our example, if we need to know what the current 'left' value of snake is, we type 'snake.left' which will return a value of '0'. + + On the second line, we declare the variable 'drawableSnake' and assign an object to its value. The object has two pieces, the first has the key 'color' and the value '"green"', and the second has the key 'pixels' which has the value 'snake'. 'snake' is the variable we defined on the previous line, which means 'drawableSnake.pixels' will return us the object we created on the first line. + + The third line declares a variable 'drawableObjects', which we assign the value of an array whose only data is the 'drawableSnake' declared on the previous line. We do this because our CHUNK engine is looking for a list (an array) or objects to draw. + + Finally, we call the CHUNK library's built-in function 'draw' and pass it the argument 'drawableObjects' which was declared on the previous line. We'll explore functions and arguments in future lessons. + MARKDOWN + end + + step "Comments" do + markdown <<-MARKDOWN + Sometimes it's nice to leave a clarifying note to future readers of the code (this could very well be you!) in plain English. This is called a comment, and it is only intended for humans to read, the computer knows to ignore them. + Here's what comments look like in JavaScript: + + ```js + // this is a one line comment, here we're creating an array + var drawableObjects = [drawableSnake]; + /* + this is a multi line comment + here we're drawing the snake + */ + CHUNK.draw(drawableObjects); + ``` + MARKDOWN + end + + step "Play Time!" do + markdown <<-MARKDOWN + * Add comments above each line explaining what it does in plain old english. + * Change the color of the snake. + * Make the snake longer than just 1 segment! + * Draw something in addition to the snake. Perhaps an apple or a wall? Make + sure it's a different color! + MARKDOWN + end + +end + +next_step "lesson-3" + diff --git a/sites/javascript-snake-game/lesson-3.step b/sites/javascript-snake-game/lesson-3.step new file mode 100644 index 000000000..dee42854c --- /dev/null +++ b/sites/javascript-snake-game/lesson-3.step @@ -0,0 +1,97 @@ +# Lesson 3 - Drawing a Snake Multiple Times + +markdown <<-MARKDOWN + Now that we have a snake being drawn on the screen, We're going + to want to be able to draw it many times, for instance every time + it moves. + + To do this, we'll define a function which draws the snake + it's given. Add the following line to the beginning of + game/snake.js: + + ```js + var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); + } + ``` + + Functions are a way to group instructions so they're easy for humans to + understand or so the computer can use them over and over without us writing + the code out multiple times. + + Functions exist somewhat independently from the code around them; this means + that if the function needs variables from the rest of the code base they should + be passed in as arguments. The `drawSnake` function takes a single argument, + `snakeToDraw` which it then uses to create the `drawableSnake`. + + Now all we have to do is call drawSnake with a snake and it will be drawn on + the screen! Write the following under the drawSnake function. + + ```js + var snake = [{ top: 0, left: 0}]; + drawSnake(snake); + ``` + + Because Javascript interprets code from top to bottom we can't call + functions until after we've defined them. Make sure you put this code at the + end of the `snake.js` file. +MARKDOWN + +js_expected_results 'lesson-3' + +markdown <<-MARKDOWN + ### Play Time! + + * Open up the console and play around with making the + snake move by calling `drawSnake` with snakes that have + different `top` and `left` values for their segment. + * Try to access `drawableSnake` from outside of the `drawSnake` function. + Check the javascript console. + * Try to access `snake` inside of the `drawSnake` function. Check the + javascript console. + * Why do you suppose you could access one but not the other? Scope is how you + know what variables a function or piece of code has access too. + Understanding scope is hugely important when reading and writing code. + + ### A bit more about Functions + + Functions are where we'll be storing most of the logic as we develop our snake game. Let's take apart the function created above just to fully understand what is happening. + + ```js + var drawSnake = function(snakeToDraw) { + var drawableSnake = { color: "green", pixels: snakeToDraw }; + var drawableObjects = [drawableSnake]; + CHUNK.draw(drawableObjects); + } + var snake = [{ top: 0, left: 0}]; + drawSnake(snake); + ``` + + A function declaration is always going to be the word `function`, then parens `( )` then curly braces `{ }`. The parens are where you declare the arguments the function expects, and the curly braces are where you write out all of the logic that the function will perform. The simplest way to call a function is to write its name followed by parentheses: `drawSnake()`. But we have a function that expects an argument, `snakeToDraw`, so when we call the function in the code we do it by `drawSnake(snake)`. + + You may have noticed that the function `drawSnake` expects an argument of `snakeToDraw`, but when we call the function we're passing in the variable `snake`. The names are different - but the code works fine. What is actually happening is we're passing around the data that the variable `snake` represents (in this case, `[{ top: 0, left: 0}]`), which the function receives and names according to the argument inside its parens, `snakeToDraw`. Inside the function, the data is now available as the variable `snakeToDraw`, which we assign to the `pixels` key inside the `drawableSnake` variable. + + The function then takes `drawableSnake`, puts it inside an array named `drawableObjects`, and calls a function in the CHUNK library called `draw`, that expects the array we just created. + + + ### Syntax Breakdown + + `function() { }` says "Hey, between the curly braces is some + code we want to be able to execute." Functions can be stored inside + variables just like words and numbers and objects and lists. + + `(` and `)` in this case are a way to say that this function expects + a value to be given to it when it is called. These values are called + `arguments`, and can be used inside of the function body (the code between + the curly braces). + + ### Helpful Links + + 1. An article explaining functions + 1. Information on Objects +MARKDOWN + +next_step "lesson-4" diff --git a/sites/javascript-snake-game/lesson-4.step b/sites/javascript-snake-game/lesson-4.step new file mode 100644 index 000000000..df2659f7e --- /dev/null +++ b/sites/javascript-snake-game/lesson-4.step @@ -0,0 +1,60 @@ +# Lesson 4 - Moving a Single Segment Snake + +markdown <<-MARKDOWN + Now that we have a function to draw the snake, we want to move the snake. + Let's write a function which takes a snake and changes its segment's top and left + values. + + To make our lives easier, let's make some assumptions: + + * The snake is only 1 segment long + * The snake always moves down + + By making these assumptions it keeps the code clear and makes it easy to + take small steps. Add the following code to your snake.js file: + + ```js + var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = { top: oldSegment.top + 1, left: oldSegment.left }; + var newSnake = [newSegment]; + return newSnake; + } + ``` + + Once we have this `moveSnake` function, we can call it and it will give us a + snake whose segment's location has changed. We can then call `drawSnake` with + that snake and see it move across the screen! + + Type the following lines into your javascript console a few times: + + ``` + snake = moveSnake(snake); + drawSnake(snake); + ``` +MARKDOWN + +js_expected_results 'lesson-4' + +markdown <<-MARKDOWN + ### Play Time! + + * Why does adding 1 to a segment's top value move it down? + * Can you make the snake move right instead of down? + * What happens to the newSnake variable? Can you use it outside of the + moveSnake function body (within the curly braces)? + * Why are we re-assigning snake the results of moveSnake? + + ### Syntax Breakdown + + `return value` - Tells the function to immediately respond with whatever + value it is given. In this case, the newSnake. + + `array[location]` -"Get the value at this location in the array." Arrays in + most programming languages are "zero-indexed" i.e. the first element is at 0, + the second is at 1, and so on. + + `+` adds the values on the left and right together. Surprise, eh? +MARKDOWN + +next_step 'lesson-5' diff --git a/sites/javascript-snake-game/lesson-5.step b/sites/javascript-snake-game/lesson-5.step new file mode 100644 index 000000000..3c54eb2a6 --- /dev/null +++ b/sites/javascript-snake-game/lesson-5.step @@ -0,0 +1,42 @@ +# Lesson 5 - Make CHUNK move the snake for us + +markdown <<-MARKDOWN + Hooray! We can both draw and move the snake! Of course, having our users + copy and paste lines of code into their javascript console isn't a wonderful + idea, so we're going to have CHUNK execute the move and draw commands for us. + + We'll start with an `advanceGame` function + + ```js + var advanceGame = function() { + snake = moveSnake(snake); + drawSnake(snake); + } + ``` + + To get this function running we'll use our CHUNK game engine to CALL the `executeNTimesPerSecond` function. It does exactly that, to do this `executeNTimesPerSecond` takes in two arguments. The first argument advances the game, and the second tells it how many times to move per-second. + + ```js + CHUNK.executeNTimesPerSecond(advanceGame, 1); + ``` + + Bam! Now the snake moves down until it runs off the game screen. + + Because functions are just another kind of value (like numbers, arrays, objects, strings, + etc.) we can pass them as arguments to other functions. + +MARKDOWN + +js_expected_results 'lesson-5' + +markdown <<-MARKDOWN + ### Play Time! + + * How can you increase the speed at which the snake moves? + + ### Helpful Links + + 1. Writing functions with more than one argument +MARKDOWN + +next_step "lesson-6" diff --git a/sites/javascript-snake-game/lesson-6.step b/sites/javascript-snake-game/lesson-6.step new file mode 100644 index 000000000..41e5c5163 --- /dev/null +++ b/sites/javascript-snake-game/lesson-6.step @@ -0,0 +1,95 @@ +# Lesson 6 - Snake can move in every direction + +markdown <<-MARKDOWN + Now that the snake is moving on it's own, let's make it so it can move in + different directions! + + First, we'll create a function that moves the segment based upon it's + direction (down, up, right, left): + + ```js + var moveSegment = function(segment) { + if (segment.direction === "down") { + return { top: segment.top + 1, left: segment.left } + } else if (segment.direction === "up") { + return { top: segment.top - 1, left: segment.left } + } else if (segment.direction === "right") { + return { top: segment.top, left: segment.left + 1 } + } else if (segment.direction === "left") { + return { top: segment.top, left: segment.left - 1 } + } + return segment; + } + ``` + + `if` statements choose what code to run. Here a series of `if` and `else if`s + to return a new object with coordinates offset from the passed in segment. + + When the passed in segment doesn't have a direction or it doesn't match the + directions in our `if`/`else if` then we return the original segment object. + + Our returned segment doesn't have a direction; which means the snake will + only move once! To keep the snake moving we need to assign every `newSegment` a + direction. + + Let's change our `moveSnake` function to give the `newSegment` the same + direction as the `oldSegment`. This will keep the snake moving in the same + direction. + + ```js + var moveSnake = function(snake) { + var oldSegment = snake[0]; + var newSegment = moveSegment(oldSegment); + newSegment.direction = oldSegment.direction; + var newSnake = [newSegment]; + return newSnake; + } + ``` + + Next add a direction to our initial snakes segments. + + ```js + var snake = [{ top: 0, left: 0, direction: "down" }]; + ``` + + Now our snake knows where to start moving! +MARKDOWN + +js_expected_results 'lesson-6' + +markdown <<-MARKDOWN + ### Switch statement + + A `switch` statement is another way to organize a seried of `if` and `else if`s. Here's an example: + + ```js + switch(color) { + case "red": + return { action: "stop" }; + case "orange": + return { action: "wait" }; + case "green": + return { action: "go" }; + default: + return { action: "unknown" }; + } + ``` + + Can you figure out how to replace our snake game code above with a `switch` statement? (Don't worry, you'll see how we do it in the next lesson) + + ### Play Time! + + * Use a switch statement instead of a series of ifs + * Make sure each direction works! + + ### Syntax Breakdown + + `===` tells the computer to check for EXACT equality. For example: "down" === "down" will return true but "down" === "DOWN" will return false. + + ### Helpful Links + + 1. switch statements + 1. === vs == +MARKDOWN + +next_step "lesson-7" diff --git a/sites/javascript-snake-game/lesson-7.step b/sites/javascript-snake-game/lesson-7.step new file mode 100644 index 000000000..ebd647ef5 --- /dev/null +++ b/sites/javascript-snake-game/lesson-7.step @@ -0,0 +1,32 @@ +# Lesson 7 - Changing the Snakes Movement Direction + +markdown <<-MARKDOWN + To change the direction of the snake, we need to change the direction of + its first segment. Add the following function to snake.js: + + ```js + var changeDirection = function(direction) { + snake[0].direction = direction; + } + ``` + + Next, we have CHUNK call changeDirection when an arrow key is pressed. + Add the following line at the end of snake.js: + + ```js + CHUNK.onArrowKey(changeDirection); + ``` + + This tells CHUNK to call the `changeDirection` function every time an arrow key + is pressed. + + Just like we pass our snake object into the `drawSnake` function, we can pass + the `changeDirection` function into the `onArrowKey` function. + + In JavaScript, we can pass functions into functions just like we can pass + arrays, strings, integers, and objects into functions +MARKDOWN + +js_expected_results 'lesson-7' + +next_step 'lesson-8' \ No newline at end of file diff --git a/sites/javascript-snake-game/lesson-8.step b/sites/javascript-snake-game/lesson-8.step new file mode 100644 index 000000000..bee225526 --- /dev/null +++ b/sites/javascript-snake-game/lesson-8.step @@ -0,0 +1,39 @@ +# Lesson 8 - Ending the Game When A Wall Is Hit + +markdown <<-MARKDOWN + Games aren't much fun if you can't lose them! Let's make it so when you hit + a wall. CHUNK comes with a function to detect collisions between things, so + we'll use that. + + Add an `if` statement to the `advanceGame` function like so: + + ```js + var advanceGame = function() { + snake = moveSnake(snake); + if (CHUNK.detectCollisionBetween(snake, CHUNK.gameBoundaries())) { + CHUNK.endGame(); + CHUNK.flashMessage("Whoops! you hit a wall!"); + } + drawSnake(snake); + } + ``` + + Now, when there is a collision between the snake and the border we can have + CHUNK end the game and let the player know the game is over. +MARKDOWN + +js_expected_results 'lesson-8' + +markdown <<-MARKDOWN + ### Syntax Breakdown + + `CHUNK.detectCollisionBetween` takes two collections of coordinates and + returns true if the collections share any pixels + + `CHUNK.endGame` tells chunk to stop calling the advanceGame function, + effectively ending the game. + + `CHUNK.flashMessage` displays text to the user. +MARKDOWN + +next_step 'lesson-9' diff --git a/sites/javascript-snake-game/lesson-9.step b/sites/javascript-snake-game/lesson-9.step new file mode 100644 index 000000000..4c0744b63 --- /dev/null +++ b/sites/javascript-snake-game/lesson-9.step @@ -0,0 +1,66 @@ +# Lesson 9 - Moving a Multiple Segment Snake + +markdown <<-MARKDOWN + Now that we can crash into a wall, lets have a snake longer than a single + segment! + + Currently, the `moveSnake` function only moves the first segment in the + snake. Let's make it to move every segment! Change `moveSnake` to the + following: + + ```js + var moveSnake = function(snake) { + var newSnake = []; + snake.forEach(function(oldSegment) { + var newSegment = moveSegment(oldSegment); + newSegment.direction = oldSegment.direction; + newSnake.push(newSegment); + }); + + return newSnake; + } + ``` + + `forEach` introduces `looping`. Looping allows us to do something multiple + times. Looping (for, while, forEach, etc) and conditionals (if, switch)` both + allow you to control the flow of the program. + + Now that we can move many segments across the screen, let's add a second + segment to our snake. Change the line that starts with `var snake = ...` to the + following: + + ```js + var snake = [{ top: 1, left: 0, direction: "down" }, { top: 0, left: 0, direction: "down" }]; + ``` +MARKDOWN + +js_expected_results 'lesson-9' + +markdown <<-MARKDOWN + You'll notice that if you play the game, the second segment always just + moves down. This is OK for now! + + ### Syntax Breakdown + + `array.forEach` allows us to call a function with each item in a collection. + This is a very common kind of loop and is very powerful. + + `array.push` adds the element it is given to the array it is called in. + + ### Play Time! + + * `array.map` + is another kind of loop that makes it easy to build a new collection from an + existing collection. Rewrite the `moveSnake` function to use `array.map` + instead of `array.forEach` + * The + `for` + and + `while` + loops are also quite common. They are more generally useful but harder to use + than the collection based `forEach` and `map` functions. How could you write + moveSnake with either `for` or `while`? +MARKDOWN + +next_step 'lesson-10' + diff --git a/sites/javascript-snake-game/zip_content/index.html b/sites/javascript-snake-game/zip_content/index.html new file mode 100644 index 000000000..d83e92eac --- /dev/null +++ b/sites/javascript-snake-game/zip_content/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/sites/javascript-snake-game/zip_content/snake.js b/sites/javascript-snake-game/zip_content/snake.js new file mode 100644 index 000000000..e69de29bb diff --git a/sites/javascript-to-do-list-with-react/AdvancedTodoList.zip-manifest b/sites/javascript-to-do-list-with-react/AdvancedTodoList.zip-manifest new file mode 100644 index 000000000..dee53707a --- /dev/null +++ b/sites/javascript-to-do-list-with-react/AdvancedTodoList.zip-manifest @@ -0,0 +1,6 @@ +zip_content/app.js +zip_content/debut_light.png +zip_content/index.html +zip_content/store.js +zip_content/styles.css +zip_content/console-polyfill.js diff --git a/sites/javascript-to-do-list-with-react/adding_an_item.step b/sites/javascript-to-do-list-with-react/adding_an_item.step new file mode 100644 index 000000000..bcaa0d08c --- /dev/null +++ b/sites/javascript-to-do-list-with-react/adding_an_item.step @@ -0,0 +1,183 @@ +goals do + goal "Allow a user to create a new item." + goal "Understand how to make an AJAX request when prompted by a user's action." +end + +overview do + message <<-MARKDOWN +JavaScript allows us to make our web page dynamic, and responsive to +the actions of our users. In this lesson, we'll allow our users to create +a new item for our list, and ask the server to save it to the database. + +To do so, we're going to use JavaScript's ability to perform a task when a +user has taken an action on the page. JavaScript refers to these actions as +__events__. React makes it easy to listen to these events, and cause the page +to change when necessary. + +Our code will take the following steps. + +1. When the user loads the page, our CreationForm component will start + listening for when the user submits the form at the top of the page. + +2. When a user submits the form (by pressing enter), we will prevent the page + from refreshing, which is the normal behavior for a form. + +3. The CreationForm component will then tell ListStore to add a new item. + +4. ListStore will make an AJAX request to our server, creating an item with the + description our user just provided. + +5. Once the request succeeds, we will update the items variable with our new item. + +6. Finally, we will tell the components to re-render themselves! + +We've already written the first two steps for you. Let's finish the rest! + MARKDOWN +end + + +steps do + step do + message <<-MARKDOWN +In the browser, try creating a new item. You should see an alert pop up with your item's value. Let's take +a look at the CreationForm component to see how that works. Open up index.html in your browser, and find the +component. It looks like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + render: function() { + return ( +
+ +
+ ) + }, + + handleSubmit: function(event) { + event.preventDefault() + var description = this.refs.description.getDOMNode().value + this.refs.description.getDOMNode().value = '' + alert('trying to create an item with description ' + description) + } + JAVASCRIPT + + message <<-MARKDOWN +When the form submits, the handleSubmit function runs! This function is preventing the page from refreshing +(using the preventDefault function) and using React to get the new description the user just wrote. See if you +can follow the code – how does the form know to run the handleSubmit function? + +Now, let's remove the alert, and instead tell the ListStore to add an item. Your code should look like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + handleSubmit: function(event) { + event.preventDefault() + var description = this.refs.description.getDOMNode().value + this.refs.description.getDOMNode().value = '' + ListStore.addItem(description) + } + JAVASCRIPT + end + + step do + message <<-MARKDOWN +Now, let's write the logic for the addItem function. We're going to ask the server to save +this item into the database. Open up store.js, and add the following code to the addItem +function. Replace 'YOUR-LIST-NAME-HERE' with your list's name. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + addItem: function(itemDescription) { + var creationRequest = $.ajax({ + type: 'POST', + url: "http://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: itemDescription, completed: false } + }) + }, + JAVASCRIPT + + message <<-MARKDOWN +Try creating an item again. After you submit the form, look at the network tab. A +new request should have occurred to http://listalous.herokuapp.com/ ! + MARKDOWN + end + + step do + message <<-MARKDOWN +Finally, we need to tell the components to re-render themselves. After the creationRequest, +add the following code: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + creationRequest.done(function(itemDataFromServer) { + items.push(itemDataFromServer) + notifyComponents() + }) + JAVASCRIPT + + message <<-MARKDOWN +Try creating an item one more time. Once you hit enter, a new item should appear +on the page! If not, flag an instructor down to help you debug the problem. + MARKDOWN + end +end + +explanation do + + message "Here's what the bottom of store.js should now look like:" + + source_code :javascript, <<-JAVASCRIPT + ListStore = { + + getItems: function() { + return items + }, + + loadItems: function() { + var loadRequest = $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + + loadRequest.done(function(dataFromServer) { + items = dataFromServer.items + notifyComponents() + }) + }, + + addItem: function(itemDescription) { + var creationRequest = $.ajax({ + type: 'POST', + url: "http://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: itemDescription, completed: false } + }) + + creationRequest.done(function(itemDataFromServer) { + items.push(itemDataFromServer) + notifyComponents() + }) + }, + toggleCompleteness: function(itemId) {} + } + JAVASCRIPT + + message <<-MARKDOWN + +### The AJAX process + +You've just done something that many JavaScript developers do daily: Use JavaScript +to make a request to a server, and then update the page with the data with which +the server responds. This abstract process is repeated over and over again: + +1. Listen for a user action on the page. +2. Parse out the information the user is submitting. +3. Prevent the default action from occurring, if necessary. +4. Make a request to the server using AJAX. +5. When the request succeeds, parse the data the server sends back. +6. Update the page with the newly received data. + +This process is the basis of most modern web pages! + MARKDOWN +end + +next_step "marking_an_item_as_complete" diff --git a/sites/javascript-to-do-list-with-react/building_complex_applications_with_react.step b/sites/javascript-to-do-list-with-react/building_complex_applications_with_react.step new file mode 100644 index 000000000..5f8df950a --- /dev/null +++ b/sites/javascript-to-do-list-with-react/building_complex_applications_with_react.step @@ -0,0 +1,179 @@ +goals do + goal "Understand how modern web developers organize their code." + goal "Familiarize with Stores, JSX, and other features of a React application." +end + +overview do + message <<-MARKDOWN + When the web was in its infancy, JavaScript was often an afterthought. Web developers + were mostly concerned writing complex servers, and thought of JavaScript as a language to + simply make things move on their user's pages. Consequently, their JavaScript code + was often unorganized, untested, and hard to maintain. + + But as browsers became more advanced, JavaScript was suddenly asked to do much more – web + developers used JavaScript for complex animations, making requests to servers, even doing + complex calculations in the browser! The old approach of writing all your code in one place, + with all the logic jumbled together, quickly became untenable. In response to that, the + JavaScript community has started developing patterns to help themselves organize their code, + making it more readable. Most of these patterns separate code into two buckets: + + * __Views (or Components, or View Controllers)__ are in charge or rendering HTML elements, and + listening for user's actions like clicks and form submissions. + * __Models (or Stores)__ are responsible for the logic of your JavaScript application. + + There are many JavaScript libraries and frameworks that implement this structure, including + [AngularJS](https://angularjs.org/), [Ember.js](http://emberjs.com/), and [Backbone.js](http://backbonejs.org/). + They each have their own strengths and weaknesses. and we wouldn't presume to tell you + which one is "better." As you continue learning, you'll form your own opinions! + + For this tutorial, we'll be using [React](http://facebook.github.io/react/), a rendering library written by Facebook that has + grown in popularity over the last few years. To use React, you write your HTML as a series of __components__ + in a language called __JSX__. Don't worry, JSX looks a lot like HTML! These components handle + the rendering of your HTML elements, as well as listen for user actions. + + You'll be using React to make your view objects, and we'll use a simple JavaScript object as a model. We've + already built a lot of this structure for you! Let's walk through the code together. + MARKDOWN +end + + + + +steps do + step do + message <<-MARKDOWN +Open up store.js in your text editor. There, you should see a ListStore object defined like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + ListStore = { + getItems: function() { + return items + }, + loadItems: function() {}, + addItem: function(itemDescription) {}, + toggleCompleteness: function(itemId) {} + } + JAVASCRIPT + + message <<-MARKDOWN +This is where all the logic of our application will be performed! Whenever we need to execute a calcuation, or +make an AJAX request, our program will call a function on the ListStore object. For instance, when we want to load +all items from our server, you would write a line of code like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + ListStore.loadItems() + JAVASCRIPT + + message <<-MARKDOWN +In future lessons, we'll be writing the logic to make these functions work! Whenever the store changes – when an item +is added or marked as completed, for example – we'll use the notifyComponents function to update the user's page. + MARKDOWN + end + + step do + + message <<-MARKDOWN +Now that we've looked at the store, let's take a look at our components. Open index.html, and find the Item component. It should +look like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var Item = React.createClass({ + render: function() { + var itemClass = this.props.completed ? 'item completed' : 'item' + return ( +
  • + {'\\u2714'} +
    {this.props.description}
    + {'\\u2718'} +
  • + ) + }, + }) + JAVASCRIPT + + message <<-MARKDOWN +This is a React component. It is responsible for rendering the HTML for our list's items! Every React component has +to have a render function, which are written in a language called JSX. It looks a like HTML, except you can write it +alongside your JavaScript code. Here's the HTML this component renders: + MARKDOWN + + source_code :HTML, <<-HTML +
  • + +
    A gallon of milk.
    + +
  • + HTML + + message <<-MARKDOWN +Take a look at the other components on the page. What do you think List component does, and how does it know to render +items? How about the CreationForm component? + MARKDOWN + end + + step do + message <<-MARKDOWN +Now you know what a React component looks like, but how does it work? Well, components can have __state__. +Whenever a component's state changes, it re-renders itself to the page! State can change in a handful +of ways. We've already written a function to make List's state change whenever the notifyComponents +function is called. This will be helpful later on! + +Components with state usually also implement a getInitialState function that tells the component what +to render immediately after the page loads. Our List component has one that looks like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + getInitialState: function() { + return ( + {items: [ + {description: 'a gallon of milk', completed: true, id: 1}, + {description: 'a stick of butter', completed: false, id: 2} + ]} + ) + }, + JAVASCRIPT + + message <<-MARKDOWN +Does that data look familiar? It's the default items that our list renders! Try changing these values. +Refresh your browser, and see what's changed! + +Before we move on, let's remove the default items altogether. Not everyone wants a gallon of milk! +Your code should look like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + getInitialState: function() { + return ( + {items: []} + ) + }, + JAVASCRIPT + end +end +explanation do + + message <<-MARKDOWN + +### Getting Comfortable with React. + +You've just looked at your first React application! There are a lot of moving parts, we know. +Don't worry if you don't understand how everything works just yet – as you keep playing with it, +it will become much clearer. Big picture, our web application has two parts: + +* A ListStore, which is responsible for fetching data from the server using AJAX, and keeps +track of all the items on our list. Whenever a new item has been added or updated, it will notify +all the components that it has changed. + +* A series of components that render our list as HTML. The List component updates whenever +the ListStore changes, and it renders a series of Item components. The CreationForm component +will be responsible for creating new items! + +Next, we'll write our first store function to load items when the user visits the page! + MARKDOWN +end + + +next_step 'loading_items' \ No newline at end of file diff --git a/sites/javascript-to-do-list-with-react/creating_a_list.step b/sites/javascript-to-do-list-with-react/creating_a_list.step new file mode 100644 index 000000000..f4816dd55 --- /dev/null +++ b/sites/javascript-to-do-list-with-react/creating_a_list.step @@ -0,0 +1,164 @@ +goals do + goal "Understand how the internet is made up of clients and servers." + goal "Write some basic requests using JavaScript, jQuery and your browser's console." + goal "Create a list and your first item." +end + +overview do + + message <<-MARKDOWN + +As you may already know, websites are built using HTML, CSS, and JavaScript. +These are the languages your browser understands, and it uses them to give +structure, style, and interactivity to your website. + +The problem, though, is that browsers don't remember anything. Every time you refresh +a page, your browser reruns all of your CSS, HTML, and JavaScript. A browser, by itself, +doesn't remember who your users are, or any of their information. + +But wait, we need our website to remember things! Specifically, we need our website +to remember all the items on our list, and whether we've completed them. That's where +a __server__ comes in. A server is a catch-all term for a web application that can be +reached over the internet. They are often attached to __databases__ that store information +about their users. If you make an __HTTP request__ with the correct __URL__ (like +http://www.google.com) and __HTTP method__ (like GET or POST), the server will +respond with helpful data. + +Servers are written in dozens of languages, including Java, PHP, and Ruby. +For this exercise, we've already built a server (using Ruby on Rails), and its +available at https://listalous.herokuapp.com/. If you want to learn more about how +to write a server, take a look at our Rails curriculum! + +Browsers, like Chrome and Internet Explorer, are in the business of making requests +to servers, and displaying the data they receive as a web page. Browsers are a +type of __client__, which consume data. There are other types of clients, too. +Your computer can be a client, using the curl command in the terminal. An iPhone +is another type of client, and servers can even communicate with each other. + +Every time you refresh a web page, your browser is making another request to +the server. On modern websites, browsers often make multiple requests to a server, +depending on what the user is doing. For instance, Twitter doesn't load your entire +feed at once – as you scroll down the page, it's making more and more requests! + +To accomplish that feat, websites can use JavaScript to make requests to servers, +updating the page without the user having to refresh. This type of +request is called an __AJAX__ request, which stands for __A__synchronous __J__avaScript +__a__nd __X__ML. It's a technique that is used by most major websites, to provide +a seamless experience to their users. + +In this lesson, we'll be making AJAX requests using our browser's console. + MARKDOWN +end + +steps do + + step do + message <<-MARKDOWN +Copy the following code into your browser's console, replacing 'YOUR-LIST-NAME-HERE' +with your name of choice. Note: it has to be unique! I'd suggest using your github username. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + $.ajax({ + type: 'POST', + url: "https://listalous.herokuapp.com/lists", + data: { name: 'YOUR-LIST-NAME-HERE' } + }) + JAVASCRIPT + + message <<-MARKDOWN +Now click over to your browser's network tab. It should look something like this. + +image of chrome's network tab. + +Find the request to listalous.herokuapp.com. Was it successful? If not, why +do you think it failed? Once you've successfully created a list, move on to the next step. + MARKDOWN + end + + step do + message <<-MARKDOWN +Now that we've created a list, let's create our list's first item. We're +going to use jQuery's AJAX function again to do so. Copy the following +code into your browser's console, replacing 'YOUR-LIST-NAME-HERE' with +your list's name, and 'DESCRIPTION-OF-YOUR-ITEM' with your item's description. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + $.ajax({ + type: 'POST', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: 'DESCRIPTION-OF-YOUR-ITEM', completed: false } + }) + JAVASCRIPT + + message <<-MARKDOWN +Check the network tab again. Was your request successful? If it was, take a look at +the server's response. You'll notice that the item has an id attribute now. This is how +the server will uniquely identify your item in the future. + MARKDOWN + end + + step do + message <<-MARKDOWN +Finally, lets fetch our list from the server. Your list application will need to do this +every time someone refreshes the page, so that it can load previously created items. Copy +the following code into your browser's console, replacing 'YOUR-LIST-NAME-HERE' with your +list's name. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + JAVASCRIPT + + message <<-MARKDOWN +Check the network tab one more time. What data did the server send back? If your +request was successful, it should have returned all the data associated with your list: +your list's name, as well as all of your list's items. This is data will be basis +of your list application! + MARKDOWN + end +end + +explanation do + message <<-MARKDOWN + +That was a lot of copying and pasting! What did it all mean? + +### Adding a Server to the Equation. + +Remember, web pages have short memories. Whenever you refresh the page, all the HTML, +CSS, and JavaScript on that page has to reload and rerun. A common way for a website +to remember information about you (the fact that you have logged in before, your previous +purchases, etc.) is by getting a server involved. A server, attached to a database, can +persist information about your web site as users come and go. + +In this case, we have a server that can be reached over the internet at +https://listalous.herokuapp.com/. It's attached to a database that +stores all lists and items, that can fetched to make hundreds of independent to do lists. + +In step 1, we used jQuery's AJAX function to ask the server to make a +new list in its database. It only did so if the list's name was unique; +otherwise, how could the server tell your list apart from someone else's list? + +In step 2, we asked the server to create an item associated with your +list. The server responded with information regarding this item, including an +id, that we can use to identify that item later (if we want to mark it as complete, +or delete it.) + +In step 3, we asked the server to return all information associated with +our list, including all of the list's items. When we start building our list +application, we will use this data to load all items on our list every time we +refresh the page. + +We did all of this in the browser's console. In the next lesson, we'll learn +how modern web applications integrate AJAX requests into their applications. + + MARKDOWN +end + + +next_step "building_complex_applications_with_react" diff --git a/sites/javascript-to-do-list-with-react/deploying_your_site.step b/sites/javascript-to-do-list-with-react/deploying_your_site.step new file mode 100644 index 000000000..433ba891d --- /dev/null +++ b/sites/javascript-to-do-list-with-react/deploying_your_site.step @@ -0,0 +1,3 @@ +insert '../javascript-to-do-list/_deploying_your_site' + +next_step "next_steps" \ No newline at end of file diff --git a/sites/javascript-to-do-list-with-react/developer_tools.step b/sites/javascript-to-do-list-with-react/developer_tools.step new file mode 100644 index 000000000..f5c756c90 --- /dev/null +++ b/sites/javascript-to-do-list-with-react/developer_tools.step @@ -0,0 +1,3 @@ +insert '../frontend/_developer_tools' + +next_step 'creating_a_list' \ No newline at end of file diff --git a/sites/javascript-to-do-list-with-react/javascript-to-do-list-with-react.step b/sites/javascript-to-do-list-with-react/javascript-to-do-list-with-react.step new file mode 100644 index 000000000..84e4a6b9f --- /dev/null +++ b/sites/javascript-to-do-list-with-react/javascript-to-do-list-with-react.step @@ -0,0 +1,58 @@ +message <<-MARKDOWN +### Goal + +By the end of this session, you should understand how to write a JavaScript application +using the [React](http://facebook.github.io/react/) library. You're going to be building a personal to do list application. +You've decided that you'd like to: + +* Add an item to your list +* Mark an item as complete +* Load your list from multiple computers. + +You've sketched up an initial screenshot of what you want it to look like: + +![Browser window with list application running](../javascript-to-do-list/img/finished_app.png) + +### Meta-Goal + +When you have completed today's goal of getting the basic +application online you should understand: + +* How to use JavaScript to interact with a server. +* How to organize your JavaScript code into models and views. +* How to use the React library to render HTML elements. +* How to incrementally add features to your application. +* How to get your application online. + +### Schedule + +* 1-ish hours of the basics of clients and servers. +* 1-ish hours on organizing JavaScript using models and views. +* 3-ish hours of adding features to you application, broken up into 1 hour chunks. + +This is just a rough guideline, not a mandate. Some steps you'll go +over and some you'll go under. It'll all work out by the end of the +day. Probably. + +### Requirements + +When programming, you'll generally want these tools on hand: + + * An internet connection. We're going to be communicating with other websites using JavaScript. + * Your browser to see the code running (I recommend Chrome, but any will do!) + * A text editor to change the code (Railsbridge recommends Atom, but you can try a different editor) + * A javascript console so you can experiment and print out debugging + messages. This is built into your browser. + +Before you can start the tutorial, you'll need to download the tutorial +to your computer to edit the source code. + +Once you've downloaded it, open index.html with your browser. You should see a mock up of your list. If you do not, get an instructor to help you out. +MARKDOWN + +insert '../javascript-to-do-list/_lesson_format' + +insert '../javascript-to-do-list/_teachers_note' + +next_step "developer_tools" + diff --git a/sites/javascript-to-do-list-with-react/loading_items.step b/sites/javascript-to-do-list-with-react/loading_items.step new file mode 100644 index 000000000..e90a9dfdc --- /dev/null +++ b/sites/javascript-to-do-list-with-react/loading_items.step @@ -0,0 +1,131 @@ +goals do + goal "Make an AJAX request and update the page based on the server's response." + goal "Load your list's items every time a user visits the page." +end + +overview do + message <<-MARKDOWN + Now that we've learned about AJAX requests and React, let's tie everything together. + Here's what we want to happen. + + 1. Every time a user visits our site, we'll ask ListStore to load all items from the server. + 2. It will use jQuery's AJAX function to make a request to http://listalous.herokuapp.com/ and + get all the data about our list's items. + 3. We will then use our React Components to render all of our items! + MARKDOWN +end + +steps do + step do + message <<-MARKDOWN +First, we need to tell ListStore to call the loadItems function whenever a user visits +the page. Open up index.html, and add this line of code to the bottom of the page, before the end +of the script tag: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + ListStore.loadItems() + JAVASCRIPT + + message "the end of index.html should now look like this:" + + source_code :HTML, <<-HTML + React.render(, document.getElementById('form-container') ) + React.render(, document.getElementById('list-container') ) + ListStore.loadItems() + + + HTML + end + + step do + message <<-MARKDOWN +Now, we have to write the loadItems function! open up Store.js, and add the following code +to the loadItems function. Replace 'YOUR-LIST-NAME-HERE' with the name of the list you created. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + loadItems: function() { + var loadRequest = $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + }, + + JAVASCRIPT + + message <<-MARKDOWN +Refresh the page, and click over to your browser's network tab. You should see +a new request there, that visits our server at https://listalous.herokuapp.com/. + MARKDOWN + end + + step do + message <<-MARKDOWN +Now that we've made the request, we need to update ListStore whenever the request +succeeds. Add the following lines of code to the bottom of the LoadItems function. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + loadRequest.done(function(dataFromServer) { + items = dataFromServer.items + notifyComponents() + }) + JAVASCRIPT + + message <<-MARKDOWN + Now refresh the page. Once the AJAX request succeeds, your site should now display + all the items you created last lesson! If not, flag an instructor down to help you + debug the problem. + MARKDOWN + end + +end + + +explanation do + message "Store.js should now look like this." + source_code :javascript, <<-JAVASCRIPT + ListStore = { + + getItems: function() { + return items + }, + + loadItems: function() { + var loadRequest = $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + + loadRequest.done(function(dataFromServer) { + items = dataFromServer.items + notifyComponents() + }) + }, + addItem: function(itemDescription) {}, + toggleCompleteness: function(itemId) {} + + JAVASCRIPT + + message <<-MARKDOWN +Let's walk through what just happened: + + 1. When the user visited the page, we told the ListStore to load items. + 2. The ListStore used jQuery's AJAX function to make a request to our server. + 3. The server responded with all the items associated with our list. + 4. We saved the server's response in the items variable, and notified the components + that it was time to re-render themselves. + 5. The components heard the message, and updated! The List component used ListStore's getItems + function to get all the items data we had stored in the items variable. + +Your web page has made its first successful AJAX request! Now, your page will +load your list's items whenever you visit it. Once we host this page on the internet, +you will be able to see your list on any computer, tablet, or phone! + MARKDOWN +end + + + + +next_step "adding_an_item" diff --git a/sites/javascript-to-do-list-with-react/marking_an_item_as_complete.step b/sites/javascript-to-do-list-with-react/marking_an_item_as_complete.step new file mode 100644 index 000000000..91c9b29e6 --- /dev/null +++ b/sites/javascript-to-do-list-with-react/marking_an_item_as_complete.step @@ -0,0 +1,187 @@ +goals do + goal "Allow a user to mark an item as complete or incomplete." + goal "Understand how to listen for user events with React." +end + +overview do + message <<-MARKDOWN +In this lesson, we'll allow our users to mark items as complete and incomplete. As they +do, we will ask the server to update the item's status in its database. This process will look +a lot like the process for adding an item, with some additional complexity. + +First off, We'll have to write the event listener ourselves in the Item component. Don't worry, +we'll walk you through every step! + +Secondly, we need to pass the appropriate information to ListStore, so it can make the correct AJAX request. +To do so, we'll have to use __props__ to fetch the item's id value. + +Let's get started! + MARKDOWN +end + +steps do + step do + message "Let's add a click listener to our Item component's complete button. Find the line of code + that is creating the completion button, and add an onClick attribute to make React listen for your users' + clicks. Your code should look like this:" + + source_code :javascript, <<-JAVASCRIPT + {'\\u2714'} + JAVASCRIPT + + message "Now, let's write the handleComplete function." + + source_code :javascript, <<-JAVASCRIPT + handleComplete: function() { + alert('trying to complete item with an id of ' + this.props.id) + } + JAVASCRIPT + + message <<-MARKDOWN +Refresh the page, and try completing an item. What happens? How does the page know the item's id? + +Your Item component should now look like this: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var Item = React.createClass({ + render: function() { + var itemClass = this.props.completed ? 'item completed' : 'item' + return ( +
  • + {'\\u2714'} +
    {this.props.description}
    + {'\\u2718'} +
  • + ) + }, + + handleComplete: function() { + alert('trying to update item with an id of ' + this.props.id) + } + }) + JAVASCRIPT + end + + step do + message "Now, we're going to tell ListStore to mark this item as complete/incomplete. Remove the alert from last step, and add the following code." + + source_code :javascript, <<-JAVASCRIPT + handleComplete: function() { + ListStore.toggleCompleteness(this.props.id) + } + JAVASCRIPT + end + + step do + message <<-MARKDOWN +Now, let's write the logic for updating an item! Open up store.js, and add the following code to +the toggleCompleteness function. replace 'YOUR-LIST-NAME-HERE' with your list's name. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var item = findItemById(itemId) + var currentCompletedValue = item.completed + + var updateRequest = $.ajax({ + type: 'PUT', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items/" + itemId, + data: { completed: !currentCompletedValue } + }) + JAVASCRIPT + + message <<-MARKDOWN +We're using the pre-written findItemById method to fetch the correct item, and then checking +its current completed value. We then tell the server to toggle its completeness from true to false, +or false to true. Refresh the page and try marking an item as complete. Check the network tab to see if +a new request was made! + MARKDOWN + end + + step do + message <<-MARKDOWN +Finally, we'll update the specified item's completeness value in the items array, and +tell the components to re-render themselves. Add this code to the bottom of the toggleCompleteness +function: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + updateRequest.done(function(itemData) { + item.completed = itemData.completed + notifyComponents() + }) + JAVASCRIPT + + message <<-MARKDOWN +Mark an item as complete, and see it turn gray! If you mark a completed item as incomplete, +it should change colors, too. + MARKDOWN + end +end + + + +explanation do + + message "Here's what store.js should now look like:" + + source_code :javascript, <<-JAVASCRIPT + ListStore = { + + getItems: function() { + return items + }, + + loadItems: function() { + var loadRequest = $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + + loadRequest.done(function(dataFromServer) { + items = dataFromServer.items + notifyComponents() + }) + }, + + addItem: function(itemDescription) { + var creationRequest = $.ajax({ + type: 'POST', + url: "http://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: itemDescription, completed: false } + }) + + creationRequest.done(function(itemDataFromServer) { + items.push(itemDataFromServer) + notifyComponents() + }) + }, + + toggleCompleteness: function(itemId) { + var item = findItemById(itemId) + var currentCompletedValue = item.completed + + var updateRequest = $.ajax({ + type: 'PUT', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items/" + itemId, + data: { completed: !currentCompletedValue } + }) + + updateRequest.done(function(itemData) { + item.completed = itemData.completed + notifyComponents() + }) + } + } + JAVASCRIPT + + message <<-MARKDOWN +You've now written three ajax requests, making a modern, dynamic web page. Don't worry if you +didn't understand every line of code – JavaScript is complex stuff! + +There are many more features you could add (deleting items, sorting items, etc.) but in our next +lesson, we'll add the most important feature: the ability of users to actually use your site! + MARKDOWN +end + +next_step "deploying_your_site" diff --git a/sites/javascript-to-do-list-with-react/next_steps.step b/sites/javascript-to-do-list-with-react/next_steps.step new file mode 100644 index 000000000..5800a740b --- /dev/null +++ b/sites/javascript-to-do-list-with-react/next_steps.step @@ -0,0 +1,30 @@ +message < + + + + + + + + + + + + +
    +

    Your List App

    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/sites/javascript-to-do-list-with-react/zip_content/store.js b/sites/javascript-to-do-list-with-react/zip_content/store.js new file mode 100644 index 000000000..ac2b9103d --- /dev/null +++ b/sites/javascript-to-do-list-with-react/zip_content/store.js @@ -0,0 +1,22 @@ +var items = [] + +var notifyComponents = function() { + $(ListStore).trigger('storeHasChanged') +} + +var findItemById = function(id) { + return items.filter(function(item) { + return item.id === id + })[0] +}, + +ListStore = { + + getItems: function() { + return items + }, + + loadItems: function() {}, + addItem: function(itemDescription) {}, + toggleCompleteness: function(itemId) {} +} \ No newline at end of file diff --git a/sites/javascript-to-do-list-with-react/zip_content/styles.css b/sites/javascript-to-do-list-with-react/zip_content/styles.css new file mode 100644 index 000000000..23c97a756 --- /dev/null +++ b/sites/javascript-to-do-list-with-react/zip_content/styles.css @@ -0,0 +1,108 @@ +body { + background: url('http://github.com/debut_light.png') +} + +#container { + width: 600px; + font-family: 'Titillium Web', 'Arial', sans-serif; + font-size: 40px; + text-align: center; + margin: 40px auto; +} + +h1 { + margin: 20px; + color: #7d82b0; +} + +#app { + box-shadow: 0 0 5px #684F91; + font-size: 35px; + color: #080D3B; + background-color: white; +} + +form { + margin: 0; + padding: 0; +} + +#create { + width: 100%; + font-family: inherit; + font-size: inherit; + padding: 10px 30px; + border-left: 1px solid #7d82b0; + border-right: 1px solid #7d82b0; + border-bottom: 2px solid #7d82b0; + border-top: 15px solid #477187; + text-align: center; +} + + +#create::-webkit-input-placeholder { + color: #C3C3C4; + text-align: center; +} + +#create::-moz-placeholder { /* Firefox 19+ */ + color: #C3C3C4; + text-align: center; +} + +#create:focus::-webkit-input-placeholder { + color: transparent; + text-align: center; +} + +#create:focus::-moz-placeholder { /* Firefox 19+ */ + color: transparent; + text-align: center; +} + +#list { + list-style-type: none; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.item { + position: relative; + box-sizing: border-box; + padding: 10px; + list-style-type: none; + width: 100%; + border-left: 1px solid #7d82b0; + border-right: 1px solid #7d82b0; + border-bottom: 2px solid #7d82b0; + border-top: 0px solid #7d82b0; + +} + +.item .complete-button { + position: absolute; + left: 20px; + top: 10px; + cursor: pointer; +} + +.item .value { + margin: 0 50px; + padding: 0 15px; +} + +.item .delete-button { + position: absolute; + right: 20px; + top: 10px; + cursor: pointer; +} + +.item.completed { + color: #C3C3C4; +} + +#templates { + display: none +} \ No newline at end of file diff --git a/sites/javascript-to-do-list/IntermediateTodoList.zip-manifest b/sites/javascript-to-do-list/IntermediateTodoList.zip-manifest new file mode 100644 index 000000000..ce489a48e --- /dev/null +++ b/sites/javascript-to-do-list/IntermediateTodoList.zip-manifest @@ -0,0 +1,4 @@ +zip_content/app.js +zip_content/debut_light.png +zip_content/index.html +zip_content/styles.css diff --git a/sites/javascript-to-do-list/_deploying_your_site.md b/sites/javascript-to-do-list/_deploying_your_site.md new file mode 100644 index 000000000..608d627ed --- /dev/null +++ b/sites/javascript-to-do-list/_deploying_your_site.md @@ -0,0 +1,54 @@ +### Goal + +Hosting your project requires you to make the HTML, JavaScript and CSS files +available over the Internet. + +There are three hosting options, depending on how much of a challenge you are looking +for: + +* Use a static site hosting service +* Use git and GitHub Pages +* Roll your own with a hosting provider + +### Using a Static Site Hosting Service + +If you are brand new to hosting websites, you may want to use a static site +hosting service. These allow you to upload a zip file of html, css and +javascript and have a working website. An easy one is +[Forge](https://getforge.com/). + +1. Sign up for Forge +2. Zip your project directory +3. Drag it into the Forge website + +Now you have a fully functioning site hosted online! Share the link +with your friends and family and wow them with your skills! + +### Using Git and GitHub Pages + +If you like with git and github, take a stab at setting up your site +with [GitHub Pages](http://pages.github.com/). The easiest thing to do is: +cd . + +1. From the terminal, `cd` into your project directory +1. Turn it into a git repository by running `git init` +1. Checkout a branch called `gh-pages` +1. Commit all the files +1. Create a remote repository on github for the project. +1. Follow their instructions for adding the github remote to your existing repo +1. Follow the instructions on [GitHub Pages](http://pages.github.com) for + setting up a project site from scratch. +1. Make the `gh-pages` the default branch +1. Push it on up! + +### Rolling Your Own Hosting With a Cloud Provider + +If you secretly are an unix admin who floats in the cloud; consider +using Amazon +[S3](http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html) or +[Rackspace Cloud +Files](http://www.rackspace.com/knowledge_center/article/use-cloud-files-to-serve-static-content-for-websites) + +Both of these services are designed to serve up static files without needing to +pay for a virtual server. This makes your monthly hosting bill for even large, +high traffic sites incredibly cheap. \ No newline at end of file diff --git a/sites/javascript-to-do-list/_lesson_format.md b/sites/javascript-to-do-list/_lesson_format.md new file mode 100644 index 000000000..1f1205287 --- /dev/null +++ b/sites/javascript-to-do-list/_lesson_format.md @@ -0,0 +1,22 @@ +### Format + +Each lesson will look like this: + +
    +

    Step Title

    +
    +

    Goal:

    +

    Description of the current step. +

    Red because big goals are scary. +

    +
    +

    Steps:

    +
    steps to take.
    +

    Yellow because we've gotten it done, but we have no clue what's going on. +

    +
    +

    Explanation

    +

    Details of what the steps actually did... spell out the cause and effect. +

    Green because we can tie everything together now. +

    +
    diff --git a/sites/javascript-to-do-list/_teachers_note.md b/sites/javascript-to-do-list/_teachers_note.md new file mode 100644 index 000000000..33b67771f --- /dev/null +++ b/sites/javascript-to-do-list/_teachers_note.md @@ -0,0 +1,5 @@ +### A note for teachers + +The backend for this curriculum is hosted at http://listalous.herokuapp.com/ + +If it isn't working, you might be able to deploy it somewhere else using the code from https://github.com/raorao/headless_todos diff --git a/sites/javascript-to-do-list/adding_an_item.step b/sites/javascript-to-do-list/adding_an_item.step new file mode 100644 index 000000000..36b0468aa --- /dev/null +++ b/sites/javascript-to-do-list/adding_an_item.step @@ -0,0 +1,143 @@ +goals do + goal "Allow a user to create a new item." + goal "Understand how to make an AJAX request when prompted by a user's action." +end + +overview do + message <<-MARKDOWN +JavaScript allows us to make our web page dynamic, and responsive to +the actions of our users. In this lesson, we'll allow our users to create +a new item for our list, and ask the server to save it to the database. + +To do so, we're going to use JavaScript's ability to perform a task when a +user has taken an action on the page. JavaScript refers to these actions as +__events__, and we can trigger code to run by using an __event listener__. We will +be using jQuery to accomplish this, as it provides a powerful and readable interface +to respond to user events. + +Our code will take the following steps. + +1. When the user loads the page, our code will start listening for when the user + submits the form at the top of the page. + +2. When a user submits the form (by pressing enter), we will prevent the page + from refreshing, which is the normal behavior for a form. + +3. We will make an AJAX request to our server, creating an item with the + description our user just provided. + +4. Once the request succeeds, we will add a new item to the page! + MARKDOWN +end + +steps do + step do + message "Add the following code the bottom of app.js." + + source_code :javascript, <<-JAVASCRIPT + $('#add-form').on('submit', function(event) { + var itemDescription = event.target.itemDescription.value + alert('trying to create a new item with a description ' + itemDescription) + }) + JAVASCRIPT + + message <<-MARKDOWN +Refresh the page and try creating an item. An alert should pop up when you try! What +happens after that? why? + MARKDOWN + end + + step do + message "Before the alert we wrote in the last step, add the following line of code." + + source_code :javascript, <<-JAVASCRIPT + event.preventDefault() + JAVASCRIPT + + message <<-MARKDOWN +Try creating an item again. What changed? Why did it change? + MARKDOWN + end + + step do + message <<-MARKDOWN +Now that we've successfully listened for a form submission and prevented the page from +refreshing, we're going to ask the server to save this item into the database. Remove +the alert that you wrote in step one, and replace it with the following code. replace +'YOUR-LIST-NAME-HERE' with your list's name. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var creationRequest = $.ajax({ + type: 'POST', + url: "http://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: itemDescription, completed: false } + }) + JAVASCRIPT + + message <<-MARKDOWN +Try creating an item again. After you submit the form, look at the network tab. A +new request should have occurred to http://listalous.herokuapp.com/ ! + MARKDOWN + end + + step do + message <<-MARKDOWN +Finally, we need to add the new item to the list. We'll use our addItemToPage function +again to do so. After the creationRequest, add the following code: + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + creationRequest.done(function(itemDataFromServer) { + addItemToPage(itemDataFromServer) + }) + JAVASCRIPT + + message <<-MARKDOWN + Try creating an item one more time. Once you hit enter, a new item should appear + on the page! If not, flag an instructor down to help you debug the problem. + MARKDOWN + end +end + +explanation do + + message "Here's what the bottom of app.js should now look like:" + + source_code :javascript, <<-JAVASCRIPT + $('#add-form').on('submit', function(event) { + event.preventDefault() + var itemDescription = event.target.itemDescription.value + var creationRequest = $.ajax({ + type: 'POST', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: itemDescription, completed: false } + }) + + creationRequest.done(function(itemDataFromServer) { + addItemToPage(itemDataFromServer) + }) + }) + + JAVASCRIPT + + message <<-MARKDOWN + +### The AJAX process + +You've just done something that many JavaScript developers do daily: Use JavaScript +to make a request to a server, and then update the page with the data with which +the server responds. This abstract process is repeated over and over again: + +1. Add an event listener. +2. Parse out the information the user is submitting. +3. Prevent the default action from occurring, if necessary. +4. Make a request to the server using AJAX. +5. When the request succeeds, parse the data the server sends back. +6. Update the page with the newly received data. + +This process is the basis of most modern web pages! + MARKDOWN +end + +next_step "marking_an_item_as_complete" diff --git a/sites/javascript-to-do-list/creating_a_list.step b/sites/javascript-to-do-list/creating_a_list.step new file mode 100644 index 000000000..b2ecf9d27 --- /dev/null +++ b/sites/javascript-to-do-list/creating_a_list.step @@ -0,0 +1,165 @@ +goals do + goal "Understand how the internet is made up of clients and servers." + goal "Write some basic requests using JavaScript, jQuery and your browser's console." + goal "Create a list and your first item." +end + +overview do + + message <<-MARKDOWN + +So far, we've learned about JavaScript, HTML, and CSS. These are the languages +your browser understands, and it uses them to give structure, style, and interactivity +to your website. + +The problem, though, is that browsers don't remember anything. Every time you refresh +a page, your browser reruns all of your CSS, HTML, and JavaScript. A browser, by itself, +doesn't remember who your users are, or any of their information. + +But wait, we need our website to remember things! Specifically, we need our website +to remember all the items on our list, and whether we've completed them. That's where +a __server__ comes in. A server is a catch-all term for a web application that can be +reached over the internet. They are often attached to __databases__ that store information +about their users. If you make an __HTTP request__ with the correct __URL__ (like +http://www.google.com) and __HTTP method__ (like GET or POST), the server will +respond with helpful data. + +Servers are written in dozens of languages, including Java, PHP, and Ruby. +For this exercise, we've already built a server (using Ruby on Rails), and its +available at https://listalous.herokuapp.com/. If you want to learn more about how +to write a server, take a look at our Rails curriculum! + +Browsers, like Chrome and Internet Explorer, are in the business of making requests +to servers, and displaying the data they receive as a web page. Browsers are a +type of __client__, which consume data. There are other types of clients, too. +Your computer can be a client, using the curl command in the terminal. An iPhone +is another type of client, and servers can even communicate with each other. + +Every time you refresh a web page, your browser is making another request to +the server. On modern websites, browsers often make multiple requests to a server, +depending on what the user is doing. For instance, Twitter doesn't load your entire +feed at once – as you scroll down the page, it's making more and more requests! + +To accomplish that feat, websites can use JavaScript to make requests to servers, +updating the page without the user having to refresh. This type of +request is called an __AJAX__ request, which stands for __A__synchronous __J__avaScript +__a__nd __X__ML. It's a technique that is used by most major websites, to provide +a seamless experience to their users. + +In this lesson, we'll be making AJAX requests using our browser's console. + MARKDOWN +end + +steps do + + step do + message <<-MARKDOWN +Copy the following code into your browser's console, replacing 'YOUR-LIST-NAME-HERE' +with your name of choice. Note: it has to be unique! I'd suggest using your github username. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + $.ajax({ + type: 'POST', + url: "https://listalous.herokuapp.com/lists", + data: { name: 'YOUR-LIST-NAME-HERE' } + }) + JAVASCRIPT + + message <<-MARKDOWN +Now click over to your browser's network tab. Next to the 'filter' box, click on either the 'All' tab or 'XHR'. Below that, click on the 'Response' tab. It should look something like this: + +image of chrome's network tab. + +Find the request to listalous.herokuapp.com and click on it. Was it successful? If not, look in the console and read the error message. Why +do you think it failed? Once you've successfully created a list, move on to the next step. + MARKDOWN + end + + step do + message <<-MARKDOWN +Now that we've created a list, let's create our list's first item. We're +going to use jQuery's AJAX function again to do so. Copy the following +code into your browser's console, replacing 'YOUR-LIST-NAME-HERE' with +your list's name, and 'DESCRIPTION-OF-YOUR-ITEM' with your item's description. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + $.ajax({ + type: 'POST', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items", + data: { description: 'DESCRIPTION-OF-YOUR-ITEM', completed: false } + }) + JAVASCRIPT + + message <<-MARKDOWN +Check the network tab again. Was your request successful? If it was, take a look at +the server's response. You'll notice that the item has an id attribute now. This is how +the server will uniquely identify your item in the future. + MARKDOWN + end + + step do + message <<-MARKDOWN +Finally, lets fetch our list from the server. Your list application will need to do this +every time someone refreshes the page, so that it can load previously created items. Copy +the following code into your browser's console, replacing 'YOUR-LIST-NAME-HERE' with your +list's name. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + JAVASCRIPT + + message <<-MARKDOWN +Check the network tab one more time. What data did the server send back? If your +request was successful, it should have returned all the data associated with your list: +your list's name, as well as all of your list's items. This is data will be basis +of your list application! + MARKDOWN + end +end + +explanation do + message <<-MARKDOWN + +That was a lot of copying and pasting! What did it all mean? + +### Adding a Server to the Equation. + +Remember, web pages have short memories. Whenever you refresh the page, all the HTML, +CSS, and JavaScript on that page has to reload and rerun. A common way for a website +to remember information about you (the fact that you have logged in before, your previous +purchases, etc.) is by getting a server involved. A server, attached to a database, can +persist information about your web site as users come and go. + +In this case, we have a server that can be reached over the internet at +https://listalous.herokuapp.com/. It's attached to a database that +stores all lists and items, that can fetched to make hundreds of independent to do lists. + +In step 1, we used jQuery's AJAX function to ask the server to make a +new list in its database. It only did so if the list's name was unique; +otherwise, how could the server tell your list apart from someone else's list? + +In step 2, we asked the server to create an item associated with your +list. The server responded with information regarding this item, including an +id, that we can use to identify that item later (if we want to mark it as complete, +or delete it.) + +In step 3, we asked the server to return all information associated with +our list, including all of the list's items. When we start building our list +application, we will use this data to load all items on our list every time we +refresh the page. + +We did all of this in the browser's console. In the next lesson, we'll integrate +some of this code into our site, so it runs automatically whenever a user +visits your web site. + + MARKDOWN +end + + +next_step "loading_items" diff --git a/sites/javascript-to-do-list/deploying_your_site.step b/sites/javascript-to-do-list/deploying_your_site.step new file mode 100644 index 000000000..099c45876 --- /dev/null +++ b/sites/javascript-to-do-list/deploying_your_site.step @@ -0,0 +1,3 @@ +insert '_deploying_your_site' + +next_step "next_steps" \ No newline at end of file diff --git a/sites/javascript-to-do-list/developer_tools.step b/sites/javascript-to-do-list/developer_tools.step new file mode 100644 index 000000000..fbe7c35a5 --- /dev/null +++ b/sites/javascript-to-do-list/developer_tools.step @@ -0,0 +1,3 @@ +insert '../frontend/_developer_tools' + +next_step 'programming_with_javascript' \ No newline at end of file diff --git a/sites/javascript-to-do-list/img/browser_console.png b/sites/javascript-to-do-list/img/browser_console.png new file mode 100644 index 000000000..965ff66c6 Binary files /dev/null and b/sites/javascript-to-do-list/img/browser_console.png differ diff --git a/sites/javascript-to-do-list/img/finished_app.png b/sites/javascript-to-do-list/img/finished_app.png new file mode 100644 index 000000000..b3269d8b9 Binary files /dev/null and b/sites/javascript-to-do-list/img/finished_app.png differ diff --git a/sites/javascript-to-do-list/img/network_tab.png b/sites/javascript-to-do-list/img/network_tab.png new file mode 100644 index 000000000..57b6fc3b6 Binary files /dev/null and b/sites/javascript-to-do-list/img/network_tab.png differ diff --git a/sites/javascript-to-do-list/img/text_editor_html.png b/sites/javascript-to-do-list/img/text_editor_html.png new file mode 100644 index 000000000..98072b963 Binary files /dev/null and b/sites/javascript-to-do-list/img/text_editor_html.png differ diff --git a/sites/javascript-to-do-list/javascript-to-do-list.step b/sites/javascript-to-do-list/javascript-to-do-list.step new file mode 100644 index 000000000..1aff077ae --- /dev/null +++ b/sites/javascript-to-do-list/javascript-to-do-list.step @@ -0,0 +1,56 @@ +message <<-MARKDOWN +### Goal + +By the end of this session, you should understand how JavaScript is used +in a modern web application. You're going to be building a personal to do list application. +You've decided that you'd like to: + +* Add an item to your list +* Mark an item as complete +* Load your list from multiple computers. + +You've sketched up an initial screenshot of what you want it to look like: + +![Browser window with list application running](img/finished_app.png) + +### Meta-Goal + +When you have completed today's goal of getting the basic +application online you should understand: + +* How JavaScript, CSS, and HTML interact with the browser. +* How to use JavaScript to interact with a server. +* How to incrementally add features to your application. +* How to get your application online. + +### Schedule + +* 2-ish hours of JavaScript and the basics of clients and servers. +* 3-ish hours of adding features to you application, broken up into 1 hour chunks. + +This is just a rough guideline, not a mandate. Some steps you'll go +over and some you'll go under. It'll all work out by the end of the +day. Probably. + +### Requirements + +When programming, you'll generally want these tools on hand: + + * An internet connection. We're going to be communicating with other websites using JavaScript. + * Your browser to see the code running (I recommend Chrome, but any will do!) + * A text editor to change the code (Railsbridge recommends Atom, but you can try a different editor) + * A Javascript console so you can experiment and print out debugging + messages. This is built into your browser. + +Before you can start the tutorial, you'll need to download the tutorial +to your computer to edit the source code. + +Once you've downloaded it, open index.html with your browser. You should see a mock up of your list. If you do not, get an instructor to help you out. +MARKDOWN + +insert 'lesson_format' + +insert 'teachers_note' + +next_step "the_basics_of_a_website" + diff --git a/sites/javascript-to-do-list/loading_items.step b/sites/javascript-to-do-list/loading_items.step new file mode 100644 index 000000000..2a4218656 --- /dev/null +++ b/sites/javascript-to-do-list/loading_items.step @@ -0,0 +1,105 @@ +goals do + goal "Make an AJAX request and update the page based on the server's response." + goal "Load your list's items every time a user visits the page." +end + +overview do + message <<-MARKDOWN + Last lesson, we made AJAX requests in the browser's console to load all of + our list's items. Now, we'll tie that AJAX request to our web site! Here's what will happen. + + 1. Every time a user visits our site, the browser will run the JavaScript in app.js. + 2. It will use jQuery's AJAX method to make a request to http://listalous.herokuapp.com/ and + get all the data about our list's items. + 3. We will then use JavaScript to turn those items into HTML elements, and attach them to our list. + MARKDOWN +end + +steps do + step do + message <<-MARKDOWN +Open app.js in your text editor. Add the following code to the bottom of the file. +Replace 'YOUR-LIST-NAME-HERE' with the name of the list you created last lesson. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var loadRequest = $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + JAVASCRIPT + + message <<-MARKDOWN +Refresh the page, and click over to your browser's network tab. You should see +a new request there, that visits our server at https://listalous.herokuapp.com/. + MARKDOWN + end + + step do + message <<-MARKDOWN + Now that we've made the request, we need to update the page whenever the request + succeeds. Add the following lines of code to the bottom of app.js. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + loadRequest.done(function(dataFromServer) { + var itemsData = dataFromServer.items + + itemsData.forEach(function(itemData) { + addItemToPage(itemData) + }) + }) + JAVASCRIPT + + message <<-MARKDOWN + Now refresh the page. Once the AJAX request succeeds, your site should now display + all the items you created last lesson! If not, flag an instructor down to help you + debug the problem. + MARKDOWN + end + +end + + +explanation do + message "App.js should now look like this." + source_code :javascript, <<-JAVASCRIPT + var itemTemplate = $('#templates .item') + var list = $('#list') + + var addItemToPage = function(itemData) { + var item = itemTemplate.clone() + item.attr('data-id',itemData.id) + item.find('.description').text(itemData.description) + if(itemData.completed) { + item.addClass('completed') + } + list.append(item) + } + + var loadRequest = $.ajax({ + type: 'GET', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/" + }) + + loadRequest.done(function(dataFromServer) { + var itemsData = dataFromServer.items + + itemsData.forEach(function(itemData) { + addItemToPage(itemData) + }) + }) + + JAVASCRIPT + + message <<-MARKDOWN +Your web page has made its first successful AJAX request! Now your page will +load your list's items whenever you visit it. Once we host this page on the internet, +you will be able to see your list on any computer, tablet, or phone! + MARKDOWN +end + + + + +next_step "adding_an_item" diff --git a/sites/javascript-to-do-list/marking_an_item_as_complete.step b/sites/javascript-to-do-list/marking_an_item_as_complete.step new file mode 100644 index 000000000..075b6ef84 --- /dev/null +++ b/sites/javascript-to-do-list/marking_an_item_as_complete.step @@ -0,0 +1,144 @@ +goals do + goal "Allow a user to mark an item as complete or incomplete." + goal "Understand how to bind a complex event listener that requires event delegation." + goal "Understand how to parse complex information from the page." + goal "Understand how to make nuanced changes to the page." +end + +overview do + message <<-MARKDOWN +In this lesson, we'll allow our users to mark items as complete and incomplete. As they +do, we will ask the server to update the item's status in its database. This process will look +a lot like the process for adding an item, with some additional complexity. + +First off, the event listener is slightly more complex. Instead of binding a listener to +a single form (like we did in the last lesson), we'll be binding a listener to every check +mark in the list. jQuery's .on function allows you to do this, using __event delegation__. + +Secondly, it will be slightly harder to get the required information from the page when +the user clicks a check mark. We need to know which item they clicked, its id (so the server +can identify it), and whether it has been completed or not. + +And finally, we need to use jQuery to add and remove classes based on whether the item is completed. +Let's get started! + MARKDOWN +end + + +steps do + step do + message "Add the following code the bottom of app.js." + + source_code :javascript, <<-JAVASCRIPT + $('#list').on('click', '.complete-button', function(event) { + alert('trying to complete an item!') + }) + JAVASCRIPT + + message <<-MARKDOWN +Refresh the page and try completing an item. An alert should pop up! + MARKDOWN + end + + step do + message "Now, we're going to get all the information we need from the page. Remove the alert from last step, and add the following code." + + source_code :javascript, <<-JAVASCRIPT + var item = $(event.target).parent() + var isItemCompleted = item.hasClass('completed') + var itemId = item.attr('data-id') + alert('clicked item ' + itemId + ', which has completed currently set to ' + isItemCompleted) + JAVASCRIPT + + message <<-MARKDOWN +Try marking an item complete again. Does the alert reference the correct item and the correct +completed value? Mark a few other items complete. Does it still work? + MARKDOWN + end + + step do + message <<-MARKDOWN +Now that we have the necessary information, we'll make another request to the server. +This is a url and type we haven't used before! What do you think it does? + +Remove the alert we wrote in the last step, and replace it with the following code. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + var updateRequest = $.ajax({ + type: 'PUT', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items/" + itemId, + data: { completed: !isItemCompleted } + }) + + JAVASCRIPT + + message <<-MARKDOWN +Refresh the page and try marking an item as complete. Check the network tab to see if +a new request was made! + MARKDOWN + end + + step do + message <<-MARKDOWN +Finally, we'll update the item that has been marked as incomplete or complete. +Instead of creating a new item, we'll simple add or remove the class 'completed' +from the specified item (using jQuery's helpful addClass and removeClass functions). +This will cause the browser to render the item differently, based on the rules +written in styles.css. Add this line of code after the updateRequest. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + updateRequest.done(function(itemData) { + if (itemData.completed) { + item.addClass('completed') + } else { + item.removeClass('completed') + } + }) + JAVASCRIPT + + message <<-MARKDOWN +Mark an item as complete, and see it turn gray! If you mark a completed item as incomplete, +it should change colors, too. + MARKDOWN + end +end + +explanation do + + message "Here's what the bottom of app.js should now look like:" + + source_code :javascript, <<-JAVASCRIPT + $('#list').on('click', '.complete-button', function(event) { + var item = $(event.target).parent() + var isItemCompleted = item.hasClass('completed') + var itemId = item.attr('data-id') + + var updateRequest = $.ajax({ + type: 'PUT', + url: "https://listalous.herokuapp.com/lists/YOUR-LIST-NAME-HERE/items/" + itemId, + data: { completed: !isItemCompleted } + }) + + updateRequest.done(function(itemData) { + if (itemData.completed) { + item.addClass('completed') + } else { + item.removeClass('completed') + } + }) + }) + + JAVASCRIPT + + message <<-MARKDOWN +You've now written three ajax requests, making a modern, dynamic web page. Don't worry if you +didn't understand every line of code – JavaScript is complex stuff! + +There are many more features you could add (deleting items, sorting items, etc.) but in our next +lesson, we'll add the most important feature: the ability of users to actually use your site! + MARKDOWN +end + +next_step "deploying_your_site" diff --git a/sites/javascript-to-do-list/next_steps.step b/sites/javascript-to-do-list/next_steps.step new file mode 100644 index 000000000..02b8a53cd --- /dev/null +++ b/sites/javascript-to-do-list/next_steps.step @@ -0,0 +1,30 @@ +message < + +Congrats, you just wrote your first lines of JavaScript code! __console.log__ is an important function – +it allows you to print information to the browser's console. It's very helpful in debugging! You can also +use the __alert__ function to make a message pop up in the browser. Try it out! + MARKDOWN + end + + step do + message "Next try some simple math that's built into JavaScript. Type these lines into console:" + + source_code :javascript, <<-JAVASCRIPT + 3 + 3 + 7 * 6 + JAVASCRIPT + end + + step do + message "**Variables** are names with values assigned to them." + + source_code :javascript, <<-JAVASCRIPT + var myVariable = 5 + JAVASCRIPT + + message "This assigns the value `5` to the name `myVariable`." + end + + step do + message "You can also do math with variables:" + source_code :javascript, <<-JAVASCRIPT + myVariable + 2 + myVariable * 3 + JAVASCRIPT + end + + step do + message "Variables can also hold more than one value. This is called an **array**." + + source_code :javascript, 'var fruits = ["kiwi", "strawberry", "plum"]' + + message "Here we're using the variable `fruits` to hold a collection of fruit names." + end + + step do + message "Arrays are a type of __object__ in JavaScript. Objects often include helpful attributes!" + + source_code :javascript, <<-JAVASCRIPT + fruits.length + JAVASCRIPT + + message <<-MARKDOWN +Objects can also have __functions__, which can be helpful for altering objects and learning more about them. +Functions are __invoked__ with parentheses, which causes them to run. + MARKDOWN + + source_code :javascript, <<-JAVASCRIPT + fruits.push("orange") + fruits.slice(1) + JAVASCRIPT + + message <<-MARKDOWN +The __push__ function allows us to add new items to an array. The slice function returns a new array with +with everything to the right of the __index__ we provided. Here, we passed the function the number 1, so +slice returned an array with everything after the first element in the array. (Note that the first element is assigned 0 as its index rather than 1.) + MARKDOWN + end + + step do + message "You can also make your own functions:" + + source_code :javascript, <<-JAVASCRIPT + var pluralize = function(word) { + return word + "s" + } + pluralize("kiwi") + JAVASCRIPT + + message "Functions take **parameters**, which are the variables they work on. In this case, we made a function called pluralize that takes one parameter, a word." + + message "Functions can also return data. In this case, pluralize returns the word with an 's' added to the end of it. In order for a function to return something, you have to use the __return__ keyword." + end + + + step do + message "Arrays have a function called **forEach** which iterates through the list running code on each item. It takes another function as a parameter." + source_code :javascript, <<-JAVASCRIPT + fruits.forEach(function(fruit) { + console.log(fruit) + }) + JAVASCRIPT + message "This takes the first item from the `fruits` array (`\"strawberry\"`), assigns it to the variable `fruit`, and runs the code between curly brackets. Then it does the same thing for each other item in the list. The code above should print a list of the fruits." + end + + step do + message "A **conditional** runs code only when a statement evaluates to true." + + source_code :javascript, <<-JAVASCRIPT + if(myVariable > 1) { + console.log('YAY') + } + JAVASCRIPT + + message "This prints `YAY!` if the value stored in `myVariable` is greater than 1." + + message "Try changing the `>` in the conditional to a `<`." + end +end + + + +next_step "playing_with_jquery" diff --git a/sites/javascript-to-do-list/the_basics_of_a_website.step b/sites/javascript-to-do-list/the_basics_of_a_website.step new file mode 100644 index 000000000..fc115070b --- /dev/null +++ b/sites/javascript-to-do-list/the_basics_of_a_website.step @@ -0,0 +1,97 @@ +goals do + goal "Understand the purposes of HTML and CSS." + goal "Write some basic code in each language." +end + +overview do + + message <<-MARKDOWN + +## What makes a website? + +Modern websites are built using three languages: __HTML, CSS, and JavaScript__. + + * __HTML__ stands for __H__yper-__t__ext __M__arkup __L__anguage. It provides the structure for your website, using a system of nested __tags__. + + * __CSS__ stands for __C__ascading __S__tyle__s__heets. It's a language for creating rules that can select various elements on the page and change their visual properties. A CSS stylesheet is composed of __rules__ that dictate how your site is presented. + + * __JavaScript__ allows users to interact with your website, making it dynamic and customizable. It's a powerful language that can be used for extraordinarily complex tasks. + +In this lesson, we'll be reviewing HTML and CSS at a high level, and see how they interact. +You'll need to fire up your text editor for these steps. +MARKDOWN +end + +steps do + + step do + message < + +Take a look around. What are the different elements doing? Why are some lines indented deeper +than others? What are those strange series of numbers and ampersands? + +Finally, you should find this line of code in index.html: +MARKDOWN + source_code :html, <<-HTML +

    Your List App

    + HTML + message "and change the text whatever you want your list application to be called. Save your file. Go back to chrome and refresh the page. Your list application should have changed!" + end + + step do + message < + + +
      +
    + HTML + end + + +end + +next_step "developer_tools" \ No newline at end of file diff --git a/sites/javascript-to-do-list/zip_content/app.js b/sites/javascript-to-do-list/zip_content/app.js new file mode 100644 index 000000000..e69de29bb diff --git a/sites/javascript-to-do-list/zip_content/debut_light.png b/sites/javascript-to-do-list/zip_content/debut_light.png new file mode 100644 index 000000000..2f4febcb6 Binary files /dev/null and b/sites/javascript-to-do-list/zip_content/debut_light.png differ diff --git a/sites/javascript-to-do-list/zip_content/index.html b/sites/javascript-to-do-list/zip_content/index.html new file mode 100644 index 000000000..c3953e709 --- /dev/null +++ b/sites/javascript-to-do-list/zip_content/index.html @@ -0,0 +1,38 @@ + + + + + + + +
    +

    Your List App

    +
    +
    + +
    +
      +
    • + +
      A gallon of milk
      + +
    • +
    • + +
      A stick of butter
      + +
    • +
    +
    +
    + +
      +
    • + +
      A stick of butter
      + +
    • +
    + + + \ No newline at end of file diff --git a/sites/javascript-to-do-list/zip_content/styles.css b/sites/javascript-to-do-list/zip_content/styles.css new file mode 100644 index 000000000..0a8d26b84 --- /dev/null +++ b/sites/javascript-to-do-list/zip_content/styles.css @@ -0,0 +1,107 @@ +body { + background: url('http://github.com/debut_light.png') +} + +#container { + width: 600px; + font-family: 'Titillium Web', 'Arial', sans-serif; + font-size: 40px; + text-align: center; + margin: 40px auto; +} + +h1 { + margin: 20px; +} + +#app { + box-shadow: 0 0 5px #684F91; + font-size: 35px; + color: #080D3B; + background-color: white; +} + +form { + margin: 0; + padding: 0; +} + +#create { + width: 100%; + font-family: inherit; + font-size: inherit; + padding: 10px 30px; + border-left: 1px solid #7d82b0; + border-right: 1px solid #7d82b0; + border-bottom: 2px solid #7d82b0; + border-top: 15px solid #477187; + text-align: center; +} + + +#create::-webkit-input-placeholder { + color: #C3C3C4; + text-align: center; +} + +#create::-moz-placeholder { /* Firefox 19+ */ + color: #C3C3C4; + text-align: center; +} + +#create:focus::-webkit-input-placeholder { + color: transparent; + text-align: center; +} + +#create:focus::-moz-placeholder { /* Firefox 19+ */ + color: transparent; + text-align: center; +} + +#list { + list-style-type: none; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.item { + position: relative; + box-sizing: border-box; + padding: 10px; + list-style-type: none; + width: 100%; + border-left: 1px solid #7d82b0; + border-right: 1px solid #7d82b0; + border-bottom: 2px solid #7d82b0; + border-top: 0px solid #7d82b0; + +} + +.item .complete-button { + position: absolute; + left: 20px; + top: 10px; + cursor: pointer; +} + +.item .value { + margin: 0 50px; + padding: 0 15px; +} + +.item .delete-button { + position: absolute; + right: 20px; + top: 10px; + cursor: pointer; +} + +.item.completed { + color: #C3C3C4; +} + +#templates { + display: none +} \ No newline at end of file diff --git a/sites/job-board/add_a_navbar.step b/sites/job-board/add_a_navbar.step new file mode 100644 index 000000000..39f237c0c --- /dev/null +++ b/sites/job-board/add_a_navbar.step @@ -0,0 +1,123 @@ +message <<-MARKDOWN + # What we're going to do + + * Add top-level navigation + * Learn about CSS organization techniques + * Add some really, really ugly CSS to our app + + # Application layout + + We'll start with the application layout, which you can find in the views directory, at `app/views/layouts/application.html.erb`. +MARKDOWN + +discussion_box "What's an application layout?", <<-MARKDOWN + None of the view pages that we've been working with so far have had the necessary structure for a valid HTML page. No `` or `` or JavaScript or CSS tags. This application layout file is where all of that stuff exists! + + * How is this page related to the individual page content? Try comparing the source in the browser to this file, and see where the individual page's code starts showing up. + * If you add something (say, an h1) to this file, where will it show up? +MARKDOWN + +message <<-MARKDOWN + # Add the markup + Copying and pasting a nav bar onto every. single. page. of our application would be the worst. So instead of putting it into our individual pages, we'll add the nav bar HTML to this file! + + Add the following code **above** the line `<%= yield %>` +MARKDOWN + +source_code :erb, <<-ERB +
    +
    +
      +
    • <%= link_to "Home", jobs_path %>
    • +
    +
    +
    +
      +
    • <%= link_to "Add Job", new_job_path %>
    • +
    +
    +
    + ERB + +message <<-MARKDOWN + Additionally, wrap the `<%= yield %>` in a containing wrapper div so it looks like this: +MARKDOWN + +source_code :erb, <<-ERB +
    + <%= yield %> +
    +ERB + +message <<-MARKDOWN + Things to note: + + * We use Rails link helpers instead of typing in `Add Job`. The reason is that if the url of a page changes in the future, without the link helpers, we’d have to update every single place we link to that page. The link helpers abstract the actual url for us, so if we change a specific url, we update the address in only one place, in the routes file; and all the places we link to it stay the same. + * The `
    ` tag is HTML5. Aren't we cool!? + + So let's take a look at it. Refresh, and ... isn't that horrifying looking? Let's make it look like a nav, albeit a very ugly one. + + # Add the styles + + Open up the assets directory, and you should have a file here: app/assets/stylesheets/jobs.scss. This is a Rails-default created stylesheet, and isn't the best. [Smart CSS people](http://www.stubbornella.org/content/2013/09/12/rails-is-mucking-up-my-css-already/) have taught us that CSS should be organized into [reusable components](https://github.com/stubbornella/oocss/wiki), not organized based on where it is used. + + So let's delete that file and make a new one. + + Actually, we're going to make two new ones. Under `app/assets/stylesheets`, add `global.scss`: +MARKDOWN + +source_code :CSS, +<<-CSS + body { + margin: 0; + } + + .content { + margin: 10px auto; + width: 70%; + } + + .clearfix { + overflow: hidden; + } +CSS + +message <<-MARKDOWN + This is where we put styles that affect the whole app. + + Now, under `app/assets/stylesheets`, add `nav.scss`: +MARKDOWN + +source_code :CSS, +<<-CSS + header { + background: grey; + padding: 10px; + ul { + margin: 0; + padding: 0; + list-style-type: none; + } + a { + text-decoration: none; + font-weight: bold; + color: white; + } + } + + .left-nav { + float: left; + } + + .right-nav { + float: right; + } +CSS + +message <<-MARKDOWN + Refresh the page, and ... it's still totally horrific. This CSS is not going to win any awards. + + But it's usable, and we can keep working on that later. It turns out sometimes job descriptions have typos, and need to be updated. Let's make that possible! +MARKDOWN + +next_step "update_job_listings" diff --git a/sites/job-board/add_a_new_job_form.step b/sites/job-board/add_a_new_job_form.step new file mode 100644 index 000000000..17520d5a3 --- /dev/null +++ b/sites/job-board/add_a_new_job_form.step @@ -0,0 +1,83 @@ +message <<-MARKDOWN + # What we're going to do + + * Use Rails form helpers to create the HTML for the form + * Learn to use the Rails server output for great good + * Update the controller so that the form saves submissions to the database + * Learn about the beauty of the params hash + + # Side Note: Web forms are not perfectly straightfoward + + Web forms. They're like, the most basic thing about the internet, other than cat gifs, right? So they should be straightforward, right? WRONG! They are exciting and just complicated enough to bake your noodle a bit. But don't worry, Rails has strong opinions about forms, so we won't have to do _too_ much typing. + + # Setting up the page for the form + + Let's take a look at our handy routes, which can help us figure out what we need to do. We're going to follow the same pattern that we did for the main `/jobs` page. + + First, visit the routing page at , and find the route for `/jobs/new`. + + That's the one we want for our form. Let's visit that page and see what it says: . +MARKDOWN + +error_box "The action 'new' could not be found for JobsController" + +source_code_with_message "This looks familiar! Let's add a method to our jobs controller called `new`:", :ruby, +<<-RUBY + def new + end +RUBY + +message <<-MARKDOWN + Refresh , and we see that familiar "Template is missing" error. So, let's add that template. + + Under app/views/jobs, add a file called new.html.erb. This will be our form. Add some html to the template to keep things moving along: +MARKDOWN + +source_code :html, +<<-HTML +

    Add a job

    +HTML + +message <<-MARKDOWN + Refresh again: + + # Add a form! + + Rails has handy helpers for forms. We'll be using a form helper specifically made for use with a model. +MARKDOWN + +source_code_with_message "In the view file you were just editing (app/views/jobs/new), add the following code:", :erb, +<<-ERB + <%= form_for @job do |f| %> +
    + <%= f.label :title %> + <%= f.text_field :title %> +
    +
    + <%= f.label :description %> + <%= f.text_area :description, size: '60x6' %> +
    +
    + <%= f.submit %> +
    + <% end %> +ERB + +message "Save that file, and reload the page." + +error_box "First argument in form cannot contain nil or be empty" + +message "What do you think that means? What is the first argument? What is it supposed to be? In order for the method `form_for` to do its job, it has to know about the thing that it's building the form for. So we need to give it an object. We do that in the controller." + +source_code_with_message "Open up the jobs_controller, and update the new method:", :ruby, +<<-RUBY + def new + @job = Job.new + end +RUBY + +message "Now we should see our mostly unstyled form!" + +discussion_box "Form HTML", "What HTML did the form helpers produce? Using the web inspector, look through the form code and compare it to the file you've been working on in Atom." + +next_step "make_the_form_work" diff --git a/sites/job-board/add_more_things.step b/sites/job-board/add_more_things.step new file mode 100644 index 000000000..96322f257 --- /dev/null +++ b/sites/job-board/add_more_things.step @@ -0,0 +1,22 @@ +message <<-MARKDOWN + + ## Time to add more things! + + The time where we tell you what to type in has finally ended. You should do one or all of the things below! + + Working independently or in pairs is super fun at this point. + + * Add a show page for each listing and move the Edit and Delete links to there. + * Add validations — postings without titles or descriptions shouldn't be allowed, right? + * Add other useful fields to jobs + * Display jobs index in a table; make sortable with data_tables + * Add tags (time for a has_many, folks!!!) + * Make it look better + * Add a user model and auth with Devise + * Test it! Write a controller spec. + + ## Credits + + This curriculum was originally written by [Lillie Chilen](http://www.twitter.com/lilliealbert) + and has been improved by all sorts of [lovely RailsBridge volunteers](https://github.com/railsbridge/docs/commits/master/sites/en/job-board). +MARKDOWN diff --git a/sites/job-board/create_a_rails_app.step b/sites/job-board/create_a_rails_app.step new file mode 100644 index 000000000..4f03b75e3 --- /dev/null +++ b/sites/job-board/create_a_rails_app.step @@ -0,0 +1,75 @@ +message <<-MARKDOWN + # What we're going to do: + + * Make a new Rails app + * Add a few gems to make life easier + * Start the Rails server and see the default Rails page + * Learn a little about Bundler and dependency management + + # Get Set Up + + First, let's get into a directory for RailsBridge projects (either by finding your existing one & moving there or making a new folder). + + If you don't already have a RailsBridge folder, use the commands `mkdir` and `cd` to create one and move into it: +MARKDOWN + +console_without_message "mkdir railsbridge" +console_without_message "cd railsbridge" + +message <<-MARKDOWN + # Rails New!!! +MARKDOWN + +console "rails new job_board -T --skip-turbolinks" + +message <<-MARKDOWN + The `-T` in that command means that when you make new files using Rails generators, it doesn't automatically create test files using Test::Unit (the default Rails testing framework). + + Watch all the files that are created! Wow! + + # Open the project in Atom +MARKDOWN + +console_with_message "Move into the directory for your new Rails app:", "cd job_board" + +message <<-MARKDOWN + And open the project in Atom: + + * Open Atom + * Under Project, choose "Add Folder to Project" + + (You must have at least one window open, so if that option is greyed out, open a window with cmd+n (Mac) or ctl+n (PC)) +MARKDOWN + +discussion_box "Text Editor vs Command Line", "Review the differences between the command line and your text editor, even if everyone already knows!" + +message "# Let's Talk About Dependencies" + +message "When we created a new Rails app, it installed a bunch of stuff by default. The list of things Rails installed is in a file called `Gemfile`. If you want to add any additional third party code (aka **gems**), you can add more lines to the `Gemfile` and install them with `bundle`." + +console_with_message "Rails has already installed all the stuff we need, but you can always run bundle again to re-install gems, or install gems newly added to the Gemfile. In the command line, run the following command:", "bundle install" + +discussion_box "What does 'bundle' do?", <<-MARKDOWN + Bundler is the tool the Ruby community uses for dependency management. + + * What's dependency management? + * Why do we need it? + * Why do we even need gems? + * Is there a shorter method to use for `bundle install`? (Hint: yes!) +MARKDOWN + +message "# Look at your empty app" + +tip "Now is a good time to figure out how to have multiple tabs or windows of your terminal or command prompt. Starting and stopping the Rails server all day is tedious, so it's good to have one terminal tab or window for running commands, and a separate one for the server." + +console_with_message "Start the Rails server by running this command in the terminal:", "rails server" + +message <<-MARKDOWN + Now, let's check out our default home page + + In the browser, visit + + Yup, that's the default Rails home page! +MARKDOWN + +next_step "the_request_cycle" diff --git a/sites/job-board/crud_and_resourceful_routing.step b/sites/job-board/crud_and_resourceful_routing.step new file mode 100644 index 000000000..0c4678418 --- /dev/null +++ b/sites/job-board/crud_and_resourceful_routing.step @@ -0,0 +1,38 @@ +message <<-MARKDOWN + # CRUD + Why do we talk about building CRUD apps? What are some examples of CRUD apps that you use? + + Each action that CRUD describes maps to a SQL statement and an HTTP verb: + + Table mapping CRUD operations to SQL and HTTP verbs + + *Source: * + + Compare this to what we see at : + + !["Screenshot of Rails routes page"](img/rails-routes.png) +MARKDOWN + +discussion_box "CRUD", "Talk through the different uses for the HTTP verbs. What is their general purpose, and what are we specifically using them for??" + +message <<-MARKDOWN + To have a useful app, we need to be able to create job postings, see them all and their details, change them, and delete them. The way that Rails wants us to do this is by following RESTful routing conventions, which are perfectly encapsulated by the output of `resources :jobs` in our routes.rb file. + + In Rails we work with seven main verbs, which map to different CRUD actions and controller actions: + + Table mapping CRUD operations to REST verbs + + What does each of these do? Go through each method and review what they do. + + # Resourceful Routing + + You could make your URLs look like anything you wanted, like: `/all_the_jobs` or `/add_a_job` or `show_job`. This would totally work if you set up your controller methods and the route file correctly. + + But we generally avoid this because the RESTful routing is a best practice that Rails makes easy to follow. In RESTful routes, the paths are structured around a specific resource — in our case, jobs. Check out more about [RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) urls. + + Another example: if we were making a puppy app, I would expect that going to `/puppies` would show me an index of all the puppies, and `/puppies/1` would find the puppy with the ID of one and show me their individual page, and `/puppies/1/edit` would show me the form to update a given puppy. + + For more info on resource-based routing, check out the [Rails Guide on routing](http://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default). +MARKDOWN + +next_step "listing_the_jobs" diff --git a/sites/job-board/delete_job_listings.step b/sites/job-board/delete_job_listings.step new file mode 100644 index 000000000..bf2f8208d --- /dev/null +++ b/sites/job-board/delete_job_listings.step @@ -0,0 +1,66 @@ +message <<-MARKDOWN + # What we're going to do + + * Remove postings + + Once a job is filled, we don't want a listing for it hanging out forever. Let's add a way to delete postings. + + # Postings be gone! + + If we look at our [handy Routes page](http://localhost:3000/rails/info/routes), we see this: +MARKDOWN + +source_code :html, "job_path DELETE /jobs/:id(.:format) jobs#destroy" + +message "So we need to send the DELETE http verb to the server, so we can get to the destroy method on the jobs controller (that we will make soon). It turns out rails link_to helpers accept specific verbs as an argument, so we can add this line below the edit posting link that we just added:" + +source_code :erb, "
    <%= link_to 'Delete Posting', job, method: :delete %>
    " + +message "Go to the index, and try to delete something." + +error_box "The action 'destroy' could not be found for JobsController" + +message "This is like my favorite error now! First, let's add the method:" + +source_code :ruby, +<<-RUBY + def destroy + end +RUBY + +message <<-MARKDOWN + This fixes the error. But just like updating, we still need to add the logic that actually deletes the job. + + See if you can figure out the right syntax for finding the job, deleting it, and then redirecting to a useful page. + + **Don't scroll down!!!** +MARKDOWN + +message <<-MARKDOWN + * here + * is + * even + * more + * strategic + * white + * space + * so + * the + * answer + * isn't + * immediately + * visible! + + Okay, here's the answer: +MARKDOWN + +source_code :ruby, +<<-RUBY + @job = Job.find(params[:id]) + @job.destroy + redirect_to jobs_path +RUBY + +message "Try it again, and... kablammo! We're destroying job listings left and right!" + +next_step "add_more_things" diff --git a/sites/job-board/img/crud_grid.jpg b/sites/job-board/img/crud_grid.jpg new file mode 100644 index 000000000..408bf0956 Binary files /dev/null and b/sites/job-board/img/crud_grid.jpg differ diff --git a/sites/job-board/img/crud_rails_methods.jpg b/sites/job-board/img/crud_rails_methods.jpg new file mode 100644 index 000000000..a37308843 Binary files /dev/null and b/sites/job-board/img/crud_rails_methods.jpg differ diff --git a/sites/job-board/img/rails-routes.png b/sites/job-board/img/rails-routes.png new file mode 100644 index 000000000..086b0eea4 Binary files /dev/null and b/sites/job-board/img/rails-routes.png differ diff --git a/sites/job-board/img/request-cycle.jpg b/sites/job-board/img/request-cycle.jpg new file mode 100644 index 000000000..53ac5ed5b Binary files /dev/null and b/sites/job-board/img/request-cycle.jpg differ diff --git a/sites/job-board/job-board.step b/sites/job-board/job-board.step new file mode 100644 index 000000000..f2c78c2b0 --- /dev/null +++ b/sites/job-board/job-board.step @@ -0,0 +1,30 @@ +message <<-MARKDOWN + We're going to build a job board in Rails using the tried-and-true method of following the errors that we make! + + Rails generators will help us avoid some tedious file creation, but provide a lot less magic than scaffolding (which is what is used in the Suggestotron to create *everything* for a Topic all at once). + + This means we'll a get a little less done today than we did when we built Suggestotron. But we're going to build an app in little pieces, so you can focus on understanding how the pieces fit together. +MARKDOWN + +tip "This is not a self-paced curriculum. You should use the discussion sections on each page to make sure everyone is together!" + +message <<-MARKDOWN + # Notable Things + + As you might have noticed, we're assuming you've already been to a RailsBridge workshop before or have otherwise already explored a Rails app, and are ready for deeper knowledge. + + We're also going to skip deploying to Heroku this time around, but you can definitely use the instructions from the Suggestotron curriculum to deploy your app to the internet! +MARKDOWN + +important "This curriculum is written for Rails 5. Things will get awkward / broken if you're using an earlier version of Rails, so if you skipped the Installfest, you need to upgrade to Rails 5 now." + +message <<-MARKDOWN + # Tips for everyone: + + * When adding code, it's awesome for students to walk through the code line by line and say out loud what is happening. (i.e., "The string is being stored in the instance variable" or "The method `snorgle` is being defined"). If you do it every time, you'll get really comfortable with the vocabulary of Rails! + * Error messages are your friend! Read them carefully, and practice understanding what Rails is telling you. Seeing an error and just diving back into your code is a natural reaction, but stop! Then read, think, and talk about what the error means before fixing it. +MARKDOWN + +insert '../intro-to-rails/working_effectively_and_efficiently' + +next_step "create_a_rails_app" diff --git a/sites/job-board/listing_the_jobs.step b/sites/job-board/listing_the_jobs.step new file mode 100644 index 000000000..1a6eeccd7 --- /dev/null +++ b/sites/job-board/listing_the_jobs.step @@ -0,0 +1,51 @@ +message <<-MARKDOWN + # What we're going to do + + * Show the jobs! + * Learn about ERB + + Going back to the jobs index (), we expect to see the new jobs. Let's actually build the job board part of this job board now! + + # Get all the jobs out of the database + + If we're going to show our jobs in view, first we have to get them out of the database and store them in an instance variable. Update the index method to look like this: +MARKDOWN + +source_code :ruby, +<<-RUBY + def index + @jobs = Job.all + end +RUBY + +message <<-MARKDOWN + Before we show the jobs, let's actually look at what that is doing. Go back to your Rails console and run `Job.all`. +MARKDOWN + +discussion_box "Rails Console", <<-MARKDOWN + The Rails console is super fun! It's giving us direct access to our local database. + + * Try running `Job.all.to_sql`. What does that do? + * Try selecting an individual Job record. + * Try updating that individual record from the console! +MARKDOWN + +message "# Show those jobs!" + +source_code_with_message "Add this to app/views/jobs/index.html.erb:", :erb, +<<-RUBY + <% @jobs.each do |job| %> +

    <%= job.title %>

    +

    <%= job.description %>

    + <% end %> +RUBY + +discussion_box "ERB", <<-MARKDOWN + What is this doing? Go through this line by line, having one person explain each line. + + Compare the ERB to the HTML that shows up on the page. ("Inspect Element" is your friend!) + + What's the difference between a line with `<% %>` brackets and `<%= %>` brackets? +MARKDOWN + +next_step "add_a_navbar" diff --git a/sites/job-board/make_a_jobs_home_page.step b/sites/job-board/make_a_jobs_home_page.step new file mode 100644 index 000000000..896107c53 --- /dev/null +++ b/sites/job-board/make_a_jobs_home_page.step @@ -0,0 +1,120 @@ +message <<-MARKDOWN + # What we're going to do: + + * Play follow-the-error to guide our development + * Add routes, a controller, and a view to make a home page + * Learn how to read the routing info at + + # Getting Started + + That Rails default page is boring. Let's add a page to list the open jobs at RailsBridgeCorp! + + To start, let's visit the page that we want to put our jobs on: . +MARKDOWN + +error_box "No route matches [GET] '/jobs'" + +message <<-MARKDOWN + That's pretty reasonable, since we currently don't have any routes at all right now. + + # Adding Routes + + So it's looking for a route, but can't find one. Let's add one! + + Open up the routes file. It's in the config directory, called `routes.rb`. If you're using Atom, you can open it using keyboard shortcuts: + + * Hitting cmd + p (on Mac) or ctl + p (on PC) + * typing in `route` + * hitting enter + + Magic! (Atom is using fuzzy search, so you can use the entire file path, or just part of the filename to go to files.) +MARKDOWN + +source_code_with_message "We're going to need a resource route, which will create EIGHT different routes for us. Add this to line two:", :ruby, "resources :jobs" + +message <<-MARKDOWN + Now, lets go look at what that made, by using the excellently helpful routes page: . +MARKDOWN + +discussion_box "How to read the routes page.", <<-MARKDOWN + In your browser, visit . Go over each of the columns and how to read it. + + * Helper + * HTTP Verb (don't worry about going to in-depth with these yet) + * Path + * Controller#Action + + Why does the Helper column sometimes not have anything? + + Can you find the line that will make `/jobs` a route? +MARKDOWN + +message <<-MARKDOWN + Since adding the line `resources :jobs` made a route matching `/jobs`, let's go visit that page again: +MARKDOWN + +error_box "uninitialized constant JobsController" + +message <<-MARKDOWN + Why does Rails now think it needs a JobsController? + + # Add a controller + + Time for a shortcut!!! Unlike the scaffold that we make in Suggestotron that makes about a million different files, we're just going to use a Rails generator to make the files we need. +MARKDOWN + +console "rails generate controller jobs" + +discussion_box "What did that command do?", <<-MARKDOWN + What files were made by that last command? Are they awesome? + + How will this change what error we see when we go to the jobs page? +MARKDOWN + +message "Now: refresh " + +error_box "The action 'index' could not be found for JobsController" + +message <<-MARKDOWN + What is telling Rails to look in JobsController? + + Let's go back to again and look at what controller and method (AKA action) the route /jobs is pointing to. + + It's looking for a method called index on the jobs controller! Since there isn't a controller method for this route, we'll need to add it. + + Open up your jobs controller (again, try to use keyboard shortcuts: cmd+p on Mac or ctl+p on PC to find the file at app/controllers/jobs_controller.rb). +MARKDOWN + +source_code_with_message "Add the index method to the controller:", :ruby, +<<-RUBY +class JobsController < ApplicationController + def index + end +end +RUBY + +message "And refresh " + +error_box "JobsController#index is missing a template for this request format and variant." + +message <<-MARKDOWN + What's a template? How does Rails decide to look for the template associated with JobsController's `index` action? + + Talk through what Rails is trying, and failing, to do, and how file names and method names are important here. + + # Add a view + + Within app/views/jobs, add a file called index.html.erb. + + Save that file, and refresh + + NO ERROR!?!?! How boring. Let's add some content so we can be more confident that this really a thing. +MARKDOWN + +source_code_with_message "Within index.html.erb, add this (or the name of your fictional megacorp):", :html, "

    RailsBridgeCorp Open Jobs

    " + +message <<-MARKDOWN + DONE! Well, except that we don't have any jobs. And even though we could hand-code a table of job titles here, that would be awfully tedious and we'd probably get sick of adding job postings every time someone came up with a new volunteer position. Let's empower people to add their own postings! +MARKDOWN + +next_step "store_jobs_in_the_database" diff --git a/sites/job-board/make_the_form_work.step b/sites/job-board/make_the_form_work.step new file mode 100644 index 000000000..747e99ad9 --- /dev/null +++ b/sites/job-board/make_the_form_work.step @@ -0,0 +1,132 @@ +message <<-MARKDOWN + # What we're going to do + + * Make the form actually work + * Learn how to read server output + * Review happy debugging techniques + + # Partytime: Use the form to Create Objects +MARKDOWN + +message "Now, try to submit your form." + +error_box "The action 'create' could not be found for JobsController" + +message <<-MARKDOWN + So, let's add it: + + (Don't forget to navigate to the jobs controller by using the search shortcut, cmd+p or ctl+p) +MARKDOWN + +source_code :ruby, +<<-RUBY + def create + end +RUBY + +message "Try to use your form again. Unfortunately, there won't be any output, not even an error page. But if you look closely at the output from your Rails server, you might find this:" + +error_box "No template found for JobsController#create, rendering head :no_content" + +<<-MARKDOWN + Familiar error, right? We don't have a template called create.html.erb. + + BUT!!! This time we're not going to make one. + + We actually want to go somewhere useful, right? Let's go back to the main jobs page (where we will eventually be listing our jobs). We need to tell the create method where to go, instead of letting it decide based on method name. +MARKDOWN + +source_code :ruby, +<<-RUBY + def create + redirect_to jobs_path + end +RUBY + +message <<-MARKDOWN + Okay, now go to again, and submit a new job. + + Hopefully it went to the right place! But did it do anything? + + Sadly, no. We just took our form contents and dropped them on the ground. +MARKDOWN + +discussion_box "Logging & Server Output", +<<-MARKDOWN + Arrange your screens so that you can see your server logs (in the terminal window) at the same time as your browser. Now, refresh the form page, and look at what happens in the server. You should see output like `Started GET "/jobs/new"`. + + As a group or in pairs, go over the output of the server, talking through the various pieces of information you see. +MARKDOWN + +message <<-MARKDOWN + # Saving form data! + + Head back to , and get your Rails server logs and your browser next to each other again. Submit the form again, this time looking for the section that looks something like: +MARKDOWN + +source_code :http, +<<-JSON + Parameters: { + "utf8"=>"✓", + "authenticity_token"=>"f48rtxanK9/MHu7TPvd6QzygGnrwv0P2/bxLllozw5U=", + "job"=>{ + "title"=>"Meta-organizer", + "description"=>"We need an somene to organize the organizers." + }, + "commit"=>"Create Job" + } +JSON + +message <<-MARKDOWN + This is the precious data that our form is sending, and right now we're just throwing it away. Let's not do that! Since we're using Rails 5 and all its great conventions, we're going to use Strong Parameters to limit what kind of data our form can submit to our app. +MARKDOWN + +source_code_with_message "Add this code to your jobs controller. (Notice that we're expanding the create method. Don't just copy and paste and end up with two create methods, folks.)", :ruby, +<<-RUBY + def create + Job.create(job_params) + redirect_to jobs_path + end + + private + + def job_params + params.require(:job).permit(:title, :description) + end +RUBY + +message <<-MARKDOWN + Walk through this code line by line with the class! (But don't get too hung up on how strong params works — suffice it to say that it limits the parameters that we'll allow in to the ones listed here.) +MARKDOWN + +console_with_message "Rails console time! Open up another tab or window in your terminal and type this:", "rails c" + +console_with_message "After that's loaded, let's see how many jobs we've saved to the database:", "Job.count" + +message <<-MARKDOWN + Now, submit a new job using the form. What's the count now? + + If the count is going up, yay!!! You got it! If not, time to debug! + + # Tips for Effective Debugging + + * Take a deep breath. Most of your time programming will be looking for bugs. + * Read the error message out loud. Even if you already think you know what it means. + * Check every assumption you can think of. You think that something is getting stored in a variable? WHO KNOWS? + * A good way to check your assumptions is to print out anything you can to the Rails server log. Add puts statements to your code! + * For example: If the jobs count isn't changing when we make jobs, make the jobs controller look like this. Now, it will print to the console the line "In the create method!!!!" and whatever is being returned from `Job.create(job_params)` +MARKDOWN + +source_code :ruby, +<<-RUBY + def create + p "In the create method!!!!!!" + job = Job.create(job_params) + p job + redirect_to jobs_path + end +RUBY + +message "* Think about how you would explain the problem to someone else, or, actually explain the problem to another student or a TA!" + +next_step "crud_and_resourceful_routing" diff --git a/sites/job-board/store_jobs_in_the_database.step b/sites/job-board/store_jobs_in_the_database.step new file mode 100644 index 000000000..c9aa51357 --- /dev/null +++ b/sites/job-board/store_jobs_in_the_database.step @@ -0,0 +1,85 @@ +message <<-MARKDOWN + # What we're going to do: + + * Review what a database is + * Create a table in the database for jobs + * Learn what a migration file is +MARKDOWN + +discussion_box "Databases & SQL", +<<-MARKDOWN + Review how relational databases are structured, how we communicate with them (hint: SQL!). +MARKDOWN + +message <<-MARKDOWN + # Make a jobs model & migration + + In order to make it possible for users to create jobs, we need: + + * A place to store the jobs (a table in the database) + * Rails to know what a job is (a model) + + We're going to use another Rails generator; this time to make our migration and model! +MARKDOWN + +console "rails g model job" + +message <<-MARKDOWN + (The g stands for *generate*, which has many more letters than is necessary to type.) + + That generated two files: a migration and models/job.rb. + + # Make that migration file useful + + Open up the migration file that you just made (cmd+p / ctl+p, type 'create jobs', and hit enter) and you'll see the following: +MARKDOWN + +source_code :ruby, <<-RUBY + class CreateJobs < ActiveRecord::Migration[5.0] + def change + create_table :jobs do |t| + + t.timestamps + end + end + end +RUBY + +message <<-MARKDOWN + Running this code will make a table in our database called jobs. Right now it just has the timestamps (`created_at` and `updated_at`). What else should a job have? Let's start with a title and description. + + Add the title and description so it looks like this: +MARKDOWN + +source_code :ruby, <<-RUBY + create_table :jobs do |t| + t.text :title + t.text :description + t.timestamps + end +RUBY + +message "Now we need to execute this file, so that the database schema gets updated." + +console "rails db:migrate" + +message <<-MARKDOWN + This tells Rails to run any migration files that haven't already been run at some point in the past. +MARKDOWN + +discussion_box "Why do we use migrations?", +<<-MARKDOWN + Talk about the pros and cons of using migrations to update the database, instead of just updating the schema directly. Also, discuss what the database schema is! + + (Pro-tip: *never* update the schema directly.) +MARKDOWN + +message <<-MARKDOWN + # Check out the model + + The migration we just ran updated the database, but that doesn't mean that we can talk to the database using Ruby yet. Look at the file `app/models/job.rb`. The `Job` class inherits from ApplicationRecord, so that we can talk to the database with Ruby instead of SQL! + + Okay, so we've got some place to store our jobs. But how can we make any? THROUGH THE MAGIC OF FORMS!!! +MARKDOWN + +next_step "add_a_new_job_form" diff --git a/sites/job-board/the_request_cycle.step b/sites/job-board/the_request_cycle.step new file mode 100644 index 000000000..ce3281348 --- /dev/null +++ b/sites/job-board/the_request_cycle.step @@ -0,0 +1,9 @@ +message "" + +discussion_box "What is this diagram?", <<-MARKDOWN + Talk through this diagram of the request cycle! + + If there's room, act out a request being made as a tiny play, in which a small object is the request and the students are the various pieces of code that the request travels through. +MARKDOWN + +next_step "make_a_jobs_home_page" diff --git a/sites/job-board/update_job_listings.step b/sites/job-board/update_job_listings.step new file mode 100644 index 000000000..52ceb2803 --- /dev/null +++ b/sites/job-board/update_job_listings.step @@ -0,0 +1,176 @@ +message <<-MARKDOWN + # What we're going to do + + * Create a form for editing job listings + * Learn about partials + * Revel in the joy of Rails form helpers + + # Add the edit page +MARKDOWN + +source_code_with_message "Say we want to edit the first job posting. If we look at , we see this line:", :html, "edit_job_path GET /jobs/:id/edit(.:format) jobs#edit" + +message "So, it looks like if we want to edit the job description, we should visit this URL: ." + +error_box "The action 'edit' could not be found for JobsController" + +source_code_with_message "We've seen this before, right? Let's add the controller action above the word `private`:", +<<-RUBY + def edit + end +RUBY + +source_code_with_message "Refresh, template is missing. Alright, let's add that edit view, under app/views/jobs/edit.html.erb", :erb, +<<-ERB +

    Edit Posting

    +ERB + +message <<-MARKDOWN + Okay, so that's awesome. Now we just have to add a form for editing. I wonder if it is any different from the create form? I guess we could copy and paste the other form? + + We could copy and paste from the other form, but we try to avoid that because duplicated code is hard to maintain. For example, if I want to add placeholder text for the inputs in the form, when the code is duplicated, I’ll need to update the code in each place the form was copied. (In large apps it’s easy to miss a place that would need to be updated.) The solution is to reuse rather than duplicate the code, and the way to reuse code in views is by using partials. +MARKDOWN + +discussion_box "Don't Repeat Yourself", +<<-MARKDOWN + * What are some reasons to DRY up our code? + * What are some strategies for DRYing up code throughout a Rails app? +MARKDOWN + +message <<-MARKDOWN + # Create a Partial + + Rails form helpers are designed beautifully for CRUD interfaces. So we're not gonna have to write very much code to make this form work for editing AND creating job postings. + + But first, a refactor: we're going to move the create form into a partial. + + (Refactoring is improving code while maintaining the behavior it produces.) + + Make a new file under jobs like so: `app/views/jobs/_form.html.erb`, and move the following code OUT of `app/views/jobs/new.html.erb` and into the `_form.html.erb` file: +MARKDOWN + +source_code :erb, +<<-ERB + <%= form_for @job do |f| %> +
    + <%= f.label :title %> + <%= f.text_field :title %> +
    +
    + <%= f.label :description %> + <%= f.text_area :description, size: '60x6' %> +
    +
    + <%= f.submit %> +
    + <% end %> +ERB + +source_code_with_message "Now, in `app/views/jobs/new.html.erb`, add the following line:", :erb, +<<-ERB + <%= render "form" %> +ERB + +message <<-MARKDOWN + Add a job posting, just to make sure that the form is working as expected. + + # Use the power of partials +MARKDOWN + +source_code_with_message "Now that we have a form partial, we can reuse it! In `app/views/jobs/edit.html.erb`, we can add the same line under the header:", :erb, +<<-ERB +<%= render "form" %> +ERB + +message "Refresh the page." + +error_box "First argument in form cannot contain nil or be empty" +source_code_with_message "It looks like we don't have a job ... because we haven't gotten our job out of the database! Let's go to the jobs\\_controller and fix that. In jobs\\_controller.rb, add the following", :ruby, +<<-RUBY + def edit + @job = Job.find(params[:id]) + end +RUBY + +discussion_box "Params", "What is `Job.find(params[:id])` doing? What is `params` again?" + +message <<-MARKDOWN + # Actually Update The Job + + So now the form works. Let's try to update that job posting. Change something about the job posting, and submit the form. +MARKDOWN + +error_box "The action 'update' could not be found for JobsController" + +source_code_with_message "So it looks like the form is finding the right route, but the method is missing from the controller. Let's add the update method--again, above the word `private`--to the file jobs_controller.rb", :ruby, +<<-RUBY + def update + end +RUBY + +source_code_with_message "Try it again, and ... template missing error! Similarly to create, we don't have a template to render for update. So let's just send them back to the jobs listing.", :ruby, +<<-RUBY + def update + redirect_to jobs_path + end +RUBY + +message <<-MARKDOWN + Try again, and ... no errors! But we're still not seeing our changes. +MARKDOWN + +discussion_box "What is this controller method missing?", <<-MARKDOWN + Who knows what we're missing? + + Take a look at the `create` method on the jobs controller and compare what we're doing in each. + + See if you can figure it out as a class. + + (Spoilers below, so don't keep scrolling!) +MARKDOWN + +message <<-MARKDOWN + * here + * is + * some + * strategic + * white + * space + * so + * the + * answer + * isn't + * immediately + * visible! +MARKDOWN + +source_code_with_message "Here's what the update method should actually look like:", :ruby, +<<-RUBY + def update + @job = Job.find(params[:id]) + @job.update(job_params) + redirect_to jobs_path + end +RUBY + +message <<-MARKDOWN + We needed to save our changes to the database so they can actually persist! If you didn't have the discussion before and work out the answer, go through this method line-by-line explaining precisely what the code is doing. + + ### Add a Link + + Our users probably aren't going to know they can hit `/jobs/:id/edit` to visit the edit form, so let's add a link so it's easy to find! + + In `app/views/jobs/index.html.erb`, just add this line with the `
    ` header in it ... don't copy and paste the whole thing! + +MARKDOWN + +source_code :erb, +<<-ERB + <% @jobs.each do |job| %> +

    <%= job.title %>

    +

    <%= job.description %>

    +
    <%= link_to "Edit Posting", edit_job_path(job)%>
    + <% end %> +ERB + +next_step "delete_job_listings" diff --git a/sites/learn-to-code/argv.md b/sites/learn-to-code/argv.md new file mode 100644 index 000000000..5d0411103 --- /dev/null +++ b/sites/learn-to-code/argv.md @@ -0,0 +1,54 @@ + + +# ARGV + +There is a magic array named `ARGV`. + +It contains the *command line arguments* to the program. + +If the user types: + + ruby hello.rb Alice Bob + +then ARGV contains: + + ["Alice", "Bob"] + +# Why ARGV? + +ARGV is a historical name. It means "Argument Vector" and has been around since the early 1970s. + +# Command-Line Hello + +Change `hello.rb` to contain: + + puts "Hello, " + ARGV[0] + +and run it a few times, e.g. + + ruby hello.rb Alice + +# LAB: Hello, Everyone! + +Change `hello.rb` to say hello to *every one* of its command line arguments. + +For instance: + + ruby hello.rb Alice Bob Charlie + Hello, Alice! + Hello, Bob! + Hello, Charlie! + +# LAB: Add + +Write a program named `add.rb` that adds all of its command line arguments together. + +e.g. + + ruby add.rb 1 2 3 + 6 + +Do you remember how to convert a string to an integer? + + + diff --git a/sites/learn-to-code/arrays.md b/sites/learn-to-code/arrays.md new file mode 100644 index 000000000..5dbc0eefc --- /dev/null +++ b/sites/learn-to-code/arrays.md @@ -0,0 +1,126 @@ + + +# Arrays + +* An ARRAY is a CONTAINER + * an object that contains other objects +* It's a list of objects + +# What makes an array an array + +* You can put any objects inside it +* In any order +* They stay in order +* Duplicates are fine + +# Creating an array + + ["apple", "banana", "cherry"] + +# Array Indexes + +* Every slot in the array has a serial number +* You can retrieve any item in an array by its INDEX +* An index is a number from 0 to infinity + * actually to the size of the array + +# Array Indexes Exercise + +Try this in IRB: + + fruits = ["apple", "banana", "cherry"] + fruits[1] + +Did you get the result you expected? + +Why or why not? + +# Start At Zero + +When counting, + +humans start at one, + +but **computers start at zero**. + +So the first item in an array is number zero, not number one. + +# The End + +Try this: + + fruits[99] + +Did you get the result you expected? + +Why or why not? + +# Array Methods + + fruits.last + fruits.first + fruits.reverse + fruits.shuffle + +# Turning an array into a string + + fruits.join + fruits.join(" and ") + +Note that `to_s` doesn't work right on arrays: + + fruits.to_s + puts fruits.to_s + +# Looping through an array + + fruits.each do |fruit| + puts fruit + end + +* `each` is like `while` for arrays +* `fruits.each do` means "for each item inside this array, do this" +* `|fruit|` means "put the current item into a variable named `fruit`" +* `puts fruit` means "print out the value of this variable" +* `end` means "we're done with the loop now" :-) + +# Lab: reverse fruit + +Given this array: + + fruits = ["apple", "banana", "cherry"] + +write a program that prints: + + yrrehc + ananab + elppa + +# Setting items in an array + +The `[]` operator works for assignment as well. + + fruits[0] = "Apricot" + fruits[1] = "Blueberry" + + puts fruits.first + +# Checking an array + +The `include?` method checks if an object is inside an array or not. + + fruits.include? "apple" + true + + fruits.include? "pizza" + false + +# LAB: enemies list refactoring + +I'd like you to **refactor** your old `hello.rb` program to use the `include?` method to check if someone is your enemy. + +# TODO: more array labs + + + + diff --git a/sites/learn-to-code/computers.md b/sites/learn-to-code/computers.md new file mode 100644 index 000000000..b8f90cb4a --- /dev/null +++ b/sites/learn-to-code/computers.md @@ -0,0 +1,106 @@ + + +# What is a computer? + +* A smart box + * desktop + * laptop + * cell phone + * smart phone + +# The Universal Machine + +* A computer can do anything + * (at least on the inside) +* This can be intimidating! +* But the basic rules are simple + +# The Terminal + +* the *TERMINAL* is a window into which you can talk directly to your computer +![Shall we play a game?](img/wargames-terminal.jpg) + +# In The Beginning Was The Command Line + +* the *TERMINAL* is a window into which you can talk directly to your computer +* aka *console* or *command line* or *command prompt* or *shell* or *prompt* + * contrast *CLI* (Command Line Interface) to *GUI* (Graphical User Interface) + +* ["In The Beginning Was The Command Line"](http://www.cryptonomicon.com/beginning.html) is an essay by Neil Stephenson describing the history of computers in an enjoyable and clever way + +# irb - the Interactive Ruby Browser + +## Exercise: Calculator + +* open a terminal +* type `irb` +* press the `return` key (also called `enter`) +* see the `>` prompt +* type `1 + 1` +* press the `return` key again +* see the `2` +* yay, a $1000 calculator! + +* Bonus: what other math can you do? + +* From now on, whenever you see text in the `code font`, try typing it into irb and see what happens! + +# Computer Anatomy: Hardware + +* CPU ("the brain") +* Memory +* Input/Output + * keyboard, mouse, touch screen, monitor + +# Computer Anatomy: Software + +* Operating System +* Libraries +* Applications +* Languages + +Every piece of software on your computer is a PROGRAM. + +# A Program Is Like A Recipe + +* a recipe is a collection of *ingredients* and *instructions* + +![Grandma's Cookie Recipe](img/cookie-recipe.gif) + +* a program is a collection of *data* and *code* + +When you are writing code, you are not baking cookies, you are writing a recipe for how to make cookies. + +(recipe from http://www.popcornpottery.com/rec.html) + +# Languages + +* every program is written in a LANGUAGE + * like Java or Python or C or Fortran + * even HTML and CSS and SQL are languages + * every computer language has a silly name +* different languages are useful in different areas, but there is a lot of overlap +* today we will learn the RUBY programming language + +# Ruby + +* Ruby was invented in the 1990s by a very nice Japanese man named Yukihiro Matsumoto (nicknamed Matz) +* The motto of Ruby is MINASWAN: + +> Matz is nice, and so we are nice. + +# Ruby is fun + +* Ruby was [designed to be fun](http://blog.crowdint.com/2013/06/11/matz-keynote-at-ruby-kaigi-2013.html) to write code in, while also being powerful and well-rounded. +* *Ruby is poetry* + * especially compared to other languages + +# Errors Are Awesome + +* Don't be afraid of errors +* Your computer is trying to help you fix your program + * It's just *really* bad at communicating +* It's not all gibberish +* Try to read it -- really try! -- and pull out the pearls from the pig slop + + diff --git a/sites/learn-to-code/extra.md b/sites/learn-to-code/extra.md new file mode 100644 index 000000000..0b38313a0 --- /dev/null +++ b/sites/learn-to-code/extra.md @@ -0,0 +1,17 @@ + + +# Extra Fun Ruby Stuff + +We've gone over the basics, but Ruby has lots of other language features. Here's a quick overview, with links to more info about them. + +[Iterators](http://codelikethis.com/lessons/ruby_blocks/iterators) are special loops that act on all the items in a collection. + +[Blocks](http://codelikethis.com/lessons/ruby_blocks) are like a cross between functions and methods. They allow Ruby to express many powerful algorithms in a compact writing style. + +[Symbols](http://codelikethis.com/lessons/ruby_basics/symbols) are like strings that start with a colon, and they're used all over the place. + +[Chaining](http://codelikethis.com/lessons/ruby_basics/chaining) is a compact way to express a chain of events. Properly executed, chaining turns Ruby from prose into poetry. + +[Methods](http://codelikethis.com/lessons/ruby_objects/objects#behavior) and [Classes](http://codelikethis.com/lessons/ruby_objects/classes) are the heart of [Object-Oriented Programming](https://en.wikipedia.org/wiki/Object-oriented_programming). They + +## ...and [lots more](http://codelikethis.com/lessons)... diff --git a/sites/learn-to-code/functions.md b/sites/learn-to-code/functions.md new file mode 100644 index 000000000..f6f5ff65d --- /dev/null +++ b/sites/learn-to-code/functions.md @@ -0,0 +1,58 @@ + + +# Functions + +* just like a VARIABLE is a name for a chunk of data +* a FUNCTION is a name for a chunk of code +* if you have some code you want to run again and again + * or just run once, but keep it organized + +# For example + +Here's a silly function: + + def add x, y + x + y + end + +* `def` means "define a function" +* `add` is the *name* of the function +* `x, y` are the *parameters* of the function +* `x + y` is the *body* of the function + * also the *return value* + +Lab: write a `multiply` method and use it to multiply 123 * 456 + +# Rant!!! + + def rant s + s.upcase.gsub(" ", "") + "!!!" + end + + puts rant "i like pizza" + +Lab: use "rant" to rant about something really important!!! + +# Capitalize Just The First Character + + def initial_cap s + s[0].upcase + s[1,s.length] + end + + puts initial_cap("smith") + puts initial_cap("deniro") + +Lab: capitalize a few things + +# Titleize + + def titleize string + string.split(' ').map(&:capitalize).join(' ') + end + +* The funny `&:` means "send this message" +* `map(&:capitalize)` means "send the message `capitalize` to every item in the array" + +# LAB: titleize your favorite movies + + diff --git a/sites/learn-to-code/hashes.md b/sites/learn-to-code/hashes.md new file mode 100644 index 000000000..14c753f44 --- /dev/null +++ b/sites/learn-to-code/hashes.md @@ -0,0 +1,14 @@ + + +# Hashes + +* `Hash` is a built-in type + * aka Map, Dictionary, Associative Array +* Like an array where the index can be *anything*, not just a number + +# TODO: + +* examples +* labs + + diff --git a/sites/learn-to-code/img/cookie-recipe.gif b/sites/learn-to-code/img/cookie-recipe.gif new file mode 100644 index 000000000..1a593848c Binary files /dev/null and b/sites/learn-to-code/img/cookie-recipe.gif differ diff --git a/sites/learn-to-code/img/dot.jpg b/sites/learn-to-code/img/dot.jpg new file mode 100644 index 000000000..e756c29fb Binary files /dev/null and b/sites/learn-to-code/img/dot.jpg differ diff --git a/sites/learn-to-code/img/fruit-banana-snack-banana.svg b/sites/learn-to-code/img/fruit-banana-snack-banana.svg new file mode 100644 index 000000000..061d03e1f --- /dev/null +++ b/sites/learn-to-code/img/fruit-banana-snack-banana.svg @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + "banana" + + + + fruit + + + + "BANANA" + + + + snack + + diff --git a/sites/learn-to-code/img/one-infinite-loop.jpg b/sites/learn-to-code/img/one-infinite-loop.jpg new file mode 100644 index 000000000..e635a0614 Binary files /dev/null and b/sites/learn-to-code/img/one-infinite-loop.jpg differ diff --git a/sites/learn-to-code/img/snack-apple.svg b/sites/learn-to-code/img/snack-apple.svg new file mode 100644 index 000000000..b64c4eaf9 --- /dev/null +++ b/sites/learn-to-code/img/snack-apple.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + "Apple" + + + + snack + snack + + + diff --git a/sites/learn-to-code/img/snack-fruit.svg b/sites/learn-to-code/img/snack-fruit.svg new file mode 100644 index 000000000..788f7d147 --- /dev/null +++ b/sites/learn-to-code/img/snack-fruit.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + "Apple" + + + + snack + snack + + + fruit + + + + diff --git a/sites/learn-to-code/img/spoon.jpg b/sites/learn-to-code/img/spoon.jpg new file mode 100644 index 000000000..a75005414 Binary files /dev/null and b/sites/learn-to-code/img/spoon.jpg differ diff --git a/sites/learn-to-code/img/truthiness.png b/sites/learn-to-code/img/truthiness.png new file mode 100644 index 000000000..ffabdecae Binary files /dev/null and b/sites/learn-to-code/img/truthiness.png differ diff --git a/sites/learn-to-code/img/warehouse.jpg b/sites/learn-to-code/img/warehouse.jpg new file mode 100644 index 000000000..b11e1f5f3 Binary files /dev/null and b/sites/learn-to-code/img/warehouse.jpg differ diff --git a/sites/learn-to-code/img/wargames-terminal.jpg b/sites/learn-to-code/img/wargames-terminal.jpg new file mode 100644 index 000000000..f82f86510 Binary files /dev/null and b/sites/learn-to-code/img/wargames-terminal.jpg differ diff --git a/sites/learn-to-code/input_and_output.md b/sites/learn-to-code/input_and_output.md new file mode 100644 index 000000000..27e8c85bd --- /dev/null +++ b/sites/learn-to-code/input_and_output.md @@ -0,0 +1,114 @@ + + +# Input and Output + +* Computers have many senses -- keyboard, mouse, network card, digital camera, etc. Collectively, these are called INPUT. + +* Computers can also express themselves in many ways -- text, graphics, sound, network, printers, etc. Collectively, these are called OUTPUT. + +* Input and Output together are called **I/O**. + +# Terminal I/O + +* In Ruby, + * `puts` means "print a line to the terminal" + * `gets` means "read a line from the terminal" + +* `gets` reads all the characters from the keyboard and puts them into a new string, until you press RETURN + +# LAB: Hello, friend! + +1. Open `hello.rb` in your text editor +2. Change it to contain the following code: + + puts "What is your name?" + name = gets + puts "Hello, " + name + "!" + +3. Save the file and switch back to the terminal +4. Run the program using `ruby hello.rb` +5. Type in your name and press the RETURN (or ENTER) key + +What happens? Is this what you expected? + +# Yikes! + +* Uh-oh! We've got trouble... what is that exclamation point doing way down there? + +* The first thing to do is DON'T PANIC! +* You are *totally* going to figure this out. +* And even if you don't, you haven't actually broken anything. +* In fact, it's really hard to break a computer, so just stay calm. + +# Breathe + +* In through the nose... +* Out through the mouth... +* In through the nose... +* Ahhhhhhhh. + +# Let's fix this + +* Have you figured out what the problem is? +* If not, I'll tell you on the next slide. +* Take a second and try to figure it out first. I'll wait. + +# The newline character + +* Here's a fun fact: +* In addition to letters, numbers, and punctuation, computers also store other keys inside strings +* Among these CONTROL CHARACTERS is the one that represents the RETURN KEY +* This character's name is NEWLINE +* Every time you use `gets`, Ruby reads *all* the characters, *including the newline*! + +# Strip it + +* Fortunately, there's an easy fix +* If you send the message `strip` to a string, it will remove all SPACES and NEWLINES from both ends + +# LAB: fixing Hello, Friend + +* Change the program to look like this: + + puts "What is your name?" + name = gets.strip + puts "Hello, " + name + "!" + +* Run it and make sure it works OK + +# LAB: Capitalization + +* What happens if you type your name in all lowercase? +* Make the program capitalize your name for you even if you forget. + +# LAB: Crazy Name + +* Now go crazy and make it do all sorts of silly things to your name! + +# LAB: Full Name + +* Write a program named `name.rb` that asks two things: + 1. Your first name + 2. Your last name +* Then it says hello to the user by her *full name*. +* Run the program by typing `ruby name.rb` on the command line. + +# CONGRATULATIONS! + +> You just wrote a program! + +You are now officially a coder. HIGH FIVE! + +# Lab: Name Length + +* Change `name.rb` so it also prints the number of characters in the user's name. +* For instance: + + What is your first name? + Alex + What is your last name? + Chaffee + Hello, Alex Chaffee! + Your name is 11 characters long. + + diff --git a/sites/learn-to-code/learn-to-code.md b/sites/learn-to-code/learn-to-code.md new file mode 100644 index 000000000..fc5c97251 --- /dev/null +++ b/sites/learn-to-code/learn-to-code.md @@ -0,0 +1,78 @@ + + +# Learn To Code + +You may have never programmed before. Now you will. + +# What is coding? + +* coding = programming + +* ...thoughts? + +# What is coding NOT? + +* coding is not mathematical + * some logic + * mostly just long todo lists +* coding is not lonely + * most coding happens in a team + * pair programming is awesome + + + +# What is coding? + +* coding is fun! +* coding is frustrating! +* coding is creative! +* coding is communication + * between you and a computer + * between you and other coders + +# What will we learn today? + +In this class, you will utilize Ruby to learn: + +* The command line and why we use it +* Learn about Strings, Arrays, Variables, Objects, Loops, Files +* Object concepts like Methods + +* How to run your Ruby code interactively (irb) or from a file +* Make a very simple website run on your own computer + +Follow along at http://docs.railsbridge.org/learn-to-code + +# Go at your own pace + +* we will **definitely not** get through all the materials today + +# What if I know some of that already? + +* Pair up and fill in each other's gaps +* Promote yourself to TA + +## And if you and your pair finish early... + +* Let us know! +* Start working on Ruby For Programmers lessons + * http://testfirst.org + * http://codelikethis.com/lessons +* Build a Rails website + * http://docs.railsbridge.org/ + +# Technical requirements + +* WIFI + * Ask for SSID and password +* a live Ruby installation + * e.g. http://railsinstaller.org -- click on the *BIG GREEN BUTTON* + * Ruby version 2.3 preferred, but 1.9+ is fine too + * run `ruby -v` to check +* a text editor + * we recommend [Atom](https://atom.io/), but you can try a [different editor](/installfest/editors) + +* If you do not have both of these things, RAISE YOUR HAND + * visit http://installfest.railsbridge.org for more instructions + + diff --git a/sites/learn-to-code/logic.md b/sites/learn-to-code/logic.md new file mode 100644 index 000000000..16769744e --- /dev/null +++ b/sites/learn-to-code/logic.md @@ -0,0 +1,111 @@ + + +# Truthiness + +Computers have a very strict idea of when things are *true* and *false*. + +![Truthiness](img/truthiness.png) + +(Unlike Stephen Colbert...) + +# True or False? + +Try the following in irb: + +* `1 < 2` +* `2 + 2 < 4` +* `2 + 2 <= 4` +* `2.even?` +* `4.odd?` +* `"apple".empty?` +* `"".empty?` + +# Conditions + +The magic word `if` is called a CONDITIONAL. + + if age < 18 then + puts "Sorry, adults only." + end + +# One-Line Condition + +Ruby has a compact way of putting an entire `if` expression on one line: + + puts "Sorry, adults only." if age < 18 + +Note that: + +* the action comes *first* in a one-line condition +* this sounds kind of natural + * "Go to bed if you're sleepy." + +# if... then... else... end + +The magic word `else` allows BRANCHING. + + if age >= 18 then + puts "allowed" + else + puts "denied" + end + +Like a fork in the road, the program chooses one path or the other. + +(In Ruby, `then` is optional, so we usually leave it off, but if it makes your code clearer, go ahead and use it.) + +# 2 + 2 = 4 + +Sadly, this expression: + + 2 + 2 = 4 + +causes a `SyntaxError`. You need to do + + 2 + 2 == 4 + +instead. Why? + +# The Tragedy of the Equal Sign + +* a single equal sign means ASSIGNMENT + * `name = "Alice"` -- "assign the variable 'name' to the value 'Alice'" +* two equal signs means COMPARISON + * `name == "Alice"` -- "does the variable 'name' contain the string 'Alice'?" + +> This is confusing, and you should feel confused. + +* (it's all FORTRAN's fault) + +# LAB: Good Friend, Bad Friend + +* Your `hello.rb` program should currently look something like this: + + puts "What is your name?" + name = gets.strip + puts "Hello, " + name + "!" + +* Now change `hello.rb` so that it doesn't always say hello! + * If the user's name is "Darth" then say "Go away!" + +# Conjunction Junction + +* You can make more complicated logical expressions using conjunctions like `and`, `or`, `not`: + * `X and Y` means "are both X and Y true?" + * `X or Y` means "is either X or Y (or both) true?" + * `not X` means "is X false?" (think about it) + +* For example: + + if age >= 18 or parent.gave_permission? then + puts "allowed" + else + puts "denied" + end + +# LAB: Enemies List + +* Change `hello.rb` so that it says "Go away!" if the user's name is any one of a number of evil names +* For instance, Voldemort, Satan, Lex Luthor... + + diff --git a/sites/learn-to-code/loops.md b/sites/learn-to-code/loops.md new file mode 100644 index 000000000..194c8b707 --- /dev/null +++ b/sites/learn-to-code/loops.md @@ -0,0 +1,232 @@ + + +# Loops + +Computers are like robots. They are good at doing things over and over and over and over again. + +A LOOP is when we ask a program to do something many times. + +# loop + +If you want something to keep going forever, use a special loop called `loop`. + + loop do + puts "Hello" + end + +To stop it, hold down the CONTROL key and press the C key. + +**Note well!** The lines between `do` and `end` are INDENTED. Indentation is very important to you and other humans. It lets our eyes follow the patterns and helps us quickly see what parts of the program go with each other. + +# One Infinite Loop + +![One Infinite Loop](img/one-infinite-loop.jpg) + +*Fun Fact:* The address of Apple HQ is + + 1 Infinite Loop + Cupertino, CA 95014 + +*Image from the Wikimedia Commons, licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license. Attribution: Joe Ravi* + +# LAB: Infinite Hello + +Let's change `hello.rb` so that it keeps saying hello over and over again. + + loop do + puts "What is your name?" + name = gets.strip + puts "Hello, " + name + "!" + end + +# LAB: Infinite Food + +Write a program called `food.rb` that + +1. asks the user for a food -- say, "pizza" +2. prints "Yum, I love pizza!" +3. asks again, and prints again, forever and ever + +Remember, CONTROL-C means "Stop everything!!!" + +# Who wants to loop forever? + +Next, we will change your `food.rb` program so that if the user types "return" -- meaning the string is empty -- then the program exits. + +Please try this yourself! But it's kind of tricky, so on the next slide I'll show you one solution. + +# Exiting a Loop with break + +The magic word `break` stops a loop immediately. + + loop do + puts "What is your name?" + name = gets.strip + break if name.empty? + puts "Hello, " + name + "!" + end + +The magic word `break` means "stop the current loop". It's less dangerous -- but still kind of weird, since it's not always clear which loop you mean. + +# LAB: Exiting a Loop + +Change your `food.rb` program so that if the user types "return" -- meaning the string is empty -- then the program exits. + +# LAB: Good Food, Bad Food + +* Change `food.rb` so that it doesn't love every food. +* If it's a food you like (let's say, pizza), make it print "Yum! I love pizza." +* If it's a food you like (let's say, cabbage), make it print "Yuck! I hate cabbage." + +# times + +Another loop in Ruby is called `times`, and it's a message you can send to a number. + +Try this in IRB: + + 3.times do + puts "Hip! Hip! Hooray!" + end + +Let's unpack this: + +* `3.times do` means what it sounds like: "do this three times" +* `end` means this is the end of what I want you to do +* and of course, `puts` means "show this on the terminal" + +# Counting in a loop + +Remember this poem? + + 1 potato + 2 potato + 3 potato + 4 + 5 potato + 6 potato + 7 potato + More + +We're going to examine a few different ways to code this. + +# `times` with a counter + +Try this: + + 4.times do |count| + puts count.to_s + " potato" + end + +`|count|` means + +> "the `count` variable points to the current value of the counter" + +# `times` output + + 0 potato + 1 potato + 2 potato + 3 potato + 4 + +Whoops! What's wrong? + +# `times` with a counter -- fixed + + 4.times do |count| + puts (count+1).to_s + " potato" + end + +# `loop` with a counter + + count = 1 + loop do + puts count.to_s + " potato" + count = count + 1 + end + +Whoops! Hit Control-C and join me on the next slide... + +# `loop` with a counter -- fixed + + count = 1 + loop do + puts count.to_s + " potato" + count = count + 1 + break if count > 4 + end + +# `while` + +The magic word `while` combines `loop` and `break`. + + count = 1 + while count <= 4 + puts count.to_s + " potato" + count = count + 1 + end + +* The `while` statement keeps checking the expression + + * if it's `true` then it loops back + * if it's `false` then it stops looping and goes on to the rest of the program + +This is fairly complicated, so let's stop here and make sure to understand everything that's happening in this little program. + +# `while` breakdown (pt.1) + + count = 1 + +creates a *variable* named `count` and sets its value to `1`. + + while count <= 4 + +starts a loop and immediately compares `count` to `4`. + +`1` is less than `4`, so the expression is `true`, so we continue with the loop. + +# `while` breakdown (pt.2) + + puts count.to_s + " potato" + +prints the current value of count (and the word "potato"). + + count = count + 1 + +*increments* the `count` variable... it was `1`, so now it's `2` + + end + +goes *back to the `while` line* and checks again + +# `while` breakdown (pt.2) + + while count <= 4 + +compares `count` to `4`. + +`2` is less than `4`, so the expression is `true`, so we continue with the loop. + +Eventually, `count` becomes `5`, and the `while` expression is `false`, and so we stop looping and go on. + +# LAB: One Potato + +Write a program called `potato.rb` that prints the entire potato poem, accurately. + +# Lab: Adder + +Write a program named `adder.rb` that keeps a *running total*. + +For example: + + ruby adder.rb + 1 + Total: 1 + 2 + Total: 3 + 4 + Total: 7 + -5 + Total: 2 + + diff --git a/sites/learn-to-code/methods.md b/sites/learn-to-code/methods.md new file mode 100644 index 000000000..0034323bf --- /dev/null +++ b/sites/learn-to-code/methods.md @@ -0,0 +1,7 @@ +# Methods vs. Functions + +* a FUNCTION is a named chunk of code with PARAMETERS and a RETURN VALUE +* a METHOD is a function that is *attached* to a specific object + * it has privileged access to that object's data +* in Ruby everything's an object, so the terms are mostly interchangeable + diff --git a/sites/learn-to-code/next_steps.md b/sites/learn-to-code/next_steps.md new file mode 100644 index 000000000..211bf927f --- /dev/null +++ b/sites/learn-to-code/next_steps.md @@ -0,0 +1,21 @@ +# Next Steps + +* [Try Ruby](http://tryruby.org/) at +* Sign up for Code School at + * Code School's "Ruby Bits" class +* Alex's online courses at [Code Like This](http://codelikethis.com) and [TestFirst.org](http://testfirst.org) +* Chris Pine's book "[Learn to Program](http://www.amazon.com/gp/product/1934356360/ref=as_li_ss_il?ie=UTF8&camp=1789&creative=390957&creativeASIN=1934356360&linkCode=as2&tag=alexchaffeeco-20)" book +* Attend or host a [RailsBridge Workshop](http://railsbridge.org) + +* _why's guide to Ruby (http://poignant.guide/) + +* +* + +# Thanks + +* to all the TAs! +* to all the students! +* to [Alex Chaffee](http://alexchaffee.com/), the original author of this curriculum, and +* to all the [RailsBridge volunteers](https://github.com/railsbridge/docs/commits/master/sites/en/learn-to-code) who have helped make it better! + diff --git a/sites/learn-to-code/nil.md b/sites/learn-to-code/nil.md new file mode 100644 index 000000000..d256fa794 --- /dev/null +++ b/sites/learn-to-code/nil.md @@ -0,0 +1,40 @@ + + +# Nil + +*nil* is a magic object + +# There Is No Spoon + +![](img/spoon.jpg) + +*nil* is the object that means "there is no object" + +# Experiment + + fruit = "apple" + fruit = nil + fruit.reverse + +*Read the error!* + +# Errors are good + +They tell you + +* you made a mistake +* what that mistake was +* (sometimes) how to fix it + +Interpret this error: + + fruit.reverse + NoMethodError: undefined method `reverse' for nil:NilClass + +# Fail Fast, Fail Often + +* Ruby has a "fail fast" philosophy +* Is this a good idea? +* Why or why not? + + diff --git a/sites/learn-to-code/numbers.md b/sites/learn-to-code/numbers.md new file mode 100644 index 000000000..9018485af --- /dev/null +++ b/sites/learn-to-code/numbers.md @@ -0,0 +1,156 @@ + + +# Numbers + +The following operations work on numbers: + + * + -- addition + * - -- subtraction + * * -- multiplication + * / -- division + * % -- modulus + * ** -- exponentiation + +# LAB: Playing With Numbers + +Answer the following questions using irb: + +* How many seconds are in an hour? +* How many minutes are in a week? +* How many seconds old are you? +* How many years old is someone who is 1 billion seconds old? + +# Order of operations + +Q: What is 1 plus 2 times 3? + +# Order of operations + +Q: What is 1 plus 2 times 3? + +A: *It depends!* + + * `(1 + 2) * 3` is 9 + * `1 + (2 * 3)` is 7 + +# Parentheses Are Free + +When in doubt, use parentheses! + +# Strings vs. Numbers + +Hmmm.... + + 1 + 2 + "1" + "2" + "1 + 2" + +# Strings plus Numbers + +Hmmm again... + + "1" + 2 + +Uh-oh! + + TypeError: can't convert Fixnum into String + +The problem is that Strings and Numbers are different TYPES, aka different CLASSES. + +Don't panic! The solution is easy. + +# Type Conversion + +Numbers know a message that converts them into strings. `to_s` means "to string". + + "1" + 2.to_s + +Likewise, strings know a message that converts them into numbers. + + 1 + "2".to_i + +`to_i` means "to integer". + +Try this in irb! + +# Advanced Number Theory (optional) + +# WTFixnum? + +The error said `can't convert Fixnum into String`. + +Q: What is a Fixnum? + +A: It's one type of number. + +# Math is hard + +There are many types of numbers! + +Each is useful in different situations. + +Without getting into too much detail, the two main number types in Ruby are: + +* `Fixnum` - for *integers* like 12 or -1023 +* `Float` - for *decimals* like 3.14 + +(Other number types include Complex, Rational, and Bignum.) + +# Number to Number + +You can convert from one type of number to another by sending a message: + +* `to_i` turns a Float into a Fixnum +* `to_f` turns a Fixnum into a Float + +Try this: + + 3.to_f + 3.14.to_i + +# String to Number + +`to_f` and `to_i` also work on Strings: + + "3.14".to_f + "3.14".to_i + +and `to_s` works on numbers: + + 3.14.to_s + +# Arithmetic + +Try this in irb: + + 1 + 2 + 3 - 4 + 5 * 6 + 7 / 8 + +Whoa! What just happened? + +# Integer Arithmetic + +7 and 8 are *Integers* + +so the result is an Integer + +7/8 is somewhere between 0 and 1 + +but there is no integer between 0 and 1 + +so the computer has to *round down* to 0 + +# Floating Point Arithmetic + + 7.0/8.0 + +7.0 and 8.0 are *Floats* + +so the result is a Float + +and `0.875` can fit in a float + +# Okay, that's enough math! + diff --git a/sites/learn-to-code/objects.md b/sites/learn-to-code/objects.md new file mode 100644 index 000000000..2075079cf --- /dev/null +++ b/sites/learn-to-code/objects.md @@ -0,0 +1,81 @@ + + +# Objects + +An OBJECT is a location in computer memory where you can store DATA (aka VALUES). + +There are many kinds of objects, including String, Number, Array, Hash, Time, ... + +(The different kinds of objects are called CLASSES or TYPES. Some day soon you will create your own classes but for now, we will use the built-in ones.) + +# Numbers + +A NUMBER is what it sounds like. + + 10 + -12 + 3.14 + +# Strings + +A STRING is an object that's a collection of characters, like a word or a sentence. + + "apple" + "banana" + "Cherry Pie" + +# Messages and Operators + +An object responds to MESSAGES. You send it messages using OPERATORS. + +The most powerful operator is DOT. + +On screen she looks like this... + + . + +# Dot up close + +...but here's what she looks like up close: + +![picture of Dot the Operator](img/dot.jpg) + +# Dot's job + +Dot can send any message she likes, by name, to any object. + + "apple".upcase + +The `upcase` message turns `"apple"` into `"APPLE"`. + +# Other Operators + +There are other operators, like PLUS (`+`) and TIMES (`*`), but they only send one message each. + +And remember, Dot is more powerful than any other operator! + + 2 + 7 + +is the same as + + 2.+ 7 + +Both send the message `+` to the object `2`. + +# Return Values + +Every time an object receives a message, it returns a response. + +The response is also called the VALUE or the RETURN VALUE. + +You can think of it as the answer to a question. + + 2 + 2 # Question: What is 2 + 2? + 4 # Answer: 4 + + "apple".upcase + # Q: What is the upcase of the string "apple"? + + "APPLE" + # A: the string "APPLE" + diff --git a/sites/learn-to-code/sinatra.md b/sites/learn-to-code/sinatra.md new file mode 100644 index 000000000..d7c672bdb --- /dev/null +++ b/sites/learn-to-code/sinatra.md @@ -0,0 +1,67 @@ + + +# Sinatra + +Sinatra is a Web Application Framework. It includes a Web Server and lets you write code to show when people request web pages. + +# Hi, Sinatra + +1. install Sinatra by running `gem install sinatra` on the command line + +2. create a file called `hi.rb` containing this: + + require 'sinatra' + + get '/hi' do + "Hi!" + end + +3. run `ruby hi.rb` + +Now open a Web Browser (like Firefox or Chrome or Safari or Internet Explorer) and enter the following URL into the address bar: + + http://localhost:4567/hi + +# Congratulations + +You just wrote a web server. + +No, really. + +# Hello, Whoever + +Change `hi.rb` to look like this: + + require 'sinatra' + + get '/hi/:who' do + "Hi " + params[:who] + "!" + end + +Now visit the following URL: + + http://localhost:4567/hi/alice + +# LAB: Yeller + +Make a route in your Sinatra application so that when someone requests this: + + /yell/ahoy + +they see this: + + AHOY!!! + +and when someone requests this: + + /yell/dinnertime + +they see this: + + DINNERTIME!!! + +# Detour: Deploying to Heroku + +* Railsbridge pages describing account setup & deploy steps + + diff --git a/sites/learn-to-code/strings.md b/sites/learn-to-code/strings.md new file mode 100644 index 000000000..3da28ac4e --- /dev/null +++ b/sites/learn-to-code/strings.md @@ -0,0 +1,45 @@ + + +# String Messages + +A string understands lots of messages. Here are a few: + + "banana".upcase + "Cherry".downcase + "titanic".capitalize + "elderberry".reverse + "fig".length + "Fig Newton".swapcase + "".empty? + "syzygy".length + +Try all of these out in irb! + +# String Operators + +A string knows DOT, but also understands several other operators: + + "blue" + "berry" + "yum" * 10 + "elderberry"[8] + +`+` `*` and `[]` are pronounced PLUS, TIMES, and SUB + +Try these out in irb! + +# Combining Messages and Operators + +You can combine messages and operators at will. + + "fig".upcase.reverse + "grape".reverse * 10 + "!!!" + +Definitely try these out in irb! It's pretty fun. + +# LAB: Playing With Strings + +* What is the reverse of "stressed"? +* How many characters long is your name? +* What does your name look like, repeated 1000 times? +* What is the tenth character of "Matz is nice"? (Trick question!) + diff --git a/sites/learn-to-code/the_command_line.md b/sites/learn-to-code/the_command_line.md new file mode 100644 index 000000000..6397782d9 --- /dev/null +++ b/sites/learn-to-code/the_command_line.md @@ -0,0 +1,106 @@ + + +# The Command Line + +* the TERMINAL is a window into which you can talk directly to your computer + * aka *console* or *command line* or *command prompt* +* very low level, based entirely on text and typing, not graphics and mousing +* when you type into the terminal, you are always issuing COMMANDS + * which is why it's called the Command Line + +# Opening the Terminal + +* to open your terminal: + * Mac OS: launch the "Terminal" application + * Windows with Railsinstaller: launch "Command Prompt with Ruby and Rails" +* **Important:** make your terminal *as tall as possible* + +# Directories + +* a DIRECTORY is a location on your hard disk + * also called a FOLDER +* directories can contain FILES +* directories can also contain other directories (called SUBDIRECTORIES) + +# The Current Directory + +* inside the Terminal, you are *always* inside a directory +* it is very important not to get lost! You must try to remember which directory you are in. +* If you forget, you can use a special command called `pwd` + +# Home Directory + +* when you open the Terminal you are in your HOME DIRECTORY +* usually you don't want to store files directly in here + +# Listing Directory Contents + +* when you type `ls` ("list") it shows the contents of the current directory + +# Making a directory + +* when you type `mkdir` ("make dir") it creates a new SUBDIRECTORY inside the current directory + + mkdir code + +# Changing directories + +* `cd` ("change dir") moves you into a different directory +* For example, `cd code` would move you into a directory named `code` +* If you ever get lost, type `cd` all on its own and press the return key. This will send you back to your home directory. + +# Basic Command Line Glossary + +* `pwd` ("print working dir") -- shows the name of the current directory +* `ls` ("list") -- shows the contents of the current directory +* `mkdir` ("make dir") -- creates a new SUBDIRECTORY inside the current directory +* `cd` ("change dir") -- move into a different directory +* `touch whatever.txt` -- creates an empty file named `whatever.txt` inside the current directory + +# LAB: make a subdirectory and then enter it + +1. open Terminal +2. make a new subdirectory using `mkdir code` +3. change into that directory using `cd code` +4. list its contents using `ls` (and note that it's empty) + +# Files + +* a file is a place on disk for storing stuff +* "stuff" here could be anything at all + * documents, pictures, sounds, applications... +* every file lives inside a directory + +# Text Editor + +* a text editor is a program that edits a text file +* a text editor is *like* a word processor +* but a text editor is **not** a word processor +* You probably have *Atom* + * or [another text editor](/installfest/editors) + * but **NOT** *TextEdit* or *Notepad* or *Microsoft Word* + +# Source File + +* source code is the essence of a program +* source files are text files that contain source code +* to RUN a program you type `ruby` and then the name of the source file + +* The Recipe Metaphor + * source file = recipe + * running = cooking + +# LAB: Hello, World + +1. Make sure you are in your `code` subdirectory using `pwd` +2. Create a file named `hello.rb` using `touch hello.rb` +3. Open `hello.rb` in your favorite text editor +4. Inside this file, put the following source code: + + puts "Hello, World!" + +5. Save the file +6. Go back to the terminal +7. Run this file using `ruby hello.rb` + +What happens? Is this what you expected? diff --git a/sites/learn-to-code/todo-learntocode.md b/sites/learn-to-code/todo-learntocode.md new file mode 100644 index 000000000..3a7bbb821 --- /dev/null +++ b/sites/learn-to-code/todo-learntocode.md @@ -0,0 +1,18 @@ +# todo: + +* diagram: memory (variables+objects) +* diagram: message passing + +* interpolation? +* more on arrays +* hashes +* more labs (for students who "get it" before the rest of the room and don't want to sit around bored (or help others)) +* functions +* more array labs +* methods +* classes +* File I/O +* testing +* symbols +* rand + diff --git a/sites/learn-to-code/variables.md b/sites/learn-to-code/variables.md new file mode 100644 index 000000000..63b40d6bf --- /dev/null +++ b/sites/learn-to-code/variables.md @@ -0,0 +1,114 @@ + + +# Variables + +A VARIABLE is a NAME for an object. You give an object a name using the ASSIGNMENT operator (it looks like an equal sign). + + color = "blue" + fruit = "berry" + +Anywhere you can use an object, you can use a variable instead. + + color + fruit + fruit.upcase + +# The Warehouse Metaphor + +![Warehouse from Raiders of the Lost Ark](img/warehouse.jpg) + +Think of memory as a giant warehouse. + +# The Warehouse Metaphor Explained + +If memory is a giant warehouse... + +...and *objects* are **boxes** in that warehouse + +...then a *value* is the **contents** of a box + +...and a *variable* is a **label** you stick on the outside of the box + +# Variables are documentation + +Which is clearer, this: + + 60 * 60 * 24 + +or this: + + seconds_per_minute = 60 + minutes_per_hour = 60 + hours_per_day = 24 + seconds_per_day = seconds_per_minute * minutes_per_hour * hours_per_day + +? + +# Lab: Play In IRB + +Let's spend a few minutes just playing around in IRB. Some things to try: + +* write a poem +* YELL THE POEM +* calculate 2 + 2 and more complicated things +* assign your best friend to a variable +* reverse your best friend's name +* get a new best friend and reverse her too + +# The Pointer Metaphor + + snack = "Apple" + +![snack-apple](img/snack-apple.svg) + +Think of a variable as **pointing** to an object. + +# Changing Variables + +You can assign and reassign variables at will. + + color = "blue" + fruit = "berry" + color + fruit + + color = "black" + color + fruit + +Changing a variable (using ASSIGNMENT) just changes the name of an object. It does *not* change the data inside the object. + +# Many pointers can point to the same thing + + fruit = "Apple" + snack = fruit + +![snack-fruit](img/snack-fruit.svg) + +After this both `snack` and `fruit`... + + * are *pointing* to the same *object* + * have the same *value* + +# Return values are new + +most messages return *new* values + + fruit = "banana" + snack = fruit.upcase + +![fruit-banana-snack-banana](img/fruit-banana-snack-banana.svg) + +`"banana"` and `"BANANA"` are two *different objects* in memory + +# Changing Values + +Most messages do not change the data inside the object. + + color.upcase + color + +But some messages do change the data! + + color.upcase! + color + +This can be dangerous so sometimes those messages end with a BANG (exclamation point). + diff --git a/sites/intermediate-rails/add_other_features_of_your_choosing.step b/sites/message-board/add_other_features_of_your_choosing.step similarity index 59% rename from sites/intermediate-rails/add_other_features_of_your_choosing.step rename to sites/message-board/add_other_features_of_your_choosing.step index 650d72717..836d3d56b 100644 --- a/sites/intermediate-rails/add_other_features_of_your_choosing.step +++ b/sites/message-board/add_other_features_of_your_choosing.step @@ -6,11 +6,18 @@ Suggestions: * Profile pages for users (enter user’s name or details, have it display alongside posts). * Post/Comment history for individual users (on their profile page?). * Easy user profile pictures with [Gravatar](https://gravatar.com/). -* Add login options with [Omniauth](https://github.com/intridea/omniauth), including Twitter, Facebook, Github, Google, and more. +* Add login options with [Omniauth](https://github.com/intridea/omniauth), including Twitter, Facebook, GitHub, Google, and more. * Check out the Devise [documentation for integrating with Omniauth](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview). -* Fiddle with the layout of the show page so it doesn’t look bad. If it looks bad. It probably looks great. +* Fiddle with the layout of the show page so it doesn't look bad. If it looks bad. It probably looks great. * Perhaps fancier post markup with Markdown or something similar. * Deploy to Heroku and send your message board to all your friends! ## ...and that's that! Good luck! Make more things! Come back again! + +# Credits + +This curriculum was originally written by +[Lillie Chilen](http://www.twitter.com/lilliealbert) and +[Travis Grathwell](https://github.com/tjgrathwell), +and has been improved by all sorts of [lovely RailsBridge volunteers](https://github.com/railsbridge/docs/commits/master/sites/en/message-board). MARKDOWN diff --git a/sites/intermediate-rails/add_pages_to_create_and_look_at_individual_posts.step b/sites/message-board/add_pages_to_create_and_look_at_individual_posts.step similarity index 77% rename from sites/intermediate-rails/add_pages_to_create_and_look_at_individual_posts.step rename to sites/message-board/add_pages_to_create_and_look_at_individual_posts.step index 962c60935..53ae6cdfe 100644 --- a/sites/intermediate-rails/add_pages_to_create_and_look_at_individual_posts.step +++ b/sites/message-board/add_pages_to_create_and_look_at_individual_posts.step @@ -2,7 +2,7 @@ requirements do message <<-MARKDOWN * The user should be able to create a post with a title, author, date published, and content. The author should be the current user. * The complete post should appear on its own page (aka its show page). -* If the user doesn’t submit all required fields, they should see some error messaging, but shouldn’t lose any of their work. +* If the user doesn't submit all required fields, they should see some error messaging, but shouldn't lose any of their work. MARKDOWN table do tr do @@ -38,10 +38,11 @@ end tools_and_references do message <<-MARKDOWN -* RailsGuides - Active Record Associations: . -* RailsGuides - Form Helpers, section 2.2: +* RailsGuides - Active Record Associations: . +* RailsGuides - Form Helpers, section 2.2: . * RailsGuides - Routes - CRUD, Verbs, and Actions: . -* RailsGuides - Active Record Validations and Callbacks: +* RailsGuides - Active Record Validations: . +* RailsGuides - Active Record Callbacks: . MARKDOWN end @@ -50,7 +51,7 @@ message <<-MARKDOWN * Rails has some built in ways to associate one model with another. See the RailsGuides link above for hints on how to make a user the owner of a post! * Don't hand code the form! You don't have to! Rails will help. See RailsGuide link above! * Rails has a built-in way to note when something was stored in the database. Probably handy for showing the date / time a post was created. -* For now, we're going to use the user's email address when displaying a posts's author. You can add names or other identifiers later! (Also, even though you're going to get the current user's email address from the User model, you'll still need a user parameter for your Post resource.) +* For now, we're going to use the user's email address when displaying a post's author. You can add names or other identifiers later! (Also, even though you're going to get the current user's email address from the User model, you'll still need a user parameter for your Post resource.) * You need a `create` method to store your post data - scroll down a little bit in this section of the Getting Started guide for very helpful information and to see an example of a create method: . MARKDOWN end diff --git a/sites/intermediate-rails/add_replying.step b/sites/message-board/add_replying.step similarity index 84% rename from sites/intermediate-rails/add_replying.step rename to sites/message-board/add_replying.step index 014a62fa1..0df64b867 100644 --- a/sites/intermediate-rails/add_replying.step +++ b/sites/message-board/add_replying.step @@ -3,7 +3,7 @@ message <<-MARKDOWN * The user should see a 'New Reply' link or button on a post's show page which takes them to a form. The form should include the title of the post being replied to. * The user should be able to create a new reply to a post using the 'New Reply' form. * The user should see all the replies to a post on the post’s show page. -* If the user doesn’t submit all required fields, they should see some error messaging, but they shouldn’t lose any of their work. +* If the user doesn't submit all required fields, they should see some error messaging, but they shouldn't lose any of their work. MARKDOWN table do tr do @@ -28,7 +28,7 @@ end discussion do message <<-MARKDOWN * What is a nested resource? When is it appropriate and how does it help? -* How do the replies_controller and posts_controller interact? +* How do the `RepliesController` and `PostsController` interact? * What should happen to the routes file for this nesting business to work? MARKDOWN end diff --git a/sites/message-board/commands.md b/sites/message-board/commands.md new file mode 100644 index 000000000..b61bd3edd --- /dev/null +++ b/sites/message-board/commands.md @@ -0,0 +1,79 @@ +## Ruby + +Open an interactive Ruby terminal (type 'exit' to quit) + + irb + +Run a ruby program named FILENAME.rb + + ruby FILENAME.rb + +Installs a gem called GEMNAME + + gem install GEMNAME + +Installs gems listed in the `Gemfile` + + bundle install + +## Rails + +Create a new rails project called `NAME` + + rails new NAME + +Auto-generate routes (this can also be done manually) + + rails generate scaffold + +Create a new [Rails model] + + rails generate model MODELNAME + +Update the database to match what you have described in your code + + rails db:migrate + +Run the application locally (Ctrl-C to quit) + + rails server + +Start an interactive Ruby session that knows about your Rails models (type 'exit' to quit) + + rails console + +Print the routes for your application + + rails routes + +## Browser + +Go to the root page of your rails application + + http://localhost:3000 + +## Git + +Creates a new git repository in your current directory. + + git init + +Add the current directory, and all sub directories, to your git repository. + + git add . + +Tells you what you've added, deleted, and changed between your current directory and you local git repository. + + git status + +Prints the difference between FILENAME and what is in your local git repository. + + git diff FILENAME + +Commit the files you've added to the local repository. + + git commit -m "Describe what has changed, and why" . + +Push *committed* changes to the remote server. + + git push diff --git a/sites/intermediate-rails/create_a_new_rails_app_with_a_static_home_page.step b/sites/message-board/create_a_new_rails_app_with_a_static_home_page.step similarity index 89% rename from sites/intermediate-rails/create_a_new_rails_app_with_a_static_home_page.step rename to sites/message-board/create_a_new_rails_app_with_a_static_home_page.step index a7aa7f7a6..59e49917d 100644 --- a/sites/intermediate-rails/create_a_new_rails_app_with_a_static_home_page.step +++ b/sites/message-board/create_a_new_rails_app_with_a_static_home_page.step @@ -22,13 +22,12 @@ end tools_and_references do message <<-MARKDOWN * RailsGuides - Setting the Application Home Page: . -* Rails Guides - controllers overview: . +* RailsGuides - controllers overview: . MARKDOWN end hints do message <<-MARKDOWN -* You'll definitely want to delete the index.html that Rails provides. If you can't find it, ask your neighbor! * If you have no idea what to put in your home controller, maybe try looking back at a past-Railsbridge app? Or another rails app you have lying about? MARKDOWN end diff --git a/sites/message-board/creating_a_new_controller.md b/sites/message-board/creating_a_new_controller.md new file mode 100644 index 000000000..b459ee313 --- /dev/null +++ b/sites/message-board/creating_a_new_controller.md @@ -0,0 +1,79 @@ +## Getting Started + +**1\.** Create your project + +**2\.** View your project in the browser. You should see a default page. + +## Create your Controller + +Controllers are classes that inherit from ApplicationController. + +Controllers in Rails have one or many **actions**. +Each action is defined as a method inside the controller. +Some standard actions are **index, create, new, edit, show, update,** and **destroy**. But you're not limited to using those names. + +We will start with index, which can be used to display the main page in your application. + +**3\.** Create your first controller called **HomeController**. + +Create a controller file in the app/controller folder. *The file should always be named with lower case letters and underscores.* + +``` +app/controllers/home_controller.rb +``` + +**4\.** Create your controller, with an empty index method. In the home_controller.rb file add: + +``` +class HomeController < ApplicationController + def index + end +end +``` + +## Create your View + +**5\.** Create your first view, to be associated with the `Home` controller and the `index` action. + +Create a new folder in app/views. *The folder name should always be lower case and the name of your controller.* + +``` +app/views/home +``` + +Create a file for your first action in the app/views/home folder. *The filename should always be lower case and the name of the action.* + +``` +app/views/home/index.html.rb +``` + +Each view created is associated with one controller action. You don't even really need to define a method in the associated controller: as long as the associated controller and route exist, Rails will pretend that the method exists, and is empty, and render as such. + +## Create your Route + +A **route** is a path that points to an action for a specific controller. This controller and action pair (often stylized as **controller#action**) also indicates which view should be used, via the file naming conventions mentioned above. + +**6\.** Edit routes.rb so that the base url points to your application entry point + +``` +root 'home#index' +``` + +NOTE: This says that your base url (i.e. root) should use the `index` action of the `home` controller. The view used will be the file in the app/views/home folder with name `index` (e.g. app/views/home/index.html.erb). + +Each subsequent route should be defined in a similar way: + +``` +get 'about' => 'home#about' +``` + +NOTE: This says that the base url with /about on the end will direct to the `about` action in the `home` controller. + +## Test your new Controller + +**7\.** Go back to your project in the browser and confirm that on the root path you now see the content from your index.html.erb file instead of the default Rails welcome page. + +**8\.** Add an `about` action to the Home controller and test that you can see it at the /about path in the web browser. + + + \ No newline at end of file diff --git a/sites/intermediate-rails/img/create_post.png b/sites/message-board/img/create_post.png similarity index 100% rename from sites/intermediate-rails/img/create_post.png rename to sites/message-board/img/create_post.png diff --git a/sites/intermediate-rails/img/create_reply.png b/sites/message-board/img/create_reply.png similarity index 100% rename from sites/intermediate-rails/img/create_reply.png rename to sites/message-board/img/create_reply.png diff --git a/sites/intermediate-rails/img/header.png b/sites/message-board/img/header.png similarity index 100% rename from sites/intermediate-rails/img/header.png rename to sites/message-board/img/header.png diff --git a/sites/intermediate-rails/img/inline_reply.png b/sites/message-board/img/inline_reply.png similarity index 100% rename from sites/intermediate-rails/img/inline_reply.png rename to sites/message-board/img/inline_reply.png diff --git a/sites/intermediate-rails/img/post_index.png b/sites/message-board/img/post_index.png similarity index 100% rename from sites/intermediate-rails/img/post_index.png rename to sites/message-board/img/post_index.png diff --git a/sites/message-board/img/request-cycle.jpg b/sites/message-board/img/request-cycle.jpg new file mode 100644 index 000000000..53ac5ed5b Binary files /dev/null and b/sites/message-board/img/request-cycle.jpg differ diff --git a/sites/intermediate-rails/img/show_post.png b/sites/message-board/img/show_post.png similarity index 100% rename from sites/intermediate-rails/img/show_post.png rename to sites/message-board/img/show_post.png diff --git a/sites/intermediate-rails/img/show_replies.png b/sites/message-board/img/show_replies.png similarity index 100% rename from sites/intermediate-rails/img/show_replies.png rename to sites/message-board/img/show_replies.png diff --git a/sites/intermediate-rails/img/static_home_page.png b/sites/message-board/img/static_home_page.png similarity index 100% rename from sites/intermediate-rails/img/static_home_page.png rename to sites/message-board/img/static_home_page.png diff --git a/sites/intermediate-rails/inline_replying_on_a_post.step b/sites/message-board/inline_replying_on_a_post.step similarity index 100% rename from sites/intermediate-rails/inline_replying_on_a_post.step rename to sites/message-board/inline_replying_on_a_post.step diff --git a/sites/intermediate-rails/install_devise.step b/sites/message-board/install_devise.step similarity index 73% rename from sites/intermediate-rails/install_devise.step rename to sites/message-board/install_devise.step index c5cd2713b..ee48163f1 100644 --- a/sites/intermediate-rails/install_devise.step +++ b/sites/message-board/install_devise.step @@ -1,18 +1,22 @@ requirements do message <<-MARKDOWN * The user should be able to create an account and log in. -* The user should see a prompt to log in if they aren’t logged in. +* The user should see a prompt to log in if they aren't logged in. * Once logged in, the user should see a static page with some kind of greeting. * The page should display the logged-in user's email address and a link to log out. -* You, the developer, should explain to a teacher, TA, or fellow student how Rails knows to render the home view. MARKDOWN end +important do + h2 "Timebox It!" + p "The purpose of this section is to allow users to log in to your app and demonstrate how to add external libraries like Devise using the Gemfile. If you find yourself needing to write more than a couple of lines of code to make that happen, you may have gone too far. Talk to a TA!" +end + discussion do message <<-MARKDOWN -* What is devise? -* What files did devise add to your rails app? -* Someone Who Knows: explain how to read the output of the command `rake routes`. What does it give you? How is it helpful? +* What is Devise? +* What files did Devise add to your rails app? +* Someone Who Knows: explain how to read the output of the command `rails routes`. What does it give you? How is it helpful? * How will you add the log out link? What's the syntax for a DELETE action? * You're going to be editing your application layout to add error messaging. If you haven't already, discuss the relationship between the application layout and all the other views you'll be creating. MARKDOWN @@ -22,7 +26,7 @@ tools_and_references do message <<-MARKDOWN * Devise: authentication magic! Learn more about it here: https://github.com/plataformatec/devise * In the readme, scroll down to "Getting Started." You'll need to add it to your gem file. If you don't remember how, ask your neighbor! - * You probably don't want to just start typing commands from the devise readme. You probably want to read through that whole section first. (i.e., don't skip bundle!) + * You probably don't want to just start typing commands from the Devise readme. You probably want to read through that whole section first. (i.e., don't skip bundle!) MARKDOWN end @@ -32,7 +36,7 @@ message <<-MARKDOWN * The convention for naming models is to capitalize the first letter, like: User. * When you run `rails generate devise:install`, you get five instructions for things to configure. 3 & 4 are good to do. * The routes file goes through many common types of routes in the comments. This is also your friend. -* Devise has some magic that will help you with your logout link. Run `rake routes` and look for a route that helps you sign out. +* Devise has some magic that will help you with your logout link. Run `rails routes` and look for a route that helps you sign out. * You'll probably want to show the current user's email address only if they are presently signed in, right? Devise has a helper for you. * Optional: any time you generate a model in rails, you can use Rails Console to look at that model's methods and behavior interactively. MARKDOWN diff --git a/sites/intermediate-rails/make_a_posts_index_page.step b/sites/message-board/make_a_posts_index_page.step similarity index 84% rename from sites/intermediate-rails/make_a_posts_index_page.step rename to sites/message-board/make_a_posts_index_page.step index 6f3557a45..017de5296 100644 --- a/sites/intermediate-rails/make_a_posts_index_page.step +++ b/sites/message-board/make_a_posts_index_page.step @@ -15,9 +15,9 @@ end tools_and_references do message <<-MARKDOWN -* RailsGuides - Listing All Posts: http://guides.rubyonrails.org/getting_started.html#listing-all-posts +* RailsGuides - Listing All Posts: http://guides.rubyonrails.org/v4.0/getting_started.html#listing-all-posts * Feel free to disregard the JSON stuff on this page, if you're so inclined. -* Bootstrap - Style that table! http://twitter.github.com/bootstrap/base-css.html#tables +* Bootstrap - Style that table! http://getbootstrap.com/css/#tables MARKDOWN end diff --git a/sites/intermediate-rails/make_it_pretty_with_bootstrap.step b/sites/message-board/make_it_pretty_with_bootstrap.step similarity index 68% rename from sites/intermediate-rails/make_it_pretty_with_bootstrap.step rename to sites/message-board/make_it_pretty_with_bootstrap.step index 8979f1999..1b816f0c6 100644 --- a/sites/intermediate-rails/make_it_pretty_with_bootstrap.step +++ b/sites/message-board/make_it_pretty_with_bootstrap.step @@ -1,25 +1,30 @@ requirements do -message <<-MARKDOWN + message <<-MARKDOWN * The site should use Bootstrap for a superfun modern look. * When logged in, the user's email address should appear in the upper-right corner of a navigation bar. MARKDOWN img class: 'noborder', src: 'img/header.png' end +important do + h2 "Timebox It!" + p "The purpose of this section is to make your app a bit prettier and demonstrate how to add CSS frameworks like Bootstrap using the Gemfile. Don't get snagged on the details of getting your CSS exactly right, unless that's valuable to you." +end + discussion do message <<-MARKDOWN * Chrome developer tools / Firefox Firebug plugin — how to use ’em! * What’s Bootstrap and (optional) why is everyone totally in love with it (or at least using it all over the place)? -* Maybe review the home/index.html.erb view file as it relates to the layouts/application.html.erb view file again, and where you might want to make some edits to take advantage of bootstrap. +* Maybe review the home/index.html.erb view file as it relates to the layouts/application.html.erb view file again, and where you might want to make some edits to take advantage of Bootstrap. * Someone should explain how erb works — you'll be using it to show the user email address in right side MARKDOWN end tools_and_references do message <<-MARKDOWN -* A gem for Bootstrap!! Let’s use the gem bootstrap-sass-rails: https://github.com/yabawock/bootstrap-sass-rails -* Bootstrap docs: http://twitter.github.com/bootstrap/base-css.html -* Here's a link to the navbar section: http://twitter.github.com/bootstrap/components.html#navbar +* A gem for Bootstrap!! Here's where to get it https://github.com/twbs/bootstrap-sass +* Bootstrap docs: http://getbootstrap.com/css/ +* Here's a link to the navbar section: http://getbootstrap.com/components/#nav * Reference for HTML and CSS: http://www.htmldog.com/ MARKDOWN end @@ -28,17 +33,19 @@ hints do message <<-MARKDOWN * Don't forget to read the readme! * There are a couple of Bootstrappy ways to move text around — look for "Component alignment" in the docs. -* Devise has a helper method for accessing the current user - go back to the devise readme to find it. +* Devise has a helper method for accessing the current user - go back to the Devise readme to find it. MARKDOWN end h1 "Really Big Hint" message "If you can't get the right HTML mojo for the Bootstrap nav bar, or want to move on quickly to the next page, here's some code you can drop into your layout (after installing Bootstrap!) that should work. The code should go immediately after your <body> tag." pre <<-MARKDOWN -