Workshop Topics

From DevchixWiki

Jump to: navigation, search

Contents

Slides

Ruby Language (for programmers)

Total beginner's guide to programming (Novice group only)

  • What's a program? Operating system?
  • What's a framework?
    • something that makes it faster to build an application because it contains most of the things you would commonly write
  • Workflow - how do you write a program?
    • Learn about customer's requirements -> translate to "stories"
    • Pick a story that seems doable and start writing code that does it
    • Show your work to the customer, get feedback
    • Based on feedback, adjust stories (customer's "up front" requirements vs. changes once they see something working)
    • Once story is finished, go back to "pick a story"...keep going until you're done! (This is an example of looping!)
  • alternate programming intro
  • Basic programming structures - or, how to do the "start writing code" step
    • variables - words that hold information
    • types of information - text, numbers, collections
    • operators - doing stuff with variables
    • loops - doing the same action a bunch of times
    • printing - to the screen, or to a file
  • Writing a simple program
    • opening the editor
    • opening the command line
    • adding two numbers together, storing in a variable
    • printing variable to the screen
    • save and run

Creating a web application.

  • What's a web application?
    • Basic structure: web server, code, database
  • Programming tools we'll be working with
    • Rails
    • Rake
    • git: for source code control
    • a database
    • an editor
    • hosting services: heroku/github
  • Model-View-Controller
    • in general
    • in Rails

Getting Started with Rails

  • creating a folder on the desktop for our programming stuff
  • opening the editor, opening the command line

rails suggestotron -m http://gist.github.com/194076.txt

  • Everything is in that folder - if you decide you don't like it, just delete it and start over with the rails command.
  • note that the -m option is just a shortcut for a few commands listed in that file (click on the link to see). It is optional. Usually you will not use that option (or maybe you will crete your own template with your frequently used options). Later we'll explain them in more detail.

ruby script/server

Open the app source code

  • Start Komodo Edit
  • File -> New -> New Project...
  • Open the "suggestotron" directory
  • View -> Tabs & Sidebars -> Projects

Edit index page

  • note that the page you are seeing at http://localhost:3000 is in public/index.html
  • make a change and reload
  • Development vs. Production
    • When you're working, everything is on your computer. Right now, you've got a server, rails, your code, a database, all on your machine. You can see your app, but nobody else can.
    • In practice, your laptop isn't powerful enough to actually have a lot of people connect to it, and you want your app to stay running even when your laptop is off.
    • So you "deploy" (copy) your application to a server at your hosting provider - they take care of security, etc.

Awesome! Let's ship it! (Deploy #1)

  • saving a copy of the code to git

git init
git add .
git commit -m 'basic web application'

  • deploy to heroku

 heroku create
 git push heroku master
 heroku rake db:migrate

  • go to your URL, see your application on the internet!

What application will we build?

  • Screen shots of the application: Workshop Application Design
  • Introduction to Behavior Driven Development (BDD)
    • We have written up the application behavior using a BDD framework called cucumber
  • Describe the web application we want to build
    • Basic concepts: topics, votes, users
    • These are described in the "features" directory that was created using the template.

Starting to develop the first feature

One of the basic features that we might build first is defined in the topics.feature file in the features directory:

   Feature: Topics
     In order to see a list of potential topics for meetings
     people need to be able to create and edit them
   
     Scenario: Getting to the new topic page
       When I go to topics
       And I follow "New topic"
       Then I should see a "Create" button
  • You can run all of the features in that file with:
   cucumber features/topics.feature
  • Of course, the feature fails because we haven't written any code yet! These feature descriptions are tests as well as documentation. Typically we run the test, watch it fail, then implement a feature, then run the test again to see if it passes. We'll be doing that today.
  • The topics features relies on some basic elements of a web app, which is what we'll build first. As we get features to pass, we'll look further at the features definitions to see what needs to be built
  • To run a single feature scenario, you can provide its name:
   cucumber features/topics.feature -n 'Getting to the new topic page'

CRUD and Databases

To develop the first feature, we need to know a bit about writing a web application that accesses a relational database.

  • CRUD??
    • Create - enter a new topic
    • Read - see everyone's topics
    • Update - change the topics you entered
    • Delete - remove your topics from the system
  • REST: conventions, etc.
  • Databases
    • storage for web apps - even when no one's using the app, even when the app isn't running, the database keeps track of all the meetings that have been entered so far.
    • like a storeroom at a restaurant - it keeps all the information, the raw materials of your application, but it's not directly accessible to your users (diners).
    • your rails app goes back to the "storeroom", gets the information the user requested, and displays it nicely - like turning raw materials into actual dishes.
    • a database is organized into tables - each one is like one worksheet of an excel spreadsheet - columns, rows
    • one table per "concept", so we need a topics table
    • we can see from our feature specification what fields we need (title, description, ... ?)
    • once we have a list, we can go back to the command line to implement it

Scaffolding

  • run command line script to generate a Topic model along with the application logic and views
   ruby script/generate scaffold topic title:string description:text 
  • holy generated files, batman
  • Open the migration file
    • explain what it does
    • migrations can also be used for modifying tables (add/remove/rename columns) and even modifying data
  • Now let's set up the database for the test
   rake db:migrate RAILS_ENV=test
  • Run the cucumber feature (and the first one should pass)
   cucumber features/topics.feature 
  • Now let's look at the feature we created. To do so, we want to set up the development environment
    • create the topics table
   rake db:migrate
    • run the server
   ruby script/server
  • http://localhost:3000/topics - do some topics CRUD
  • highlight that documentation for all this code is online at railsapi.com
  • Check out SQLite Manager for Firefox
  • Now let's take another look at our topics table (check it out using the sqlite plugin)
  • You can also look at the database using the command line tool that comes with it. These command line tools are slightly different for each database and rails provides a convenient shortcut for accessing whichever one you are using:
   ruby script/dbconsole
  • To stop the server
    • control-c in the terminal window or git bash where it is running

Deploy #2

  • saving a copy of the code to git
  git add .
  git commit -m 'basic topic crud'
  • deploy to heroku
   git push heroku master
   heroku rake db:migrate
  • go to your URL/topics, see topic CRUD! that you wrote! on the internet!
  • Show to customer? Nope! In this case, the customer has already documented the expected features using cucumber. We can see what needs to be done next just by running the features as tests.
  • This is a lot to take in the first time, but it was actually not a lot of work to get to this point. Now we'll look at the code we just created and explain what Rails generated for us before we move on to new features

What did we just do?

  • TODO: link MVC slide
  • How URLs map to code
   $ rake routes
       topics GET    /topics(.:format)                  {:action=>"index", :controller=>"topics"}
             POST   /topics(.:format)                  {:action=>"create", :controller=>"topics"}
   new_topic GET    /topics/new(.:format)              {:action=>"new", :controller=>"topics"}
   edit_topic GET    /topics/:id/edit(.:format)         {:action=>"edit", :controller=>"topics"}
       topic GET    /topics/:id(.:format)              {:action=>"show", :controller=>"topics"}
             PUT    /topics/:id(.:format)              {:action=>"update", :controller=>"topics"}
             DELETE /topics/:id(.:format)              {:action=>"destroy", :controller=>"topics"}
                     /:controller/:action/:id           
                     /:controller/:action/:id(.:format)

Next feature

  • Let's run the next feature and see what we need to build next
   Scenario: Creating a topic
     Given I go to topics
     And I follow "New topic"
     When I fill in "Title" with "Rails Fixtures"
     And I fill in "Description" with "Introduce how to add test data with fixtures."
     And I press "Create"
     Then I should see "Rails Fixtures"
     And I should see a "New topic" link
  • Run the server, look at the app and see how the scaffold template differs from the desired application as we've described it using cucumber
  • In the desired behavior, after someone creates a topic, the expected behavior is for the application to display the list of topics; however, scaffold instead displays a page showing the individual topic that we just created

Controller: adjusting the flow of your application

  • Earlier we looked at the scaffold generated code a bit. To change this behavior, we will look closely at the controller, which controls the general flow of your application (which page is displayed when someone clicks a link or a button)
  • Take a look at: app/controllers/topics_controller.rb
  • Walk thru new/create actions
  • notice that in create there is a redirect to
   format.html { redirect_to(@topic) }
  • instead we want to re-direct to the list of topics
   format.html { redirect_to(topics_path) }

Next feature: topics page

  • Note for this next set of feature scenarios we have a "Background" set of steps which will be executed before each scenario in the file. These rely on behavior that already works, so are green (pass).
   cucumber features/topics_list_and_details.feature
   Scenario: Clicking on the topic title
     When I follow "Rails Fixtures"
     Then I should see "Introduce how to add test data with fixtures."
     And I should not see "add a topic"

in views/topics/index.html.erb

     <td><%= link_to h(topic.title), topic %></td>
  • change "Destroy" to "Delete" in views/topics/index.html.erb

Next feature: votes

   cucumber features/votes.feature


Add votes

  • TODO: this section needs a lot of work and has not yet been edited to match the cucumber feature workflow
  • TODO* link to assn images
  • use generate resource to create model & controller (no views)
   script/generate resource vote topic_id:integer
  • creates the following:
    • model (including migration, unit, fixture)
    • controller (and route)
 class Topic < ActiveRecord::Base
   has_many :votes
 end
 class Vote < ActiveRecord::Base
   belongs_to :topic
 end
  • check it out in irb
   >> t = Topic.new
   => #<Topic id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
   >> t.votes
   => []
  • now you can use it in your view (along with a handy view helper)
   <td><%= pluralize(topic.votes.length, "vote") %></td>

Allow people to Vote

This is good to do one bit at a time and let the test failures drive what you do next. Check rake routes for figuring out the path.

   <td><%= link_to '+1', votes_path(:topic => topic.id), :method => :post%></td>

now we need to create the controller action

   class VotesController < ApplicationController
     def create
       vote = Vote.new(:topic_id => params[:topic])
       vote.save
       
       if vote.save
         flash[:notice] = 'Vote was successfully created.'
       else
         flash[:notice] = 'Sorry we could not count your vote.'
       end
       redirect_to(topics_path) 
     end
   end

OLD CONTENT

Associations

  • write the test

  test "Topic has votes" do
    t = Topic.new(:title => "my title")
    assert t.votes == []
  end

  • add to topic model:

 has_many :votes

  • Rails can also support accessing topic from a vote. Write the test

  test "Vote has a topic" do
    v = Vote.new()
    assert v.topic == nil
  end

  • add to vote model:

class Vote < ActiveRecord::Base
  belongs_to :topic
end

Validations

  • TDD (this needs some more description, maybe some slides)
 require 'test_helper'
 
 class TopicTest < ActiveSupport::TestCase
 
   test "Require title to not be blank" do
     t = Topic.new
     assert t.valid? == false, "Expected Topic to not be valid"
   end
 
 end
 
  • add validation to the vote model
  validates_presence_of :topic
  • based on a question from the class we also added the positive test
   test "Topic with title is valid" do
     t = Topic.new(:title => "my title")
     assert t.valid?, "Topic with title should be valid"
   end

Nested Routes

What is the test for which this is required?

  • edit the routes, from:

 map.resources :votes

 map.resources :topics

  • to:

 map.resources :topics, :has_many => :votes

  • show before and after rake routes
master $ rake routes
  (in /Users/sarah/src/topicr)
       votes GET    /votes(.:format)                   {:controller=>"votes", :action=>"index"}
             POST   /votes(.:format)                   {:controller=>"votes", :action=>"create"}
    new_vote GET    /votes/new(.:format)               {:controller=>"votes", :action=>"new"}
   edit_vote GET    /votes/:id/edit(.:format)          {:controller=>"votes", :action=>"edit"}
        vote GET    /votes/:id(.:format)               {:controller=>"votes", :action=>"show"}
             PUT    /votes/:id(.:format)               {:controller=>"votes", :action=>"update"}
             DELETE /votes/:id(.:format)               {:controller=>"votes", :action=>"destroy"}
      topics GET    /topics(.:format)                  {:controller=>"topics", :action=>"index"}
             POST   /topics(.:format)                  {:controller=>"topics", :action=>"create"}
   new_topic GET    /topics/new(.:format)              {:controller=>"topics", :action=>"new"}
  edit_topic GET    /topics/:id/edit(.:format)         {:controller=>"topics", :action=>"edit"}
       topic GET    /topics/:id(.:format)              {:controller=>"topics", :action=>"show"}
             PUT    /topics/:id(.:format)              {:controller=>"topics", :action=>"update"}
             DELETE /topics/:id(.:format)              {:controller=>"topics", :action=>"destroy"}
                    /:controller/:action/:id           
                    /:controller/:action/:id(.:format) 
  master $ rake routes
  (in /Users/sarah/src/topicr)
           topics GET    /topics(.:format)                          {:controller=>"topics", :action=>"index"}
                  POST   /topics(.:format)                          {:controller=>"topics", :action=>"create"}
        new_topic GET    /topics/new(.:format)                      {:controller=>"topics", :action=>"new"}
       edit_topic GET    /topics/:id/edit(.:format)                 {:controller=>"topics", :action=>"edit"}
            topic GET    /topics/:id(.:format)                      {:controller=>"topics", :action=>"show"}
                  PUT    /topics/:id(.:format)                      {:controller=>"topics", :action=>"update"}
                  DELETE /topics/:id(.:format)                      {:controller=>"topics", :action=>"destroy"}
      topic_votes GET    /topics/:topic_id/votes(.:format)          {:controller=>"votes", :action=>"index"}
                  POST   /topics/:topic_id/votes(.:format)          {:controller=>"votes", :action=>"create"}
   new_topic_vote GET    /topics/:topic_id/votes/new(.:format)      {:controller=>"votes", :action=>"new"}
  edit_topic_vote GET    /topics/:topic_id/votes/:id/edit(.:format) {:controller=>"votes", :action=>"edit"}
       topic_vote GET    /topics/:topic_id/votes/:id(.:format)      {:controller=>"votes", :action=>"show"}
                  PUT    /topics/:topic_id/votes/:id(.:format)      {:controller=>"votes", :action=>"update"}
                  DELETE /topics/:topic_id/votes/:id(.:format)      {:controller=>"votes", :action=>"destroy"}
                         /:controller/:action/:id                   
                         /:controller/:action/:id(.:format)     
  

Edit View

  • Add to the topics index (refer to rake routes to see what the path should be)

   <td>
     <%= button_to "+1", topic_votes_path(topic), :method => :post %>
   </td>

  • Look the page, click the button, see an error

Controller

  • add controller methods
class VotesController < ApplicationController
 
  def create
    @topic = Topic.find(params[:topic_id])
    @vote = @topic.votes.create!
    
    flash[:notice] = "You voted for #{@vote.topic.title}"
    redirect_to(topics_path)
  end
 
  # DELETE /topics/votes/1
  def destroy
    @vote = Topic.find(params[:topic_id]).votes.last
    @vote.destroy
    redirect_to(topics_url)
  end
end

editing views

  • edit the index view
  • add link to title

   <td><%= link_to h(topic.title), topic %></td>

  • remove description & show
  • add the same vote controls to the show view (explain making a partial: create _vote.erb in /app/views/topics)
    <td><%= render :partial => "vote", :locals => {:topic => topic} %></td>
  • copy paste same partial code and get this error
  • note: need @topic instead, since topic is not a local variable

Next feature: authenticated votes

   cucumber features/authenticated_votes.feature

Authentication

  • Different options for auth

adding users with restful authentication

script/plugin install git://github.com/technoweenie/restful-authentication.git
mv vendor/plugins/restful-authentication/ vendor/plugins/restful_authentication/
  • note: renaming to change '-' to '_' is required > Rails 2.1
  • using rspec with restful auth
sudo gem install rspec rspec-rails cucumber
script/generate rspec
script/generate authenticated user sessions --rspec  
  • or if you don't care about rspec just do
script/generate authenticated user sessions 

After running these commands take some time to walk through the generated files.

Move the declaration

include AuthenticatedSystem

from the UsersController to the ApplicationController. Make sure to examine the lib/authenticated_system.rb file to get a sense of what methods your controllers now have available.

Update your local database

 rake db:migrate

Now you should be able to use the paths /login, and /users/new. (Note: if you get the error "error: uninitialized constant User::Authentication" either you forgot to rename the plugin with '_' or restart the server note from ruby-forum)

Push the new system to heroku

 git add -A
 git commit -a -m "generated auth system"
 git push heroku

And update your databases

 heroku rake db:migrate

updating views with Login/Sign Up

You may wish to create an application layout and add some links for "Sign Up" and "Login". You will have to remove the existing layouts to make a single application layout work for the Topic and Vote pages:

 cp app/views/layouts/topics.html.erb  app/views/layouts/application.html.erb
 rm app/views/layouts/topics.html.erb app/views/layouts/votes.html.erb

You can add a menu to the header of the layout like so:

Requiring authentication for voting

Now it's easy to sign up for your own account. But anyone can still vote, or add a topic. Let's add some protection to our VotesController:

before_filter :authenticated?
 def authenticated?
   if !logged_in?
     flash[:notice] = "You must login to do that"
     redirect_to root_path
     false
   end 
 end 

Now only logged-in users can vote. But it might be nicer to hide the voting buttons from them until they log in. Now you can change app/views/topic/_vote.html.erb to include the following:

 <% if logged_in? %>
  <%= button_to "+1", topic_votes_path(topic) %> 
<% end %>
<%= topic.votes.size %> votes

Add votes to users

  • create a migration to add the user_id column to the vote model
script/generate migration user_votes
  • Add the following to db/migrate/[...]_user_votes.rb:
class UserVotes < ActiveRecord::Migration
 def self.up
   add_column :votes, :user_id, :integer
 end
 def self.down
   remove_column :votes, :user_id
 end 
end
  • add to the vote model:

class Vote < ActiveRecord::Base
  belongs_to :user
  belongs_to :topic
end

  • add to user model:

 has_many :votes

  • and change the VotesController to record this information as well
def create
    @vote = @topic.votes.build(params[:vote])
    @vote.user = current_user
    ...
end
  • Now you can make it so someone can change their mind, add vote destroy, etc.

Notes

The following stuff should be covered above

  • Models, Views, and Controllers
  • we're paranoid, so let's save a copy in github.
    • go to github.com, create an application
    • git remote add origin git@github.com:your_github_username/your_app_name.git
    • git push origin master
  • heroku/git
  • irb and scirpt/console
  • Ruby language
  • Ruby gems: what are they exactly? how do you install them? what happens when you do?
  • TDD
  • MVC - the structure of the web app
  • what's a relational database?
  • migrations, belongs_to
  • drag, re-order (this seems too ambitious)
Personal tools