%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Start off with some headers
Title: Ruby/Rails Workshop
Author: Stumptown Syndicate and friends
Generator: text
%css
body {
font-family: ‘Helvetica Neue’, Helvetica, sans-serif;
}
.slide h2 {
font-size: 24pt;
}
table td {
padding: .5em;
border: 1px solid #ccc;
}
%end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Start Presentation
Good morning!
- 10:00-11:30 Introduction to Ruby
- 11:30-Noon Ruby Koans
- Noon-1:00 Lunch
- 1:00-2:45 Rails
- 2:45-3:00 Break
- 3:00-4:30 Rails part 2
- 4:30 What Next?
- Basic concepts of programming
- Syntax and simple operations of Ruby
- Objects, classes, and methods
- An introduction to Rails
- How to use Rails machinery to manipulate application data
- Data validation in Rails
- Displaying data in Rails
- Using scaffolding to start a simple application
- Programming is the act of writing programs
- A program is a precise, text-based, description of how to perform a computation on data
- Data
- Numbers
- Strings: pieces of text
- Lists of things
- Booleans: true and false
- Computations
- Add 3 and 4
- Find the largest number in a list of numbers
- Contact Trimet’s network and find the most convenient bus route
- A human readable format for describing computations
- Any language can describe the same computations
- Different languages are really just a different vocabulary
% * Compilers or interpreters convert human-readable text to machine-readable %
% * Different vocabularies offer different ways of expressing an idea: like human languages, each programming language makes some things easy to “say” and some things harder %
- It’s easy to learn
- Portland has a strong Ruby community
- Rails is a popular web framework built on Ruby
- Rails gives Ruby a good, simple, vocabulary for web development
Open a terminal window. Type “irb” and hit enter.
% TODO say something about irb is an interpreter
Try entering a few things:
1
4 + 3
"hat"
[1, 2, 3, "happy"].each { |i| puts i }
>>
is your prompt to enter text and =>
will appear at the start of each response.
Use the up and down arrow keys to navigate back and forth through the previous commands you’ve entered.
% TODO more tabley
Numbers | 1 , 2 , 3 |
Strings | "chicken" |
Booleans | true , false |
Symbols | :chicken |
Arrays | [1, 7, "cake"] |
Hashes | {:chicken => "chicken", :sillyexample => :chicken} |
Type these at the command line! What happens?
That’s the data, where are the computations?
2 + 2 |
||
3 * 3 |
||
"chicken".reverse |
||
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
||
"chicken".reverse |
||
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
||
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
||
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
||
2 + 2 == 5 |
||
[1 , 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
true |
|
2 + 2 == 5 |
||
[1, 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
true |
|
2 + 2 == 5 |
false |
|
[1, 2, 3].include?(2) |
2 + 2 |
4 |
|
3 * 3 |
9 |
|
"chicken".reverse |
"nekcihc" |
|
"chicken".length |
7 |
|
2 + 2 == 4 |
true |
|
2 + 2 == 5 |
false |
|
[1, 2, 3].include?(2) |
true |
What do you think "chicken".reverse.length
does?
What about "puppy".include?("p")
?
- How do we combine steps or use the result of an action?
- Variables name particular values
- The same variable can be set to different values at different times
var1 = 4
var1
var2 = "chicken"
var2
var2 = var1
var2
Something you might want to do is print out a string that has a value
contained within it.
For example, try typing
"Our array is #{[1,2,3]}"
Most useful when dealing with variables!
var = [1,2,3,4,5,6]
"Our array is #{var}"
Now back to variables!
Try typing the following, noting what happens at each step
thing = "chicken"
thing
thing.reverse
thing
thing = thing.reverse
thing.reverse
didn’t permanently reverse the string!- We had to set the value with another assignment statement
- Some functions do change state
Try
awesomelist = [5,2,1,8]
awesomelist.sort!
awesomelist
How did that happen? Actions that end with a !
change the data!
This is a Ruby convention, but a good one to follow.
Let’s consider a book rating system:
- We use numeric values 0-5 to represent a book that you’ve read
- :not_rated will represent a book that you haven’t rated
- We’ll store all this data in a hash
Fill in a hash with some books, e.g.
books = { "Left Hand of Darkness" => 5,
"The Word for World Is Forest" => 5,
"Nevermind the Pollacks" => 0,
"Only Revolutions" => :not_rated }
We can retrieve the rating for a particular book like so
books["Left Hand of Darkness"]
We can also set values like so
books["Only Revolutions"] = 3
How can we add a rating for a new book? Any guesses?
We can retrieve the rating for a particular book like so
books["Left Hand of Darkness"]
We can also set values like so
books["Only Revolutions"] = 3
How can we add a rating for a new book? Any guesses?
We set the value of the book just like before!
books["White Teeth"] = 4
Now type books
to see the whole hash.
% TODO what is a method
40.reverse
What happens?
40.reverse
What happens?
Ruby just reported NoMethodError: undefined method `reverse' for 40:Fixnum
That means that reverse
is not something you can do to the number 40.
Most computations in Ruby are performed by methods.
Type 40.methods
to see which methods are available for basic numbers.
Notice +
, -
, et al. in the list?
What about "chicken".methods
?
% TODO better term for piece
An object is data along with the methods, computations, that
can operate on it.
Everything in Ruby is an object: numbers, strings, hashes, etc.
How do you know what kind of object something is? Ask!
40.class
What is a class?
% TODO wording of templates, descriptions
- Classes are templates for making objects
- Classes create different types of objects
- All numbers can be added
- All strings can be reversed
- They define kinds of data and methods on that data
Let’s learn the syntax by example:
class Counter
end
What can we do with Counter
?
c = Counter.new
% Step 1 %
Let’s learn the syntax by example:
class Counter attr_accessor :value
end
Now try
c = Counter.new
c.value
c.value = 10
c.value
What happened? What does attr_accessor :value
do?
Let’s learn the syntax by example:
class Counter attr_accessor :value
def initialize @value = 0 end
end
Now, again, try
c = Counter.new
c.value
initialize
gives new
instructions on how to create an object
% Step 2 %
Let’s learn the syntax by example:
class Counter attr_accessor :value
def initialize @value = 0 end
def increment @value = @value + 1 end
end
% Step 4 %
Let’s use our Counter class:
count = Counter.new
count.increment
count.increment
count.value
count.class
count.methods
% change to use respond_to? , have a note about what respond_to? does in speech
Try count.respond_to?("increment")
. What did you see?
Let’s add a Counter.increment_by
method that takes an argument for how much to increment.
Don’t need to start all over – we open the class instead
class Counter
def increment_by(n) # fill in here end
end
Test your code as follows:
c = Counter.new
c.increment_by(5)
c.value
You can add methods to existing classes as well:
class String
def chicken?
self == "chicken"
end
end
"chicken".chicken?
"puppy".chicken?
self
is a way of referring to the object that the method is being called on.
In "puppy".chicken?
, self
is "puppy"
.
Add a method to String that will test to see if a string is a palindrome.
A palindrome is any string that is the same read forwards or backwards.
To get you started type the following at the command line:
class String
def palindrome?
and finish the rest! Test it on "abba".palindrome?
and "puppy".palindrome?
http://rubydoc.info/stdlib/core/
Good places to start: Array, Enumerable, Fixnum, Float, Hash, NilClass, String, Time
% introduce blocks by example first then explain what
% they are – have other examples of block usage first, sort_by and map as
% well as just each
% pull up array documentation or something like that in terms of showing
% OH HAI some methods take blocks
Some methods take blocks.
list.each {|p| code}
runs code
on every element of list
list = [1,2,3,4]
list.each { |n| puts (n + 1) }
- A block is a computation, but not a method
- A block is of the form
{ |arg1, arg2, ..| code }
{ |v| v.palindrome? }
{ |x, y| x * y }
% TODO different block iterator than the previous things, something more complicated
A more complicated example of using each:
reviews = Hash.new(0)
books.values.each { |rate| reviews[rate] = reviews[rate] + 1 }
reviews
reviews
is a count of how many reviews you gave with a particular rating
reviews[5]
There’s another way to write blocks. This is commonly used for multi-line expressions.
reviews = Hash.new(0)
books.values.each do |rate|
reviews[rate] = reviews[rate] + 1
# more code can go here...
end
Ruby provides control structures for writing more complicated code.
If statements are a switch on whether the argument is true or false.
if true
1
else
2
end
if false
1
else
2
end
Case statements evaluate a statement and execute the code in the corresponding branch:
case favorite_color
when "green"
puts "Grass is green"
when "blue"
puts "Skies are blue"
when "red"
puts "Tomatoes are red"
else
puts "Are you sure that's a color?"
end
% TODO LOSE THIS IN FAVOR OF CASE OR SOMETHING MORE USEFUL
For-in statements allow iteration over a structure such as a list
list = [1,2,3,4,5]
sum = 0
for n in list
sum = sum + n
end
sum
- Rails is a framework for building and deploying web applications
- It’s structured using an architecture called MVC: Model-View-Controller
- MVC separates different kinds of application logic into sections based on what it’s used for. In Rails, these live in separate folders.
- Rails values “convention over configuration”—the platform will make certain assumptions about how you’re going to build your application. This makes tasks that suit Rails’ conventions very easy, but actions that don’t follow these conventions will take more work.
To get started:
- Go to the microrant directory
- Type
bundle install
- Then type
rails server
- Open http://localhost:3000 in your web browser
% (directions for how to run this application and what to do with it as
% examples – need a script for manipulating it in the web browser.
% Need examples of
% # Viewing data
% # Creating data
% # Modifying data
% # Destroying data
% End with the Gospel(in the old German sense) of Rails: much of the code
% needed to do those things can automatically be created by rails
% ) —>
- Twitter? Never heard of it.
- 10 characters to express your anger
- Basic activities: create, read, update, delete (CRUD)
- Go to http://localhost:3000/users/new to create a new user with your name
- Then go to /rants/new and create a new rant
- Rants can also be edited and deleted—give it a try!
- What’s behind the curtain?
- Rails provides classes that allow us to retrieve and manipulate data easily
- Logic for the application is written in normal Ruby, and a module called ActiveRecord does all of the database connection work for you
% Describe what a database is, and talk about how the items they’re creating are stored in tables %
- Close down the Rails server with Ctrl-C
- Run
rails console
- You can modify the data using
Rant
andUser
classes - The
User
class hasid
andname
attributes - The
Rant
class hasid
,message
, anduser_id
attributes
How can we look up a rant after it’s been created?
r = Rant.find(1)
r.message
r.user_id
What about modifying a rant?
r.message = "RANTING!"
r.save
We need to save
the changes when we’re done editing.
Rails models have built-in finder methods that help you retrieve records from the database.
Try:
Rant.all
Rant.order("created_at DESC")
Rant.first
Rant.where("user_id = 1")
Rant.where(:user_id => 1)
Let’s try creating a new Rant, for user_id
1
user = User.find(1)
rant = user.rants.build
rant.message = "WHAT IS TH"
rant.save
Note that we didn’t need to set the id
field of the message!
That was automatically set by new
.
We save
the record when we’re done, in order to create it, just like when we were editing a rant before.
Creating a rant that way was verbose. Is there a better way?
User.find(1).rants.create(:message => "E POINT!?")
Notice that you don’t need to save when you use create
—it combines new
and save
into a single action.
Open up the user.rb and rant.rb model files.
class Rant < ActiveRecord::Base
belongs_to :user
validates :user, :presence => true validates :message, :presence => true, :length => { :maximum => 10 }
end
class User < ActiveRecord::Base
has_many :rants
validates :name, :presence => true
end
% Instructor should walk through what each part of the model does, including explaining belongs_to and has_many associations %
Some people are writing in lowercase! This won’t do!
Let’s write a method that can convert all lowercase rants to uppercase. First, go find the rant.rb file in your Rails models directory.
class Rant < ActiveRecord::Base
before_save :convert_message_to_uppercase
def convert_message_to_uppercase
...
Rails provides many extensions to Ruby classes to make common tasks easier. Look in http://api.rubyonrails.org/classes/ActiveSupport/Multibyte/Chars.html to find the helper you need for this exercise.
Once you’ve added the code, try creating a lowercase rant and see what happens.
Sometimes you want to work with your data in a different format. Go back to the Rails console and write a program that creates a hash that contains rant messages paired with user names.
How do you create a new Hash again?
- Earlier we saw
Hash.new
, but{}
is a simpler way. - Try
new_hash = {}
The result should look like:
=> {"RANTING!"=>"caylee", "WTF"=>"audrey"}
Can you use an iterator to print this out as a series of formatted strings?
% Bonus exercise: amend your code to print out each pair in a formatted string (you’ll need an iteration loop like for..each and the Ruby function puts
to print out each line). %
% TODO NOTE – have example output for the exercises, more guidance for how to get
% started, e.g. showing how to define the Hash
The before_save
code we wrote earlier uses a callback. Rails defines several callbacks that allow you to change the input to a model before or after actions like save, create, update, and delete.
before_save :geocode_address
after_create :notify_admins
before_validate :check_over_some_other_details
Validations provide an easy wrapper for checking that a particular attribute is present, in the right format, or other requirements before the application will save the data. The microblog application already contains some of these. Look at the Rant and User models to see the ones we’ve included.
Can you think of other validations you’d like to add? Try changing the length of a valid Rant message.
% TODO split into two slides [DONE]
% callback slide, slide on validations – explain validations as constraints
% example code with validations/showing actual code
Rails will automatically create views with content when you use the scaffold
command. Go to /app/views and have a look around.
These files are making use of a templating system called ERB that converts Ruby statements in the view into static HTML to display. A typical ERB statement might look like:
% TODO: figure out how to display this without getting clobbered by slideshow’s parser %
Documentation on ERB syntax: http://ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
The scaffolding system is quick to use, but not always pretty. Our list of rants lists the user_id
for the person who said it, and not their name. Let’s change that.
Go to /app/views/rants/ and open the index.html file. By default, the scaffolder makes the index view show a list of all records of the related model’s type. Find the line that displays the user id and change it to show the user’s name instead.
% Instructional note: this may require explaining that you can chain method calls to get rant.user.name %
On the users index page, let’s add a column to the table to show how many rants each person has written. ActiveRecord makes this easy by providing a method called count
that you can add to a collection of records to get the database count instead of the full records. For example, User.count
or Rants.count
.
These also work through has_many associations, so given a particular user record, you could try user.kittens.count
or user.books.count
(if those associations existed) or …
% TODO Add, in addition, a calculation for who has written the most rants
% and a message that says “Congrats, , you’re the angriest person here!”
% AE note – this might be a good one for people moving fast, but the way I would probably recommend doing it is involved enough I’d keep this as a bonus exercise only %
How does Rails know what page to go to when you visit http://localhost:3000/rants/new ?
Open /config/routes.rb to see how this works.
Now that we’ve seen and edited models and views, let’s take a quick look at the controller files in our project.
What does a controller do?
- It connects the view and the model, calling methods on the models to get the data needed for the view, and allowing you to access input parameters, such as those from a form.
- It responds to the HTTP request from the browser, and renders the requested type of output (in our scaffolds, this is html or xml, but other things like json can easily be added—instant API!)
- The controller is also where you would add access control, like only allowing the user who wrote the rant to delete it.
- Exit
microrant
directory - Run
rails new betterrant
- Enter the betterrant directory and run:
bundle install
rails generate scaffold User name:string
rails generate scaffold Rant user_id:integer message:string
rake db:migrate
At this point you can start the server and take a look around.
- We’ve reviewed the basics of the Ruby language
- For a refresher, Try Ruby is a browser based overview of Ruby
- Ruby has a number of simple types of data such as strings, numbers, arrays, and hashes
- Most computations in Ruby are handled by methods.
- A class is a template for creating objects that bundles data and methods
- Classes can be extended as new needs arise
- We’ve covered a small Rails application: microrants
- A good next tutorial is Rails For Zombies
- More detail can be found in the Ruby Guides
- ActiveRecord allows us to manipulate our persistent data like normal objects
- .erb files allow us to intergrate Ruby code with html
- Scaffolding gives a way of automating the start of a Rails project
- Try to make betterrant match or improve upon microrant
- Get involved in the local Ruby user group
- Start coming to Code-n-Splode
% TODO what else should go here? mention some materials in the take-home packet?